cve-2018-20062是thinkphp 5.0.0–5.0.23中因路由解析误处理反斜杠导致的“零配置”rce漏洞,攻击者通过/s=/index/ hinkpp/invokefunction等路径直接调用危险方法,参数完全用户可控,无需调试模式或特殊配置,至今仍在老旧后台、政府外包系统中被广泛利用。

ThinkPHP 5.0.23 RCE(CVE-2018-20062)为什么至今还在被利用
不是因为没人修,而是大量系统卡在“已升级但未清理旧逻辑”的状态。这个漏洞本质是路由解析时对反斜杠的误处理:当URL中出现/index/ hinkpp/invokefunction,框架会把 hinkpp当作命名空间类名去实例化,而thinkApp类的invokeFunction方法又直接调用call_user_func_array,参数完全来自用户输入。
关键风险点在于它不依赖任何配置——只要版本在5.0.0–5.0.23区间,开箱即黑。2026年真实攻防事件中,仍有约17%的RCE攻击链以该漏洞为起点,主要出现在老旧后台管理系统、政府外包项目和未维护的CMS二次开发站点。
- 检测方式:访问
?s=/index/ hinkpp/invokefunction&function=phpinfo&vars[0]=1,返回PHP信息即存在 - 修复不是只改版本号:必须确认
application/route.php中未启用Route::any()泛路由,且禁用__construct中对$this->request->param()的无校验反射调用 - WAF几乎无效:因为
s=参数名可任意替换为module、c、act等,且路径分隔符可用%5C、/、\绕过
ThinkPHP 6.x 的缓存文件包含(CVE-2025-63888)真实利用条件
这个漏洞不是模板引擎本身的问题,而是业务代码与缓存机制耦合失控的结果。典型场景是开发者用view()动态拼接路径:view('../runtime/cache/' . input('tpl')),再配合上传功能把恶意PHP代码塞进public/uploads/目录,最后用?tpl=../../uploads/shell.jpg触发包含执行。
它在6.1.0+仍活跃,根本原因在于框架默认关闭了../跳转,但只要你显式调用view()->engine->layout(false)或重写Template类的fetch方法,绕过就成立。
- 影响前提:缓存驱动为
File(非Redis/Memcached),且runtime目录可写 - 常见误判:认为“用了6.1.0就安全”——错。漏洞触发点在业务层
view()调用方式,而非框架核心 - 审计重点:全局搜索
view(、fetch(、display(),检查所有参数是否来自input()、$request->param()或路由变量
ThinkPHP反序列化POP链在6.0.12+为何更难发现
ThinkPHP 6.0.12引入TagCache后,serialize()不再限制反序列化类型,而thinkContainer中的__destruct会自动调用绑定对象的close方法,如果该对象是自定义类且close里有file_put_contents或eval,链就闭合了。
这类漏洞90%存在于自研扩展模块,比如日志导出插件、低代码表单渲染器、第三方支付回调处理器——它们往往继承thinkModel或thinkCache,但没重写__wakeup做类型白名单校验。
- 典型入口点:
unserialize($_SESSION['data'])、unserialize(cache('user_config'))、unserialize($redis->get('queue_task')) - 识别特征:类中存在
__destruct、__wakeup、__call,且方法体内调用file_*、system、eval、call_user_func - 修复不能只禁用
unserialize:要统一改用json_decode+ 白名单类映射,或在__wakeup中强制if (!in_array($this->type, ['User', 'Order'])) { throw new Exception(); }
SQL注入在ThinkPHP里最隐蔽的三种写法
ThinkPHP的ORM本可杜绝SQL注入,但开发者常因“图快”或“兼容老代码”写出三类高危模式,它们绕过参数绑定、逃逸WAF规则、且在日志中不留痕迹。
-
Db::query("SELECT * FROM user WHERE id = " . input('id')):直接字符串拼接,input()不等于安全,它只是取值函数 -
Db::name('user')->where('status = ' . input('status'))->select():where()第二个参数缺失,第一个参数被当原生SQL解析 -
Db::name('user')->whereRaw('username LIKE "%' . input('q') . '%"')->select():whereRaw不自动转义,%和'全由用户控制
真正安全的写法只有两种:where('field', input('val'))(走绑定)或whereRaw('field LIKE ?', ['%' . input('q') . '%'])(手动占位)。别信“我过滤了单引号”,SQL注入早就不靠单引号了。
复杂点在于,这些漏洞常藏在中间件、命令行脚本、定时任务里——它们不走HTTP请求生命周期,input()可能返回空,但$_GET或$_SERVER['argv']却没被清空。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











