Java代码审计中的sql注入2.0
Java代码审计中的sql注入2.0
参考
Java OWASP 中的 SQL 注入代码审计 | Drunkbaby’s Blog (drun1baby.top)
JAVA常用框架SQL注入审计-先知社区 (aliyun.com)
预编译真的能完美防御SQL注入吗?_预编译能完全防止sql注入吗-CSDN博客
【MyBatis】预编译SQL与即时SQL_mybatis sql预处理-CSDN博客
MyBatis-Plus快速入门-(干货满满+超详细)_mybatis-plus 入门-CSDN博客
Mybatis-Plus 的 SQL 注入探讨
使用apply直接拼接sql语句
实际的apply场景
1 | |
可以直接看到这里的参数传入是直接拼接的,存在sql注入漏洞
但是由于selectOne函数的存在(只返回一条符合条件的记录),这里只能进行报错注入,万能密码会返回全部数据,由于特殊函数限制,无法注入成功
apply场景的防护
在语句后加上参数占位符{0}即可
1 | |
last方法产生的sql注入
last()方法重写后有两个方法
1 | |
此方法中lastSql可以直接用来编写SQL语句,写一个新接口
猜测实战黑盒利用场景为在线sql语句执行程序,可以编写新接口进行渗透
这里直接使用Drunkbaby师傅的漏洞环境
项目地址:[JavaSecurityLearning/JavaSecurity/Java 代码审计/CodeReview/JavaSec-Code/MybatisPluSqli at main · Drun1baby/JavaSecurityLearning (github.com)](https://github.com/Drun1baby/JavaSecurityLearning/tree/main/JavaSecurity/Java 代码审计/CodeReview/JavaSec-Code/MybatisPluSqli)
1 | |
启动环境时出现报错

在MybatisPluSqliApplication.java文件加上一行代码即可
1 | |
文件全部
1 | |
实操后发现不需要payload也是直接爆出所有数据的

可能是我的操作出了什么问题,但是本来控制代码就不是很多,感觉应该是没有问题的
正常payload
1 | |
order by相关的sql注入
刚开始想要搭这个环境的主要原因是没见过order by后拼接万能密码的
再次复习了一下order by相关
在 SQL 中,ORDER BY 子句用于对查询结果进行排序。ORDER BY 后的参数有以下要求和注意事项:
- 列名或别名
可以使用表中的列名或查询中定义的列别名。
示例:
1
SELECT name, age FROM students ORDER BY age;
- 列的序号(sql注入中常用于判断列数)
可以使用 SELECT 子句中列的序号(从 1 开始)。
示例:
1
SELECT name, age FROM students ORDER BY 2; -- 按第二列 age 排序
- 排序方式
默认是升序(
ASC),也可以显式指定。降序使用
DESC。示例:
1
SELECT name, age FROM students ORDER BY age DESC;4.多个排序条件
可以指定多个列,按优先级依次排序。
示例:
1
SELECT name, age, grade FROM students ORDER BY grade DESC, age ASC;
- 表达式或计算结果
可以使用表达式或计算结果进行排序。
示例:
1
SELECT name, salary, bonus FROM employees ORDER BY (salary + bonus) DESC;
- NULL 值排序
不同数据库对
1
NULL的排序处理可能不同:
- 一般情况下,
NULL在升序中排在最前,在降序中排在最后。 - 可以使用
NULLS FIRST或NULLS LAST明确指定。
- 一般情况下,
示例:
1
SELECT name, age FROM students ORDER BY age ASC NULLS LAST;
注意事项:
- 列必须存在:
ORDER BY中引用的列或别名必须在查询结果中有效。 - 性能影响:排序操作可能会影响查询性能,尤其是大数据集时。
并且order by语句后无法拼接变量,但是可以拼接sql语句,如果万能密码能够使用,猜测是被当作sql语句执行
exists/notExists 拼接产生的SQL 注入
1 | |
having 语句
1 | |
order by 语句(写order by 的时候不能预编译,下面有一个模块详细讲解)
相关接口写法
1 | |
group By/order by
inSql/notInSql
1 | |
这几种方法都是不支持预编译绑定参数,会直接将字符串拼接到最终 SQL 末尾**,**不会做任何参数绑定或转义处理。导致攻击者传入恶意代码造成语句拼接,用户信息泄露。
分页插件的 SQL 注入情况
分页插件自带的 addOrder() 方法
配置分页插件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31package com.drunkbaby.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MybatisPlusConfig {
/**
* 注册插件
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 添加分页插件
PaginationInnerInterceptor pageInterceptor = new PaginationInnerInterceptor();
// 设置请求的页面大于最大页后操作,true调回到首页,false继续请求。默认false
pageInterceptor.setOverflow(false);
// 单页分页条数限制,默认无限制
pageInterceptor.setMaxLimit(500L);
// 设置数据库类型
pageInterceptor.setDbType(DbType.MYSQL);
interceptor.addInnerInterceptor(pageInterceptor);
return interceptor;
}
}漏洞接口
1
2
3
4
5
6
7
8
9@RequestMapping("/mybatis_plus/PageVul01")
public List<Person> mybatisPlusPageVuln01(Long page, Long size, String id){
QueryWrapper<Person> queryWrapper = new QueryWrapper<>();
Page<Person> personPage = new Page<>(1,2);
personPage.addOrder(OrderItem.asc(id));
IPage<Person> iPage= personMapper.selectPage(personPage, queryWrapper);
List<Person> people = iPage.getRecords();
return people;
}这里的
Page<Person> personPage = new Page<>(1,2);的参数由自己定义这里对应的 payload
1
2
3
4?id=1%20and%20extractvalue(1,concat(0x7e,(select%20database()),0x7e)))
// 或者是
?id=1' and sleep(5)必须是通过盲注的形式,如果是普通的注入,是不会有回显的;因为这里分页查找,size 就把你的数据数量限定死了,如果超过这个数据就会报错,所以只能盲注。
pagehelper
这里的原理就和 order by 一样,不赘述了
因为Order by排序时不能进行预编译处理,所以在使用插件时需要额外注意如下function,同样会存在SQL注入风险:
- com.github.pagehelper.Page
- 主要是setOrderBy(java.lang.String)方法
- com.github.pagehelper.page.PageMethod
- 主要是startPage(int,int,java.lang.String)方法
- com.github.pagehelper.PageHelper
- 主要是startPage(int,int,java.lang.String)方法
mybatis Plus SQL 注入的修复
以上列出的所有方法,除了apply方法可以使用参数占位符进行防护,其他方法全部不支持预编译绑定参数,会直接将字符串拼接到最终 SQL 末尾**,**不会做任何参数绑定或转义处理。导致攻击者传入恶意代码造成语句拼接,用户信息泄露。
能想到的防护方法只有对传入的参数进行检测和过滤
还有写Filter进行过滤
过滤器集成:Drun1baby/AWD-AWDP_SecFilters: 为了准备 AWD,写了个 Filter 的集合 (github.com)
Hibernate框架下的SQL注入
Hibernate是一个开放源代码的对象关系映射框架,它对JDBC进行了非常轻量级的对象封装,使得Java程序员可以随心所欲的使用对象编程思维来操纵数据库。
一般是默认进行预编译的
Hibernate可以使用hql来执行SQL语句,也可以直接执行SQL语句,无论是哪种方式都有可能导致SQL注入
HQL
hql语句:
1 | |
这种拼接方式存在SQL注入
正确使用以下几种HQL参数绑定的方式可以有效避免注入的产生:
1.命名参数(named parameter)
1 | |
2.位置参数(Positional parameter)
1 | |
3.命名参数列表(named parameter list)
1 | |
4.类实例(JavaBean)
1 | |
5.HQL拼接方法
这种方式是最常用,而且容易忽视且容易被注入的,通常做法就是对参数的特殊字符进行过滤,推荐大家使用 Spring工具包的StringEscapeUtils.escapeSql()方法对参数进行过滤:
1 | |
SQL
Hibernate支持使用原生SQL语句执行,所以其风险和JDBC是一致的,直接使用拼接的方法时会导致SQL注入
语句如下:
1 | |
正确写法:
1 | |
预编译下的sql注入
原理
预编译是将sql语句参数化,可预编译的语句,如 where语句中的内容是被参数化的。这就是说,预编译仅仅只能防御住可参数化位置的sql注入。那么,对于不可参数化的位置,预编译将没有任何办法。
不可参数化的位置:
- 表名、列名
- order by、group by
- limit
- join
- 等
我们以order by举例,现在有一个sql语句如下(以下均为伪代码)
1 | |
其中user_input是传递过来的参数,例如 id
1 | |
这个语句是没有问题的,但是如果user_input输入为 id;drop table users –
1 | |
这样就被成功注入了,而这种位置是不可被参数化的,所以是无法通过预编译防御的
如何防御
所以,对于sql注入存在两种情况,可参数化的,不可参数化的。
对于可参数化没商量,直接预编译解决一切。
而对于不可参数化的,只能通过设置白名单,过滤特殊符号,通过加引号强制转为字符串等方式进行拦截。