CSRF原理
CSRF:Cross-site request forgery,跨站请求伪造,也被称为 one-click attack / session riding,缩写: CSRF 或者 XSRF。
攻击方式:挟制用户在当前已登录的Web应用程序上执行非本意的操作。简单理解为盗用用户的身份,发送恶意请求,此攻击容易造成个人隐私泄露和财产安全。
与跨网站脚本(XSS)相比,XSS 利用的是用户对指定网站的信任,CSRF 利用的是网站对用户网页浏览器的信任。
浏览器在接收到这些攻击性代码后,根据网站B的请求,在用户不知情的情况下携带Cookie信息,向网站A发出请求。网站A并不知道该请求其实是由B发起的,所以会根据用户的Cookie信息以用户的权限处理该请求,导致来自网站B的恶意代码被执行。
如上图所示,完成一次CSRF攻击,需要用户:
1、登录受信任网站A,并在本地浏览器存储Cookie
2、在Cookie有效期内,同一浏览器访问恶意网站B
示例1:
银行网站A,用户以GET方式完成银行转账操作:https://www.mybank.com/transfer.php?userID=1&money=1000
恶意网站B,有一段HTLML代码:
<img src="https://www.mybank.com/transfer.php?userId=1&money=1000"/>
网站B以GET方式请求银行网站A,浏览器会带着用户访问银行网站A时的Cookie发出GET请求,银行网站服务器收到请求后,认为是合理操作,所以就继续进行转账操作.
示例2:
为了杜绝上面问题,银行网站A改用POST方式提交:
<form action="transfer.php" method="POST">
<p>userId: <input type="text" name="userId" /></p>
<p>Money: <input type="text" name="money" /></p>
<p><input type="submit" value="transfer" /></p>
</form>
transfer.php处理如下:
<?php
session_start();
if (isset($_REQUEST['userId'] && isset($_REQUEST['money'])){
buy_stocks($_REQUEST['userId'], $_REQUEST['money']);
}
?>
这里恶意网站B仍然可以利用
<img src="https://www.mybank.com/transfer.php?userId=1&money=1000"/>
完成恶意操作,因为$_REQUEST可以获取POST和GET提交的数据。
示例3:
银行网站A把获取数据方法改成$_POST,transfer.php代码如下:
<?php
session_start();
if (isset($_POST['userId'] && isset($_POST['money'])){
buy_stocks($_POST['userId'], $_POST['money']);
}
?>
恶意网站B通过隐藏表单完成恶意攻击,代码如下:
<html>
<head>
<script type="text/javascript">
function steal()
{
iframe = document.frames["steal"];
iframe.document.Submit("transfer");
}
</script>
</head>
<body onload="steal()">
<iframe name="steal" display="none">
<form method="POST" name="transfer" action="http://www.myBank.com/transfer.php">
<input type="hidden" name="userId" value="1">
<input type="hidden" name="money" value="1000">
</form>
</iframe>
</body>
</html>
通过以上示例可以看出:
CSRF攻击是源于web的隐式身份验证机制!web的身份验证虽然可以保证一个请求是来自某个用户的浏览器,但却无法保证该请求是用户允许发送的。
CSRF防御
CSRF防御可以从客户端、服务端两方面考虑,从服务端进行防御效果比较好。
预防CSRF只要在关键部分增加一些小操作即可。
- 验证码:使用验证码与用户互动,可以有效防御CSRF攻击,但频繁输入验证码可能会影响用户体验,所以一般在登录或者注册时使用。
- 检测Referer:通过检查Referer值可以判断请求是否合法,PHP可以通过$_SERVER[‘HTTP_REFERER’]获取referer值。但有时服务器不是随时能接收到Referer值,故Referer Check 一般用于监控CSRF攻击的发生,而不用来抵御攻击。
注:一般在浏览器直接打开的url,或者https跳转到http会产生空Referer值。 - Token:服务端在每次页面加载时生成随机令牌,并隐藏在表单中。django中在Setting.py中启用CsrfViewMiddleware。服务端会检查随机字符串Token是否存在或者正确。
<form action="transfer.php" method="POST">
<p>userId: <input type="text" name="userId" /></p>
<p>Money: <input type="text" name="money" /></p>
<p><input type="hidden" value="token" /></p>
<p><input type="submit" value="transfer" /></p>
</form>
注意:1)Token要足够随机,使攻击者无法预测;2)Token具有一次性,每次请求都要更新Token;3)Token具有保密性,敏感操作使用POST,防止Token出现在URL中。
4. 检查Origin:Origin字段只存在于POST请求,包含是谁发起的请求(URL格式、主机名和端口)。
参考文章:谈谈对web安全的理解