JAVA常用框架SQL注入审计
字数 1606 2025-08-26 22:11:28

Java常用框架SQL注入审计指南

一、JDBC拼接不当造成的SQL注入

1. Statement与PreparedStatement的区别

JDBC执行SQL语句有两种方式:

  • Statement:直接拼接SQL语句,存在SQL注入漏洞
  • PreparedStatement:对SQL语句进行预编译,能有效防止SQL注入

2. Statement示例(存在漏洞)

@RequestMapping("/jdbc/vuln")
public String jdbc_sqli_vul(@RequestParam("username") String username) {
    Statement statement = con.createStatement();
    String sql = "select * from users where username = '" + username + "'";
    ResultSet rs = statement.executeQuery(sql);
    // ...
}

攻击示例:username = joychoun' or '1' = '1'

3. PreparedStatement示例(安全)

@RequestMapping("/jdbc/sec")
public String jdbc_sqli_sec(@RequestParam("username") String username) {
    String sql = "select * from users where username = ?";
    PreparedStatement st = con.prepareStatement(sql);
    st.setString(1, username);
    ResultSet rs = st.executeQuery();
    // ...
}

4. JDBC易产生漏洞点

4.1 未使用占位符

直接拼接SQL语句仍会存在SQL注入漏洞

4.2 使用IN语句

String sql = "delete from users where id in(" + delIds + "); //存在SQL注入

解决方案:遍历传入的对象个数,使用"?"占位符

4.3 使用LIKE语句

String sql = "select * from users where password like '%" + con + "%'"; //存在SQL注入

4.4 %和_未过滤

预编译不能处理这些符号,需要手动过滤,否则会造成慢查询(DOS)

4.5 ORDER BY等无法预编译

String sql = "Select * from news where title =? " + "order by '" + time + "' asc";

解决方案:需要手动过滤

二、MyBatis框架下的SQL注入

1. ${}与#{}的区别

  • ${Parameter}:直接拼接SQL,存在注入风险
  • #{Parameter}:预编译方式,安全

2. ${}示例(存在漏洞)

@Select("select * from users where username = '${username}'")
List<User> findByUserNameVuln01(@Param("username") String username);

3. #{}示例(安全)

@Select("select * from users where username = #{username}")
User findByUserName(@Param("username") String username);

4. MyBatis易产生SQL注入的场景

4.1 模糊查询

错误写法:

<select id="findByUserNameVuln02" parameterType="String" resultMap="User">
    select * from users where username like '%${_parameter}%'
</select>

正确写法:

<select id="findByUserNamesec" parameterType="String" resultMap="User">
    select * from users where username like concat('%',#{_parameter},'%')
</select>

不同数据库的正确写法:

  • MySQL: concat('%',#{username},'%')
  • Oracle: '%' || #{username} || '%'
  • SQLServer: '%' + #{username} + '%'

4.2 使用IN语句

错误写法:

<select id="findByUserNameVuln04" parameterType="String" resultMap="User">
    select * from users where id in (${id})
</select>

正确写法:使用foreach

id in <foreach collection="ids" item="item" open="(" separator="," close=")">
    #{ids}
</foreach>

4.3 使用ORDER BY语句

与JDBC同理,需要手动过滤

三、MyBatis-Plus框架下的SQL注入

1. 条件构造器Wrapper

  • Wrapper:条件构造抽象类
  • AbstractWrapper:查询条件封装
  • LambdaQueryWrapper:Lambda语法查询
  • QueryWrapper:Entity对象封装操作

2. 常见注入场景

2.1 apply方法

错误写法(存在漏洞):

wrapper.eq("author", author).apply("title=" + title);

正确写法:

wrapper.eq("author", author).apply("title={0}", title);

2.2 last方法

wrapper.last("order by " + column); //存在SQL注入风险

2.3 exists/notExists

wrapper.exists("select title from tutorials where title = " + title); //存在风险

2.4 having

wrapper.select().groupBy("author").having("id > " + id); //存在风险

2.5 orderBy

wrapper.select().orderBy(true, true, column); //存在风险

2.6 groupBy

wrapper.select().groupBy(column); //存在风险

2.7 inSql/notInSql

wrapper.select().inSql(column, "select id from tutorials where id >" + id); //存在风险

3. 分页插件风险

3.1 PaginationInnerInterceptor

personPage.addOrder(OrderItem.asc(order)); //order未过滤则存在SQL注入

3.2 pagehelper风险方法

  • com.github.pagehelper.Page.setOrderBy(String)
  • com.github.pagehelper.page.PageMethod.startPage(int,int,String)
  • com.github.pagehelper.PageHelper.startPage(int,int,String)

四、Hibernate框架下的SQL注入

1. HQL注入

错误写法:

String hql = "from People where username = '" + username + "' and password = '" + password + "'";

2. 安全参数绑定方式

2.1 命名参数

Query<User> query = session.createQuery("from users name = :name", User.class);
query.setParameter("name", parameter);

2.2 位置参数

Query<User> query = session.createQuery("from users name = ?1", User.class);
query.setParameter(1, parameter);

2.3 命名参数列表

Query<User> query = session.createQuery("from users where name in (:names)", User.class);
query.setParameter("names", names);

2.4 类实例

user1.setName("g1ts");
Query<User> query = session.createQuery("from users where name =:name", User.class);
query.setProperties(user1);

2.5 HQL拼接过滤

String str = StringEscapeUtils.escapeSql("'");

3. 原生SQL风险

错误写法:

Query<People> query = session.createNativeQuery("select * from user where username = '" + username + "' and password = '" + password + "'");

正确写法:

Query<User> query = session.createNativeQuery("select * from user where name = :name");
query.setParameter("name", parameter);

总结

  1. 始终优先使用预编译:在JDBC中使用PreparedStatement,在MyBatis中使用#{}
  2. 特殊场景需注意:ORDER BY、GROUP BY、表名等无法预编译的场景需要手动过滤
  3. 框架特定风险:了解各ORM框架的特殊风险点(如MyBatis的${},Hibernate的HQL拼接)
  4. 过滤特殊字符:对于必须拼接的场景,使用StringEscapeUtils等工具过滤
  5. 审计重点:关注SQL语句拼接点、动态SQL生成点、ORM框架的特殊方法
Java常用框架SQL注入审计指南 一、JDBC拼接不当造成的SQL注入 1. Statement与PreparedStatement的区别 JDBC执行SQL语句有两种方式: Statement :直接拼接SQL语句,存在SQL注入漏洞 PreparedStatement :对SQL语句进行预编译,能有效防止SQL注入 2. Statement示例(存在漏洞) 攻击示例: username = joychoun' or '1' = '1' 3. PreparedStatement示例(安全) 4. JDBC易产生漏洞点 4.1 未使用占位符 直接拼接SQL语句仍会存在SQL注入漏洞 4.2 使用IN语句 解决方案:遍历传入的对象个数,使用"?"占位符 4.3 使用LIKE语句 4.4 %和_ 未过滤 预编译不能处理这些符号,需要手动过滤,否则会造成慢查询(DOS) 4.5 ORDER BY等无法预编译 解决方案:需要手动过滤 二、MyBatis框架下的SQL注入 1. ${}与#{}的区别 ${Parameter} :直接拼接SQL,存在注入风险 #{Parameter} :预编译方式,安全 2. ${}示例(存在漏洞) 3. #{}示例(安全) 4. MyBatis易产生SQL注入的场景 4.1 模糊查询 错误写法: 正确写法: 不同数据库的正确写法: MySQL: concat('%',#{username},'%') Oracle: '%' || #{username} || '%' SQLServer: '%' + #{username} + '%' 4.2 使用IN语句 错误写法: 正确写法:使用foreach 4.3 使用ORDER BY语句 与JDBC同理,需要手动过滤 三、MyBatis-Plus框架下的SQL注入 1. 条件构造器Wrapper Wrapper :条件构造抽象类 AbstractWrapper :查询条件封装 LambdaQueryWrapper :Lambda语法查询 QueryWrapper :Entity对象封装操作 2. 常见注入场景 2.1 apply方法 错误写法(存在漏洞): 正确写法: 2.2 last方法 2.3 exists/notExists 2.4 having 2.5 orderBy 2.6 groupBy 2.7 inSql/notInSql 3. 分页插件风险 3.1 PaginationInnerInterceptor 3.2 pagehelper风险方法 com.github.pagehelper.Page.setOrderBy(String) com.github.pagehelper.page.PageMethod.startPage(int,int,String) com.github.pagehelper.PageHelper.startPage(int,int,String) 四、Hibernate框架下的SQL注入 1. HQL注入 错误写法: 2. 安全参数绑定方式 2.1 命名参数 2.2 位置参数 2.3 命名参数列表 2.4 类实例 2.5 HQL拼接过滤 3. 原生SQL风险 错误写法: 正确写法: 总结 始终优先使用预编译 :在JDBC中使用PreparedStatement,在MyBatis中使用#{} 特殊场景需注意 :ORDER BY、GROUP BY、表名等无法预编译的场景需要手动过滤 框架特定风险 :了解各ORM框架的特殊风险点(如MyBatis的${},Hibernate的HQL拼接) 过滤特殊字符 :对于必须拼接的场景,使用StringEscapeUtils等工具过滤 审计重点 :关注SQL语句拼接点、动态SQL生成点、ORM框架的特殊方法