【Java代码审计 | 第三篇】SQL注入之Hibernate、MyBatis

未经许可,不得转载。

在这里插入图片描述

在 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。

在这里插入图片描述

<think> 首先,用户的问题是基于之前的引用内容,查找使用Java解决SQL注入问题的方法。用户引用了几个来源:[^1]、[^2]、[^3]、[^4]、[^5],这些引用提供了关于JavaSQL注入的解决方法。 关键点从引用中提取: - 引用[1]:提到使用PreparedStatement的预编译来防范大部分SQL注入攻击。 - 引用[2]:强调了使用预编译语句、ORM框架、输入验证和过滤以及最小化数据库权限。 - 引用[3]:展示了使用Statement可能导致SQL注入问题的例子。 - 引用[4]:讨论了使用setter方法(如setShort, setString)来设置IN参数值,确保类型兼容。 - 引用[5]:描述了预编译时如何转义输入,防止注入。 用户的需求是“请问Java解决SQL注入的方法”,所以我需要提供清晰、结构化的回答,使用中文,并遵循系统指令。 系统指令回顾: - 行内数学表达式用$...$,但这里可能不涉及数学。 - 独立公式用$$...$$,但SQL注入不涉及公式。 - LaTeX语法正确:确保代码块正确。 - 使用中文回答。 - 生成相关问题:在回答末尾添加相关问题。 - 引用段落末尾添加引用标识:[^1], [^2], 等。 - 回答结构清晰,帮助用户逐步解决问题,保证真实可靠。 回答结构: 1. **引言**:简要解释SQL注入问题和在Java中的重要性。 2. **主要解决方法**:基于引用,重点介绍PreparedStatement。 - 解释为什么PreparedStatement有效。 - 提供Java代码示例。 3. **其他方法**:提到ORM框架、输入验证等。 4. **总结**:强调最佳实践。 5. **相关问题**:添加2-3个相关问题。 代码示例:从引用中,引用[1]和[5]提到了代码,我需要使用代码块展示。 引用标识:在回答中引用时,在相关段落末尾添加[^1], [^2], 等。 相关问题生成: - 基于主题,如优化、其他安全措施等。 - 例如: 1. 如何验证用户输入以防止SQL注入? 2. ORM框架如何帮助防范SQL注入? 3. 在Java中,除了PreparedStatement,还有哪些安全措施? 确保回答真实可靠:基于提供的引用内容,不要添加外部知识。 完整回答框架: - 开头:介绍SQL注入Java解决方法。 - 主体: - 使用PreparedStatement:详细解释和代码。 - 其他方法:输入验证、ORM、最小权限。 - 结尾:总结。 - 相关问题。 代码示例:使用引用[1]的例子或通用例子。 - 引用[1]有代码:SELECT * FROM `user` where user = ... 和PreparedStatement使用。 - 引用[5]有登录示例。 在回答中自然地添加引用。 </think>基于您提供的引用内容,我将详细解释Java中解决SQL注入问题的核心方法。SQL注入是一种常见的安全漏洞,攻击者通过恶意输入篡改SQL查询语句,从而窃取或破坏数据库数据[^2]。在Java中,主要通过**预编译语句**(如PreparedStatement)来防范SQL注入,因为它能自动转义特殊字符并分离SQL逻辑与用户输入[^1][^5]。以下是结构化的解决方法,逐步说明。 ### 主要解决方法:使用PreparedStatement PreparedStatement是Java JDBC API的核心组件,它通过参数化查询(Parameterized Queries)防止SQL注入。工作原理是将SQL语句预编译后绑定用户输入值,而非直接将输入拼接到语句中,从而避免恶意代码执行[^1][^4]。 #### 步骤说明: 1. **定义SQL模板**:使用问号 `?` 作为占位符,代替直接嵌入变量。 2. **创建PreparedStatement对象**:通过`Connection.prepareStatement()`方法。 3. **设置参数值**:使用`setXxx()`方法(如`setString()`、`setInt()`)为占位符赋值,确保输入值类型与SQL类型兼容,并自动转义特殊字符(如单引号被转义为`\'`)[^4][^5]。 4. **执行查询**:调用`executeQuery()`或`executeUpdate()`方法。 #### Java代码示例: 以下是一个安全登录验证的示例,基于引用[1]和[5]的案例。假设我们有一个用户表`user`,包含`username`和`password`字段。 ```java import java.sql.*; public class UserAuthentication { public boolean authenticate(String username, String password) throws SQLException { // SQL模板使用占位符? String sql = "SELECT * FROM user WHERE username = ? AND password = ?"; try (Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/db", "user", "pass"); PreparedStatement pstmt = conn.prepareStatement(sql)) { // 设置参数值(自动转义输入) pstmt.setString(1, username); // 第一个占位符对应username pstmt.setString(2, password); // 第二个占位符对应password try (ResultSet rs = pstmt.executeQuery()) { return rs.next(); // 如果查询到记录,返回true } } } } ``` #### 为什么有效? - 当输入如`username = "admin' -- "`时,`setString()`方法会将单引号转义为`\'`,使攻击失效(例如,转换为`username = 'admin\' -- '`),从而阻止了注释符`--`篡改查询[^5]。 - 对比不安全的`Statement`方式(如引用[3]所示),直接拼接字符串(`"SELECT * FROM user WHERE username='" + username + "'"`)会导致注入漏洞[^3]。 ### 其他辅助方法 除了PreparedStatement,结合以下措施可增强安全性: - **输入验证与过滤**:在业务层对用户输入进行校验(如使用正则表达式检查用户名是否仅含字母数字),拒绝非法字符[^2]。 - **使用ORM框架**:如HibernateMyBatis,它们自动生成参数化查询,减少手动SQL编写错误[^2]。 - **最小化数据库权限**:应用数据库账户仅授予必要的操作权限(如只读或特定表的访问),限制攻击影响[^2]。 - **错误处理**:避免在错误消息中暴露数据库结构(如表名),防止信息泄露。 ### 总结 在Java中,解决SQL注入最可靠的方法是采用PreparedStatement进行参数化查询,它能有效隔离用户输入和SQL逻辑[^1][^4]。结合输入验证和ORM框架,可构建更健壮的安全防线[^2]。实际开发中,应始终避免使用`Statement`类执行动态SQL,并定期进行安全审计。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

秋说

感谢打赏

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值