sql注入--雙查詢報錯注入
背景:在sqli-labs第五關時,即使sql語句構造成功頁面也沒有回顯出我們需要的信息,看到了有使用雙查詢操作造成報錯的方式獲得數據庫信息,於是研究了一下雙查詢的報錯原理,總結了探索的過程,整理出此文希望可以幫到感興趣的人。
sqli-labs闖關游戲下載地址:https://github.com/Audi-1/sqli-labs
雙查詢報錯注入
需用到四個函數和一個group by
語句:
-
group by ...
--->分組語句 //將查詢的結果分類匯總 -
rand()
--->隨機數生成函數 -
floor()
--->取整函數 //用來對生成的隨機數取整 -
concat()
--->連接字符串 -
count()
--->統計函數 //結合group by語句統計分組后的數據
用sqli-labs中的數據庫為示例,首先先了解一下子查詢的概念。
子查詢又稱為內部查詢,子查詢允許把一個查詢嵌套在另一個查詢當中
簡單的來說就是一個select中又嵌套了一個select,嵌套的這個select語句就是一個子查詢。
連接數據庫后用子查詢測試一下:
mysql> SELECT concat("test:",(SELECT database()))as a;
執行查詢操作時,子查詢先開始,所以SELECT database()
先執行,然后查詢到當前數據庫名稱”security“,並將其傳給concat函數,concat函數在對字符進行連接,於是顯示出圖上的結果。
然后是rand()函數,其作用是生成一個大於0小於1的隨機浮點數,如下:
floor()函數的作用是對傳入的參數取整,這里將rand()生成的隨機數做處理進行取整,由於rand()生成的值取整結果只能為0,所以我們這里做一點處理,使其生成一個大於0小於2的隨機值,並對其取整:
mysql> SELECT floor(rand()*2);
結果要么為0要么為1
接下來結合子查詢,顯示出數據庫信息:
mysql> SELECT concat((SELECT database()), floor(rand()*2)) from users;
由於users表中只有13條數據,所以這里返回了13條數據
在注入中,我們不知道庫名表名,往往借助information_schema這個庫進行猜解
其中information_schema.schemata中包含了mysql的所有庫名,information_schema.tables中包含了所有的表名,information_schema.columns中包含了所有的列名。
示例如下:(我電腦中有7個數據庫,所以返回了7條數據)
現在加上group by語句對返回的數據進行分組處理
mysql> SELECT concat((SELECT database()), floor(rand()*2))as a from information_schema.schemata group by a;
from前的as a
,是為concat((SELECT database()), floor(rand()*2))
這一串取了個別名,后面使用group by分組時就不用打那么長一串了,直接使用別名就行。
到這里都是基礎知識的鋪墊,而且前面所有的查詢操作都是返回庫名和“0、1”的拼接結果,然而在sqli-labs第五關這樣網頁無回顯的環境下,我們是看不到任何的信息的,所以接下來才是正題,我們要利用count函數和上面的操作構成mysql內部錯誤,然后通過報錯的提示獲得我們想要的信息。
(上面的database()函數在實際注入中也可以換成其他的,如version(),具體看你想要通過報錯獲得的信息)
這里增加一個聚合函數count,構造的語句如下:
mysql> SELECT count(*),concat((SELECT database()), floor(rand()*2))as a from information_schema.schemata group by a;
這里利用count(*)對前面的返回數據進行統計,由於group by 和隨機數的原因,有可能會出現重復的鍵值,當鍵值重復時就會觸發錯誤,然后報錯,由於子查詢在錯誤發生之前就已經完成,所以子查詢的內容會隨着報錯信息一起顯示出來:
這里我使用的是information_schema中的schemata表,因為我的數據庫有7個,生成的隨機結果中0和1有一定比例,不容易出現全是0或者全是1的情況,實際情況下推薦使用information_schema中的tables或者columns兩個表,里面的數據條目較多,容易生成較多的隨機值。
例如:
mysql> SELECT count(*), concat((SELECT database()), floor(rand()*2))as a from information_schema.tables group by a;
在sqli-labs闖關的第五關中payload如下:
XXX.php/?id=-1' union select 1,count(*), concat((select database()), floor(rand()*2))as a from information_schema.tables group by a --+
注意,由於有隨機性,可能成功執行了語句所以不會報錯,正常的顯示頁面(即不報錯)如下:
這種情況多提交幾次就行,理論上每次都有百分之50的可能性
但可以通過修改rand()使用的種子來使其百分百報錯,如下將rand()改為rand(1),測試百分之百報錯:
XXX.php/?id=-1' union select 1,count(*), concat((select database()), floor(rand(1)*2))as a from information_schema.tables group by a --+
注入原理
以下是學習過程中看到的不同作者對該問題原因的解釋:
這個是最初看到的原理,但是個人覺得闡述的不太正確:
當在一個聚合函數后面(比如count)后面使用分組語句,就會把查詢的一部分以錯誤形式顯示出來;因為concat函數執行兩次,比如select database(),這樣就執行了兩次select database,與后面的隨機函數鏈接在一起,可能會隨機重復,就會報錯;
另一個博客中提出的深層次的原因,比較合理:
通過floor報錯的方法來爆數據的本質是group by語句的報錯。group by語句報錯的原因是floor(random(0)*2)
的不確定性,即可能為0也可能為1,(group by key
的原理是循環讀取數據的每一行,將結果保存於臨時表中。讀取每一行的key時,如果key存在於臨時表中,則不在臨時表中更新臨時表中的數據;如果該key不存在於臨時表中,則在臨時表中插入key所在行的數據。group by floor(random(0)*2)
出錯的原因是key是個隨機數,檢測臨時表中key是否存在時計算了一下floor(random(0)*2)
可能為0,如果此時臨時表只有key為1的行和不存在key為0的行,那么數據庫要將該條記錄插入臨時表,由於是隨機數,插時又要計算一下隨機值,此時floor(random(0)*2)
結果可能為1,就會導致插入時沖突而報錯。即檢測時和插入時兩次計算了隨機數的值。結論是:當與臨時表里面的值進行比較,如果不同,就插入,但是插入的時候又計算了一次,所以如果插入時計算的值與直接比較的值不一樣,則報錯!
但是上述兩個理由我看了感覺還是有一些地方不明白,感覺沒有說到地方,所以又自己探索了一番,這一篇文章篇幅已經很長了,所以留在下一篇里單獨探討吧。
下一篇:雙查詢報錯注入原理探索
本文參考鏈接:
初步了解雙查詢注入:
https://www.2cto.com/article/201303/192718.html
深入理解:
https://www.cnblogs.com/BloodZero/p/4660971.html
rand()的隨機數種子的影響: