|
2017年,入漏Equifax公司泄露了1.43亿美国人的入漏社保号码、驾照信息和信用卡数据。入漏事后调查显示,入漏攻击者只是入漏通过一个未打补丁的Apache Struts漏洞,用SQL注入就掏空了数据库。入漏这场事故让Equifax付出了超过10亿美元的入漏代价,而整个攻击过程,入漏可能只是入漏有人在搜索框里敲了几行特殊字符。 SQL注入的入漏本质,是入漏程序把用户输入直接拼进了SQL查询语句。比如一个登录框,入漏开发者写的入漏是SELECT * FROM users WHERE username='$user' AND password='$pass'。正常用户输入"admin"和"123456",入漏查询就变成WHERE username='admin' AND password='123456'。入漏但如果攻击者在用户名栏输入' OR '1'='1,查询就变成WHERE username='' OR '1'='1' AND password='123456'——因为'1'='1'永远为真,整个WHERE条件直接短路,攻击者不需要知道任何密码就能登录。 这招老吗?老。1998年Rain Forest Puppy在Phrack杂志上公开讨论这种攻击手法时,很多数据库还没联网。但到了2023年,Verizon的数据泄露报告仍把SQL注入列为web应用攻击的第三大初始入口。为什么一个25岁的漏洞还活着?因为代码是人写的,人总会偷懒。很多开发者在写查询语句时,脑子里想的是"这个输入框只有内部人员能看到",或者"我们加了WAF应该能挡住"。可WAF不是万能药,它只能拦截已知的攻击模式,遇到编码绕过、分块传输、参数污染就抓瞎。 我见过最离谱的案例,是一家医院的预约系统。开发者在拼接患者姓名时没做转义,结果某个患者预约时在姓名栏填了'; DROP TABLE appointments; --。系统没崩,因为数据库用户权限不够,但这个恶作剧暴露了权限配置的混乱——应用账号居然有DROP权限。正规做法是应用账号只给SELECT、INSERT、UPDATE、DELETE,连DDL都不能碰,更别提管理员权限了。 防御SQL注入,最有效的手段是参数化查询,也就是预编译。把SQL结构固定,用户输入只当作参数值传入。Java的PreparedStatement、Python的cursor.execute带占位符、PHP的PDO预处理,原理都一样:数据库先解析SQL结构,再绑定参数,用户输入永远不可能改变结构。这招基本能堵死99%的注入点。剩下的1%是存储过程动态拼接、表名/列名需要动态传入这类场景,那就得靠白名单过滤加严格转义。 光有技术还不够,得看人。很多老系统是十年前写的,当时没人在意安全,现在改起来牵一发动全身。银行核心系统跑着COBOL和存储过程,里面可能藏着几十个注入点,但没人敢动,动坏了就是生产事故。这种系统只能靠数据库防火墙和深度检测来缓解,治标不治本。真正靠谱的做法,是在新项目里把参数化查询当铁律写进代码规范,Code Review时看到字符串拼接SQL直接打回。这活儿枯燥,但比出事后再开复盘会强。 SQL注入死不了,因为它扎根在人性里——懒、侥幸、图省事。每次看到有人还在写${ sql}这种模板,我都想叹气。数据库是最后一道防线,但防线前的代码,才是真正该守的地方。 |