游客发表
2011年,入漏一位安全研究员在测试某电商网站时,洞论只用了不到三十秒就拿到了整个用户数据库的入漏管理权限。他没写一行恶意代码,洞论也没破解任何密码——只是入漏在搜索框里敲了一段带单引号的SQL语句。这个漏洞叫SQL注入,洞论到今天依然是入漏Web安全领域最致命的威胁之一。
它的洞论原理其实很朴素。网站需要从数据库取数据,入漏比如根据用户ID查订单,洞论后台代码可能是入漏这样拼SQL的:"SELECT * FROM orders WHERE user_id = " + input。如果用户老老实实输入数字,洞论一切正常。入漏但如果他输入的洞论是1 OR 1=1,SQL语句就变成了SELECT * FROM orders WHERE user_id = 1 OR 1=1。入漏条件永远为真,所有订单全部返回。攻击者要做的,只是猜对字段数量和表名。
我见过一个真实案例。某地方政务系统有一个查询接口,参数直接拼进SQL。安全人员用UNION SELECT把管理员表的用户名和密码哈希拖了出来。密码用的是MD5,没加盐,彩虹表一查就出来了。整个过程不到两分钟。系统上线三年,没人发现。这种漏洞的可怕之处不在于技术复杂度,而在于它太容易被利用,又太容易被忽略。
为什么SQL注入至今没被消灭?一个原因是开发习惯。很多老系统用PHP或ASP写的时候,框架本身不强制参数化查询。程序员图省事,直接字符串拼接。另一个原因是防御手段经常被绕过。比如有人用addslashes()转义单引号,但攻击者可以用0x27十六进制编码,或者利用GBK编码宽字节注入。黑名单过滤更是千疮百孔——你禁了UNION,他用UNI/**/ON;你禁了空格,他用%09制表符。绕过方式比防御规则多得多。
真正有效的办法只有一个:参数化查询。把SQL语句的结构和用户输入的数据彻底分开。Java用PreparedStatement,Python用cursor.execute(sql, params),PHP用PDO的prepare()。数据库会先把SQL模板编译好,再把参数填进去。这时候即使用户输入' OR '1'='1,它也只是一个普通字符串,不会被当作SQL语法解析。我见过一个团队把老系统全部改成参数化查询后,渗透测试从“到处是洞”变成“无从下手”。
但参数化查询也有覆盖不到的地方。比如ORDER BY后面跟的列名、LIMIT后面的数字,这些位置不能直接绑参数。有些开发者图方便,直接拼进去,结果又留了注入点。这种场景下,白名单校验是唯一出路——只允许asc或desc,只允许数字。别想着用正则去过滤,正则写不好就是自欺欺人。
另一个常被忽视的是ORM框架。很多人以为用了Hibernate或MyBatis就万事大吉。但MyBatis的${ }和#{ }区别巨大——前者直接拼接,后者预编译。我审计过一个项目,开发者在ORDER BY里用了${ },结果整个系统被拖库。ORM不是护身符,用错了照样出事。
SQL注入的检测其实不难。手工测试时,在参数后面加单引号看是否报错,加AND 1=1和AND 1=2看返回结果是否不同。工具方面,sqlmap依然是首选,但别完全依赖它——有些注入点需要特定编码或延时判断,工具不一定能自动识别。我习惯先手工确认,再用工具跑数据。手工能发现工具漏掉的边缘情况。
最后说一个个人观察:很多SQL注入漏洞的根源不是技术问题,而是态度问题。开发时觉得“先上线再说”,安全测试时觉得“哪有那么巧被人发现”。但攻击者不这么想。他们用扫描器批量扫全网,一个漏洞几秒钟就能定位。你侥幸没被发现,只是还没轮到而已。把参数化查询当成肌肉记忆,比事后补救便宜得多。
友情链接