dvwa-CSRF

Cross-Site Request Forgery ,跨站请求伪造。通俗来讲,就是冒用用户身份进行攻击,有点像中间人攻击。它还有个亲戚,叫XSS,跨站脚本。

请求页面

比较直白,让我们修改 password
1691837018096

响应页面

难绷,不进行输入直接点 Change,居然直接修改了密码……
1691837338094

low

http请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
GET /vulnerabilities/csrf/?password_new=&password_conf=&Change=Change HTTP/1.1
Host: localhost
sec-ch-ua:
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: ""
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.199 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: http://localhost/vulnerabilities/csrf/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: PHPSESSID=re4k3en5q4a2e9pqidrd3it4a3; security=low
Connection: close


源码

仅仅是对两次输入进行了简单比较,然而没有丝毫过滤

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

?>

攻击思路

  1. 将GET请求行URL伪装成短网址,诱导受害者点击。

这里我用 http://dwurl.cn/U/Index,将 http://localhost/vulnerabilities/csrf/?password_new=&password_conf=&Change=Change#生成短链 https://dwurl.cn/v7ef4Q

1691838559286

curl -i可以看到响应头,显示这个链接被重定向到 Location,直接修改密码为空。千言万语一句话,陌生链接不不要点,除非多方面求证。

  1. 配合XSS ,将修改密码的URL隐藏在html标签中,比如 <iframe> <script> <img>,操作会更加隐蔽

medium

源码

相比low 级别,striops()函数增加了 refererserver_name的判断,检查了保留变量 HTTP_REFERER(http 包头的 Referer 参数的值,表示来源地址)中是否包含 SERVER_NAME(http 包头的 Host 参数,要访问的主机名),希望通过验证 http 来源的机制抵御 CSRF 攻击。

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' ] ) ) {
// 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. 构造造一个exp.html,实现访问该exp.html就会自动修改密码。(怎么把exp.html放到服务器先不考虑)
  2. 绕过referer手段(三种均可)
  • 目录混淆。将上述html放在127.0.0.1文件夹下面
    http://localhost/127.0.0.1/exp.html
  • 文件名混淆。将上述exp.html重命名为127.0.0.1.html
    http://localhost/127.0.0.1.html
  • URL混淆。
    http://localhost/exp.html?127.0.0.1
  1. 使受害者点击构造的html

high

http请求

请求中间增加了重要参数 user_token,这个参数会成为接下来的老熟人。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
GET /vulnerabilities/csrf/?password_new=&password_conf=&Change=Change&user_token=2e509f6c9d959f644cca6f90b75f7f49 HTTP/1.1
Host: localhost
sec-ch-ua:
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: ""
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.199 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: http://localhost/vulnerabilities/csrf/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: PHPSESSID=re4k3en5q4a2e9pqidrd3it4a3; security=high
Connection: close

源码

如下,代码加入了 Anti-CSRF token 机制,当用户每次访问改密页面时,服务器会返回一个随机的 token。向服务器发起请求时,需要提交 token 参数,而服务器在收到请求时会检查 token,只有 token 正确时才会处理客户端的请求。

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' ] ) ) {
// 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();

?>

攻击思路

现在要想进行 CSRF 攻击就必须获取到用户的 token,而要想获取到 token 就必须利用用户的 cookie 去访问修改密码的页面,然后截取服务器返回的 token 值。
这里可以利用后面的XSS(Reflected) 的 high 级别的漏洞,,我们注入一个攻击脚本,使得每次打开页面时都弹出 token 值。
注入的 payload 如下。
<iframe src="../csrf/" onload=alert(frames[0].document.getElementsByName('user_token')[0].value)>

impossible

学习时间到!

源码

这里相对于 High 级别主要就是增加了输入当前密码的选项,这个在实战中还是一种比较主流的防护方式,攻击者不知道原始密码的情况下是无法发起 CSRF 攻击的,另外常见的防护方法还有加验证码来防护。

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
40
41
42
43
44
45
46
47
48
<?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();

?>

总结

CSRF 我们算是搞完了。攻击者目的就是在受害者不知情的情况下冒充受害者,执行恶意操作。防护方法主要有

  • 设置请求头中Referer字段,确认请求来源页面。注意这个单词的拼写,它可有些故事。
  • 设置Token,确认请求来自知道正在做什么的用户。有点绕,也就是确认是本人有意操作。
  • 验证码,也是确认本人有意操作。

dvwa-CSRF
http://blog.lingyuanming.site/2022/06/04/dvwa-CSRF/
作者
LYM
发布于
2022年6月4日
许可协议