游客发表
2019年夏天,入攻某电商平台的入攻安全团队在例行巡检时发现了一个奇怪现象:一个用户的订单查询请求里,order_id参数的入攻值不是数字,而是入攻一串看起来像数学公式的东西——1 AND (SELECT COUNT(*) FROM users)>0。这条请求返回了正常页面,入攻但后台日志显示数据库执行了一条完全不同的入攻查询。攻击者已经在试探了。入攻
这就是入攻SQL注入。说白了,入攻它利用的入攻是应用程序对用户输入"太信任"这件事。你打开一个网页,入攻输入用户名密码,入攻点登录。入攻前端把你的入攻输入打包发给后端,后端把这段输入拼进一条SQL语句里,入攻扔给数据库执行。问题就出在"拼"这个动作上。
假设后端代码写的是这样:
SELECT * FROM users WHERE username = '"+input+"' AND password = '"+pwd+"'" 正常输入admin和123456,拼出来的语句没毛病。但如果攻击者在用户名框里填' OR '1'='1,整条语句就变成了:
SELECT * FROM users WHERE username = '' OR '1'='1' AND password = '...' '1'='1'永远为真。数据库一看,条件成立,放行。攻击者连密码都不用猜,直接登录进去了。
这还只是最基础的玩法。真正让我觉得后背发凉的是UNION注入。攻击者可以构造一条查询,把另一张表的数据"接"到正常结果后面一起返回。比如一个商品详情页,URL里带?id=5,后端查的是SELECT name, price FROM products WHERE id=5。攻击者把id改成5 UNION SELECT username, password FROM users,页面上商品名称的位置直接显示出管理员账号和密码哈希。他什么都没黑,就是让数据库帮他把数据"顺便"查出来了。
更隐蔽的是盲注。有些网站不显示数据库返回内容,只告诉你"查询成功"或"查询失败"。攻击者就靠这个布尔值一位一位地猜数据。比如AND SUBSTRING((SELECT password FROM users LIMIT 1),1,1)='a',如果页面正常,说明密码第一位是a。不对就换b、c、d……一个字符一个字符试,听起来笨,但脚本跑起来几秒钟就能试完。还有一种时间盲注,用IF(条件, SLEEP(5), 0),页面延迟5秒返回就说明条件成立。连报错信息都不需要。
我见过一个真实案例。某小型SaaS服务商的登录接口用了字符串拼接,攻击者通过SQL注入拿到了整个用户表——包括邮箱、手机号、密码哈希。更糟的是,数据库账号有FILE权限,攻击者直接往服务器写了一个webshell。从注入到拿下服务器,前后不到二十分钟。
防御手段其实不复杂,但很多团队就是不做。参数化查询是第一道墙——让SQL语句的骨架先编译好,用户输入只作为数据填入,数据库根本不会把它当代码执行。这不是什么新技术,PreparedStatement在Java里存在了二十年。但总有人图省事,觉得拼接字符串写起来快。
第二道墙是权限控制。连接数据库的账号不该有DROP、FILE这类权限。一个只读接口就用只读账号,别偷懒用root连一切。第三,输入验证。不是让你过滤所有特殊字符——那太容易误伤——而是对参数类型做严格校验。id是整数就只接受整数,正则匹配不过的直接拒绝。
WAF能挡一部分自动化扫描,但别指望它。绕过WAF的技巧比WAF规则更新得快。真正靠谱的还是代码层面把口子堵死。每次看到有人用字符串拼接写SQL,我都觉得像把家门钥匙插在门上还贴了张纸条写"欢迎光临"。
SQL注入活了二十多年,不是因为它多高明,而是因为偷懒的代码一直存在。
友情链接