Java代码审计之SQL注入

准备:

创建数据库:

1
2
3
4
5
6
7
8
9
10
11
12
13
CREATE DATABASE sqlidemo;
use sqlidemo;

CREATE TABLE IF NOT EXISTS `users`(
`id` INT UNSIGNED AUTO_INCREMENT,
`username` VARCHAR(255) NOT NULL,
`password` VARCHAR(255) NOT NULL,
PRIMARY KEY (`id`)
)ENGINE=InnoDB DEFAULT CHARSET=utf8;


INSERT INTO `users` VALUES (1, 'admin', 'admin');
INSERT INTO `users` VALUES (2, 'power7089', 'power7089');

**一、**Jdbc SQL 注入

1.1 、动态拼接

SQL语句动态拼接导致的SQL注入漏洞是先前最为常见的场景

其主要原因是后端代码将前端获取的参数动态直接拼接到SQL语句中使用 java.sql.Statement 执行SQL语句从而导致SQL注入漏洞的出现。

在这里关键点有两个:

  1. 动态拼接参数。
  2. 使用 java.sql.Statement 执行SQL语句。

1.1.1 java.sql.Statement

Statement 对象用于执行一条静态的 SQL 语句并获取它的结果。

createStatement() :创建一个 Statement 对象,之后可使用 exec uteQuery() 方法执行SQL语句。

有漏洞:executeQuery() 方法 + 动态拼接

executeQuery(String sql) 方法:执行指定的 SQL 语句,返回单个 ResultSet 对象。

1
2
3
4
5
6
7
8
9
10
public String jdbcDynamic(@RequestParam("id") String id) throws    ClassNotFoundException, SQLException {    //id是String类型可以SQL注入
StringBuilder result = new StringBuilder();
// 注册驱动
Class.forName(driver);
// 获取连接
Connection conn = DriverManager.getConnection(url, user, password);
Statement statement = conn.createStatement();
//动态拼接字符串
String sql = "select * from users where id = '" + id + "'";
ResultSet rs = statement.executeQuery(sql);

暂时没有漏洞:

1
2
3
4
5
6
7
8
9
10
11
public String jdbcDynamicc(@RequestParam("id") int id) throws ClassNotFoundException, SQLException {   //id是int类型不可以SQL注入

StringBuilder result = new StringBuilder();
// 注册驱动
Class.forName(driver);
// 获取连接
Connection conn = DriverManager.getConnection(url, user, password);
Statement statement = conn.createStatement();
//动态拼接字符串
String sql = "select * from users where id = '" + id + "'";
ResultSet rs = statement.executeQuery(sql);

1.1.2 预编译:(java.sql.PreparedStatement)

提前编译sql语句执行模板,后续将参数(视为普通字符串,非数据库的语句)填入到模板中,避免了sql注入

PreparedStatement 是继承 Statement 的子接口。

PreparedStatement 会对SQL语句进行预编译,不论输入什么,经过预编译后全都以字符串来执行SQL语句。

PreparedStatement 会先使用 ? 作为占位符将 SQL 语句进行预编译,确定语句结构,再传入参数进行执行查询。

没有sql注入:

1
2
3
4
 // 安全代码
String sql = "select * from users where username = ?";
PreparedStatement preparestatement = conn.prepareStatement(sql);
preparestatement. (1, username); //1:第一个问号

有SQL注入:

1
2
3
4
//没有正确使用预编译方式,SQL语句还是进行了动态拼接
String sql = "select * from users where username = '" + username + "'";
PreparedStatement preparestatement = conn.prepareStatement(sql);
//也少了预编译的操作

预编译就像 “填空题模板”,它能安全地填入参数值,但不能动态改变SQL 结构(如列名、表名)。ORDER BY后面接的列名属于 SQL 结构,所以必须手动校验,不能直接用预编译的?

不能使用预编译的语句Order by

1.1.3 order by 注入

(order by 对结果进行排序)

在SQL语句中, order by 语句用于对结果集进行排序。 order by 语句后面需要是字段名或者字段位置。

1
2
3
4
// 错误的预编译用法(无法生效)
String sql = "SELECT * FROM products ORDER BY ?"; // 错误 ?不能代替列名
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, "price DESC"); // 无效,会被当作字符串参数
1
2
执行的sql语句为:
SELECT * FROM products ORDER BY 'price DESC'

二、Mybatis

MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

1、 Mybatis 中 #{} 和 ${} 区别

在Mybatis中拼接SQL语句有两种方式:一种是占位符 #{} ,另一种是拼接符 ${} 。

占位符 #{} :对传入的参数进行预编译转义处理。类似 JDBC 中的PreparedStatement 。

1
比如: select * from user where id = #{number} ,如果传入数值为1,最终会被解析成 select * from user where id = "1"

拼接符 ${} :对传入的参数不做处理,直接拼接,进而会造成SQL注入漏洞

1
2
比如:比如: select * from user where id = ${number} ,如果传入数值为1,最终会被解析成
select * from user where id = 1

#{} 可以有效防止SQL注入漏洞。 ${} 则无法防止SQL注入漏洞。

因此在对 JavaWeb 整合 Mybatis系统 进行代码审计时,应着重审计SQL语句拼接的地方。除非开发人员的粗心对拼接语句使用了 ${} 方式造成的SQL注入漏洞。

在Mybatis中有几种场景是不能使用预编译方式的,比如: order by 、 in , like


Java代码审计之SQL注入
http://example.com/2025/06/06/Java代码审计之SQL注入/
作者
XCDH
发布于
2025年6月6日
许可协议