准备:
创建数据库:
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注入漏洞的出现。
在这里关键点有两个:
- 动态拼接参数。
- 使用 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 { 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 {
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);
|
有SQL注入:
1 2 3 4
| 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