Mysql 执行优先级和sleep函数延时注入的一个Tip

0x00前言

代码审计发现了一个基于时间延迟的SQL盲注,但是发现 sleep(2)成功获取数据时页面返回时间均为4秒,即为设置时间的两倍,所以有了下面的分析。

0x01Mysql 执行优先级和sleep函数

Mysql语句关于AND和OR执行优先级和Mysql查询优化策略有以下几点:

  • AND的优先级大于OR,先计算AND的结果,再计算or的结果
  • 同优先级AND中如果有恒为0(如and 1=2),则直接返回0,不执行同级中的其他语句
  • 多个OR连接的语句中如果有恒为1(如or 1=1),则直接返回1,不执行的其他语句
  1. 首先测试数据表结构和数据如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    mysql> select * from ctf;
    +--------+----------+-----------+------+
    | userid | username | signature | mood |
    +--------+----------+-----------+------+
    | 1002 | test2 | test2 | 1 |
    | 1001 | test1 | test1 | 1 |
    | 100 | 32313333 | test | 0 |
    +--------+----------+-----------+------+
    3 rows in set (0.00 sec)
  2. 执行select * from ctf where mood=sleep(1),延时了3秒。
    (没有限定条件,和数据表的每一条进行对比,进行了3次sleep(1)函数)

    1
    2
    3
    4
    5
    6
    7
    mysql> select * from ctf where mood=sleep(1) ;
    +--------+----------+-----------+------+
    | userid | username | signature | mood |
    +--------+----------+-----------+------+
    | 100 | 32313333 | test | 0 |
    +--------+----------+-----------+------+
    1 row in set (3.00 sec)
  3. 执行select * from ctf where username=’test2’ and mood=sleep(1);延时1秒。
    (先筛选username=’test2’,只有1条记录,进行了1次sleep(1)函数)

    1
    2
    mysql> select * from ctf where username='test2' and mood=sleep(1);
    Empty set (1.00 sec)
  4. 执行 select * from ctf where userid>100 and mood=sleep(1);延时2秒。
    (先筛选userid>100,有2条记录,进行了2次sleep(1)函数)

    1
    2
    mysql> select * from ctf where userid>100 and mood=sleep(1);
    Empty set (2.00 sec)
  5. select * from ctf where userid>100 and 1=2 and mood=sleep(1);没有延时直接返回空。(由于sleep之前有and 1=2,不可能成立,直接返回空,没有执行sleep(1))

    1
    2
    mysql> select * from ctf where userid>100 and 1=2 and mood=sleep(1);
    Empty set (0.00 sec)
  6. 执行select * from ctf where userid>100 and 1=2 or mood=sleep(1);延时3秒
    (根据优先级,先计算执行100 and 1=2,不可能成立,没有符合要求的数据记录,但是还需要判断or之后的条件,所以仍然要查询比对三次,执行3次sleep(1))

    1
    2
    3
    4
    5
    6
    7
    mysql> select * from ctf where userid>100 and 1=2 or mood=sleep(1);
    +--------+----------+-----------+------+
    | userid | username | signature | mood |
    +--------+----------+-----------+------+
    | 100 | 32313333 | test | 0 |
    +--------+----------+-----------+------+
    1 row in set (3.00 sec)
  7. 执行select from ctf where userid>100 and 1=sleep(1) or mood=0;延时2秒,执行select from ctf where 1=sleep(1) and userid>100 or mood=0;延时3秒,两者返回结果一致,但是由于执行顺序不同,导致延时不同。(前一种情况根据优先级,执行userid>100 and 1=sleep(1),通过userid>100筛选只有两条记录,然后再sleep(1),所以只延时2秒。后一种情况,根据优先级,执行1=sleep(1) and userid>100,直接利用1=sleep(1)的条件和数据库记录对比以后,再判断userid>100的条件,要全部比对执行三次,所以延时3秒。)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    mysql> select * from ctf where userid>100 and 1=sleep(1) or mood=0;
    +--------+----------+-----------+------+
    | userid | username | signature | mood |
    +--------+----------+-----------+------+
    | 100 | 32313333 | test | 0 |
    +--------+----------+-----------+------+
    1 row in set (2.00 sec)
    mysql> select * from ctf where 1=sleep(1) and userid>100 or mood=0;
    +--------+----------+-----------+------+
    | userid | username | signature | mood |
    +--------+----------+-----------+------+
    | 100 | 32313333 | test | 0 |
    +--------+----------+-----------+------+
    1 row in set (3.00 sec)
  8. 执行select * from ctf where userid>100 and 1=sleep(1) or mood=0 or 1=1;延时0秒。
    (userid>100 and 1=sleep(1) or mood=0 or 1=1,在OR连接的语句中有恒成立的1=1,所以直接返回结果,没有延时。)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    mysql> select * from ctf where userid>100 and 1=sleep(1) or mood=0 or 1=1;
    +--------+----------+-----------+------+
    | userid | username | signature | mood |
    +--------+----------+-----------+------+
    | 1002 | test2 | test2 | 1 |
    | 1001 | test1 | test1 | 1 |
    | 100 | 32313333 | test | 0 |
    +--------+----------+-----------+------+
    3 rows in set (0.00 sec)

0x02小结

由于MySQL的条件优先级的不同,在不同语句中执行sleep()函数导致的延迟时间(执行次数)不同,一个比较简单的判断就是,判断sleep()函数所在的点,进行数据查询时需要对比的数据记录数,即等于sleep()函数执行的次数。