一、紧急临时修复(未升级前先用,止损优先)
适用于无法立即升级框架版本的场景,核心是拦截危险请求、过滤敏感参数、禁用高危功能。
1. 入口文件加固(public/index.php)
在define('APP_PATH', __DIR__ . '/../app/');之后添加以下代码,拦截 RCE/SQL 注入的核心攻击参数:
php
<?php
// 1. 禁用危险函数调用(按需添加)
if (!function_exists('disable_danger_functions')) {
function disable_danger_functions() {
$danger_functions = ['eval', 'system', 'exec', 'passthru', 'shell_exec', 'proc_open', 'popen'];
foreach ($danger_functions as $func) {
if (function_exists($func)) {
rename_function($func, '_' . $func); // 重命名危险函数(需安装runkit扩展)
}
}
}
// 仅生产环境启用
if (env('app_debug') === false) {
disable_danger_functions();
}
}
// 2. 过滤危险请求参数
$danger_params = [
's', '_method', 'function', 'vars', 'filter', 'order', 'group', 'having',
'\\think\\', 'call_user_func', 'call_user_func_array'
];
// 检查GET/POST/COOKIE参数
$request_data = array_merge($_GET, $_POST, $_COOKIE);
foreach ($request_data as $key => $value) {
if (in_array($key, $danger_params) || is_string($value) && strpos($value, '\\think\\') !== false) {
http_response_code(403);
die('Illegal Request: ' . $key);
}
}
// 3. 限制请求方法(仅允许必要的)
$allow_methods = ['GET', 'POST', 'PUT', 'DELETE'];
if (!in_array($_SERVER['REQUEST_METHOD'], $allow_methods)) {
http_response_code(405);
die('Method Not Allowed');
}
?>
2. 禁用自动路由与调试模式
修改config/app.php,关闭高风险默认配置:
php
return [
// 禁用自动路由(防止未授权访问)
'auto_route' => false,
// 生产环境必须关闭调试模式(防止信息泄露)
'app_debug' => false,
'app_trace' => false,
// 禁用远程调试
'remote_debug' => false,
// 限制日志级别(不输出敏感信息)
'log_level' => ['error', 'warning'],
];
3. Nginx/Apache 层面拦截
在 Web 服务器配置中添加规则,拦截恶意请求:
nginx
# Nginx配置(nginx.conf / 站点配置)
server {
# 拦截包含ThinkPHP RCE特征的请求
if ($request_uri ~* "(\\/think\\/|call_user_func|eval\\(|system\\(|exec\\()") {
return 403;
}
# 拦截SQL注入关键词
if ($request_uri ~* "(union select|insert into|update |delete from|drop table|-- |#)") {
return 403;
}
# 禁止访问敏感目录
location ~ ^/(runtime|config|vendor|.git)/ {
deny all;
}
# 禁止访问PHP配置文件
location ~ \.(php|php5|php7)$ {
try_files $uri =404;
fastcgi_pass 127.0.0.1:9000;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}
二、版本根治修复(唯一彻底解决漏洞的方式)
不同版本的升级路径明确,优先升级到对应分支的最新稳定版:
| ThinkPHP 版本 | 存在漏洞版本范围 | 升级目标版本 | 升级注意事项 |
|---|---|---|---|
| 3.2.x | 全版本 | 建议直接升级到 6.x | 3.2.x 已停止维护,无安全更新,需重构部分代码(如控制器、模型写法) |
| 5.0.x | 5.0.0-5.0.23 | 5.0.24+ | 仅需替换框架核心文件(thinkphp / 目录),代码兼容度 99% |
| 5.1.x | 5.1.0-5.1.37 | 5.1.38+ / 6.1.x | 5.1.x 升级到 6.x 需适配命名空间、中间件写法 |
| 6.0.x | 6.0.0-6.0.12 | 6.0.13+ / 6.1.x | 6.x 升级仅需执行composer update topthink/framework |
| 8.0.x | 8.0.0-8.0.x(如有漏洞) | 8.0.x 最新稳定版 | 8.x 为最新分支,通过 composer 自动更新即可 |
升级操作步骤(以 6.x 为例):
# 1. 备份项目代码和数据库
# 2. 进入项目根目录,执行composer升级
composer update topthink/framework --no-dev
# 3. 清理缓存
php think clear
# 4. 验证升级成功
php think version
三、分类型漏洞专项修复
针对不同类型的漏洞,给出精准的修复代码和配置:
1. RCE 漏洞修复(核心)
-
根本:升级框架版本;
-
代码层面 :禁止直接调用危险函数,统一使用框架封装的方法:
php// 错误:直接使用call_user_func call_user_func($_GET['func'], $_GET['param']); // 正确:白名单限制可调用函数 $allow_functions = ['getUser', 'getOrder', 'getGoods']; $func = input('get.func/s'); if (in_array($func, $allow_functions) && function_exists($func)) { $func(input('get.param/d')); }
2. SQL 注入漏洞修复
核心原则:所有 SQL 操作必须使用参数绑定,禁止手动拼接。
php
// 错误写法(拼接变量)
$id = input('get.id');
$user = Db::table('user')->where("id = $id")->find();
// 正确写法1:数组条件
$user = Db::table('user')->where(['id' => $id])->find();
// 正确写法2:占位符绑定
$user = Db::table('user')->where("id = :id", ['id' => $id])->find();
// 针对order/group注入:白名单限制
$allow_sort = ['id', 'name', 'create_time'];
$sort = input('get.sort/s', 'id');
$sort = in_array($sort, $allow_sort) ? $sort : 'id';
$list = Db::table('goods')->order($sort)->select();
3. 任意文件操作修复
(1)文件读取 / 下载
php
// 错误:直接使用用户传入的文件路径
$file = input('get.file');
return download($file);
// 正确:白名单+真实路径校验
$allow_files = [
realpath(ROOT_PATH . 'public/static/download/guide.pdf'),
realpath(ROOT_PATH . 'public/static/download/readme.txt')
];
$file = realpath(input('get.file')); // 解析真实路径,防止../遍历
if (!in_array($file, $allow_files) || strpos($file, ROOT_PATH) === false) {
die('Illegal File');
}
return download($file);
(2)文件上传
php
// 上传验证:后缀+MIME+内容三重校验
$file = request()->file('upload');
// 1. 后缀白名单
$validate = new \think\file\Validate([
'ext' => 'jpg,png,pdf', // 仅允许这些后缀
'size' => 1024*1024*2, // 限制2MB
]);
if (!$file->validate($validate)->check()) {
return json(['code' => 0, 'msg' => $file->getError()]);
}
// 2. MIME类型校验
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime = finfo_file($finfo, $file->getRealPath());
$allow_mime = ['image/jpeg', 'image/png', 'application/pdf'];
if (!in_array($mime, $allow_mime)) {
return json(['code' => 0, 'msg' => '文件类型非法']);
}
// 3. 重命名文件,防止覆盖
$save_name = \think\facade\Filesystem::disk('public')->putFile('upload', $file, 'md5');
4. 未授权访问 / 权限绕过修复
(1)全局权限中间件(ThinkPHP6+)
创建app/middleware/Auth.php:
php
<?php
namespace app\middleware;
class Auth
{
public function handle($request, \Closure $next)
{
// 白名单路由(无需登录)
$white_list = ['/login', '/register', '/api/captcha', '/static/*'];
$current_path = $request->path();
// 检查白名单
$is_white = false;
foreach ($white_list as $white) {
if ($white === $current_path || (strpos($white, '*') !== false && strpos($current_path, rtrim($white, '*')) === 0)) {
$is_white = true;
break;
}
}
if ($is_white) {
return $next($request);
}
// 检查登录状态
if (!session('user_id')) {
// AJAX请求返回JSON,普通请求重定向
if ($request->isAjax()) {
return json(['code' => 401, 'msg' => '未登录']);
} else {
return redirect('/login');
}
}
// 检查角色权限(按需添加)
$role = session('role');
$admin_routes = ['/admin/*', '/api/admin/*'];
if (in_array($role, ['admin']) === false && $this->matchRoute($current_path, $admin_routes)) {
return json(['code' => 403, 'msg' => '无权限']);
}
return $next($request);
}
// 路由匹配辅助方法
private function matchRoute($path, $routes)
{
foreach ($routes as $route) {
if (strpos($route, '*') !== false && strpos($path, rtrim($route, '*')) === 0) {
return true;
} elseif ($route === $path) {
return true;
}
}
return false;
}
}
在app/middleware.php中注册:
php
return [
\app\middleware\Auth::class, // 全局生效
];
(2)禁用空控制器
修改config/route.php:
php
return [
'empty_controller' => '', // 禁用空控制器,防止绕过
'route_check' => true, // 开启路由校验
];
四、全维度长期加固(生产环境必做)
1. 配置层面
-
数据库配置加密:使用环境变量 / 配置中心存储数据库账号密码,不直接写在
database.php中;php// config/database.php return [ 'username' => env('DB_USERNAME', ''), 'password' => env('DB_PASSWORD', ''), ]; -
禁用函数:在
php.ini中配置disable_functions = eval,system,exec,passthru,shell_exec,proc_open,popen; -
限制 PHP 执行权限:
open_basedir = /var/www/your-project/:/tmp/,防止跨目录访问。
2. 代码层面
-
统一使用
input()方法获取参数,指定类型过滤:php$id = input('get.id/d', 0); // 强制转为数字 $name = input('post.name/s', '', 'trim,htmlspecialchars'); // 过滤HTML字符 -
日志脱敏:对日志中的密码、Token 等敏感信息进行替换;
-
避免使用
$_GET/$_POST/$_COOKIE全局变量,统一用request()对象。
3. 部署层面
- 启用 HTTPS,配置 HSTS,防止中间人攻击;
- 部署 WAF(如阿里云 WAF、宝塔 WAF),拦截 SQL 注入、XSS、RCE 等攻击;
- 定期备份代码和数据库,防止漏洞被利用后数据丢失;
- 以非 root 用户运行 PHP-FPM/Nginx,限制文件读写权限。
总结
- ThinkPHP 漏洞修复的核心优先级:先升级框架版本(根治)→ 紧急拦截危险请求(止损)→ 分类型修复专项漏洞(精准)→ 全维度加固(长期);
- 所有修复的底层逻辑:禁用高危功能 + 白名单校验输入 + 最小权限运行,尤其是禁用调试模式、强制参数绑定、配置全局权限中间件这三点;
- 临时修复仅用于应急,长期必须升级到最新稳定版,同时定期扫描依赖漏洞(如使用
composer audit)。