DVWA CSRF

前言

CSRF,全称Cross-Site Request Forgery跨站请求伪造。其攻击过程如下;

CSRF与XSS的区别

XSS主要利用用户对站点的信任,而CSRF主要是利用站点对已知身份认证的信任。

XSS是用户自己点击链接来访问相应的网页的,而CSRF是在用户并不知情的情况下来提交请求的。

另外,两者的产生的原因也不一样,CSRF的是因为采用了隐式的认证方式,而XSS的是因为对用户输入没有进行有效的过滤。

CSRF与XSS的关系

均利用用户的会话执行某些操作;

若一个站点存在XSS漏洞,则很大可能也存在CSRF漏洞;

若CSRF的恶意代码存在于第三方的站点,即使能有效地过滤用户的输入而防止XSS,也未必能防御CSRF。

产生CSRF漏洞的原因

由于程序员的不严谨导致Web应用程序存在漏洞

Web浏览器对Cookie和HTTP身份验证等会话信息的处理存在缺陷

漏洞利用的前提

  • 用户已经完成身份认证
  • 新请求的提交不需要重新身份认证或确认机制
  • 攻击者必须了解Web APP请求的参数构造
  • 用户会被吸引去点击链接

几种常见的CSRF方式

1
2
3
4
1. <img>标签属性
2. <iframe>标签属性
3. <script>标签属性
4. JavaScript方法:Image对象、XMLHTTP对象

LOW

分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
 <?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);
}
?>

分析:

根据源代码功能,可以更改密码,但密码通过POST方式传输。

当攻击者发送一封邮件又如下链接http://localhost/DVWA/vulnerabilities/csrf/?password_new=123456&password_conf=123456&Change=Change,在不退出站点的情况下点击链接后,密码就会被改成123456;

或者链接是指向的是一个恶意网站。网站里面有一张图片,无法打开,但是这张图片的链接是http://localhost/DVWA/vulnerabilities/csrf/?password_new=123456&password_conf=123456&Change=Change密码也会被更改为123456;

MEDIUM

分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
 <?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),来源地址与服务器地址一致才能修改密码。

从邮件中点击链接或者从另外网站点击无法修改密码。

验证的来源地址和服务器地址是否一致的代码如下:

1
stripos( $_SERVER[ 'HTTP_REFERER' ] ,$_SERVER[ 'SERVER_NAME' ]) !== false )

stripos() 函数查找字符串在另一字符串中第一次出现的位置(不区分大小写)

语法如下:

1
stripos(string,find,start)
参数 描述
string 必需,规定被搜索的字符串
find 必需,规定要查找的字符
start 可选,规定开始搜索的位置

返回字符串在另一字符串中第一次出现的位置,如果没有找到字符串则返回 FALSE。注释:字符串位置从 0 开始,不是从 1 开始。

假如服务器的地址是 localhost($_SERVER[ 'SERVER_NAME' ]),而恶意的网站的地址是 xyz.club/localhost.php($_SERVER[ 'HTTP_REFERER' ]) 仍然可以绕过。

localhost.php可以是

1
2
3
4
5
6
7
8
9
<form action="http://192.168.0.110:5678/vulnerabilities/csrf/?" method="GET">
<h1>Click Me</h1>
<input type="text" name="password_new" value="123456">
<input type="text" name="password_conf" value="123456">
<input type="submit" value="Change" name="Change">
</form>
<script>
document.getElementsByTagName("form")[0].submit()
<script>

HIGH

分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?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();
?>

增加了Anti-CSRF token机制,可以防御绝大部分的CSRF利用;

只有获取token才能进行CSRF,但是游览器的同源策略,无法直接获取。

但是如果目标网站存在着存储性的XSS漏洞,可以利用存储型XSS漏洞来获取token,这个可以参考前面的文章,然后构造url和代码进行CSRF利用;

IMPOSSIBLE

分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<?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();
?>

分析:

必须输入正确的当前使用的密码,有进一步的身份检测机制;

同时增加stripslashes函数和PDO防护防止sql的注入;