雙查詢報錯注入原理探索
上一篇講了雙查詢報錯查詢注入,后又參考了一些博客,今天來探究其原理
實際上該報錯是由於rand(),count(),group by 三個語句聯合使用造成的,缺一不可。
上一篇的地址:https://www.cnblogs.com/laoxiajiadeyun/p/10278512.html
part 1 場景復現
首先我們新建一個數據庫,並創建一個表用作實驗:
mysql> CREATE DATABASE sql_test;
mysql> USE sql_test;
mysql> CREATE TABLE test(id int(2),name varchar(10));

接下來先插入一條數據測試:
mysql> INSERT INTO test VALUES("1","aaa");
下面看見已經插入了一條數據:

接下來我們構造一個報錯條件,讓其報錯,顯示出當前數據庫名:
mysql> SELECT count(*),concat((SELECT database()),"~",floor(rand()*2))as a FROM test GROUP BY a;
查詢的結果要么為sql_test0,要么是sql_test1,取決於隨機數取整結果,不會觸發報錯。


接下來再在表中插入一條數據進行測試:
mysql> INSERT INTO test VALUES("2","bbb");

雙查詢語句與之前一樣
運氣很好,第一次就報錯了,錯誤內容意思:group by 操作時主鍵 ‘sql_test~1’ 重復

還有其他正常執行不報錯的情況:

可以看到,這里存在兩條數據就可以引發報錯,得到數據庫信息。
part 2 形成原因
接下來我們再分析其報錯的形成 原因:
先談group by 函數:
在表中再插入兩條數據,name值都為“bbb”:
mysql> INSERT INTO test VALUES("3","bbb");
mysql> INSERT INTO test VALUES("4","bbb");
成功后表如下:

這時候我們使用group by 語句時,MySQL會將查詢結果分類匯總,重復的內容會合並為一項:
mysql> SELECT name FROM test GROUP BY name;

這時候再使用count()函數就可以對不同的條目計數:
mysql> SELECT count(*),name FROM test GROUP BY name;
如圖:aaa有一條,bbb有3條

其背后的實現原理如下:
在執行group by name語句時,MySQL會在內部建立一個虛擬表,用來儲存列的數據,表中會有一個group_key值作為表的主鍵,這里的主鍵就是用來分類的name列中獲取,當查詢數據時,取數據庫數據,然后查看虛擬表中存在不,不存在則插入新記錄

當讀取到第一行數據時,aaa不存在,將aaa放入主鍵列中,1放在id列中

然后繼續往下走,到了bbb,不存在,也放進去

往下執行,遇到多余的bbb,已經有bbb存在,就匯總在一起,內部情況如下:

如下,最后在查詢的時候根據group by內部的實現方式返回分類后的結果:

當我們加上count(*)函數時,操作過程為:查看虛擬表是否存在該主鍵值,不存在則插入新記錄,存在則count(*)字段直接加1
這樣就能對上面的分類結果進行統計,然后將統計結果返回:

所以雙查詢報錯的關鍵就在這里,主要的原因在於rand()函數在group by的過程中被觸發了多次,
part 3 報錯原理
讓我們回看一下構造的報錯語句:
mysql> SELECT count(*),concat((SELECT database()),"~",floor(rand()*2))as a FROM test GROUP BY a;
執行前虛擬表為空:

當第一次執行時,group by 分組,其取的數據的是以a為別名的這條語句,假設這時的concat((SELECT database()),"~",floor(rand()*2))生成結果為sql_test~0,group就以sql_test~0查詢虛擬表,發現表中沒有該值的主鍵,於是將這條語句的結果插入到虛擬表中。
注意!是將這條語句的結果插入到虛擬表中,而不是將 sql_test~0 插入到虛擬表中,如下:
(將concat((SELECT database()),"~",floor(rand()*2)) 以a為別名,方便作圖)

由於虛擬表沒有內容,所以會將其插入到虛擬表中,這里的插入過程中,由於插入的是a語句的結果,所以在插入時a語句中的rand()函數會再次執行,即插入的值可能為sql_test~0 也可能為 sql_test~1 ,這里假設插入時a執行的結果為sql_test~0 :

所以上面的情況就是用sql_test~1這個結果查詢虛擬表,不存在該數據,於是插入虛擬表,插入時又運算一次,然后插入的值變成了sql_test~0,所以這就是主要的沖突,表中只有一條數據還好,即使查詢虛擬表的值和插入虛擬表的值不是同一個,但虛擬表也只生成一條記錄,不會出現問題。
然而當表的數據出現兩條以上的時候,第group by 在處理完第一條數據后會往下繼續處理第二條,於是第二條還會按第一條的處理方式進行:

於是就會報錯,報錯內容如下:
ERROR 1062 (23000): Duplicate entry 'sql_test~0' for key 'group_key'

如果第二次查詢和插入的結果都一致:就會有下面兩種情況:
-
都是
sql_test~0:表里已存在,該主鍵的count(*)值+1 -
都是
sql_test~1:表里沒有,插入形成新的主鍵

part 4 探索小結
所以成因已經明白了:當group by 在查詢虛擬表和插入虛擬表時,如果這兩次a語句執行的結果不一致就會引發錯誤,錯誤提示信息是插入的主鍵重復,通過自定義提示里報錯信息中的主鍵值來獲得敏感信息。
其中還可以通過修改rand()函數的隨機因子,指定隨機數生成方式來提高報錯的效率,具體見深入分析的參考鏈接,這里不過多贅述。
希望這篇文章能給你帶來幫助,如果文中有不正確的地方,還請私信或評論留言,我會仔細查看。
參考鏈接:
group by:https://blog.csdn.net/hao1066821456/article/details/69556644
雙查詢注入:https://www.cnblogs.com/BloodZero/p/4660971.html
http://www.lijiejie.com/mysql-injection-error-based-duplicate-entry/
Mysql報錯注入原理分析:https://www.cnblogs.com/xdans/p/5412468.html#undefined
