Mysql報錯注入原理分析(count()、rand()、group by)
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遇到該語句時會建立一個虛擬表(實際上就是會建立虛擬表),那整個工作流程就會如下圖所示:
- 先建立虛擬表,如下圖(其中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,三者缺一不可。