PHP防SQL注入:13年模块开发者亲授三层硬核防御
|
去年9月份那会儿,运维突然在群里甩了张截图——某台MySQL的CPU直接飙到98%,慢查询日志里全是带单引号的SQL片段。当时我正啃着煎饼果子,手机屏幕都快被运维的咆哮刷爆了:“这他妈谁写的代码?预编译都不开?”我原以为又是哪个实习生手滑,结果一查监控,好家伙,是三年前离职的老王写的用户登录接口,那代码现在还在线上跑着呢。 那天下午我盯着那堆SQL看了俩小时,发现防御这事儿根本不是“加过滤函数”这么简单。老王那套代码里,用户输入直接拼进SQL的写法,比我家楼下早餐摊的油条还脆——他居然用`addslashes()`处理参数,我查了下PHP文档,这函数在GBK编码下能被直接绕过,连测试环境都不用搭就能复现注入。运维在旁边冷笑:“这代码能活三年,全靠咱们用户太老实。” 第一层防御我直接上了预编译,PDO的`prepare()`+参数绑定,这招现在连新手都知道,但老代码改起来比拆承重墙还麻烦。有个接口里嵌了七层循环,每个循环都在拼SQL,我改到第三层的时候,测试突然喊:“杨哥,登录页面白屏了!”我过去一看,好家伙,循环里有个变量名拼错了,导致预编译失败,直接抛了500错误——那天下午我喝了四杯咖啡,改完代码的时候,窗外雨下得跟运维骂人时的唾沫星子似的。 但预编译不是万能的。上周二,产品突然提了个需求:要在用户搜索里加模糊匹配。我原以为就是加个`LIKE`,结果测试用例里有个输入是`'%' OR '1'='1`——这明显是测注入的。我查了日志,发现这接口居然没用预编译,而是用了字符串拼接!一问才知道,是半年前优化查询性能时,有人觉得预编译慢,偷偷改成了拼接。我当场就把那哥们叫到会议室:“你知道这代码在生产环境跑了多久吗?六个月!这六个月里,攻击者随便发个请求就能清空用户表!” 第二层防御我加了存储过程。不是所有场景都适合预编译,比如动态表名、动态列名这种,预编译根本搞不定。我写了套存储过程,把所有可能被注入的点都锁死在数据库层。结果运维又跳出来:“存储过程会影响性能!”我直接甩给他一张压测图:“看清楚,同样的查询,存储过程比拼接SQL快15%。”他盯着图看了半天,最后憋出一句:“那…那为啥之前没人用?”我笑了笑:“因为之前没人像我这样,被注入攻击搞过三次。” 那天晚上我加班到十点,走的时候发现测试在工位上啃泡面。他抬头问我:“杨哥,存储过程真能防住所有注入吗?”我摇了摇头:“防不住傻逼。”他愣了下,我接着说:“比如有人非要在存储过程里用`EXECUTE IMMEDIATE`,那跟直接拼接SQL有什么区别?”他恍然大悟,我拍了拍他肩膀:“所以第三层防御才是关键——权限控制。” 权限控制这事儿,说白了就是“最小权限原则”。我查了所有数据库账号,发现有个账号居然有`DROP`权限——这账号是三年前搭建环境时用的,一直没收回。我当场就把这账号的权限砍到只剩`SELECT`和`UPDATE`,结果第二天产品就找上门:“杨哥,报表生成不了!”我查了日志,发现报表需要`CREATE TEMPORARY TABLE`权限,我直接怼回去:“那就改报表逻辑,别用临时表!”产品悻悻地走了,我转头对运维说:“以后新账号只给`SELECT`,其他权限必须走审批。” 这三层防御加起来,效果比我想象中还好。上周五,安全团队做了次渗透测试,用了二十种注入手法,结果全被挡在门外。测试报告里写了句:“这是近三年最干净的防御体系。”我拿着报告去找领导,结果他正跟产品吵架——产品非要给某个接口开`INSERT`权限,说“用户反馈操作太麻烦”。我站在门口听了五分钟,最后忍不住插了句:“开权限可以,但出了事你得背锅。”产品愣了下,转头对领导说:“那还是按杨哥的方案来吧。”
文章配图,仅供参考 现在回头看,这三层防御里,最管用的其实是权限控制。预编译和存储过程能防住大部分攻击,但总有人会绕过它们——比如通过文件上传、命令注入等其他方式。但权限控制是最后一道闸门,就算攻击者拿到代码,没权限也白搭。我原以为权限控制是最难推的,结果发现只要把锅甩清楚,产品比谁都积极——毕竟没人想背安全事故的锅。对了,那天改完代码后,我发现桌上多了杯奶茶,杯壁上贴着张纸条:“杨哥,谢谢。”我笑了笑,把纸条撕下来扔进垃圾桶——这行干久了,最清楚的就是:安全从来不是靠感谢维持的,是靠一次次被攻击、一次次修补,最后活下来的那口气。 (编辑:站长网) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |

