让我们一起来消灭CSRF跨站请求伪造(下)

安全 漏洞
在本系列文章的上集中,我们跟大家介绍了关于CSRF的一些基本概念,并对常见的几种CSRF漏洞类型进行了讲解。那么接下来,我们就要跟大家讨论一下如何才能消灭CSRF。

写在前面的话

本系列文章的上集中,我们跟大家介绍了关于CSRF的一些基本概念,并对常见的几种CSRF漏洞类型进行了讲解。那么接下来,我们就要跟大家讨论一下如何才能消灭CSRF。

[[208441]]

现代保护机制

实际上,通过修改应用程序源代码来实现CSRF保护在很多情况下是不现实的,要么就是源代码无法获取,要么就是修改应用程序的风险太高了。但我们所设计的解决方案可以轻松地部署到RASP、WAF、反向代理或均衡负载器中,并且可以同时保护一个或多个配置相同的应用程序。

首先我们要知道,正确地使用安全或不安全的HTTP verb是非常重要的。虽然这一点并不能构成一个有效的解决方案,但它是另外两种方法实现的基础。在构建应用程序之前,我们需要对其进行架构设计。幸运的是,大多数现代Web框架都有路由的概念,并且可以强制让节点与HTTP verb配对。在现代框架中,带有错误verb的请求将会导致错误的产生。如果你的应用程序中不能实现这种机制的话,请继续往下看。

另一种方法是验证请求的发送源,这种方法可以确保发送给应用程序的请求来自于一个受信任的源。在这里,正确使用HTTP verb同样是非常重要的,如果我们假设只有改变状态的请求会来自于不安全的请求,那我们就只需要对不安全的请求源进行验证就可以了。但正如我们之前所讨论的,在验证源的可靠性时我们还会遇到很多的问题。其中的一种解决方案是创建一个安全URL白名单,这样就可以防止来自外部源的CSRF。

第三种方法,也是最常见的方法,即使用令牌Token。令牌本身有多种形式,但大多数使用的都是同步器令牌(synchronizer token)。说得更加详细一点,这种令牌主要分为“双提交令牌”以及“加密令牌”。事实证明,结合使用双提交令牌以及加密令牌可以提供最好的安全性。

简单说来,所谓的同步器令牌,就是服务器和浏览器之间需要同步一个令牌(唯一的)。安全的请求方法会返回一个令牌,当浏览器在发送请求时会携带这个令牌,而服务器在处理请求之前,会验证令牌的有效性。处理完请求之后,服务器还会提供一个新的令牌以保证之前的令牌无法继续使用(防止重放攻击)。此时,攻击者将无法访问到令牌或者将其插入到恶意请求之中,因为如果攻击者想这样做的话,他必须要强迫目标用户向远程网站发送请求并访问请求内容,但SOP可以防止这种情况的发生。这样一来,攻击者所能使用的最后一种方法就是利用目标程序可能存在的XSS漏洞了。

需要注意的是,令牌主要有四个部分(一个随机数,用户识别符,过期时间以及真实性验证信息)组成,因此保持其“整体完整性”就非常重要了,其中缺少任何一项都将导致令牌的安全性大打折扣。

在令牌机制的实现过程中,有两个方面我们需要仔细斟酌,即服务器端和客户端。其中,服务器端负责生成和验证令牌,而客户端负责向需要请求资源的服务器发送令牌。需要注意的是,大家绝对有必要为每一个请求生成一个新的令牌,即使这样会牺牲一定的性能。除此之外,你也可以在cookie中添加令牌,但你需要确保cookie没有使用HttpOnly标记。下面这段简单的示例代码是生成令牌的常用方法:

  1. String generateToken(int userId, int key) { 
  2. byte[16] data = random() 
  3. expires = time() + 3600 
  4. raw = hex(data) + "-" + userId + "-" + expires 
  5. signature = hmac(sha256, raw, key) 
  6. return raw + "-" + signature 

大家可以从上面这段代码中看到组成令牌的那四个部分。其中,HMAC是用于验证前三个元素有效性的令牌,并最终会添加到raw的结尾。

  1. bool validateToken(token, user) { 
  2. parts = token.split("-") 
  3. str = parts[0] + "-" + parts[1] + "-" + parts[2] 
  4. generated = hmac(sha256, str, key) 
  5. if !constantCompare(generated, parts[3]) { 
  6. return false 
  1. if parts[2] < time() { 
  2. return false 
  3. if parts[1] != user { 
  4. return false 
  5. return true 

上面这段示例代码演示的是验证和计算令牌有效性的常用方法。首先我们需要将令牌拆分成它的四个组成部分,然后第一步就是利用前三个部分生成并验证HMAC的有效性(与之前的HMAC进行对比)。对比时间一定要确保使用的是固定时间,这样可以避免基于时间的攻击。如果验证成功,我们接下来就要确保令牌没有过期,最后进行用户匹配。但在真实场景中,最麻烦的事情就是让用户的浏览器在发送所有请求时自动提交令牌。

实际上在开发应用的过程中,绝大多数的现代框架都已经帮我们搞定这一切了。框架库可以处理XHR,并将令牌自动插入到请求信息(包括表单)中。但是如果框架没有帮我们实现的话,我们也可以自己实现这种功能。这一步主要可以分为两个部分,一个是处理表单提交,另一个是处理XHR。下面这段示例代码可以处理onclick事件回调:

  1. var target = evt.target; 
  2. while (target !== null) { 
  3. if (target.nodeName === 'A' || target.nodeName === 
  4. 'INPUT' || target.nodeName === 'BUTTON') { 
  5. break; 
  6.    
  7. targettarget = target.parentNode; 
  8.    
  9. // We didn't find any of the delegates, bail out 
  10. if (target === null) { 
  11. return; 

我们可以将这段代码添加到文档中,而不是添加到单独的表单或可点击的元素之中,因为很有可能表单或元素根本就不存在与页面DOM之中。我们所指的元素是用户可以点击的东西,由于DOM树的结构以及事件处理系统的不同,所以我们要寻找的是那种可以提交表单的元素,例如input或button标签。

接下来,我们可以检测一个标签是否为input标签。如果它是,那么我们就可以确保这里有一个提交按钮了。当我们验证提交事件已经被触发之后,我们就可以继续搜索DOM树并寻找form标签了。如果找遍了DOM树却没有找到form标签,那么就说明元素没有被提交,除非它使用了XHR。找到form标签之后,最后一步就是将令牌以一个隐藏input元素添加到表单之中,即创建一个新的元素并将其添加到表单。

  1. var token = 
  2. form.querySelector('input[name="csrf_token"]'); 
  3.    
  4. var tokenValue = getCookieValue('CSRF-TOKEN'); 
  5. if (token !== undefined && token !== null) { 
  6. if (token.value !== tokenValue) { 
  7. token.value = tokenValue
  8. return; 
  9.    
  10. var newToken = document.createElement('input'); 
  11. newToken.setAttribute('type', 'hidden'); 
  12. newToken.setAttribute('name', 'csrf_token'); 
  13. newToken.setAttribute('value', tokenValue); 
  14. form.appendChild(newToken); 

对于那些并非基于表单的请求,我们就需要想办法将令牌插入到XHR请求之中了。大多数代码库都提供了相关的抽象方法,包括jQuery,但我们需要针对标准XHR API创建我们自己的函数钩子。通过利用JavaScript的原型继承机制以及动态特性,我们可以直接将原始的发送方法添加到对象之中,这样我们就可以随时调用这些方法了。接下来,我们需要创建一个新的函数并将令牌插入到cookie中,然后再在请求信息中添加一个带值的header。

不过需要注意的是,对于IE浏览器,我们所设计的这种方法只适用于IE 8及其以上版本的IE浏览器,因为这些版本才支持方法原型和XHR,虽然IE 支持XHR但并不支持方法原型。具体的浏览器支持情况如下图所示:

 

总结

在本系列文章中,我们跟大家介绍了关于CSRF的一些基本概念,并对常见的几种CSRF漏洞类型进行了讲解。除此之外,我们还给大家提供了一些用于对付CSRF漏洞的最佳实践方法。这里我给大家推荐一款名叫Same-Site的扩展插件,它可以帮助我们对cookie进行检测,并对浏览器所发送的cookie进行严格的安全限制。这款插件的浏览器支持情况如下图所示:

责任编辑:赵宁宁 来源: 安全客
相关推荐

2017-11-02 14:39:54

2012-04-14 20:47:45

Android

2013-08-19 10:59:48

2021-01-20 15:31:00

区块链比特币数字货币

2013-05-22 18:32:57

2021-08-27 07:06:10

IOJava抽象

2021-12-29 08:27:05

ByteBuffer磁盘服务器

2022-03-08 17:52:58

TCP格式IP

2022-03-31 18:59:43

数据库InnoDBMySQL

2016-09-12 15:35:38

新华三

2019-01-09 10:06:29

交换机运维 软硬件

2022-02-14 07:03:31

网站安全MFA

2022-02-14 10:16:22

Axios接口HTTP

2016-09-06 10:39:30

Dell Techno

2022-06-26 09:40:55

Django框架服务

2021-11-26 07:00:05

反转整数数字

2021-07-15 07:23:28

Singlefligh设计

2023-08-14 08:38:26

反射reflect结构体

2011-04-19 13:40:27

2021-07-31 11:40:55

Openresty开源
点赞
收藏

51CTO技术栈公众号