Mysql報錯注入原理分析(count()、rand()、group by)

wyzsk發表於2020-08-19
作者: T-Safe · 2016/04/11 9:50

0x00 疑問


一直在用mysql資料庫報錯注入方法,但為何會報錯?

百度谷歌知乎了一番,發現大家都是把官網的結論發一下截圖,然後執行sql語句證明一下結論,但是沒有人去深入研究為什麼rand不能和order by一起使用,也沒徹底說明三者同時使用報錯的原理。

0x01 位置問題?


select count(*),(floor(rand(0)*2))x from information_schema.tables group by x; 這是網上最常見的語句,目前位置看到的網上sql注入教程,floor 都是直接放count(*) 後面,為了排除干擾,我們直接對比了兩個報錯語句,如下圖

由上面的圖片,可以知道報錯跟位置無關。

0x02 絕對報錯還是相對報錯?


是不是報錯語句有了floor(rand(0)*2)以及其他幾個條件就一定報錯?其實並不是如此,我們先建建個表,新增一條記錄看看,如下圖:

確認表中只有一條記錄後,再執行報錯語句看看,如下圖:

多次執行均未發現報錯。

然後我們新增一條記錄。

然後再測試下報錯語句

多次執行並沒有報錯

OK 那我們再增加一條

執行報錯語句

ok 成功報錯

由此可證明floor(rand(0)*2)報錯是有條件的,記錄必須3條以上,而且在3條以上必定報錯,到底為何?請繼續往下看。

0x03 隨機因子具有決定權麼(rand()和rand(0))


為了更徹底的說明報錯原因,直接把隨機因子去掉,再來一遍看看,先看一條記錄的時候,如下圖:

一條記錄的話 無論執行多少次也不報錯

然後增加一條記錄。

兩條記錄的話 結果就變成不確定性了

隨機出現報錯。

然後再插入一條

三條記錄之後,也和2條記錄一樣進行隨機報錯。

由此可見報錯和隨機因子是有關聯的,但有什麼關聯呢,為什麼直接使用rand(),有兩條記錄的情況下就會報錯,而且是有時候報錯,有時候不報錯,而rand(0)的時候在兩條的時候不報錯,在三條以上就絕對報錯?我們繼續往下看。

0x04 不確定性與確定性


前面說過,floor(rand(0)*2)報錯的原理是恰恰是由於它的確定性,這到底是為什麼呢?從0x03我們大致可以猜想到,因為floor(rand()*2)不加隨機因子的時候是隨機出錯的,而在3條記錄以上用floor(rand(0)*2)就一定報錯,由此可猜想floor(rand()*2)是比較隨機的,不具備確定性因素,而floor(rand(0)*2)具備某方面的確定性。

為了證明我們猜想,分別對floor(rand()*2)floor(rand(0)*2)在多記錄表中執行多次(記錄選擇10條以上),在有12條記錄表中執行結果如下圖:

連續3次查詢,毫無規則,接下來看看select floor(rand(0)*2) from `T-Safe`;,如下圖:

可以看到floor(rand(0)*2)是有規律的,而且是固定的,這個就是上面提到的由於是確定性才導致的報錯,那為何會報錯呢,我們接著往下看。

0x05 count與group by的虛擬表


使用select count(*) from `T-Safe` group by x;這種語句的時候我們經常可以看到下面類似的結果:

可以看出 test12的記錄有5條

count(*)的結果相符合,那麼mysql在遇到select count(*) from TSafe group by x;這語句的時候到底做了哪些操作呢,我們果斷猜測mysql遇到該語句時會建立一個虛擬表(實際上就是會建立虛擬表),那整個工作流程就會如下圖所示:

  1. 先建立虛擬表,如下圖(其中key是主鍵,不可重複):

2.開始查詢資料,取資料庫資料,然後檢視虛擬表存在不,不存在則插入新記錄,存在則count(*)欄位直接加1,如下圖:

由此看到 如果key存在的話就+1, 不存在的話就新建一個key。

那這個和報錯有啥內在聯絡,我們直接往下來,其實到這裡,結合前面的內容大家也能猜個一二了。

0x06 floor(rand(0)*2)報錯


其實mysql官方有給過提示,就是查詢的時候如果使用rand()的話,該值會被計算多次,那這個“被計算多次”到底是什麼意思,就是在使用group by的時候,floor(rand(0)*2)會被執行一次,如果虛表不存在記錄,插入虛表的時候會再被執行一次,我們來看下floor(rand(0)*2)報錯的過程就知道了,從0x04可以看到在一次多記錄的查詢過程中floor(rand(0)*2)的值是定性的,為011011…(記住這個順序很重要),報錯實際上就是floor(rand(0)*2)被計算多次導致的,具體看看select count(*) from TSafe group by floor(rand(0)*2);的查詢過程:

1.查詢前預設會建立空虛擬表如下圖:

2.取第一條記錄,執行floor(rand(0)*2),發現結果為0(第一次計算),查詢虛擬表,發現0的鍵值不存在,則floor(rand(0)*2)會被再計算一次,結果為1(第二次計算),插入虛表,這時第一條記錄查詢完畢,如下圖:

3.查詢第二條記錄,再次計算floor(rand(0)*2),發現結果為1(第三次計算),查詢虛表,發現1的鍵值存在,所以floor(rand(0)*2)不會被計算第二次,直接count(*)加1,第二條記錄查詢完畢,結果如下:

4.查詢第三條記錄,再次計算floor(rand(0)*2),發現結果為0(第4次計算),查詢虛表,發現鍵值沒有0,則資料庫嘗試插入一條新的資料,在插入資料時floor(rand(0)*2)被再次計算,作為虛表的主鍵,其值為1(第5次計算),然而1這個主鍵已經存在於虛擬表中,而新計算的值也為1(主鍵鍵值必須唯一),所以插入的時候就直接報錯了。

5.整個查詢過程floor(rand(0)*2)被計算了5次,查詢原資料表3次,所以這就是為什麼資料表中需要3條資料,使用該語句才會報錯的原因。

0x07 floor(rand()*2)報錯


由0x05我們可以同樣推理出不加入隨機因子的情況,由於沒加入隨機因子,所以floor(rand()*2)是不可測的,因此在兩條資料的時候,只要出現下面情況,即可報錯,如下圖:

最重要的是前面幾條記錄查詢後不能讓虛表存在0,1鍵值,如果存在了,那無論多少條記錄,也都沒辦法報錯,因為floor(rand()*2)不會再被計算做為虛表的鍵值,這也就是為什麼不加隨機因子有時候會報錯,有時候不會報錯的原因。如圖:

當前面記錄讓虛表長成這樣子後,由於不管查詢多少條記錄,floor(rand()*2)的值在虛表中都能找到,所以不會被再次計算,只是簡單的增加count(*)欄位的數量,所以不會報錯,比如floor(rand(1)*2),如圖:

在前兩條記錄查詢後,虛擬表已經存在0和1兩個鍵值了,所以後面再怎麼弄還是不會報錯。

總之報錯需要count(*)rand()group by,三者缺一不可。

本文章來源於烏雲知識庫,此映象為了方便大家學習研究,文章版權歸烏雲知識庫!

相關文章