thinkphp5 漏洞
ThinkPHP 5 漏洞:那些年被玩坏的漏洞`where`表达式
2018年12月,一个关于ThinkPHP 5的漏洞远程代码执行漏洞在圈内炸了锅。不是漏洞那种需要登录后台、需要特定条件的漏洞“伪漏洞”,而是漏洞直接打`index.php`,参数里塞一段SQL就能拿下权限。漏洞当时我还在用TP5写一个二手交易平台,漏洞看到漏洞通告后背脊发凉——我写的漏洞`where`条件,可能早被人盯上了。漏洞
问题出在`think\db\Query`类的漏洞`where`方法。它太“灵活”了,漏洞允许传入数组作为条件,漏洞比如`where(['id' => 1])`。漏洞但如果你传入的漏洞是`where(['id' => $_GET['id']])`,而`$_GET['id']`恰好是漏洞`exp`开头的字符串,比如`exp`加`(select 1)`,框架就会直接拼接进SQL。更致命的是,TP5的`where`还支持`where`表达式数组,比如`where(['id' => ['exp', '=1']])`。攻击者只要把参数构造成`id[0]=exp&id[1]==1`,就能注入任意SQL片段。
这还不是最狠的。真正让全网疯狂的是后续爆出的`Request::method()`方法。TP5的请求类为了兼容RESTful,允许通过`_method`参数伪造请求方法。攻击者直接POST一个`_method=__construct`,再配合`filter`参数指定`system`,就能触发`Request`类的构造函数,把`filter`设为回调函数。接着,任意参数都会经过这个回调,`system('whoami')`就这么执行了。整个过程不需要任何登录,不需要知道数据库结构,甚至不需要`exp`——只要请求头里带个`X-Requested-With: XMLHttpRequest`,就能绕过某些安全校验。
我亲测过那个漏洞利用链。本地起个TP5.0.23版本,浏览器访问`/index.php?s=/index/\think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=whoami`,页面直接输出当前系统用户名。那种感觉就像有人把你家保险柜的密码写在门框上——不是技术多高深,而是框架设计者给攻击者留了后门。后来官方在5.0.24版本修复,强制要求`where`的`exp`表达式必须加`(`和`)`,同时禁止`Request`类的`filter`参数接受数组。
但漏洞远没结束。TP5的`order`字段同样能注入,比如`order`参数直接拼接用户输入,没有过滤`if`、`case`等函数。还有`field`参数支持`field('name,password')`这种写法,攻击者可以用`field('name,updatexml(1,concat(0x7e,user()),1)')`报错注入。这些漏洞在2019年陆续被公开,每次修复都像打地鼠——堵住`where`,又冒出`order`。
说实话,TP5的漏洞这么多,根子在于它把用户输入和SQL语法混在一起。框架为了“便捷”,把动态查询做得太“智能”,却忘了安全需要“笨”一点。后来TP6完全重写了查询构造器,强制使用参数绑定,才彻底断了这条路。但5.x版本至今还在很多老项目里跑着,我见过一家外包公司2019年接的政府项目,到现在还在用TP5.0.15,问就是“系统稳定不想动”。
你猜后来怎么着?那个政府网站被扫描到一个`index.php?s=/home/user/order`的接口,`order`参数直接拼SQL,攻击者用`order=if(1=1,1,(select 1 from (select sleep(10))a))`就拖慢了整个数据库。管理员还以为是服务器配置低,重启了三次才找我们排查。最后把框架升到5.0.24,加了个WAF规则,才算消停。
漏洞不是代码的错,是设计的错。TP5教会我的不是怎么防御,而是别太信任“框架帮你搞定一切”。现在写SQL查询,我宁可手写预处理语句,也不想再依赖那种“智能”的数组条件。毕竟,最安全的代码,往往是最笨拙的代码。
(责任编辑:时尚)














