Loading

Java审计之SQL注入

Java审计之SQL注入

写在前面

目前已知的操作数据库的方式应该也就JDBC、Mybatis、Hibernate。现在应该比较少见SSH所以可能较少遇到Hibernate中的SQL注入审计,后续遇到会进行补充。大部分情况下应该都是SSM居多,应该Mybatis会经常遇到些。本文主要来记录些JDBC与Mybatis中可能存在SQL注入的情况。

JDBC

About JDBC

先简单回顾一下JDBC:

Java通过java.sql.DriverManager来管理所有数据库的驱动注册,所以如果想要建立数据库连接需要先在java.sql.DriverManager中注册对应的驱动类,然后调用getConnection方法才能连接上数据库。

JDBC定义了一个叫java.sql.Driver的接口类负责实现对数据库的连接,所有的数据库驱动包都必须实现这个接口才能够完成数据库的连接操作。java.sql.DriverManager.getConnection(xx)其实就是间接的调用了java.sql.Driver类的connect方法实现数据库连接的。数据库连接成功后会返回一个叫做java.sql.Connection的数据库连接对象,一切对数据库的查询操作都将依赖于这个Connection对象。

后面主要涉及的3个对象

  1. connection

    connection对象代表数据库

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

  2. statement

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

    是具体执行sql语句

  3. PreparedStatement

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

那么在使用JDBC的时候在使用statement直接拼接SQL语句而不是PreparedStatement预编译方式执行SQL语句的话可能就会造成SQL注入。

GET&POST注入

示例代码,POST和GET同理,只是会把代码写在doPost方法里。

@WebServlet("/demo")
public class domain extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String url = "jdbc:mysql://localhost:3306/test?useUnicode=true&useSSL=false&characterEncoding=utf8&serverTimezone=Asia/Shanghai";
    		String username = "root";
    		String password = "123456";
        String id = req.getParameter("id");
        Connection conn = null;
        try {
            Class.forName("com.mysql.jdbc.Driver");
            conn = DriverManager.getConnection(url, username, password);
            String sql = "select * from users where id = '"+id+"' ";

            Statement statement = conn.createStatement();
            ResultSet resultSet = statement.executeQuery(sql);
        } catch (ClassNotFoundException | SQLException e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doGet(req,resp);
    }
}

这里就是一个比较经典的使用Statement对象,没有进行预编译,将前端传入的参数id直接拼接到SQL语句里可能导致SQL注入。如果没有自己加一些对恶意传入字符的过滤就会导致SQL注入。在审计的时候如果发现了是使用JDBC的方式可以跟一下或者全局搜索,看执行SQL时使用的是statement还是PreparedStatement,如果是statement多半就存在注入了。

Like注入

大概率会出现在搜索框等会用到模糊查询的地方

同理如果是用statement直接拼接的可能就会存在SQL注入了。

 String sql = "select * from users where name like '%'+name+'%'";

Header注入

String sql = "update user set referer ='"+referer+"'";

采用预编译方式

public static void main(String[] args) {

        Connection connection = null;
        PreparedStatement preparedStatement = null;

        try {
            //使用?占位符
            String sqlU = "DELETE FROM people where id = ?";
            connection = JdbcUtils.getConnection();
            //获得preparedStatement对象,并进行预编译
            preparedStatement = connection.prepareStatement(sqlU);

            //对?进行赋值
            preparedStatement.setInt(1,10); //第1个? , 赋值为12
            //执行SQL
            int i = preparedStatement.executeUpdate();

            if (i>0){
                System.out.println("数据删除成功");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //  释放资源
            boolean flag = JdbcUtils.closeResources(connection, null,null,preparedStatement);
            if (flag){
                System.out.println("资源释放完成!");
            }else {
                System.out.println("资源释放失败!");
            }
        }

    }

以上是 JDBC在使用Statement执行SQL时可能会存在注入的点,在审计时可以尝试直接搜索如Statement字段去定位危险方法,如果参数可控基本就是一个SQL注入。

Mybatis

Mybatis中有两种获取前端传入值的方式

#{} 相当于占位符问号,使用了预编译。
${} 直接获取值

Mybatis中#{}与预编译

当参数通过 #{} 声明的,会通过 PreparedStatement ,即预编译的方式来执行。

预编译不仅可以对 SQL 语句进行转义,避免 SQL 注入,还可以增加执行效率。

而Mybatis底层也是JDBC实现的,但值得注意的一点是,这里的 PreparedStatement 严格意义上来说并不是完全等同于预编译。其实预编译分为客户端的预编译以及服务端的预编译,4.1 之后的 MySql 服务器端已经支持了预编译功能。很多主流持久层框架(MyBatis,Hibernate)其实都没有真正的用上预编译,预编译是要我们自己在参数列表上面配置的,如果我们不手动开启,JDBC 驱动程序 5.0.5 以后版本 默认预编译都是关闭的。需要通过配置连接数据库时的url参数来进行开启useServerPrepStmts=true&cachePrepStmts=true

即MySQL是否开启服务端的预编译是由客户端连接时的参数useServerPrepStmts决定的,而在MySQL提供的Connector/J版本5.0.5(release 2007-03-02)之后,默认情况下,useServerPrepStmts=false。即如果没有显式设置成true,默认情况下,MySQL不启用服务端预编译

like注入

通常写模糊查询时就会用到like语句,不过一般用#{}在SQL中拼接%会直接报错,比如下面这个

select id="findlike" resultType="com.test.domain.User" parameterType="string">
    select * from user where name like   '%#{name}%',
</select>

但是用${}在SQL中拼接%就并不会报错,这也是导致在模糊查询处出现SQL注入的原因之一。

select id="findlike" resultType="com.test.domain.User" parameterType="string">
    select * from user where name like   '%${name}%',
</select>

安全写法:

<select id="findlike" resultType="com.test.domain.User" parameterType="string">
    select * from user where name like concat('%',#{name},'%')
</select>

另外也可参考此文章https://www.anquanke.com/post/id/190170利用Mybatis generator编写安全的demo

类似于模糊查询,其他的还有SQL语句中的一些部分,例如order by字段、表名、in后注入等,是无法使用预编译语句的,可能会出现存在${}拼接的情况就会导致SQL注入。

审计时可以重点关注是否存在Statement和${}直接拼接SQL语句的情况(Mybatis),大概率都会是可能存在注入的点。

posted @ 2021-09-03 23:04  Zh1z3ven  阅读(473)  评论(0编辑  收藏  举报