美文网首页安全收集Kali Linux
网络安全之SQL注入深入分析

网络安全之SQL注入深入分析

作者: 顶风作案7号 | 来源:发表于2021-12-07 22:17 被阅读0次

    0x00 前言

    我们知道代码审计Java的SQL注入主要有两点:参数可控和SQL语句可拼接(没有预编译)。并且我们也清楚修复SQL注入的方式就是预编译,但是可能我们并不清晰内部预编译的具体实现。本文主要从代码层面深入分析三种Java不同数据库框架下的SQL注入以及预编译。

    0x01 JDBC SQLi

    不使用占位符拼接情况分析

    Statement statement = connection.createStatement();
    String sql = "select * from user where id=" + value;
    ResultSet resultSet = statement.executeQuery(sql);
    

    不使用占位符时,输入的内容和sql拼接形成最终的sql语句:

    image.png

    预编译情况:

    String sql = "select * from user where id=?";
    PreparedStatement preparedStatement = connection.prepareStatement(sql);
    preparedStatement.setString(1,value);
    ResultSet resultSet = preparedStatement.executeQuery();
    

    预编译会在传入的字符串前后添加',然后再进行拼接,保证了输入的字符串在SQL语句中是数值而不是关键字。

    image.png

    最终在执行的时候select * from user where id='2 and 1=2 union select * from user'

    image.png

    到这里我们肯定会想就算在两边加了',也可以在value中添加'来闭合绕过:

    2' and 1=2 union select * from user where '1'='1

    然而事实并非那么简单,JDBC在ClientPreparedQueryBindings.setString()中对一些特殊符号包括'做了转义处理,因此预编译可以防止SQL注入:

    image.png

    【一>所有资源获取<一】
    1、200份很多已经买不到的绝版电子书
    2、30G安全大厂内部的视频资料
    3、100份src文档
    4、常见安全面试题
    5、ctf大赛经典题目解析
    6、全套工具包
    7、应急响应笔记
    8、网络安全学习路线

    0x02 Mybatis SQLi

    Mybatis解析执行过程

    Mybatis解析执行过程如下图:

    image.png

    以查询SQL分析,主要步骤如下:

    1. SqlSession创建过程:SqlSessionFactoryBuilder().build(inputStream)创建一个SqlSession,创建的时候会进行配置文件解析生成Configuration属性实例,解析时会将mapper解析成MapperStatement加到Configuration中,MapperStatement是执行SQL的必要准备,SqlSource是MapperStatement的属性,实例化前会先创建动态和非动态SqlSource即DynamicSqlSource和RawSqlSource,DynamicSqlSource对应解析$以及动态标签如foreach,RawSqlSource创建时解析#并将#{}换成占位符?

    2. 执行准备过程:DefaultSqlSession.selectOne()执行sql(如果是从接口getMapper方式执行,首先会从MapperProxy动态代理获取DefaultSqlSession执行方法selectxxx|update|delete|insert),首先从Configuration获取MapperStatement,执行executor.query()。executor执行的第一步会先通过MapperStatement.getBoundSql()获取SQL,此时如果MapperStatement.SqlSource是动态即DynamicSqlSource,会先解析其中的动态标签比如${}会换成具体传入的参数值进行拼接,获取到SQL之后调用executor.doQuery(),如果存在预编译首先会调用JDBC处理预编译的SQL,最终通过PreparedStatementHandler调用JDBC执行SQL;

    3. JDBC执行SQL并返回结果集

    如下是mapper的select示例,第一个使用${id},第二个使用#{id},我们具体通过调试来看下#$这两种符号的解析和执行过程中的处理方式。

    <select id="getById" resultType="com.r17a.commonvuln.injection.sqli.mybatis.pojo.User">
        SELECT * FROM user where id=${id}
    </select>
    <select id="getByIdPrepare" resultType="com.r17a.commonvuln.injection.sqli.mybatis.pojo.User">
        SELECT * FROM user where id=#{id}
    </select>
    

    解析过程中$#的不同

    在解析StatementNode过程中创建SqlSource时,会调用XMLScriptBuilder.parseScriptNode()来生成动态和非动态SqlSource

    image.png

    深入分析XMLScriptBuilder.parseScriptNode(),先调用XMLScriptBuilder.parseDynamicTags()解析动态tag

    image.png

    在解析时会先通过TextSqlNode.isDynamic()判断是否存在动态标志

    image.png

    TextSqlNode.isDynamic()首先创建一个DynamicCheckerTokenParser用来解析动态标识符,调用createParser创建GenericTokenParser

    image.png

    createParser会返回一个${}标识符的标识符解析

    image.png

    $解析过程:

    继续下一步调用GenericTokenParser.parse()

    image.png

    GenericTokenParser.parse中找到了openhandler即${,会调用builder.append(handler.handleToken(expression.toString()))

    image.png

    handler.handleToken()将isDynamic标志为true

    image.png

    当isDynamic为true,会实例化一个DynamicSqlSource对象,至此$动态SqlSource创建完成。

    image.png image.png

    #解析过程:

    当SQL是SELECT * FROM user where id=#{id}的情况下调用isDynamic() ,进一步调用GenericTokenParser.parse()

    image.png

    GenericTokenParser.parse()中没有找到openhandler即${,就不会进入后面的处理,直接将原来的text进行返回,因此isDynamic还是false

    image.png

    返回后初始化一个RawSqlSource实例

    image.png

    在RawSqlSource初始化时会自动进行解析:

    image.png

    SqlSourceBuilder$ParameterMappingTokenHandler主要解析#{}的情况

    image.png

    #{id}替换成?进行占位,此时sql变成了SELECT * FROM user where id=?

    image.png image.png

    小结:在创建SqlSource时,会根据$及动态标签来创建DynamicSqlSource,DynamicSqlSource不会对${}进行具体的处理,而非动态情况会创建RawSqlSource,在其初始化过程会直接将#{}替换成?占位符。

    执行过程中$#的不同:

    $在执行过程中的解析:

    在调用MappedStatement.getBoundSql()时,由于$对应的是DynamicSqlSource,会调用DynamicSqlSource.getBoundSql()获取sql

    image.png

    DynamicSqlSource.getBoundSql()会调用rootSqlNode.apply()处理,此时调用的是TextSqlNode.apply()

    image.png

    TextSqlNode.apply()中会创建一个${}的GenericTokenParser然后进行parse解析和追加

    image.png

    在解析时,调用handler.handleToken()根据标识符获取参数的内容

    image.png

    handleToken()中会将参数值1 and 1=2 union select Host,User,1,authentication_string from mysql.user limit 1返回

    image.png

    拼接 最终获取的sql是SELECT * FROM user where id=1 and 1=2 union select Host,User,1,authentication_string from mysql.user limit 1

    image.png

    #在执行过程中的解析:

    $是在getBoundSql()获取sql过程中就将符号进行了处理,跟$不同的是,#是在执行器的执行过程中(本例是doQuery)进行处理,先通过调用SimpleExecutor.prepareStatement()处理预编译情况后,获取statement,然后调用JDBC执行

    image.png

    深入prepareStatement(),发现其最终通过动态代理调用ClientPreparedStatement.setString()

    image.png

    调用JDBCClientPreparedStatement.setString()处理过程跟上述0x01部分的JDBC预编译处理statement一样。

    image.png

    相关文章

      网友评论

        本文标题:网络安全之SQL注入深入分析

        本文链接:https://www.haomeiwen.com/subject/nwfzxrtx.html