开发网站时,很多人只关注功能能不能跑通,却忽略了数据库查询的安全性。一个典型的例子是用户登录功能。假设你写了一段代码,把用户名和密码直接拼接到 SQL 语句中:
String query = "SELECT * FROM users WHERE username = '" + username + "' AND password = '" + password + "'";
看起来没问题,但如果有人在登录框里输入用户名 ' OR '1'='1,那整个 SQL 就变成了:
SELECT * FROM users WHERE username = '' OR '1'='1' AND password = '...'
由于 '1'='1' 永远成立,攻击者可能绕过验证直接登录。这就是典型的 SQL 注入攻击。
别再手动拼接 SQL 字符串
很多漏洞都源于字符串拼接。无论用的是 Java、PHP 还是 Python,只要把用户输入直接塞进 SQL 语句,风险就埋下了。正确的做法是使用参数化查询(Prepared Statements),让数据库引擎自动处理输入内容的转义。
比如在 Java 中,应该这样写:
String sql = "SELECT * FROM users WHERE username = ? AND password = ?";
PreparedStatement stmt = connection.prepareStatement(sql);
stmt.setString(1, username);
stmt.setString(2, password);
ResultSet rs = stmt.executeQuery();
这里的问号是占位符,实际值由数据库驱动安全填充,根本不会被当作 SQL 代码执行。
ORM 框架也不能完全依赖
有些人觉得用了 Hibernate 或 MyBatis 就高枕无忧,其实不然。如果在 HQL 或 XML 映射文件里又手动拼接字符串,照样会出事。比如这段 MyBatis 的写法:
<select id="findUser" resultType="User">
SELECT * FROM users WHERE username = ${username}
</select>
注意这里的 ${} 是直接替换,非常危险。应该改用 #{} 来启用参数化:
<select id="findUser" resultType="User">
SELECT * FROM users WHERE username = #{username}
</select>
对输入做基本过滤和类型检查
虽然参数化能挡住大部分攻击,但加上输入校验更保险。比如手机号字段,如果用户输了个 SQL 语句进去,系统就应该直接拒绝。可以在前端提示,更要在后端验证。
例如,邮箱字段必须符合基本格式,年龄只能是数字,搜索关键词长度要有上限。这些规则不仅防注入,还能提升整体健壮性。
错误信息别暴露太多细节
调试时看到“SQL syntax error near 'xxx'”挺方便,但上线后这等于给攻击者指路。应该统一返回模糊错误,比如“操作失败”,具体日志记在服务器上就行。
曾经有个电商网站,搜索商品时报错直接打印了完整 SQL,连表名字段名都暴露了。攻击者顺着这条线挖出了整个数据库结构,最后拖走了几万条用户数据。
定期审查代码里的 SQL 片段
团队协作开发时,新人可能不了解规范,随手写了拼接语句。建议在代码评审中加入一条:所有数据库查询是否都使用了参数化?可以用工具扫描项目里有没有 SELECT.*+ variable 这类危险模式。
一个小疏忽可能带来大麻烦。安全不是某个环节的事,而是每个开发者在写每一行代码时都要绷着的那根弦。