本文章主要涉及group by報錯注入的原理講解,如有錯誤,望指出。(附有目錄,如需查看請點右下角)
一、下圖為本次文章所使用到 user表,該表所在的數據庫為 test
二、首先介紹一下本文章所使用的到的語法:(第5、6條必須看,這涉及到之后的原理講解)
1、group by語句:用於結合合計函數,根據一個或多個列對結果集進行分組。
如下圖:
2、rand()函數:用於產生一個0-1之間的隨機數:
如下圖:
注意:
當以某個整數值作為參數來調用的時候,rand() 會將該值作為隨機數發生器的種子。對於每一個給定的種子,rand() 函數都會產生一列【可以復現】的數字
3、floor()函數:向下取整:
如下圖:
4、count()函數:返回指定列的值的數目(NULL 不計入),count(*):返回表中的記錄數
如下圖
5、floor(rand()*2):rand()*2 函數生成 0-2之間的數,使用floor()函數向下取整,得到的值就是【不固定】的 “0” 或 “1”
如下圖:
6、floor(rand(0)*2):rand(0)*2 函數生成 0-2之間的數,使用floor()函數向下取整,但是得到的值【前6位(包括第六位)是固定的】。(為:011011)
如下圖:
三、接下來我們開始講解group by進行分組的原理:(如果你覺得已經理解了該原理請直接轉:四)
首先讓我們思考一下下面三個sql語句,從中我們可以得知什么:
1、select username,count(*) from user group by username;
2、select username,count(*) from user group by "username";
3、select username,count(*) from user group by userna;
運行結果如下:
結論:
我們發現group by后面的參數可以是一個column_name(字段名),可以是一個字符串(或返回值為字符串的函數),不可以是不完整的column_name。這時你們可能會想,參數是column_name我倒是可以理解是怎么分組的,但是參數是 字符串 是怎么回事?username字段的值中沒有"username"啊?只有"admin","chen"兩個,結果怎么會是 7 呢?讓我們接着往下看。
原因:上面sql語句的分組原理(雖然是我的推測,但是這樣說明的確可以解釋的通):
1、如果參數是 column_name,即 username,不是字符串("username")。
語句執行的時候會建立一個虛擬表(里面有兩個字段,分別是 key 主鍵,count(*)),如果參數是 column_name,系統便會在 user 表中【 依次查詢 [相應的] 字段的值(即:參數指明的字段中的值) 】,取username字段第一個值為 admin,這時會在虛擬表的 主鍵key 中查找 admin 這個字符串,如果存在,就使 count(*) 的值加 1 ;如果不存在就將 admin 這個字符串插入到 主鍵key 字段中,並且使 count(*) 變為 1;接着取username字段第二個值也為 admin ,查找虛擬表中的 主鍵key 中已經存在 admin 字符串,就直接將 count(*) 加 1;…… …… ……;到username字段第四個值為 chen 時,查找虛擬表中的 主鍵key 字段不存在 chen 這個值,此時就將 chen 這個字符串再次插入到 主鍵key 字段中,並且使 count(*) 變為 1,就這樣一直執行下去,直到所有的字段值分組完畢。之后系統就按照虛擬表中的結果將其顯示出來。
取完username字段第四個值(即:chen)時的 虛擬表 ,如下圖:
2、如果參數是字符串:"username",而不是字段名:
語句執行的時候仍會建立一個虛擬表(里面有兩個字段,分別是 key 主鍵,count(*)),如果參數是字符串 "username",那系統就不會去取user表中的字段值了,而是直接取字符串:"username"作為值,然后查找比對虛擬表中 key 字段的值,發現沒有字符串 "username",便插入 "username" 這個字符串,並將count(*) 變為1;然后執行第二次,在虛擬表 key 字段中查找 "username" 這個字符串,發現有,便使 count(*) 加 1,就這樣執行 7 次,count(*)便變成了 7。
四、理解完上面之后,讓我們進入正題,請看下兩條的sql group by報錯注入語句,以及其運行結果:
1、select count(*) from information_schema.tables group by concat(database(),floor(rand(0)*2));
2、select count(*) from information_schema.tables group by concat(database(),floor(rand()*2));
可以看到,user表所在的 test 數據庫被成功的爆了出來。但是你們仔細觀察的話會發現第二個sql語句爆率並不是100%,有時會爆不出來,為什么呢?別着急,繼續往下看:
原因:在我們已經有上面的鋪墊之后其實要理解這個sql group by報錯注入的原理已經不難了:
以第一條語句為例:select count(*) from information_schema.tables group by concat(database(),floor(rand(0)*2));
首先我們知道
- floor(rand(0)*2) 產生的隨機數的前六位 一定是 “011011”(上面已經提到過了),
- concat()函數用於將前后兩個字符串相連
- database ()函數由於返回當前使用數據庫的名稱。
- concat(database(),floor(rand(0)*2))生成由‘database()+‘0’’和‘database()+‘1’’組成的隨機數列,則前六個數列一定依次是:
- 'database()+'0''
- 'database()+'1''
- 'database()+'1''
- 'database()+'0''
- 'database()+'1''
- 'database()+'1''
報錯的過程:
- 查詢前默認會建立空的虛擬表
- 取第一條記錄,執行concat(database(),floor(rand(0)*2))(第一次執行),計算結果為'database()+'0'',查詢虛擬表,發現'database()+'0''主鍵值不存在,則會執行插入命令,此時又會再次執行一次concat(database(),floor(rand(0)*2))(第二次執行),計算結果為'database()+'1'',然后插入該值。(即:雖然查詢比對的是'database()+'0'',但是真正插入的是執行第二次的結果'database()+'1'',這個過程,concat(database(),floor(rand(0)*2))執行了兩次,查詢比對時執行了一次,插入時執行了一次)。
- 取第二條記錄,執行concat(database(),floor(rand(0)*2))(第三次執行),計算結果為'database()+'1'',查詢虛擬表,發現'database()+'1''主鍵值存在,所以不再執行插入指令,也就不會執行第二次concat(database(),floor(rand(0)*2)),count(*) 直接加1,(即,查詢為'database()+'1'',直接加1,這個過程,concat(database(),floor(rand(0)*2))執行了一次)。
- 取第三條記錄,執行concat(database(),floor(rand(0)*2))(第四次執行),計算結果為'database()+'0'',查詢虛擬表,發現'database()+'0''主鍵值不存在,則會執行插入命令,此時又會再次執行一次concat(database(),floor(rand(0)*2))(第五次執行),計算結果為'database()+'1''將其作為主鍵值,但是'database()+'1''這個主鍵值已經存在於虛擬表中了,由於主鍵值必需唯一,所以會發生報錯。而報錯的結果就是 'database()+'1''即 'test1',從而得出數據庫的名稱 test。%e6%b5%85%e6%98%93%e6%b7%b1
由以上過程發現,總共取了三條記錄(所以表中的記錄數至少為三條),floor(rand(0)*2)執行了五次。
五、總結
現在,解釋了group by報錯注入的原理,想必大家已經知道為什么:
- select count(*) from information_schema.tables group by concat(database(),floor(rand(0)*2));一定可以注入成功(要成功注入,前提表中的記錄數至少為三條)
- 而select count(*) from information_schema.tables group by concat(database(),floor(rand()*2));卻不一定了吧。(要成功注入,前提表中的記錄數至少為兩條)
沒錯是因為floor(rand()*2)的前幾位隨機數順序是不固定的,所以並不能保證一定會注入成功,但是其只需兩條記錄數就行了(因為它可能會產出 “0101” ,這樣只需兩條記錄就可以成功注入,你可以試試推導一下),這也算是它的優勢吧。