CSRF
跨站请求伪造(Cross-Site Request Forgery,CSRF),攻击者通过伪装来自某个网站受信任用户,对该网站发送恶意请求。
XSS 与 CSRF 的区别:
- XSS:
攻击者发现XSS漏洞 —> 构造代码 —> 发送给受害人 —> 受害人打开 —> 攻击者获取受害人的 cookie —> 完成攻击 - CSRF:
攻击者发现CSRF漏洞 —> 构造代码 —> 发送给受害人 —> 受害人打开 —> 受害人执行代码 —> 完成攻击
可以发现,与 XSS 相比,CSRF 在受害人执行代码后攻击就已完成。
举个例子
GET请求
假设某银行网站 xbank 的转账是采用 GET 方式进行操作的,如:
1 | http://www.xbank.com/transfer.php?toUserId=88&Money=1000 |
给 ID 为 88 的账户转账 1000 元。
攻击者构造了一个恶意页面,如:
1 | <img src="http://www.xbank.com/transfer.php?toUserId=88&Money=1000"> |
受害者打开了这个界面,浏览器访问图片的url,会携带 xbank 的 cookie,如果该受害者的浏览器中 xbank 的 Cookie 或 Session 还没有过期,xbank 就会认为是受害者主动发送的请求,那么就成功转账了。
POST请求
xbank 改为了用 POST 提交表单进行转账操作:
1 | <form action="./transfer.php" method="POST"> |
恶意攻击者根据转账表单进行伪造了一份一模一样的转账表单,并且嵌入到 iframe中:
index.html:
1 | <h1>Waiting...</h1> |
csrf.html:
1 | <form action="http://www.xbank.com/transfer.php" method="POST"> |
成功转账。
JSON 格式
这次 xbank 一个端口是要提交 JSON 格式的数据:
1 | POST /check.php HTTP/1.1 |
仍然可以构造表单进行攻击:
1 | <form action="http://www.xbank.com/check.php" method="POST" enctype="text/plain"> |
这里数据编码为 text/plain,不对 JSON 中的特殊字符编码。注意 form 标签的 enctype 只能设为:application/x-www-form-urlencoded、multipart/form-data、text/plain 三种。因此如果服务器如果检查 Content-Type 必须为 application/json,那就只能用XMLHttpRequest 了:
1 | <script> |
注意这里将 withCredentials 设为 true,这样才会一并发送 cookie。但使用 XMLHttpRequest 又会牵扯到一个问题,那就是 CORS。增添 Content-Type 头部后,这是一个非简单请求,浏览器会发送一个 OPTIONS 的预检,服务端很有可能不会响应这个请求,浏览器也就不会发送 POST 请求了。即使是一个简单请求,如果服务器检查 Orgin 头部,那就凉了。
防御
- 使用验证码。只要涉及到数据交互就先进行验证码验证,可以完全解决 CSRF。但是用户体验极差,慎重考虑。
- 验证 HTTP Referer 字段。但不是很安全,可以绕过。
- 为每个表单添加令牌 Token 并验证。强烈推荐。
- 对于特殊的 Content-Type 进行校验,并检查 Orign 头部。
令牌(Token)
服务端为每一个表单生成一个随机字符串,并在服务端验证这个 Token,如果请求中没有 Token 或者 Token 内容不正确,则认为可能是 CSRF 攻击而拒绝该请求。由于这个 Token 是随机不可预测,因此恶意攻击者就不能够伪造这个表单进行 CSRF 攻击了。
目前有两种方式来存储令牌:SESSION 和 COOKIE。
利用 SESSION
- 后端生成随机字符串 Token,储存在 Session 中。
- 每当有表单时,从 Session 中取出 Token,放入表单中,并隐藏。用户提交表单一并将 Token 提交。
- 服务端先验证
$_POST['token'] === $_SESSION['token']
,再执行其他逻辑。
一个简单的利用 SESSION 防止 CSRF 的登陆界面:
1 |
|
利用 Session 防御 CSRF,很难找出其破绽。但 Session 有两个致命弱点:
- 所有用户,不论是否会提交表单,都将生成一个 Session,这将是很大的资源浪费,对服务器的要求很高。
- 除了 php 的很多开发语言中,Session 是可选项,很多网站根本没有 Server Session。开发框架不能强迫开发者使用 Session,所以在设计防御机制的时候也不会使用 Session。
所以,像 Django 之类的 python 框架,会选择基于 Cookie 的 CSRF 防御方式。
利用 COOKIE
与 Session 唯一的不同,只是将 Token 放入 Cookie中,然后每次验证后将之销毁。网上有文章说要生成 Token 和 Token 的散列,服务端再验证,这是完全没必要的。因为仔细思考一下,就会发现,攻击者无法轻易修改用户在目标网页上的 Cookie。
但如果可以写入 Cookie,也会使这种防御手法失效:
- 某些低级网页可以直接写入 Cookie
- 利用XSS漏洞写入 Cookie
- 利用CRLF漏洞注入 Cookie
- 利用畸形字符使后端解析 Cookie 出错,注入 Cookie
XSS + CSRF
但如果网页还存在 XSS 漏洞,那么 Token 有可能会被窃取,甚至 Cookie 都会被盗,那么还用什么 CSRF。
可以看 RCTF-2015 的 xss 这道题:
http://www.hackdig.com/11/hack-28667.htm
参考
- https://www.cnblogs.com/phpstudy2015-6/p/6771239.html
- https://www.leavesongs.com/PENETRATION/think-about-cookie-form-csrf-protected.html
- https://mp.weixin.qq.com/s?__biz=MzIzMTc1MjExOQ==&mid=2247484126&idx=1&sn=f437882b19bed8d99d0a00938accc0c8&chksm=e89e2a06dfe9a310506419467ada63bee80f10c32267d0b11ea7d1f5491c5afdb344c5dac74e