未经许可,不得转载。
文章目录

在 Java 应用程序中,常见的数据库访问方式包括 JDBC、MyBatis 和 Hibernate。其中,JDBC 的 SQL 注入风险及代码审计方法已在 【Java 代码审计 | 第二篇】SQL 注入之 JDBC 详细介绍过,本文将重点介绍 MyBatis 和 Hibernate 可能存在的安全风险及代码审计方法。
Hibernate
在 Java 开发中,Hibernate 是一种流行的 ORM(对象关系映射) 框架,它将数据库中的表与 Java 类进行映射,简化了数据库操作。通过 Spring 的依赖注入,可以轻松地与 Hibernate 集成,从而提高开发效率并减少 SQL 操作的复杂性。
以下是一个简单的 Hibernate 使用示例:
@Autowired
CategoryDAO categoryDAO; // 依赖注入
@RequestMapping("/hibernate")
public String hibernate(@RequestParam(name = "id") int id) {
Category category = categoryDAO.getOne(id); // 使用 Hibernate 获取数据
return category.getName(); // 返回类别名称
}
在这个例子中,CategoryDAO 是通过 依赖注入 获取的一个数据访问对象,它封装了对数据库的操作。通过 getOne(id) 方法,我们可以直接从数据库中查询指定 ID 的 Category 实体,并返回其名称。
Hibernate 默认采用 预编译 SQL 机制来执行查询。当我们使用 Hibernate 的方法时,它会自动处理查询语句并使用预编译的方式与数据库交互,从而避免了 SQL 注入风险。
然而,在某些复杂场景下,我们可能需要使用 手写 SQL。例如,当我们使用 Hibernate 的 @Query 注解或 Session.createSQLQuery() 方法时,涉及动态拼接 SQL 的场景(如 LIKE、ORDER BY、IN 等)时,Hibernate 不再自动生成 SQL 语句。
MyBatis
MyBatis 是一个持久层框架,用于简化 Java 应用程序与数据库之间的交互。它本质上是对 JDBC(Java Database Connectivity)的封装,提供了 SQL 语句的映射功能,使开发者能够更方便地执行数据库操作。
在 MyBatis 中,获取参数值的方式主要有两种:${} 和 #{}
#{}(安全,防止 SQL 注入)
#{} 解析时会转换为 SQL 预编译的占位符 ?,并使用 PreparedStatement 进行参数绑定。由于 MyBatis 通过预编译机制来自动转义参数,因此可以有效防止 SQL 注入。
示例:
<select id="getUserById" resultType="User">
SELECT * FROM users WHERE id = #{id}
</select>
最终执行的 SQL:
SELECT * FROM users WHERE id = ?
${}(不安全,易受 SQL 注入攻击)
${} 直接将参数值拼接到 SQL 语句中,相当于字符串替换,不会使用预编译机制,因此容易受到 SQL 注入攻击。
示例:
<select id="getUserById" resultType="User">
SELECT * FROM users WHERE id = ${id}
</select>
若用户输入 1 OR 1=1,最终执行的 SQL 可能变为:
SELECT * FROM users WHERE id = 1 OR 1=1
这将导致 SQL 注入攻击。
在 MyBatis 中,映射文件(Mapper XML) 的命名规则通常是:
表所对应的实体类的类名 + Mapper.xml
例如,若有一个 User 实体类,则其对应的 MyBatis 映射文件一般命名为:
UserMapper.xml
因此,此处的代码审计方法为:在所有 *Mapper.xml 文件中搜索 ${},查看是否有用户输入的数据直接拼接到 SQL 语句中。
MyBatis 的两种写法
注意:在 Maven 项目 中,mapper.xml 文件必须存放在 resources 目录 下,才能被 MyBatis 正确识别并加载。
MyBatis 主要提供了两种方式来执行 SQL 查询:基于注解的方式 和 基于 XML 配置的方式。
1. 基于注解的 MyBatis 写法
@Mapper
public interface CategoryMapper {
@Select("SELECT * FROM category_ WHERE name= '${name}' ")
CategoryM getByName(@Param("name") String name);
}
在这种方式下,SQL 语句直接写在 @Select 注解 中,MyBatis 会在运行时解析并执行该 SQL 语句。
2. 基于 XML 配置的 MyBatis 写法
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.seaii.springboot.mapper.CategoryMapper">
<select id="get" resultType="cn.seaii.springboot.pojo.CategoryM">
SELECT * FROM category_ WHERE id= ${id}
</select>
</mapper>
在 XML 方式中,SQL 语句被写入独立的 mapper.xml 文件中,并通过 namespace 关联接口,实现 SQL 的分离管理。
上述代码中的 WHERE name= '${name}' 和 WHERE id= ${id} 直接拼接用户输入,可能导致 SQL 注入漏洞。
3. 解决方案
应该使用 #{} 方式 绑定参数,避免 SQL 注入问题。
基于注解
@Mapper
public interface CategoryMapper {
@Select("SELECT * FROM category_ WHERE name= #{name}")
CategoryM getByName(@Param("name") String name);
}
基于 XML
<select id="get" resultType="cn.seaii.springboot.pojo.CategoryM">
SELECT * FROM category_ WHERE id = #{id}
</select>
这样,MyBatis 会自动 将参数作为占位符处理,避免 SQL 注入风险。
4.漏洞案例
让我们看看代码审计SQL注入的全流程。
1、在所有 *Mapper.xml 文件中搜索 ${}
2、如图,UserMapper.xml 中的 namespace 指向了 UserDao.java 接口,UserMapper.xml 里的每个 SQL 标签(如 <select> )的 id 属性值对应 UserDao.java 中的一个方法。并且 SQL 语句存在 nickName 及 userName。

来到 UserDao.java 界面后,按 “Ctrl + 鼠标右键”,查看哪些代码文件或类引用了UserDao.java中的getFuzzyUserByPage方法:

如上图所示,UserDao.java里的getFuzzyUserByPage方法被UserServiceImpl.java的第 45 行代码引用,执行语句为List<MyUser> fuzzyUserByPage = userDao.getFuzzyUserByPage(myUser);
跟踪到UserServiceImpl.java的第 45 行代码:

如上图箭头所示,userService.getAllUsersByPage方法调用时传入了MyUser类型的参数myUser 。
可以推断,MyUser.java定义了与用户相关的数据模型类,包含用户的各种属性,比如用户ID等。
上图给出了调用接口:/api/user
进入MyUser.java,确实存在用户属性:

因此以上是整个的传参处理流程。
由第一张图可知,${}的参数处理方式存在SQL注入,因此可直接调用接口传参payload。

300

被折叠的 条评论
为什么被折叠?



