CSRF相关
简述
跨站请求伪造(CSRF),本质是诱使用户在已经认证成功的Web应用中执行非本意的敏感操作。
与XSS的异同:
XSS本质是html代码中的注入,利用的是Web应用对站内用户的信任;而CSRF则是通过伪装成特定网站的受信任用户去执行敏感操作。
XSS可获取用户Cookie等身份信息,而CSRF仅仅是模仿伪装,并没有获取用户身份信息。
原理
前提
-
Cookie值是从本地存储中取出并填充进数据包
-
用户必须在同一个浏览器中点开链接
-
目标后台身份验证机制不健全(仅有Cookie机制,缺少其他验证)
原理
当某个用户访问目标网站并认证成功获得Cookie后,攻击者通过伪造用户身份(利用已经获得的Cookie)向目标网站发起敏感请求,而目标网站认为此Cookie有效,误认为此请求是真实用户发起,便响应请求。
利用方式
GET
# 假设一个简单的修改密码的场景
http://example.com/haha?password1=123456&password2=123456&op=change
- 直接构造URL,诱使用户更改密码
- 短链接
- 使用html标签实现无感触发
# 可配合XSS
<script src="http://example.com/haha?password1=123&password2=123&op=change"></script>
# style="display:none;"
<iframe src="http://example.com/haha?password1=123&password2=123&op=change" style="display:none;"></iframe>
# border="0" style="display:none;"
<img src="http://example.com/haha?password1=123&password2=123&op=change"
border="0" style="display:none;">
POST
POST方式下攻击者可提供一个存在HTML表单自动提交的页面。
<form action="http://example.com/haha?" id="csrf" method="POST">
<input type="hidden" name="password1" value="123">
<input type="hidden" name="password2" value="123">
<input type="hidden" name="op" value="change">
</form>
JSON
JSON CSRF 与普通 CSRF 的区别在于 :
- JSON 数据无法像 post 那样通过一般HTML表单进行构造
- 且Content-Type 一般要求为 application/json,也无法通过表单构造
虽然 XMLHttpRequest、fetch 能构造出 JSON 请求,并且可以设置 Content-Type,但是无法跨域。
未验证或未严格验证 Content-Type 头 和 JSON 格式
- 利用 JSON 格式容错性,例如加入
=
号仍然可以被解析
{"name": "haha", "age": 18}=
- 伪造 JSON 格式
<input name='{"name":"haha","age": 18, "test":"' value='test"}' type='hidden'>
严格验证 Content-Type 头 和 JSON 格式
一般情况下需要 Flash + 307 重定向(但实际情况下 Flash 一般都会被禁用)。
- Flash 文件
- 跨域 XML 文件(如果 flash 文件和 PHP 同域,则不需要)
- 具有 307 状态码的PHP文件(307跟其他 3XX HTTP 状态码之间的区别就在于,HTTP 307 可以确保重定向请求发送之后,请求方法和请求主体不会发生任何改变)
参考:
防御方式
Referer头
限制Referer头必须为本站相关。当然该值可随意修改,因此不可信。
Token
CSRF 成功的原因就在于站点对于用户身份的辨别完全依赖于 Cookie,因此攻击者可以直接使用用户的 Cookie 来完成认证以执行敏感操作。
所以可在请求中加入一个随机产生的由服务端维护的Token,服务器接收到用户请求后会验证 Token,如果无 Token 或者 Token 不正确的请求都会直接丢弃。
注意:
既然是向页面内的请求中加入Token,就需要辨别内外链,如果是外链也加入Token,当攻击者诱使用户访问攻击者在目标站内所设的外链,则会泄露Token。
SameSite
同站判断规则:
根据 Mozilla 维护的公共后缀表(Pulic Suffix List)中的有效顶级域名(eTLD)+1的规则查找得到的一级域名是否相同来判断是否是同站请求。
Cookie的SameSite属性用来限制第三方Cookie。
有三个值:Strict、Lax、None
- Strict
最为严格,完全禁止第三方Cookie。当跨站点时,任何情况下都不会带有Cookie。
- Lax
相对放宽,但大多数情况也是不发送第三方Cookie。
其允许导航到目标网址的GET请求:
请求类型 | 示例 | 正常情况 | Lax |
---|---|---|---|
链接 | <a href="..."></a> |
发送 | 发送 |
预加载 | <link rel="prerender" href="..."/> |
发送 | 发送 |
GET 表单 | <form method="GET" action="..."> |
发送 | 发送 |
POST 表单 | <form method="POST" action="..."> |
发送 | 不发送 |
iframe | <iframe src="..."></iframe> |
发送 | 不发送 |
AJAX | $.get("...") |
发送 | 不发送 |
Image | <img src="..."> |
发送 | 不发送 |
- None
None是无限制。
Chrome升级到85版本后SameSite默认值由None变为Lax。用户可以修改其为None,但必须设置Secure属性(Cookie只能通过 HTTPS 协议发送),否则无效。
多次验证
对敏感请求加入验证码等验证机制,这样防御效果好,但会降低体验。
对于json格式CSRF的防御
严格校验 Content-Type 和 数据体格式