CSRF(Cross-site request forgery,中文为跨站请求伪造)是一种利用网站可信用户的权限去执行未授权的命令的一种恶意攻击。通过伪装可信用户的请求来利用信任该用户的网站,这种攻击方式虽然不是很流行,但是却难以防范,其危害也不比其他安全漏洞小。
本文将简要介绍 CSRF 产生的原因以及利用方式,然后对如何避免这种攻击方式提供一些可供参考的方案,希望广大程序猿们都能够对这种攻击方式有所了解,避免自己开发的应用被别人利用。
CSRF 也称作 one-click attack 或者 session riding,其简写有时候也会使用 *XSRF**。
什么是 CSRF?
简单点说,CSRF 攻击就是攻击者利用受害者的身份,以受害者的名义发送恶意请求。与 XSS(Cross-site scripting,跨站脚本攻击)不同的是,XSS 的目的是获取用户的身份信息,攻击者窃取到的是用户的身份(session/cookie),而 CSRF 则是利用用户当前的身份去做一些未经过授权的操作。
CSRF 攻击最早在 2001 年被发现,由于它的请求是从用户的 IP 地址发起的,因此在服务器上的 web 日志中可能无法检测到是否受到了 CSRF 攻击,正是由于它的这种隐蔽性,很长时间以来都没有被公开的报告出来,直到 2007 年才真正的被人们所重视。
CSRF 有哪些危害
CSRF 可以盗用受害者的身份,完成受害者在 web 浏览器有权限进行的任何操作,想想吧,能做的事情太多了。
- 以你的名义发送诈骗邮件,消息
- 用你的账号购买商品
- 用你的名义完成虚拟货币转账
- 泄露个人隐私
- …
产生原理以及利用方式
要完成一个 CSRF 攻击,必须具备以下几个条件:
- 受害者已经登录到了目标网站(你的网站)并且没有退出
- 受害者有意或者无意的访问了攻击者发布的页面或者链接地址
整个步骤大致是这个样子的:
- 用户小明在你的网站 A 上面登录了,A 返回了一个 session ID(使用 cookie 存储)
- 小明的浏览器保持着在 A 网站的登录状态,事实上几乎所有的网站都是这样做的,一般至少是用户关闭浏览器之前用户的会话是不会结束的
- 攻击者小强给小明发送了一个链接地址,小明打开了这个地址,查看了网页的内容
- 小明在打开这个地址的时候,这个页面已经自动的对网站 A 发送了一个请求,这时候因为 A 网站没有退出,因此只要请求的地址是 A 的就会携带 A 的 cookie 信息,也就是使用 A 与小明之间的会话
- 这时候 A 网站肯定是不知道这个请求其实是小强伪造的网页上发送的,而是误以为小明就是要这样操作,这样小强就可以随意的更改小明在 A 上的信息,以小明的身份在 A 网站上进行操作
利用方式
利用 CSRF 攻击,主要包含两种方式,一种是基于 GET 请求方式的利用,另一种是基于 POST 请求方式的利用。
GET 请求利用
使用 GET 请求方式的利用是最简单的一种利用方式,其隐患的来源主要是由于在开发系统的时候没有按照 HTTP 动词的正确使用方式来使用造成的。对于 GET 请求来说,它所发起的请求应该是只读的,不允许对网站的任何内容进行修改。
但是事实上并不是如此,很多网站在开发的时候,研发人员错误的认为 GET/POST 的使用区别仅仅是在于发送请求的数据是在 Body 中还是在请求地址中,以及请求内容的大小不同。对于一些危险的操作比如删除文章,用户授权等允许使用 GET 方式发送请求,在请求参数中加上文章或者用户的 ID,这样就造成了只要请求地址被调用,数据就会产生修改。
现在假设攻击者(用户 ID=121)想将自己的身份添加为网站的管理员,他在网站 A 上面发了一个帖子,里面包含一张图片,其地址为http://a.com/user/grant_super_user/121
1. <img src="http://a.com/user/grant\_super\_user/121" />
设想管理员看到这个帖子的时候,这个图片肯定会自动加载显示的。于是在管理员不知情的情况下,一个赋予用户管理员权限的操作已经悄悄的以他的身份执行了。这时候攻击者 121 就获取到了网站的管理员权限。
POST 请求利用
相对于 GET 方式的利用,POST 方式的利用更加复杂一些,难度也大了一些。攻击者需要伪造一个能够自动提交的表单来发送 POST 请求。
1. <script>
2. $(function() {
3. $('#csrf_form').trigger('submit');
4. });
5. </script>
6. <form action="http://a.com/user/grant\_super\_user" id="csrf_form" method="post">
7. <input name="uid" value="121" type="hidden">
8. </form>
只要想办法实现用户访问的时候自动提交表单就可以了。
如何防范
防范原理
防范 Csrf 攻击,其实本质就是要求网站能够识别出哪些请求是非正常用户主动发起的。这就要求我们在请求中嵌入一些额外的授权数据,让网站服务器能够区分出这些未授权的请求,比如说在请求参数中添加一个字段,这个字段的值从登录用户的 Cookie 或者页面中获取的(这个字段的值必须对每个用户来说是随机的,不能有规律可循)。攻击者伪造请求的时候, 是无法获取页面中与登录用户有关的一个随机值或者用户当前 cookie 中的内容的,因此就可以避免这种攻击。
防范技术
Synchronizer token pattern
令牌同步模式(Synchronizer token pattern,简称 STP)是在用户请求的页面中的所有表单中嵌入一个 token,在服务端验证这个 token 的技术。token 可以是任意的内容,但是一定要保证无法被攻击者猜测到或者查询到。攻击者在请求中无法使用正确的 token,因此可以判断出未授权的请求。
Cookie-to-Header Token
对于使用 Js 作为主要交互技术的网站,将 csrf 的 token 写入到 cookie 中
1. Set-Cookie: Csrf-token=i8XNjC4b8KVok4uw5RftR38Wgp2BFwql; expires=Thu, 23-Jul-2015 10:25:33 GMT; Max-Age=31449600; Path=/
然后使用 javascript 读取 token 的值,在发送 http 请求的时候将其作为请求的 header
1. X-Csrf-Token: i8XNjC4b8KVok4uw5RftR38Wgp2BFwql
最后服务器验证请求头中的 token 是否合法。
验证码
使用验证码可以杜绝 Csrf 攻击,但是这种方式要求每个请求都输入一个验证码,显然没有哪个网站愿意使用这种粗暴的方式,用户体验太差,用户会疯掉的。
简单实现 STP
首先在 index.php 中,创建一个表单,在表单中,我们将 session 中存储的 token 放入到隐藏域,这样,表单提交的时候 token 会随表单一起提交
1. <?php
2. $token = sha1(uniqid(rand(), true));
3. $_SESSION\['token'\] = $token;
4. ?>
5. <form action="buy.php" method="post">
6. <input type="hidden" name="token" value="<?=$token; ?>" />
7. ... 表单内容
8. </form>
在服务端校验请求参数的buy.php
中,对表单提交过来的 token 与 session 中存储的 token 进行比对,如果一致说明 token 是有效的
1. <?php
2. if ($_POST\['token'\] != $_SESSION\['token'\]) {
3. // TOKEN无效
4. throw new \Exception('Token无效,请求为伪造请求');
5. }
6. // TOKEN有效,表单内容处理
对于攻击者来说,在伪造请求的时候是无法获取到用户页面中的这个token
值的,因此就可以识别出其创建的伪造请求。
解析 Laravel 框架中的 VerifyCsrfToken 中间件
在 Laravel 框架中,使用了VerifyCsrfToken
这个中间件来防范 CSRF 攻击。
在页面的表单中使用{{ csrf_field() }}
来生成 token,该函数会在表单中添加一个名为_token
的隐藏域,该隐藏域的值为 Laravel 生成的 token,Laravel 使用随机生成的 40 个字符作为防范 csrf 攻击的 token。
1. $this->put('_token', Str::random(40));
如果请求是 ajax 异步请求,可以在meta
标签中添加 token
1. <meta name="csrf-token" content="{{ csrf_token() }}">
使用jquery
作为前端的框架时候,可以通过以下配置将该值添加到所有的异步请求头中
1. $.ajaxSetup({
2. headers: {
3. 'X-CSRF-TOKEN': $('meta\[name="csrf-token"\]').attr('content')
4. }
5. });
在启用 session 的时候,Laravel 会生成一个名为_token
的值存储到 session 中。而使用前面两种方式在页面中加入的 token 就是使用的这一个值。在用户请求到来时,VerifyCsrfToken
中间件会对符合条件的请求进行 Csrf 检查
1. if (
2. $this->isReading($request) ||
3. $this->runningUnitTests() ||
4. $this->shouldPassThrough($request) ||
5. $this->tokensMatch($request)
6. ) {
7. return $this->addCookieToResponse($request, $next($request));
8. }
10. throw new TokenMismatchException;
在if
语句中有四个条件,只要任何一个条件结果为true
则任何该请求是合法的,否则就会抛出TokenMismatchException
异常,告诉用户请求不合法,存在 Csrf 攻击。
第一个条件$this->isReading($request)
用来检查请求是否会对数据产生修改
1. protected function isReading($request)
2. {
3. return in_array($request->method(), \['HEAD', 'GET', 'OPTIONS'\]);
4. }
这里判断了请求方式,如果是HEAD
,GET
,OPTIONS
这三种请求方式则直接放行。你可能会感到疑惑,为什么 GET 请求也要放行呢?这是因为 Laravel 认为这三个请求都是请求查询数据的,如果一个请求是使用 GET 方式,那无论请求多少次,无论请求参数如何,都不应该最数据做任何修改。
第二个条件顾名思义是对单元测试进行放行,第三个是为开发者提供了一个可以对某些请求添加例外的功能,最后一个$this->tokensMatch($request)
则是真正起作用的一个,它是 Laravel 防范 Csrf 攻击的关键
1. $sessionToken = $request->session()->token();
2. $token = $request->input('_token') ?: $request->header('X-CSRF-TOKEN');
4. if (! $token && $header = $request->header('X-XSRF-TOKEN')) {
5. $token = $this->encrypter->decrypt($header);
6. }
8. if (! is_string($sessionToken) || ! is_string($token)) {
9. return false;
10. }
12. return hash_equals($sessionToken, $token);
Laravel 会从请求中读取_token
参数的的值,这个值就是在前面表单中添加的csrf_field()
函数生成的。如果请求是异步的,那么会读取X-CSRF-TOKEN
请求头,从请求头中读取 token 的值。
最后使用hash_equals
函数验证请求参数中提供的 token 值和 session 中存储的 token 值是否一致,如果一致则说明请求是合法的。
你可能注意到,这个检查过程中也会读取一个名为X-XSRF-TOKEN
的请求头,这个值是为了提供对一些 javascript 框架的支持(比如 Angular),它们会自动的对异步请求中添加该请求头,而该值是从 Cookie 中的XSRF-TOKEN
中读取的,因此在每个请求结束的时候,Laravel 会发送给客户端一个名为XSRF-TOKEN
的 Cookie 值
1. $response->headers->setCookie(
2. new Cookie(
3. 'XSRF-TOKEN', $request->session()->token(), time() \+ 60 \* $config\['lifetime'\],
4. $config\['path'\], $config\['domain'\], $config\['secure'\], false
5. )
6. );
写在最后
本文只是对 CSRF 做了一个简单的介绍,主要是侧重于 CSRF 是什么以及如何应对 CSRF 攻击。有一个事实是我们无法回避的:没有绝对安全的系统,你有一千种防御对策,攻击者就有一千零一种攻击方式,但不管如何,我们都要尽最大的努力去将攻击者拦截在门外。如果希望深入了解如何发起一个 CSRF 攻击,可以参考一下这篇文章从零开始学 CSRF。
作为一名 web 方向的研发人员,无论你是从事业务逻辑开发还是做单纯的技术研究,了解一些安全方面的知识都是很有必要的,多关注一些安全方向的动态,了解常见的攻击方式以及应对策略,必将在你成长为一名大牛的路上为你“推波助澜”。
参考
您可能感兴趣的文章