JAVA审计SQL注入

0x00 前言

记录一下学习java审计的过程。

0x01 JDBC方式

1. 三个对象:

  • connection

    connection对象代表数据库

    可以设置数据库自动提交。事务提交(connection.commit()),事务回滚(connection.rollback())。

  • statement

    调用connnection.createStatement()方法会返回一个statement对象。

    是具体执行sql语句

  • PreparedStatement

    与statement对象的区别是,不直接放入sql语句,先用?作为占位符进行预编译,等预编译完成后,对?进行赋值,之后调用execute等方法不需要添加参数即可完成执行SQL语句。

所以我们在使用JDBC的时候,使用statement直接拼接SQL语句可能会造成SQL注入。

2. 举例

request.getParameter("userId")

private String getNameByUserId(String userId) {
    Connection conn = getConn();//获得连接
    String sql = "select name from user where id=" + userId;
    Statement stmt =  conn.createStatement();
    ResultSet rs=stmt.executeUpdate(sql);
}

上面的代码没有经过预编译,然后直接拼接前段获取的参数到sql语句后执行,会产生sql注入;

这边还有另外一个例子:

request.getParameter("userId")

private String getNameByUserId(String userId) {
    Connection conn = getConn();//获得连接
    String sql = "select name from user where id=" + userId;
    PreparedStatement pstmt =  conn.prepareStatement(sql);
    ResultSet rs=pstmt.executeUpdate();
}

这样虽然使用了PreparedStatement,但是没有规范的使用预编译,同样是拼接的方式也会造成sql注入问题;

下面是规范的采用预编译的方式:

//安全的,预编译的,防止了sql注入
Connection conn = getConn();//获得连接
String sql = "select id, username, password, role from user where id=?"; //执行sql前会预编译号该条语句
PreparedStatement pstmt = conn.prepareStatement(sql); 
pstmt.setString(1, id); 
ResultSet rs=pstmt.executeUpdate();

0x02 Mybatis

Mybatis回顾

  • mybatis的maven配置

    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.2</version>
    </dependency>
    
  • config.xml 配置数据库连接的文件

    <?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="dev">
            <environment id="dev">
                <transactionManager type="JDBC"/>
                <dataSource type="POOLED">
                    <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                    <property name="url"
                              value="jdbc:mysql://localhost:3306/mybatistest"/>
                    <property name="username" value="root"/>
                    <property name="password" value="123456"/>
                </dataSource>
            </environment>
        </environments>
        <mappers>
            <mapper resource="UserMapper.xml"/>
        </mappers>
    </configuration>
    

    主要注意配置文件中注册的xml,获取值的方式有两种,分别是${}#{}

    区别:

    "#{}"将传入的数据都当成一个字符串,会对自动传入的数据加一个双引号,属于正确的预编译,输入的参数会全部变成查询的部分;

    "${}"将传入的数据直接拼接在sql中,造成sql注入。如where username={username},如果传入的值是111,那么解析成sql时的值为where username=111;如果传入的值是1 and 1=1 ;,则解析成的sql为:select id, username, password, role from user where username=1 and 1=1。

MyBatis容易产生问题的三个地方

1. like模糊查询

在Mybatis中使用like进行模糊查询:

Select * from news where title like ‘%#{title}%’

但是这种写法会报错抛异常,这种时候把#改成$能正常运行,但是也产生了安全问题;

正确写法:

select * from news where tile like concat(‘%’,#{title}, ‘%’)

2. in 之后的多个参数

in之后多个id查询时使用# 同样会报错

Select * from news where id in (#{ids})

正确用法为使用foreach,而不是将#简单替换为$

id in<foreach collection="ids" item="item" open="("separatosr="," close=")">#{ids} </foreach>

3. order by之后

Select * from news where title ='#{titlename}' order by #{time} asc

需要注意order by之后通过#执行也会报错,使用order by语句时是无法使用预编译的,原因是order by子句后面需要加字段名或者字段位置,而字段名是不能带引号的,否则就会被认为是一个字符串而不是字段名,然而使用PreapareStatement将会强制给参数加上',所以还要在过滤上做好防御的准备

正确写法:

Select * from news where title ='#{titlename}' order by ${time} asc

所以审计时候可以多观察这几个容易出现问题的地方。

总结:可以使用idea 搜索$关键字,先筛选xml文件搜索$,逐个分析,要特别注意mybatis-generator的order by注入,全局搜索调出Find in Path,筛选后缀xml,搜索$关键字,找到是mybatis的数据库文件,找到调用函数后,⌘+f7查看调用链,检查中间是否被过滤

参考

https://www.cnblogs.com/CoLo/p/15225346.html

https://www.cnblogs.com/nice0e3/p/13647511.html

https://zeo.cool/2020/07/24/java代码审计自学:sql注入篇/

https://xz.aliyun.com/t/10686

https://cloud.tencent.com/developer/article/1123127

原文地址:https://www.cnblogs.com/N0r4h/p/15743200.html