thinkphp 5.1 rce漏洞需通过堵起点、断执行、白名单三重防御,禁用调试与自动路由,入口层硬拦截高危参数,并全面替换动态函数调用、eval及sql拼接等危险模式。

ThinkPHP 5.1 的远程代码执行(RCE)漏洞不是个别问题,而是多个可串联利用的高危入口叠加的结果。最核心的防御逻辑是:**堵住调用链起点、切断敏感函数执行、强制参数白名单化**。修复不能只靠升级——尤其你用的是 5.1.41 这类“伪安全”版本(官方已停止维护,且未包含 5.1.30 后的关键补丁)。
确认并立即处理真实风险版本
打开 composer.json,检查 "topthink/framework" 的版本号:
- 若为 5.1.30 及更早:存在控制器名绕过 RCE(如
?s=index/ hinkpp/invokefunction),必须升级到 5.1.31+ 或手动修补 - 若为 5.1.31–5.1.41:虽修复了部分早期变种,但反序列化 POP 链(
Windows::__destruct → file_exists → __toString → Request::__call)仍有效,且无官方补丁 - 所有 5.1.x 版本默认开启调试模式或自动路由时,都可能被用于控制器遍历和方法触发
入口层硬拦截(无需升级即可生效)
在 public/index.php 最顶部(require 框架前)加入以下校验逻辑,直接拒绝高危请求:
if (isset($_GET['s']) || isset($_POST['_method']) || isset($_GET['function']) || isset($_POST['function'])) {
$dangerous = ['\think\', 'invokefunction', 'call_user_func', 'call_user_func_array', 'eval', 'assert', 'system', 'exec', 'shell_exec', 'passthru'];
$input = array_merge($_GET, $_POST, $_REQUEST);
foreach ($input as $k => $v) {
$v = is_string($v) ? $v : '';
foreach ($dangerous as $word) {
if (stripos($v, $word) !== false || preg_match('/${[@s]*eval/i', $v)) {
header('HTTP/1.1 403 Forbidden');
exit('Access Denied');
}
}
}
}
该段代码覆盖常见 RCE 触发特征:s= 路由参数、_method 构造、动态函数名、内联 PHP 表达式等,实测可拦截 CVE-2018-20062、CVE-2022-25481 等主流 payload。
禁用危险配置与默认行为
修改 application/config.php,关闭所有非必要高危开关:
'app_debug' => false,
'auto_route' => false,
'default_filter' => '', // 彻底禁用 default_filter,它不防 RCE,反而制造虚假安全感
'exception_handle' => '\think\exception\Handle',
// 强制关闭模板引擎中可能引入的动态执行能力
'template' => [
'tpl_begin' => '',
'tpl_end' => '?>',
'layout_on' => false,
],
同时检查中间件或全局过滤器中是否残留 input() 或 param() 的自动过滤链,全部移除——RCE 防御靠的是结构控制,不是字符串替换。
代码层加固(重点审计三类调用)
全项目搜索并替换以下危险模式,替换为白名单或框架原生安全接口:
-
禁止裸调用动态函数:
❌call_user_func($_GET['func'], ...)
✅ 改为固定方法映射:$allowed = ['sendMail', 'exportCsv']; if (in_array($_GET['action'], $allowed)) { $this->$action(); } -
禁止 eval / assert / create_function:
全部删除,模板渲染统一走View::fetch(),配置加载用Config::get(),不拼接、不解析、不执行 -
禁止 raw() 绕过预处理:
❌where('id = ' . input('id'))
✅ 改为:where('id', input('id'))或where(['id' => input('id')])
不复杂但容易忽略











