目錄
一、查看源碼
二、功能分析
三、CSRF防范分析
1、CSRF令牌驗證機制
(1)核心原理
(2)防范機制
2、舊密碼確認防御實現
(1)核心原理
(2)為什么舊密碼確認能有效防范CSRF?
本系列為通過《DVWA靶場通關筆記》的CSRF關卡(low,medium,high,impossible共4關)滲透集合,通過對相應關卡源碼的代碼審計找到講解滲透原理并進行滲透實踐,本文為CSRF impossible關卡的原理分析部分,講解相對于low、medium和high級別,為何對其進行滲透測試是Impossible的。
一、查看源碼
進入DVWA靶場CSRF的源碼目錄,找到impossible.php源碼,分析其為何能讓這一關卡名為不可能實現命令執行滲透。
打開impossible.php文件,對其進行代碼審計,這段PHP代碼實現了一個密碼修改功能,通過CSRF令牌驗證防止跨站請求偽造,使用預處理語句防范SQL注入,并對用戶輸入的當前密碼進行MD5加密驗證,確保新舊密碼匹配后才更新數據庫。
-
密碼修改驗證:處理用戶發起的密碼更改請求
-
CSRF防護:通過token驗證防止跨站請求偽造攻擊
-
密碼驗證:確認當前密碼正確且新密碼匹配
詳細注釋后的impossible.php源碼如下所示,主要實現密碼修改功能,修復了CSRF安全風險。
<?php
// 檢查是否通過 GET 方法提交了 'Change' 參數(觸發密碼修改操作)
if( isset( $_GET[ 'Change' ] ) ) {// 驗證 CSRF 令牌,防止跨站請求偽造攻擊checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );// 獲取用戶輸入的當前密碼、新密碼和確認密碼$pass_curr = $_GET[ 'password_current' ];$pass_new = $_GET[ 'password_new' ];$pass_conf = $_GET[ 'password_conf' ];// 處理當前密碼:// 1. 移除可能的轉義字符// 2. 使用 mysqli_real_escape_string 進行 SQL 轉義(盡管后續使用了預處理語句)// 3. 計算 MD5 哈希值(用于與數據庫中存儲的密碼比較)$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 );// 查詢數據庫,驗證當前密碼是否正確// 使用 PDO 預處理語句防止 SQL 注入$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();// 驗證條件:// 1. 新密碼和確認密碼是否一致// 2. 當前密碼是否與數據庫中存儲的密碼匹配if( ( $pass_new == $pass_conf ) && ( $data->rowCount() == 1 ) ) {// 條件滿足,處理新密碼:// 1. 移除可能的轉義字符// 2. 使用 mysqli_real_escape_string 進行 SQL 轉義(盡管后續使用了預處理語句)// 3. 計算 MD5 哈希值(用于存儲到數據庫)$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 );// 更新數據庫中的密碼// 使用 PDO 預處理語句確保安全性$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();// 顯示成功信息$html .= "<pre>Password Changed.</pre>";}else {// 顯示失敗信息(密碼不匹配或當前密碼錯誤)$html .= "<pre>Passwords did not match or current password incorrect.</pre>";}
}// 生成新的 CSRF 令牌,用于后續請求的驗證
generateSessionToken();
?>
二、功能分析
這段 PHP 代碼實現了一個密碼修改功能,用戶提交當前密碼、新密碼和確認密碼后,系統先驗證Anti-CSRF令牌防止跨站請求偽造,然后檢查當前密碼是否正確(通過數據庫查詢)以及新密碼是否匹配。驗證通過后,對新密碼進行MD5哈希處理并更新到數據庫。代碼使用PDO預處理語句防止SQL注入,同時兼容性地使用mysqli_real_escape_string進行額外防護。
- Anti-CSRF 防護:
- 使用?
checkToken()
?驗證用戶提交的 Token,防止 CSRF 攻擊。 - 每次頁面加載時生成新的 Token(
generateSessionToken()
)。
- 使用?
- 密碼修改邏輯:
- 用戶提交?
password_current
(當前密碼)、password_new
(新密碼)、password_conf
(確認密碼)。 - 檢查?
password_new
?和?password_conf
?是否一致。 - 檢查?
password_current
?是否正確(查詢數據庫)。 - 如果驗證通過,更新數據庫中的密碼。
- 用戶提交?
- 輸入清理與哈希:
- 使用?
stripslashes()
?去除可能的反斜杠(防止魔術引號影響)。 - 使用?
mysqli_real_escape_string
?防止 SQL 注入(兼容舊代碼)。 - 使用?
md5()
?對密碼進行哈希。
- 使用?
- 數據庫操作:
- 使用 PDO 預處理語句(
prepare
?+?bindParam
)防止 SQL 注入。 - 查詢當前密碼是否正確(
SELECT ... LIMIT 1
)。 - 更新密碼(
UPDATE users SET password = ...
)。
- 使用 PDO 預處理語句(
- 反饋信息:
- 成功時返回?
Password Changed.
。 - 失敗時返回?
Passwords did not match or current password incorrect.
- 成功時返回?
三、CSRF防范分析
代碼中主要使用兩種方法進行防御,一個惡意請求必須同時滿足以下兩個條件才能成功:
-
攜帶正確且最新的CSRF Token。(第一道關卡)
-
攜帶正確的當前用戶密碼。(第二道關卡)
缺少任何一個,攻擊都會失敗。具體如下所示。
防御層 | 機制 | 優點 | 局限性 |
---|---|---|---|
第一層:CSRF Token | 驗證請求是否來源于真正的網站表單。 | 直接針對CSRF攻擊原理,防范絕大多數自動化攻擊。 | 如果Token生成、存儲或驗證邏輯存在缺陷,可能被繞過。 |
第二層:舊密碼確認 | 驗證執行敏感操作的用戶是否知曉某個機密。 | 極其強大。即使CSRF Token因某種原因失效,此層依然堅挺。攻擊者幾乎無法繞過。 | 對用戶體驗有輕微影響(需要多輸入一個字段)。 |
1、CSRF令牌驗證機制
(1)核心原理
核心原因在于,它不僅僅驗證用戶的登錄憑證(Session Cookie),還要求每個敏感請求(如修改密碼)都必須攜帶一個一次性、隨機的、與會話綁定的令牌。
// Check Anti-CSRF token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
(2)防范機制
同步令牌模式:服務器為每個會話生成一個隨機、不可預測的令牌(Token),并將其同時存儲在服務器端(Session)和客戶端(表單隱藏域)。當客戶端提交請求時,必須帶回此令牌。服務器通過比對兩者是否一致來驗證請求是否來源于真實的網站頁面,從而拒絕偽造的跨站請求。
-
生成令牌 (Generate):在用戶訪問包含表單的頁面時(如第一次訪問密碼修改頁面),服務器端會調用?
generateSessionToken()
?函數生成一個隨機令牌,并將其存儲在用戶的會話($_SESSION
)中。 -
分發令牌 (Distribute):服務器將這個令牌值嵌入到返回給用戶的HTML表單中,作為一個隱藏字段(hidden field)。
-
提交令牌 (Submit):當用戶提交表單時,瀏覽器會自動將這個隱藏字段的值連同其他表單數據一并提交到服務器。
-
驗證令牌 (Verify):服務器接收到請求后,首先調用?
checkToken()
?函數,比對用戶提交的令牌($_REQUEST['user_token']
)和存儲在服務器端會話中的令牌($_SESSION['session_token']
)是否完全一致。-
一致:請求被認為是合法的,繼續執行密碼修改邏輯。
-
不一致或缺失:請求被認為是偽造的,立即終止,并將用戶重定向到首頁(
'index.php'
)
-
2、舊密碼確認防御實現
(1)核心原理
權限復核(Re-authentication):要求用戶在執行極高權限操作(如修改密碼、郵箱)時,再次提供一項只有本人才知道的機密信息(當前密碼)。這確保了即使是已經通過Cookie認證的用戶會話,也必須證明操作者確實知道密碼,而不僅僅是持有會話ID。這有效防御了CSRF、會話劫持以及在已登錄設備上離開后的未授權操作。
(2)為什么舊密碼確認能有效防范CSRF?
CSRF攻擊的核心是?“借刀殺人”?:利用用戶瀏覽器中已有的身份憑證(如Session Cookie)來執行非用戶本意的操作。這里特別強調非本意,即用戶根部不知道要點擊這個鏈接會直接導致什么后果。
舊密碼確認能防御CSRF,并不是因為它是一個“明顯的確認步驟”來提醒用戶(雖然這算是一個次要的副作用),而是因為它在服務器端的技術層面,為執行敏感操作增加了一個攻擊者無法提供的、必須驗證通過的必要參數。
它將請求從?“有Cookie就能執行”?變成了?“有Cookie且知道密碼才能執行”?,從而從根本上破壞了CSRF攻擊成立的條件。這是一種非常強大且根本的防御措施。
-
未知的“秘密”:用戶的當前密碼是一個只有用戶自己知道的秘密。攻擊者可以偽造請求的URL和參數,但他們無法偽造用戶當前的密碼。
-
攻擊請求必然失敗:攻擊者構造的CSRF惡意鏈接中,
password_current
?參數只能是一個猜測值(如?123456
、password
?等常見密碼)或空值。