译者 | 陈峻
审校 | 孙淑娟
跨站点请求伪造(Cross-site request forgery,又称:跨站点引用伪造)是一种针对Web应用的攻击形式。黑客通过伪装恶意请求,诱骗用户运行他们本不打算执行的任务。尽管CSRF可能听起来与XSS攻击类似,但它们的执行方式存在根本差异。对此,Web服务器需要一种机制,来确定浏览器所产生的请求,是否源于合法用户的真实意图,而非受攻击的胁迫。针对此类问题,服务器端可以生成一个唯一的、且不可预测的密钥值,作为CSRF令牌被包含在客户端的HTTP请求中。当有后续请求发出时,Web服务器会验证包含了令牌的请求参数,以拒绝那些不包含有令牌的请求参数。由于黑客几乎不可能构造一个完整、有效的HTTP请求来欺骗Web用户,因此该方法通常可被用于防范CSRF攻击。下面,我将和您讨论CSRF令牌的工作原理,及其在应用安全中的重要性。
一、为什么需要有效的CSRF令牌?
CSRF令牌通常被建议添加到所有状态更改(state-changing)的请求中,以便在后端被执行验证。由于只有应用服务器和客户端可以识别令牌,因此后端必须确保传入的请求包含有效的CSRF令牌,以避免XSS或跨站点请求伪造攻击的得逞。
在基于Cookie的会话期间,作为密钥值的CSRF令牌需要被安全处理以保持有效。为此,令牌应当被放置在HTML表单的隐藏字段中,被传输到客户端,并使用HTTP的POST请求被提交。作为优秀的实践,我们建议使用标准的标头来验证请求的来源,并使用其他措施去识别和比较来源和目标。如果来源匹配,则判定请求是合法的;如果不匹配,则表明疑似跨域请求,应予以丢弃。
二、CSRF令牌在防止攻击中的意义
由于令牌在生成过程中使用到了伪随机数(pseudo-random number)生成器、静态密钥、以及种子时间戳,因此CSRF令牌的值是不可预测的。同时,每个用户的令牌也是不同的,而且只会存储活动的用户会话。据此,安全团队可以通过将随机数生成器的输出,与用户特定的熵(entropy)连接起来,并对整个结构进行散列处理,以提高令牌值的唯一性。对此,黑客将很难在早期的会话Cookie中,根据已发布的令牌样本,去猜测CSRF令牌。
三、如何使用CSRF令牌
尽管我们可以在URL查询字符串中放置令牌,但是查询的字符串记录会被留存在服务器和客户端的多条记录中。因此,查询字符串可以在客户端屏幕的浏览器上被访问到,甚至会在HTTP引用标头中,被传输给第三方应用程序。可见,这种方法是不安全的。对此,我们建议CSRF令牌应该被存储在服务器端的应用程序中,并在自定义请求标头中传输CSRF令牌。服务器端应用程序通过按需验证每个请求,以确保各种有效的请求,包含了与用户活动会话中存储的值相匹配的令牌。同时,CSRF令牌也可以对包括POST、PUT和DELETE在内的所有HTTP方法执行验证。
四、如何在Java中实现CSRF令牌
由于Java应用缺乏了针对CSRF攻击的固有保护。因此,我们建议在Java中实现CSRF令牌的时候,请使用一个过滤器和一些辅助类,来启用令牌的创建、资源的分析、以及响应的构建。例如,您可以使用通用无状态(Generic Stateless)过滤器,来实现了双重提交(double-submit)的Cookie模式,以启用CSRF保护。同时,您可以采用如下工作流程:首先,如下面代码段所示,过滤器会在Java应用的web.xml文件中被定义:
<filter>
<filter-name>CSRFFilter</filter-name>
<filter-class>com.github.adriancitu.csrf.GenericCSRFStatelessFilter</filter-class>
<filter>
<filter-mapping>
<filter-name>CSRFFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
该过滤器包括了两个可选的初始化变量:
1.csrfHeadername– 包含了令牌的标头名称
2.csrfCookieName– 用于存储CSRF令牌的Cookie的ID。
对于每个HTTP请求,过滤器都会提供一个ExecutionContext类的实例。它包含了CSRFcookie、以及HTTP请求与响应的Java对象。同时,该类也包含了ResourceCheckerHook、ResponseBuilderHook和TokenBuilderHook等辅助类的实现。
接着,过滤器会根据如下三种情况,去检查所请求的HTTP资源的保护状态:
1.MUST_NOT_BE_PROTECTED
2.MUST_BE_PROTECTED_BUT_NO_COOKIE_ATTACHED
3.MUST_BE_PROTECTED_AND_COOKIE_ATTACHED
对于状态为MUST_NOT_BE_PROTECTED和MUST_BE_PROTECTED_BUT_NO_COOKIE_ATTACHED的资源,过滤器会生成一个由TokenBuilderHook类提供的CSRF令牌的Cookie。对于带有标签MUST_BE_PROTECTED_AND_COOKIE_ATTACHED的资源,过滤器则使用ResourceCheckerHook去检查资源的CSRF保护状态,然后使用类ResponseBuilderHook向客户端返回一个响应。当然,上述代码只是一个参考示例,在实际运用中,还需要开发团队在源代码中进一步构建CSRF缓解机制。
五、如何在PHP中实现CSRF令牌
由于PHP使得开发人员能够创建具有交互功能的动态网站,因此它在内容管理系统领域备受欢迎。不过,我们需要在PHP联系人和用户输入的表单中,通过实现post处理程序,对传入请求予以验证,让网站免受CSRF的攻击。在PHP联系人表单中,实现CSRF保护的典型工作流程如下:首先,我们需要在登录页面上创建一个表单的footer脚本。该脚本会调用SecurityService(一个PHP类),生成令牌,并启动PHP会话。SecurityService会被写入这个用于验证请求的令牌,并将令牌加载到隐藏字段中。如下代码展示了典型的SecurityService.php配置:
public function getCSRFToken()
{
if (empty($this->session[$this->sessionTokenLabel])) {
$this->session[$this->sessionTokenLabel] = bin2hex(openssl_random_pseudo_bytes(32));
}
if ($this->hmac_ip !== false) {
$token = $this->hMacWithIp($this->session[$this->sessionTokenLabel]);
} else {
$token = $this->session[$this->sessionTokenLabel];
}
return $token;
}
左右滑动查看完整代码然后,页面上呈现PHP联系人表单,以便用户输入诸如主题、消息、姓名和电子邮件等详细信息。表单还应当在隐藏字段中包含已生成的标记--csrf-token。在用户单击提交按钮后,应用程序将执行JQuery表单验证,并将各种参数发布到PHP上。
此外,在联系人表单被提交后,该表单会执行一个脚本,将已嵌入的令牌与会话中存储的令牌进行比较。如果令牌匹配,应用程序会去响应用户的请求。如果不匹配,PHP将通过错误消息去告知用户。
六、如何在Django中实现CSRF令牌
Django提供了一个开箱即用的CSRF中间件标签,您可以轻松启用它,以抵御CSRF攻击。如下工作流将向您展示如何在已有的框架内,实现针对CSRF的防护:在Django中,CSRF中间件是被默认启用的。如果开发人员想覆盖此设置,则应该在任何视图之前事先声明django.middleware.csrf.CsrfViewMiddleware,以启用CSRF令牌验证。对于一些特定视图,开发人员可以调用csrf-protect装饰器(decorator)。此类装饰器可用于在输出中插入CSRF令牌的视图。如下代码段展示了装饰器的基本配置:
from django.shortcuts import render
from django.views.decorators.csrf import csrf_protect
@csrf_protect
def my_view(request):
c = {}
# ...
return render(request, "a_template.html", c)
左右滑动查看完整代码开发人员可以通过在<form>元素中包含csrf_token标签,来启用对使用POST表单的模板的保护。当然,这仅适用于那些具有内部URL的表单,并不适用于针对外部URL的POST表单。对于外部URL,我们可以使用如下方法,来调用CSRF令牌的标记:
<form method="post">{% csrf_token %}
此外,我们还建议使用RequestContext,在各种相应的Django视图中呈现响应。
七、如何在Javascript中实现CSRF令牌
所有Javascript框架都带有实现CSRF防护的默认选项。例如,Express.js就包含了csurf中间件,可协助创建和验证各种令牌。请使用如下流程来启用csurf,并达到CSRF的防护效果:首先,请在index.js文件中包含以下代码:
app.get('/', csrfProtection, (req, res) => {
res.render('index', { csrfToken: req.csrfToken() });
左右滑动查看完整代码接着,请使用类似如下代码的配置,将index.ejs添加到各个视图文件夹中:
<input type='hidden' name='_csrf' value='<%= csrfToken %>'>
<label for='name'> Name:</label>
<input type='text' name='name'>
<button type='submit'> Update </button>
</form>
左右滑动查看完整代码主配置文件中的“/”路由会在index.ejs模板中呈现csrfToken变量,并在隐藏字段中插入令牌。当用户提交表单时,对应的请求会被添加到“/profile” 路由中,以供CSRF令牌验证。如果缺少此CSRF令牌,应用程序将返回一个无效的CSRF令牌错误。
八、如何修复无效的CSRF令牌
正如前文所述,CSRF攻击将导致用户会话被未经验证的访问,并产生严重的后果。为了防范此类攻击,确保用户使用有效令牌来发布请求,我们需要通过如下方法来及时修复和防止无效令牌:
- 使用自定义的请求标头
在易受攻击的应用程序中,添加CSRF令牌往往需要更改用户界面等复杂的管理任务。而作为替代方案,安全团队可以构建自定义的请求标头,以使用同源策略(same-origin policy)来加强CSRF防御。安全策略通过强制限制自定义的标头只能在Javascript中被构建,并且只能在其源头中被使用的方式,来拒绝各种跨域请求。
- 利用内置和现有的CSRF处置措施
在大多数情况下,许多开发框架都包含了,内置于安全套件中的同步器令牌的防御机制。它们可以有效地保护整个应用程序的技术栈。如果在开发团队现有的技术栈中,所用到的框架已经内置提供了默认的抵御CSRF的选项,那么您完全可以尝试着在此基础上,根据业务用例的实际,进行自定义和配置实施。
- 部署基于UI的CSRF防御
CAPTCHA、重新验证(re-authentication)的认证机制、以及一次性令牌之类的机制,都可以通过分析请求,来防范那些通过CSRF进行未经授权的操作,并过滤掉各种非法操作请求。它们虽然提供了强大的验证与防御措施,但是我们需要通过部署UI来改变用户的体验,以便更好地处理关键性的安全操作。
原文链接:https://dzone.com/articles/what-is-a-csrf-token
译者介绍
陈峻 (Julian Chen),51CTO社区编辑,具有十多年的IT项目实施经验,善于对内外部资源与风险实施管控,专注传播网络与信息安全知识与经验;持续以博文、专题和译文等形式,分享前沿技术与新知;经常以线上、线下等方式,开展信息安全类培训与授课。