2026-07-11 19:17:27
该事故的核心原因是实习生在MyBatis的Mapper文件中错误使用了${}代替#{},导致SQL注入漏洞被利用,攻击者通过构造恶意输入篡改了SQL逻辑,最终引发用户信息泄露和乱码问题。
一、技术原理:#{}与${}的本质区别#{}是预编译占位符
MyBatis将其转换为JDBC的PreparedStatement,参数以安全占位符(?)形式传递,数据库直接处理参数值,不会解析SQL语法。
示例:WHERE username = #{username} → 实际执行时参数被替换为?,攻击者无法修改SQL结构。
${}是字符串拼接
MyBatis直接将参数值拼接到SQL语句中,等同于原始字符串拼接,数据库会解析拼接后的完整SQL。
示例:WHERE username = ${username} → 若输入'admin' OR 1=1 --,最终SQL变为WHERE username = '' OR 1=1 --',导致条件恒真,返回所有数据。
恶意输入构造攻击者输入:username=' OR 1=1 --
'闭合原字符串,OR 1=1使条件恒真,--注释后续SQL,避免语法错误。
SQL动态拼接MyBatis直接拼接参数:
SELECT * FROM users WHERE username = '' OR 1=1 --'数据库执行时忽略注释部分,实际查询所有用户。
后果
信息泄露:攻击者获取全部用户数据(如私密信息、手机号)。
数据篡改:若漏洞存在于更新语句(如UPDATE users SET phone=${phone}),攻击者可输入乱码覆盖原数据。
基础修复:替换为#{}
<select id="getUserInfo" resultType="User"> SELECT * FROM users WHERE username = #{username} <!-- 正确写法 --></select>强制预编译配置在mybatis-config.xml中设置默认语句类型为预编译:
<settings> <setting name="defaultStatementType" value="PREPARED"/></settings>敏感字段加密存储
存储时加密(如AES):
public void setPhone(String phone) { this.phone = AES.encrypt(phone, secretKey);}查询时解密,即使泄露也为密文。
数据库权限最小化
创建只读用户,禁止删除、更新操作:
CREATE USER 'web_user'@'%' IDENTIFIED BY 'StrongPassword!';GRANT SELECT ON user_table TO 'web_user'@'%';REVOKE DELETE, UPDATE, DROP ON *.* FROM 'web_user'@'%';全局SQL注入检测
通过GitHub Actions集成SQLMap,自动扫描注入点:
name: SQL Injection Scanon: [push]jobs: sqlscan: steps: - uses: sqlmapproject/sqlmap@master with: args: -uMyBatis插件拦截危险SQL
自定义插件检测${}并抛出异常:
@Intercepts({@Signature(type=Executor.class, method="query", args={MappedStatement.class, Object.class})})public class SqlInspectPlugin implements Interceptor { public Object intercept(Invocation invocation) { String sql = ((MappedStatement)invocation.getArgs()[0]).getBoundSql(params).getSql(); if (sql.contains("${")) throw new DangerousSqlException("禁止使用${}!"); return invocation.proceed(); }}自动化代码审查规则(SonarQube)
配置规则禁止${},发现即标记为严重漏洞:
<rule key="MyBatisDynamicSqlCheck"> <regexp>(${w+})</regexp> <severity>CRITICAL</severity></rule>代码审查必查项
Mapper文件中是否出现${}。
SQL语句是否存在字符串拼接(如+或concat函数)。
新人培训重点
MyBatis安全规范:
所有查询参数必须用#{}。
动态表名/列名需使用<script>标签并严格校验:
<script>SELECT * FROM ${tableName} WHERE id=#{id}</script> <!-- 需白名单校验tableName -->监控预警
日志关键字告警:${、OR 1=1、--。
异常数据查询告警(如单次返回1000+条记录)。
OWASP Top 10:明确SQL注入为高危漏洞。
《Web安全攻防:渗透测试实战指南》第3章:详细讲解SQL注入原理与防御。
安全不是功能,而是开发者的基本素养,需贯穿需求、设计、编码、测试全流程。