PHP防SQL注入:三层硬核防御体系
|
去年七月,总编把选题单拍在玻璃隔断上,说“PHP防SQL注入:三层硬核防御体系”这个标题,毙了三次——第三次他拿红笔圈住“硬核”俩字,在旁边写了个“?”还带个箭头指向我。我伸手想解释,他手机响了,诺基亚老款那种“嘟——嘟——嘟——”,震得桌上那支蓝墨水笔滚了半寸,没倒,就斜着杵在便签纸边沿,笔帽都没动过。 我原以为他是嫌“硬核”太虚,结果他开口说:“你这三层,是不是又把预处理当第一层、WAF当第二层、输入过滤当第三层?上个月运维日志里那个union select from information_schema.tables的payload,就是从你们网关漏过去的——漏的点在PDO::ATTR_EMULATE_PREPARES = true那个默认值上,不是没用预处理。”我张了下嘴,没接上,因为隔壁桌小编正端着咖啡起身,手腕一晃,深褐色液体泼在键盘右半区,她“啧”了一声,没擦,先按了三下Ctrl+S,手背还滴着水。 优胜劣汰——这词真不是我编的。是实测跑出来的。去年七月份,我们把三套方案全扔进灰度环境:一套纯PDO预处理+白名单字段校验(A组),一套加了mod_security规则集再套一层Lua SQL解析(B组),一套直接用AST重写SQL语法树做拦截(C组)。A组扛住了八成流量,但被一个用十六进制编码的' union /'绕过;B组在慢查询场景下延迟飙到三百七十二毫秒,监控报警邮件发了四十七封,其中二十一封标题是“CPU爆了但没死”;C组只活了十八分钟,被一个带反引号嵌套的MySQL 5.7兼容模式语句干翻,报错信息里连“syntax error near `select`”都没打印全,直接panic。最后活下来的,是A组和B组各抠出半截代码拼的——预处理兜底,但绕过时触发二级WAF规则;WAF失效时降级到AST的简化子集,只校验关键字+括号平衡+字符串闭合,不解析函数名。这个组合拳,我敢打包票:它不是最安全的,但它是活下来的唯一一个。 外卖凉了没人吃。我指桌上那盒黄焖鸡,铝箔盖掀了一半,米饭结了层膜,鸡块浮在酱汁上像几块泡发的木耳。这顿饭点了两小时四十三分,送餐小哥打电话说“就在楼下”,结果被保安拦在闸机外,理由是“没预约访客码”。我没下楼接,盯着监控屏看那个小哥反复扫二维码——扫、失败、重扫、失败、掏出身份证、对准读卡器、嘀、绿灯亮、他刚抬脚,闸机突然咔嗒一声锁死了。后来查日志,是访客系统版本号没同步,旧API返回200却没开闸。这种事儿,和防SQL注入一样:技术方案再漂亮,卡在某个版本号、某个默认值、某个没写进文档的兼容模式上,就是零。 总编当时翻了个白眼。不是朝我,是朝自己电脑屏幕——他刚打开的钉钉群里,作者发来一张截图:某技术公众号推文标题《PHP防SQL注入:五层终极防护》,底下评论第一条写着“作者用的是PDO::prepare()但没禁用emulate,求问这算哪五层”。总编把手机倒扣在键盘上,发出“啪”的轻响。我忽然想起去年三月,我们在网关中间件里埋了个trace_id透传逻辑,结果上线第二天,PHP-FPM进程莫名重启,查了半天发现是log_format里用了$upstream_http_trace_id,而上游某些遗留服务根本没回这个header,Nginx就直接吐core了——不是语法错误,是变量未定义时Nginx的内存越界。所以现在我看任何“五层”“七层”“终极”的标题,第一反应不是点进去,是先看他们用的PDO配置和Nginx版本号。
文章配图,仅供参考 有次调试漏掉的payload,抓包发现攻击者在URL参数里塞了base64_decode("JyB1bmlvbiBzZWxlY3QgMSwxLDEsMSwxLDEsMSwxLDEsMSwxLDE="). 这个我敢打包票,decode之后是' union select 1,1,1,1,1,1,1,1,1,1,1,1 ——十二个1。但我们网关在WAF规则里写了“检测union select后跟大于8个逗号分隔字段”,他改成了11个。第十二个1是故意凑数的,就为了骗过正则。结果呢?我们降级策略里的AST简化子集,看到12个参数就直接标为high-risk,不管内容——不是它懂SQL,是它数了左括号、右括号、逗号、单引号的数量差,差值超过阈值就熔断。这种笨办法,比那些吹“AI语义分析”的强十条街。可这话我说不准——因为上个月测试部提了个bug:当union后面跟的是子查询,而子查询里有嵌套括号,我们的计数器就崩了,误判率飙升到六成七。我们连夜回滚了那行代码,换成更蠢的办法:只要看到union select + 字母数字以外的字符超三个,就强制进人审流程。这办法土,但它活着。小编昨天问我工资条为啥比前年少两百。我没答,盯着她键盘上那滩干掉的咖啡渍——边缘已经泛白,像一圈盐霜。KPI要写“全年漏洞归零”,可归零的前提是所有分支都覆盖,而PHP里有个东西叫register_globals,早被废了,但有些老系统还在.ini里开着,只是注释掉了;还有magic_quotes_gpc,虽然废弃十年,但你猜怎么着?上周审计扫出来两个Docker镜像,基础镜像是ubuntu:14.04,里面php.ini里magic_quotes_gpc=on是开着的,注释都没加。这种事儿,讲给谁听?总编?他正忙着把“三层硬核防御体系”改成“三层生存型防御实践”,说是怕审核组觉得“硬核”太刺眼。改完他又删了“生存型”,换成了“演化型”。演什么化?演着演着就没了呗。 下次选题会我打算闭嘴。就坐那儿,等笔滚到地上,等咖啡洒出来,等手机再响一次。反正“三层”这词,他们爱用多少层用多少层——我只管把PDO::ATTR_EMULATE_PREPARES=false那一行,钉死在每份配置模板第一行注释里,哪怕注释写成“本行删了,服务器今晚就炸”。 (编辑:站长网) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |


PHP实时交互卡顿?云架构师3步极速优化
PHP建站避坑:90%新手选错框架的真相
PHP工程师转行新能源小程序,半年月入12万
Go视角下的跨界融合:PHP工程师的技术新启迪