AMao
小菜鸡目前对一些东西的认知,希望师傅们可以帮忙纠正!

预编译与SQL注入

2020-09-13 CTF Web安全 渗透测试
Word count: 1.6k | Reading time: 6min

预编译过程

  • 开启MySQL日志功能

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    # 查看是否开启
    mysql> show global variables like "%genera%";
    +------------------+----------------------------------------------------------+
    | Variable_name | Value |
    +------------------+----------------------------------------------------------+
    | general_log | OFF |
    | general_log_file | D:\MySQL5.7.26\data\AMao-Desktop.log |
    +------------------+----------------------------------------------------------+

    # 开启日志功能
    mysql> set global general_log = on;
  • 执行简单预编译

    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
    mysql> prepare stmt_name from 'select * from user where `name`=?';
    Query OK, 0 rows affected (0.00 sec)
    Statement prepared

    mysql> set @query_name='allen';
    Query OK, 0 rows affected (0.00 sec)

    mysql> execute stmt_name using @query_name;
    +----+-------+--------+-----+------+
    | id | name | gender | age | flag |
    +----+-------+--------+-----+------+
    | 1 | allen | 1 | 20 | 1 |
    +----+-------+--------+-----+------+
    1 row in set (0.00 sec)

    mysql> set @query_name2 = 'joy';
    Query OK, 0 rows affected (0.00 sec)

    mysql> execute stmt_name using @query_name2;
    +----+------+--------+-----+------+
    | id | name | gender | age | flag |
    +----+------+--------+-----+------+
    | 6 | joy | 1 | 21 | 1 |
    +----+------+--------+-----+------+
    1 row in set (0.00 sec)
  • 查看日志

    1
    2
    3
    4
    5
    6
    7
    8
        Argument
    Prepare select * from user where `name`=?
    Query set @query_name='allen'
    Query execute stmt_name using @query_name
    Execute select * from user where `name`='allen'
    Query set @query_name2 = 'joy'
    Query execute stmt_name using @query_name2
    Execute select * from user where `name`='joy'
  • 对比正常请求过程

    1
    2
    3
    4
    5
    6
     select * from user where name = 'joy';
    +----+------+--------+-----+------+
    | id | name | gender | age | flag |
    +----+------+--------+-----+------+
    | 6 | joy | 1 | 21 | 1 |
    +----+------+--------+-----+------+
    1
    2
        Argument
    Query select * from user where name = 'joy'
  • 普通SQL语句执行过程:

    在数据库接收到SQL语句后,首先对其进行语义解析,生成语法树,随后对SQL语句进行优化并制定执行计划并执行

  • 预编译操作过程:

    在预编译过程中,数据库首先接收到带有预编译占位符的SQL语句,解析生成语法树(Lex),并缓存在cache中,然后接收对应的参数信息,从cache中取出语法树设置参数,然后再进行优化和执行。

    由于参数信息传入前语法树就已生成,执行的语法结构也就无法因参数而改变,自然也就杜绝了SQL注入的出现。

TIPS:预编译可以实现一次编译、多次执行(程序将使用第一次存储于缓冲区的预编译后的模板进行解析),省去了解析优化等过程

预编译防止SQL注入的原理

  • 预编译脚本

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    <?php
    try {
    $user = 'root';
    $pass = 'root';
    $name = $_GET['name'];
    $dbh = new PDO('mysql:host=localhost;dbname=tt', $user, $pass);
    // 设置为预编译模式
    $dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES,false);
    $stmt = $dbh->prepare("select * from user where name = :user_name");
    $stmt->bindParam(':user_name',$name);
    if($stmt->execute()){
    while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
    print_r($row);
    }
    }
    } catch (PDOException $e) {
    print "Error!: " . $e->getMessage() . "<br/>";
    die();
    }
    ?>
  • ?name=joy

    1
    2
    3
    4
    5
    6
    # 日志
    Connect root@localhost on tt using TCP/IP
    Prepare select * from user where name = ?
    Execute select * from user where name = 'joy'
    Close stmt
    Quit

    整个流程分为五步:连接、预编译、传入参数并执行、关闭预编译语句、退出

  • ?name=joy%27and 1=1%23

    1
    2
    3
    4
    5
    Connect	      root@localhost on tt using TCP/IP
    Prepare select * from user where name = ?
    Execute select * from user where name = 'joy\'and 1=1#'
    Close stmt
    Quit

    可以看到输入的 ' 被转义了,而在引号内的 # 并不会被解释成注释符

    由引号就想到了整型注入,进一步测试

  • ?id=1 and 1=1#

    1
    2
    Prepare	select * from user where id = ?
    Execute select * from user where id = '1 and 1=1#'

    查看日志发现,在执行的SQL语句中,变量位置还是加了引号,因为在MySQL存在一个弱类型转换,所以两者的插叙结果是一样的

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    mysql> select * from user where id =1;
    +----+-------+--------+-----+------+
    | id | name | gender | age | flag |
    +----+-------+--------+-----+------+
    | 1 | allen | 1 | 20 | 1 |
    +----+-------+--------+-----+------+

    mysql> select * from user where id ='1';
    +----+-------+--------+-----+------+
    | id | name | gender | age | flag |
    +----+-------+--------+-----+------+
    | 1 | allen | 1 | 20 | 1 |
    +----+-------+--------+-----+------+
    1 row in set (0.00 sec)

    mysql> select * from user where id ='1aaa';
    +----+-------+--------+-----+------+
    | id | name | gender | age | flag |
    +----+-------+--------+-----+------+
    | 1 | allen | 1 | 20 | 1 |
    +----+-------+--------+-----+------+

输入的引被转义,联想到宽字节注入,在查阅资料得知,在PHP 5.3.6之前,PDO确实存在宽字节注入的问题

上述预编译代码在 LNMP、MySQL 5.5.62、PHP 5.2.17 环境下下实测成功

模拟预编译

模拟预编译是防止某些数据库不支持预编译而设置的(如sqllite与低版本mysql)

如果模拟预处理开启,那么数据库客户端程序内部会模拟 mysql 数据库中的参数绑定这一过程,即程序会在内部模拟prepare的过程,当执行execute时,再将拼接后的完整SQL语句发送给mysql数据库执行

  • 测试

    PDO默认使用的是模拟预编译,只要注释掉上面的脚本里的

    $dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES,false);

    日志信息和执行普通SQL语句一样

    1
    2
    Connect	root@localhost on tt using TCP/IP
    Query select * from user where name = 'joy'

模拟预处理防止SQL注入的本质是在参数绑定过程中对参数值进行转义与过滤,这一点与真正的sql数据库预处理是不一样的

  • 常见接口的预编译方式

    接口 默认预编译方式
    Python-MySQLdb 模拟预编译
    Python-Pymysql 模拟预编译
    Python-Oursql 预编译
    PHP-Mysqli 预编译
    PHP-PDO 模拟预编译
    JAVA-PreparedStatement 模拟预编译

预编译中的转义

通过上面查看日志可以发现,使用预编译后,会对一些特殊字符进行转义,而这个转义实在后端的接口进行还是数据库进行的?

这里尝试阅读pdo的源码,PHP的扩展都是基于C语言,太菜了看起来很费劲,于是通过流量分析来验证:通过流量分析发现Mysql服务器收到的数据包是没有经过转义,即转义操作是在MySQL数据库进行的

预编译中的注入思路

  1. 白盒审计中PDO、PreparedStatement中开发者直接拼接SQL语句的行为
  2. 白盒审计中ORDER BY后的字段名动态传入的SQL语句;渗透测试中允许用户传入按某个字段进行排序的行为,这很有可能是直接拼接的。
  3. 白盒审计中ORDER BY后排序方式(ASC/DESC)动态传入的SQL语句;渗透测试中允许用户选择正序倒序排列的行为,需要抓包查看是否直接传入ASC/DESC,若是则很有可能存在拼接行为。
  4. 白盒审计中模糊查询是否拼接;渗透测试中针对搜索行为进行SQL注入测试。
  5. 白盒审计中IN语句后是否拼接。

参考

https://xz.aliyun.com/t/7132#toc-5

https://blog.nowcoder.net/n/be73b8f592504ae8b1d00368433061be

声明

  1. 博主初衷为分享网络安全知识,请勿利用技术做出任何危害网络安全的行为,否则后果自负,与本人无关!
  2. 部分学习内容来自网络,回馈网络,如涉及版权问题,请联系删除 orz
< PreviousPost
CBC字节翻转与Padding Oracle Attack
NextPost >
Docker 逃逸
CATALOG
  1. 1. 预编译过程
  2. 2. 预编译防止SQL注入的原理
  3. 3. 模拟预编译
  4. 4. 预编译中的转义
  5. 5. 预编译中的注入思路
  6. 6. 参考
  7. 7. 声明