1 登录认证的SQL注入
再以前没有orm框架时,很多都是拼接的sql语句,有了hibernate、jpa、mybatis 等orm框架后, 开发变得更加容易,但是自定义的sql 场景仍然存在,这里我们以登录接口为例。
1.1 自定义sql漏洞代码示例
这是一个登陆接口,使用自定义的sql, 原理是使用Statement对象执行查询操作。
// 用户登录验证(存在SQL注入)
public boolean login(String username, String password) throws Exception {
Connection conn = DriverManager.getConnection(DB_URL, DB_USER, DB_PASS);
String sql = "SELECT * FROM users WHERE username = '" + username + "' AND password = '" + password + "'";
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql);
return rs.next(); // 若查询到记录则登录成功
}
1.2 正常输入逻辑
-- 用户输入:username="admin", password="123456"
SELECT * FROM users WHERE username = 'admin' AND password = '123456';
按照预期逻辑,仅当用户名密码匹配时返回数据,一切都很完美。
1.3 非正常输入会怎么样呢?
-
入参: username = "admin’ – "
SQL:SELECT … WHERE username = ‘admin’ – ’ AND password = ‘…’
结果:密码被绕过了 -
入参:username = “’ OR ‘1’='1”
SQL:SELECT … WHERE username = ‘’ OR ‘1’=‘1’ AND password = ‘…’
结果:布尔逻辑注入, 返回永远为true -
入参:username = "’ UNION SELECT null, username, password FROM users – "
SQL:SELECT … WHERE username = ‘’ UNION SELECT … FROM users – ‘…’
结果:联合查询泄露敏感数据
1.4 防御方案
public boolean safeLogin(String username, String password) throws Exception {
Connection conn = DriverManager.getConnection(DB_URL, DB_USER, DB_PASS);
String sql = "SELECT * FROM users WHERE username = ? AND password = ?";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, username); // 参数1绑定username
pstmt.setString(2, password); // 参数2绑定password
ResultSet rs = pstmt.executeQuery();
return rs.next();
}
使用PreparedStatement参数化查询, 预编译使SQL结构与参数分离,数据库将输入视为纯数据而非代码,从而实现非正常输入导致的sql注入。
1.2 Mybatis框架中的sql注入
错误示例:使用$符号传参
<slect id = 'login' paramType = "java.util.Map" resultType="java.util.Map">
SELECT * FROM t_user where user_name = ${userName} and password = ${password}
</select>
使用$传参,相当于直接使用参数拼接SQL语句,若是恶意输入,未作校验处理,则就有SQL注入的风险。
正确示例:使用#传入参数
<slect id = 'login' paramType = "java.util.Map" resultType="java.util.Map">
SELECT * FROM t_user where user_name = #{userName} and password = #{password} </select>
使用#传参, 预编译才能够真正达到将SQL语义逻辑和数据分离的效果。
2 什么是SQL注入?
根据上面的登陆场景,想必你已经对sql注入有了基础的了解,它是一种通过恶意构造输入参数,欺骗后端数据库执行非法SQL代码的攻击手段。随着互联网的发展,SQL注入攻击在过去二十年中一直是导致数据泄露的主要原因之一,因此了解并预防SQL注入对保护信息安全至关重要。
2.1 SQL 注入本质
在理解SQL注入之前,我们需要了解一下典型的Web应用程序如何与数据库进行交互。Web应用程序通常分为前端、后端和数据库三层架构。用户在前端输入信息后,后端会将这些信息传递给数据库执行相应的SQL查询或命令。数据库处理这些查询后,将结果返回给后端,然后再展示给用户。
它的本质是把用户输入的数据当作代码来执行,违背了“数据与代码分离”的原则。本质是程序未严格区分代码与数据,导致攻击者输入的恶意字符串被拼接为SQL语句的一部分,从而操控数据库行为。
- 用户能控制输入的内容;
- web应用把用户输入的内容带入到数据库执行;
2.2 SQL注入攻击流程
• 输入点的识别:
攻击者首先需要识别出应用程序中可能存在SQL注入漏洞的输入点。这些输入点通常是用户可以输入数据的位置,如登录表单、搜索框、URL参数等。
• 构造恶意SQL语句
攻击者在输入点注入恶意SQL代码。由于应用程序未对输入数据进行充分的验证或过滤,注入的SQL代码会直接拼接到SQL查询中。
• 执行恶意SQL代码
数据库接收到由攻击者构造的恶意SQL查询并执行。根据查询的内容,攻击者可以获取数据库中的敏感数据、修改表结构、删除数据等。
• 获取或破坏数据
执行后的SQL查询结果通常会返回给攻击者,从而获取到敏感信息或对数据库进行破坏。
2.3 典型危害场景
• 数据泄露
窃取用户隐私、支付信息等敏感数据(如通过UNION SELECT提取其他表内容)。
• 数据篡改
修改订单金额、删除业务数据(如利用UPDATE或DELETE语句)。
• 权限提升
利用数据库漏洞执行系统命令(如MySQL的xp_cmdshell)或上传WebShell。
• 拒绝服务
通过大规模注入攻击耗尽数据库资源。
2.4 防御流程
• 输入验证与过滤
在后端代码中严格验证和过滤用户输入,确保只接受合法的数据格式。
• 使用预编译语句
通过使用预编译语句(Prepared Statements)和参数化查询,避免SQL语句与用户输入直接拼接,从根本上防止SQL注入。
• 最小权限原则
限制数据库用户的权限,确保即使攻击者成功注入SQL语句,也无法对数据库造成严重破坏。
• 定期安全测试与监控
定期进行安全测试(如渗透测试)和数据库访问日志监控,及时发现和修复潜在的安全漏洞。
写在最后
本文主要讲解了SQL注入攻击的一些原理和场景案例,以及日常开发中的用法。其实系统的攻击还有很多方面,比如XML注入,OS注入等,虽然没有SQL注入攻击常见,也是我们需要注意防范的。
SQL注入防御需代码层(参数化查询+输入校验)、架构层(最小权限+错误抑制)、流程层(代码审计+渗透测试)三重联动。尤其注意:预编译不能解决所有问题(如动态表名、存储过程注入),需结合业务场景设计防护策略。建议定期使用SQLMap扫描接口,并参考OWASP Cheat Sheet更新防护方案。