ThinkPHP 3.2老项目漏洞修复遵循“不升级框架、不改业务逻辑、只堵入口、最小干预”原则,聚焦RCE、SQL注入、日志泄露、模板包含四类风险,通过关闭调试模式、限制路由参数、加固数据库查询、约束模板加载路径三道防线精准加固。

关闭调试模式并清理日志路径
这是最基础也最关键的一步。APP_DEBUG=true 是绝大多数RCE和信息泄露的温床。
- 检查 Application/Common/Conf/config.php 或入口文件同级的 Conf/config.php,确认
'APP_DEBUG' => false已生效;若配置被多处覆盖(如模块级config),需全局搜索并统一设为false - 删除或禁用 Runtime/Logs/ 目录的Web可访问权限:在Nginx中添加
location ^~ /Runtime/Logs/ { deny all; };Apache下用.htaccess禁止访问 - 清空现有日志文件,并设置 Runtime 目录仅应用进程可写(如 www-data 用户),禁止其他用户读取
拦截危险路由参数防止RCE
ThinkPHP 3.2 的 ?s= 路由机制是CVE-2018-20062的主要利用入口,但直接删路由会破坏所有URL访问。正确做法是过滤非法控制器/方法名。
- 在 Application/Common/Conf/config.php 中增加路由规则白名单,例如:
'URL_ROUTER_ON' => true,'URL_ROUTE_RULES' => array( '/^/Index/[a-zA-Z]+/[a-zA-Z]+$/i' => 'Index/index', ), - 更稳妥的方式是在入口文件 index.php 最顶部加入参数校验:
if (isset($_GET['s']) && preg_match('/[\\/]|invokefunction|call_user_func|eval|assert|system|shell_exec/i', $_GET['s'])) { die('Access denied'); } - 对
m=、c=、a=参数同样做正则限制,只允许字母+下划线,拒绝反斜杠、点号、双冒号等特殊字符
加固数据库查询防止SQL注入
ThinkPHP 3.2 的 where() 方法支持数组语法,但 array('exp', ...) 类型极易被绕过。修复不靠删功能,而靠收紧输入解析。
- 全局替换所有
D('xxx')->where($map)->select()中的动态数组构造,改为显式类型判断:
例如将$map['id'] = array('exp', "1=1 union select password from admin");改为$map['id'] = (int)$_GET['id']; - 在模型基类 Application/Common/Model/BaseModel.class.php(若无则新建)中重写
_parseWhereItem方法,在调用前校验$val[0]是否为预设安全关键字(如 'eq'、'neq'、'in'),非白名单一律转为字符串并加引号 - 禁用 I() 函数中的 EXP 等危险表达式:修改 ThinkPHP/Common/functions.php 中的
think_filter函数,把'EXP|NEQ|GT|EGT|LT|ELT|OR|XOR'加入过滤关键词,替换为空格或直接 return false
限制模板与文件加载范围
攻击者常通过 assign() 覆盖 _filename 或利用 fetch() 加载任意路径日志文件执行代码。
立即学习“PHP免费学习笔记(深入)”;
- 将 HomebaseController.class.php 和 AdminbaseController.class.php 中的
display()和fetch()方法访问修饰符从public改为protected(ThinkCMF系项目必须做) - 在 ThinkPHP/Library/Think/View.class.php 的
fetch方法开头添加路径白名单检查:if (!in_array(dirname($template), array(APP_PATH.'Home/View/', APP_PATH.'Admin/View/'))) { E('Template path not allowed'); } - 禁用
include类动态加载:搜索全项目中include、require、eval(、create_function(的调用,凡参数含用户输入(如$_GET['tpl'])一律改为配置数组映射,例如:$allowed_tpls = array('mail_welcome'=>'welcome.html', 'mail_reset'=>'reset.html');if (isset($allowed_tpls[$_GET['tpl']])) { $this->display($allowed_tpls[$_GET['tpl']]); }



















