一、漏洞描述
CSRF 定义 :跨站请求伪造(英语:Cross-site request forgery),也被称为 one-click attack 或者 session riding,通常缩写为 CSRF,是一种挟制用户在当前已登录的 Web 应用程序上执行非本意的操作的攻击方法。
简单地说,是攻击者通过一些技术手段欺骗用户的浏览器去访问一个自己曾经认证过的网站并执行一些操作(如发邮件,发消息,甚至财产操作如转账和购买商品)。由于浏览器曾经认证过,所以被访问的网站会认为是真正的用户操作而去执行。这利用了 web 中用户身份验证的一个漏洞:简单的身份验证只能保证请求发自某个用户的浏览器,却不能保证请求本身是用户自愿发出的。
漏洞危害
1、篡改目标网站上的用户数据
2、盗取用户隐私数据
3、作为其他攻击向量的辅助攻击手法
4、传播 CSRF 蠕虫
二、攻击原理
用户打开浏览器,访问登陆受信任的 A 网站;
在用户信息通过验证后,服务器会返回一个 cookie 给浏览器,用户登陆网站 A 成功,可以正常发送请求到网站 A;
用户未退出网站 A,在同一浏览器中,打开一个危险网站 B;
网站 B 收到用户请求后,返回一些恶意代码,并发出请求要求访问网站 A;
浏览器收到这些恶意代码以后,在用户不知情的情况下,利用 cookie 信息,向网站 A 发送恶意请求,网站 A 会根据 cookie 信息以用户的权限去处理该请求,导致来自网站 B 的恶意代码被执行。

三、防御方案
1. 增加 Token 验证(常用做法)
对关键操作增加 Token 参数,token 必须随机,每次都不一样。
2 关于安全的会话管理(避免会话被利用)
不要在客户端保存敏感信息(比如身份验证信息);
退出、关闭浏览器时的会话过期机制;
设置会话过机制,比如 15 分钟无操作,则自动登录超时。
3 访问控制安全管理
敏感信息的修改时需要身份进行二次认证,比如修改账号密码,需要判断旧密码;
敏感信息的修改使用 POST,而不是 GET;
通过 HTTP 头部中的 REFERER 来限制原页面。
4 增加验证码
一般在登录(防暴力破解),也可以用在其他重要信息操作的表单中(需要考虑可用性)。
四、漏洞常见类型
你可以这么来理解 CSRF: 攻击者盗用了你的身份,以你的名义发送恶意请求,对服务器来说这个请求是完全合法的,但是却完成了攻击者所期望的一个操作,比如以你的名义发送邮件、发消息,盗取你的账号,添加系统管理员,甚至于购买商品、虚拟货币转账等。
1. GET 类型的 CSRF
一个 HTTP 请求。就能够构造一次简单的 CSRF。
银行站点 A:它以 GET 请求来完毕银行转账的操作,如:
http://www.mybank.com/Transfer.php?toBankId=11&money=1000
危险站点 B:它里面有一段 HTML 的代码例如以下:
<img src=http://www.mybank.com/Transfer.php?toBankId=11&money=1000>
首先。你登录了银行站点 A,然后访问危险站点 B,噢,这时你会发现你的银行账户少了 1000 块。为什么会这样呢?原因是银行站点 A 违反了 HTTP 规范,使用 GET 请求更新资源。在访问危险站点 B 的之前。你已经登录了银行站点 A,而 B 中的 一个合法的请求,但这里被不法分子利用了。所以你的浏览器会带上你的银行站点 A 的 Cookie 发出 Get 请求,去获取资源以 GET 的方式请求第三方资源(这里的第三方就是指银行站点了)。
http://www.mybank.com/Transfer.php?toBankId=11&money=1000
结果银行站点服务器收到请求后,觉得这是一个更新资源操作(转账操作),所以就立马进行转账操作。
2. POST 类型的 CSRF
在 CSRF 攻击流行之初,曾经有一种错误的观点,认为 CSRF 攻击只能由 GET 请求发起。因此很多开发者都认为只要把重要的操作改成只允许 POST 请求,就能防止 CSRF 攻击。这样的错误观点形成的原因主要在于,大多数 CSRF 攻击发起时,使用的 HTML 标签都是、、
而对于很多网站的应用来说,一些重要操作并未严格地区分 GET 与 POST,攻击者可以使用 GET 来请求表单的提交地址。比如在 PHP 中,如果使用的是_ R E Q U E S T,而非 _REQUEST,而非_REQUEST,而非_POST 获取变量,则会存在这个问题。
假如银行转账界面为 money.php,其代码如下所示
<?php
$money=$_POST['money'];
$user=$_POST['user'];
print("向 $user 转账成功,金额为 $money");
?>
我们访问这个界面,自己先赋值一下,获取到参数,然后抓包。

构造 poc


复制,生成 ToMoney.php
<html>
<!-- CSRF PoC - generated by Burp Suite Professional -->
<body>
<form action="http://172.16.182.143/money.php" method="POST">
<input type="hidden" name="user" value="hacker" />
<input type="hidden" name="money" value="100" />
<input type="submit" value="Submit request" />
</form>
<script>
history.pushState('','', '/');
document.forms[0].submit();
</script>
</body>
</html>
访问 ToMoney.php


3.Token 验证
原理:CSRF 的主要问题是敏感操作的链接容易被伪造。而只要在每次请求时都增加一个随机码 Token,后台每次都对这个随机码进行验证,则可以有效地防止 CSRF。在源码 token_get_edit.php 中看到,每次刷新页面,都会调用 set_token()函数,该函数会把 SESSION 中 Token 销毁,然后生成一个新的 Token,并将这个 Token 传到前端表单中。
<div id="per_info">
<form method="get">
<h1 class="per_title">hello,{$name}, 欢迎来到个人会员中心 | <a style="color:bule;" href="token_get.php?logout=1"> 退出登录 </a></h1>
<p class="per_name"> 姓名:{$name}</p>
<p class="per_sex"> 性别:<input type="text" name="sex" value="{$sex}"/></p>
<p class="per_phone"> 手机:<input class="phonenum" type="text" name="phonenum" value="{$phonenum}"/></p>
<p class="per_add"> 住址:<input class="add" type="text" name="add" value="{$add}"/></p>
<p class="per_email"> 邮箱:<input class="email" type="text" name="email" value="{$email}"/></p>
<input type="hidden" name="token" value="{$_SESSION['token']}" />
<input class="sub" type="submit" name="submit" value="submit"/>
</form>
</div>
而当每次提交表单时,这个 Token 值就会传到后台与 SESSION 中的 Token 进行比较,若不相等,此次表单则提交失败。所以黑客由于不能得知用户当前的 Token 值,从而无法进行 CSRF 攻击。