Security 为什么不使用会话ID作为XSRF令牌?
为什么使用[会话id的签名版本]作为(XSRF/CSRF)预防令牌,而不是会话id本身 (对于XSRF预防令牌,我指的是表单提交中必须包含的魔法值,webapp才能接受表单。) 如果有窃听者,他/她将找到XSRF令牌和SID cookie(?) 如果存在XSS攻击,则恶意JavaScript代码可以读取XSRF令牌和SID cookie(?) 然而:Security 为什么不使用会话ID作为XSRF令牌?,security,cookies,playframework,csrf,Security,Cookies,Playframework,Csrf,为什么使用[会话id的签名版本]作为(XSRF/CSRF)预防令牌,而不是会话id本身 (对于XSRF预防令牌,我指的是表单提交中必须包含的魔法值,webapp才能接受表单。) 如果有窃听者,他/她将找到XSRF令牌和SID cookie(?) 如果存在XSS攻击,则恶意JavaScript代码可以读取XSRF令牌和SID cookie(?) 然而: 在给定SID的情况下,攻击者无法构造有效的XSRF令牌,因为在对SID进行签名以获取XSRF令牌时,他/她没有使用的密钥。-但是,攻击者怎么可能只
getId
返回会话ID):
(play/framework/src/play/mvc/Scope.java)
此处播放检查
是否具有有效的XSRF令牌:
(play/framework/src/play/mvc/Controller.java)
更新:
Play已经改变了它生成XSRF令牌的方式,现在不再使用SID,而是使用随机值进行签名和使用!(我刚刚将我的Play Framework Git repo克隆版从旧的Play版本1.1更新到新的1.2。也许我昨天就应该这么做,嗯。) 那他们为什么要改变呢 我找到了提交:
[#669]再次修复并申请闪存和错误
D6E5DC50EA11FA7EF626CBDF0161555CBDDA54C 发文日期:
仅在绝对必要时创建会话
在资源的每个请求上都会创建会话cookie。只有在会话中确实存在要存储的数据时,play才应该创建会话cookie
因此,他们使用的是随机值,而不是SID,因为SID可能尚未创建。这就是不使用SID的导数作为XSRF标记的原因。但是没有澄清他们为什么在过去使用SID时对其进行签名/哈希处理。纯粹的CSRF攻击无法访问浏览器的cookies,因此当你说“窃听者”时,只有在他们嗅探数据包(即没有SSL、公共wifi)的情况下才能实现 根据Play框架的配置(我不熟悉,所以将此作为一般web应用程序建议),会话和身份验证cookie几乎肯定会被标记,因为它们无法通过XSS从客户端读取
最终,使用同步器令牌模式保护XSRF的想法是使用唯一的值(最好是加密强度高的值),该值仅为服务器和客户端所知,并且是该会话所独有的。基于这个目标,Play框架似乎做得很好。也许Play框架不希望HTML中有SID。最终用户Bob可能会下载一个网页,如果该网页中有
,则SID将包含在下载的HTML中(如果SID本身被用作XSRF令牌)。如果Bob然后通过电子邮件将下载的页面发送给Mallory,那么Mallory将找到SID并可以模拟Bob
(不使用SID的另一个次要原因:正如我在更新中提到的,SID可能根本不可用。为了节省CPU资源,它可能会尽可能晚地生成。)首先要说的是,您可以将会话ID重新用作CSRF令牌,只要它能保护您免受CSRF攻击,并且不会自动造成任何严重的安全漏洞。然而,出于一些合理的原因。(他们现在根本没有解决这个问题。) 反对将会话ID重新用作CSRF令牌的论点可以总结如下(要点以粗体显示,下面有理由):
- 他们仍然需要用相应的会话令牌引诱用户进入攻击页面(或让他们阅读攻击电子邮件,或在iframe中查看攻击广告等),以任何方式利用CSRF令牌。有了会话ID,他们只需将其放在浏览器中,然后像使用该用户一样使用该网站
- 虽然他们可以使用用户的凭据发送请求,但仍然阻止他们查看对这些请求的响应。这可能(也可能不是,取决于您保护的API的结构和攻击者的独创性)在实践中意味着,虽然攻击者可以代表用户执行操作,但他们无法获取用户有权查看的敏感信息。(您更关心的是哪一个取决于上下文-我们假设攻击者倾向于获取您银行账户的内容,而不是仅仅知道其金额,但他们也更希望了解您的病史,而不是破坏它。)
- XSS攻击可能允许攻击者获取CSRF令牌,因为将其烘焙到DOM中是常见的做法(例如,作为
中
元素的值)。另一方面,会话cookie可能会要求攻击者进行更多的前期工作,以有效利用XSS漏洞 - 如果CSRF令牌被发回t
public String getAuthenticityToken() { return Crypto.sign(getId()); }
protected static void checkAuthenticity() { if(Scope.Params.current().get("authenticityToken") == null || !Scope.Params.current().get("authenticityToken").equals( Scope.Session.current().getAuthenticityToken())) { forbidden("Bad authenticity token"); } }
public String getAuthenticityToken() { if (!data.containsKey(AT_KEY)) { data.put(AT_KEY, Crypto.sign(UUID.randomUUID().toString())); } return data.get(AT_KEY); }