您的浏览器过于古老 & 陈旧。为了更好的访问体验, 请 升级你的浏览器
Ready 发布于2019年12月17日 02:32 最近更新于 2019年12月17日 14:40

原创 面向开发者的 Web 应用安全入门指南(5):CSRF攻击

4080 次浏览 读完需要≈ 13 分钟 Java

内容目录

跨站请求伪造,英文为 Cross-site request forgery,通常缩写为 CSRF 攻击

什么是CSRF攻击?

假设在某银行网站(www.bank.com)上,有一个转账功能,当用户登录银行网站之后,再输入对方的银行卡号、收款人名称、转账金额,然后提交表单,即可完成交易操作。

当然,作为技术人员,我们知道,究其本质,这是该用户的浏览器向服务器发送了一个类似如下的请求:

https://www.bank.com/transferAccount?bankAccount=6227330600811245485&amount=10000&name=CodePlayer

正常情况下,这种转账的业务请求都是由用户主动登录银行网站,然后通过点击【转账】按钮来主动发起的,用户对整个转账交易也是知情的。

可是,设想有这样的一种情况:用户已经登录了银行的网站,并没有打算进行任何转账操作。这个时候,有一个「坏人」通过邮件、QQ或者其它通信方式发来了类似上述的转账业务请求链接,而不知情的「小白」用户又恰好点击并在浏览器中打开了这个链接,由于在这个浏览器中,用户已经是登录状态,那么这就会导致——在用户并没有主观转账意愿的情况下向「坏人」指定的银行卡转账了一笔钱!

甚至,整个转账过程可以再隐晦一点,用户打开该链接显示的仍然是一个看似「天然无公害」的资讯网页。

只不过在这个网页里面偷偷地隐藏了一个iframe或 图片,其加载的URL指定的就是上述业务请求网址:

<!-- 以加载 图片 src 的形式加载 转账交易的请求链接 -->
<img src="https://www.bank.com/transferAccount?bankAccount=6227330600811245485&amount=10000&name=CodePlayer" style="display: none;" />

<!-- 以加载 iframe src 的形式加载 转账交易的请求链接 -->
<iframe src="https://www.bank.com/transferAccount?bankAccount=6227330600811245485&amount=10000&name=CodePlayer" title="偷偷自动转账" style="display: none;"></iframe>

这样,就可以在用户完全不知情的情况下,偷偷地将用户银行卡里的钱转走。

当然,银行一般也会提醒用户,不要点击一些陌生网站的链接。

可是,假如是这样的链接呢?

https://www.baidu.com/redirect?url=https%3A%2F%2Fwww.bank.com%2FtransferAccount%3FbankAccount%3D6227330600811245485%26amount%3D10000%26name%3DCodePlayer

或者是类似这样呢?

https://t.cn/FSAKLKSa

大家可以看到,这种恶意的网址可以有很多种形式,藏身于网页中的许多地方。此外,攻击者也不需要能够完全控制放置恶意网址的网站。例如,他可以将这种地址藏在论坛、博客等任何用户都可以生成内容的网站中。这意味着如果服务端没有合适的防御措施的话,用户即使访问熟悉的可信网站也有受攻击的危险

当然,在真实世界中,想要完成银行转账绝对不是只要简单地发送一个参数合法的业务请求到服务器就能够完成的(至少还需要验证码、支付密码、短信验证码、U盾之类的吧)。

但是,这并不影响我们对CSRF攻击造成的危害的认知。比如 这里 就有一个比较典型的利用多种骗术+技术来骗钱的案例。

再退一步说,即使不能划走用户银行卡的真金白银,利用这种方式以用户的身份 添加/编辑/删除/审核 一些有价值的东西,也是很有可能的。而且,这些数据对于某些人来说,可能比真金白银更有价值。

如何预防CSRF攻击?

透过上面的多个例子,我们能够看出,攻击者并不能通过CSRF攻击来直接获取用户的账户控制权,也不能直接窃取用户的任何信息。他们能做到的,就是欺骗用户的浏览器,让其以用户的名义发起恶意的业务操作

我们都知道,浏览器和服务器之间的HTTP请求是无状态的。绝大多数情况下,服务器之所以能够识别客户终端,是因为用户每次打开浏览器第一次访问站点网页时,服务器会向浏览器发送并要求其保存唯一标识该终端的Cookie信息(例如JSESSIONIDPHPSESSID),并在服务器端保存一对一的会话状态信息。之后浏览器每次向该网站发送请求,都会在请求头中自动带上这些Cookie信息,从而让服务器有能力甄别并维持与该浏览器之间的会话状态信息。

CSRF攻击正是利用了浏览器和服务器之间的这种会话交互机制。在用户已经登录某站点的前提下,攻击者构造出同样是发送到该站点的恶意请求链接,并想尽各种办法让用户的浏览器偷偷向该目标地址发出请求。这是非常典型的「借刀杀人」之计。

预防CSRF攻击,我们就得想办法甄别出哪些请求是用户自己基于主观意愿主动发起的,哪些是用户不知情、被「奸人」所利用的。

下面我们列举一些预防CSRF攻击的常用措施:

限制GET请求

我们知道HTTP请求有GET、POST、PUT等多种请求方式。其中,GET请求一般用于各种资源的加载,它也是最最常见、使用频率最高的请求方式。

因此,多数情况下,浏览器对于GET请求非常宽松,在异步请求、跨域请求等方面并没有严格的安全限制。

所以,当我们在进行事务性操作(会对业务数据进行变更的操作,比如添加、编辑、删除等)时,不应该使用GET请求;多数情况下,我们应该使用POST请求来完成事务性操作

进一步地,在服务器端,我们应该明确拒绝GET请求对事务性操作方法的访问。

检查Referer请求头

浏览器发送的HTTP请求的请求头中有一个Referer字段,这个字段用以标明当前请求是从哪个网页发出的。比如你在 网页A 里面点击了 网页B 的链接,那么浏览器在准备发送到 网页B 的请求时,会在请求头中自动附带Referer字段,其值即为 网页A 的网址。

在处理敏感业务数据请求时,通常来说,Referer字段应和请求的目标地址应该属于同一个站点(在同一个域名之下)。

以上述银行的转账操作为例,Referer字段通常应该是【转账】按钮所在的网页地址,它应该也位于www.bank.com之下。而如果是攻击者发来的请求,Referer字段的值则是恶意网页的地址为空(比如直接打开QQ里的链接),不会位于www.bank.com之下,这时候服务器就能识别出恶意的访问。

这种办法简单易行,工作量低,仅需要在关键访问处增加一步校验。但这种办法也有其局限性,因其完全依赖浏览器发送正确的Referer字段。虽然HTTP协议对此字段的内容有明确的规定,但并无法保证来访的浏览器的具体实现,也无法保证浏览器没有安全漏洞影响到此字段。并且也可能存在攻击者攻击某些浏览器,篡改其Referer字段的可能。

当然,检查Referer字段对于防御来自用户浏览器的CSRF攻击依然非常有效,能够甄别并阻止常规情况下大多数的CSRF攻击。

添加校验Token

上一种方法是检查业务请求的来源。而这一种方法的目的就是增加攻击者构造合法请求参数的难度

还是以银行转账为例,由于正规的转账页面是由我们银行自己的技术人员完全控制的,因此,我们在向用户展示该页面之前,可以在该页面的转账表单中隐藏一个有时效性的、唯一的、随机的、只能使用一次的「防伪」字符串(业界一般称之为「Token」)。

当用户提交转账请求到服务器时,我们需要在服务器端检查请求参数中的Token是否与我们之前放在转账页面中的Token保持一致,且能否通过我们的时效性、唯一性、一次性及其他校验性算法的严格验证

禁止页面嵌套

前面我们提到过,我们的业务请求链接可能会被隐藏嵌套在他人的网页之中,并偷偷发起CSRF攻击。

为了避免主流浏览器中出现这种情况,我们应该通过设置服务器的响应头字段 X-Frame-Optionssameorigin,来避免跨域iframe。

// response is HttpServletResponse
response.setHeader("X-Frame-Options", "sameorigin");

// 你也可以直接在 Apache 或 Nginx 等服务器的配置文件中进行相应的响应头设置

另外,为了避免某些旧版本浏览器不支持该功能。

我们还可以在所有的网页中添加类似如下JS代码,用JS来进行简单的跨域判断:

<script>
	if( window.location.host !== top.location.host  ){
		// 如果 顶级窗口页面 和 当前页面 的域名不一致,则跳转到首页
		top.location.href = "https://codeplayer.vip";
	}
</script>
  • CodePlayer技术交流群1
  • CodePlayer技术交流群2

1 条评论

二周 · 4年前

添加校验Token禁止页面嵌套 是非常靠谱的行为。同时 采用POST提交 也是非常有必要的,后端程序必须要对请求进行校验。

0 0 0

撰写评论

打开导航菜单