sql注入防御方法
2019年,入防某安全团队在GitHub上爬取了上万条包含SQL注入漏洞的御方代码片段。结果挺吓人:超过六成的入防漏洞,根源就一句话——把用户输入直接拼进了SQL字符串。御方这不是入防什么高深攻击,就是御方程序员图省事留下的口子。 防御SQL注入,入防核心就一条:别让用户输入变成SQL代码的御方一部分。听起来简单,入防做起来五花八门。御方 几乎所有主流语言都支持参数化查询,但很多人用错了。御方比如Java里有人写 Python的sqlite3、PHP的PDO、C#的SqlParameter,原理都一样。我见过一个老项目从拼接改成参数化之后,安全扫描的告警数从47条掉到0。改动量?不到二十行。 有人喜欢用正则过滤 一个真实案例:某电商网站被注入后,攻击者直接 Hibernate、MyBatis这些框架默认会做参数化,但开发者一旦手写HQL或MyBatis的 云WAF能挡掉大部分自动化扫描工具,但别把它当保险箱。有个客户买了某商业WAF,结果攻击者用分块传输编码就绕过去了。WAF更适合争取修复时间,不是根治方案。另外,数据库日志要开,至少记录所有执行过的SQL。某次应急响应,我们就是从慢查询日志里翻出了攻击者的注入语句,才确认了入口点。 说到底,SQL注入防御没有银弹。参数化查询是地基,最小权限是安全网,输入验证和WAF是补充。最怕的是开发者觉得“我的代码不会被盯上”——攻击者可不挑食。一个自动扫描器一天能扫几十万个IP,你的疏忽就是它的机会。参数化查询:最省心的入防那道墙
String sql = "SELECT * FROM users WHERE name = '" + name + "'",入防然后指望PreparedStatement能救命——没用的御方,拼接发生在PreparedStatement之前,入防数据库看到的已经是一条完整SQL了。正确写法是SELECT * FROM users WHERE name = ?,再把name塞进占位符。数据库会先编译SQL结构,参数永远只是数据,不可能变成指令。输入验证:别指望它当主力
union、select这些关键词。这招在CTF比赛里经常被绕过——大小写混写、注释符插入、编码变形,花样太多了。输入验证适合做辅助,比如限制用户名只能字母数字下划线,长度不超过20。但它不能替代参数化查询。把过滤当唯一防线,等于用纱窗挡子弹。最小权限:数据库账号别用root
DROP TABLE orders。为什么能删表?因为Web应用连数据库用的账号有DROP权限。如果那个账号只有SELECT和INSERT,最坏情况也就是数据被拖走,不至于整个业务停摆。给每个应用单独建数据库账号,只授予它真正需要的操作。这条建议老生常谈,但每次渗透测试都能发现有人偷懒。ORM不是万能药
${ }占位符,照样出事。MyBatis里#{ }是预编译,${ }是直接拼接。我见过一个项目为了“动态排序”,用${ orderBy}接收前端参数,结果注入点就在那儿。ORM用对了省心,用错了比手写SQL还危险。WAF和日志:事后补救