Cookie混沌:如何绕过__Host和__Secure Cookie前缀

本文详细介绍了如何利用浏览器和服务器在Cookie解析中的不一致性来绕过__Host和__Secure前缀的安全防护,包括Unicode空白字符和传统解析技术,展示了完整的攻击场景和实际漏洞利用方法。

Cookie混沌:如何绕过__Host和__Secure Cookie前缀

浏览器添加了Cookie前缀来保护会话并阻止攻击者设置有害Cookie。在本文中,您将看到如何利用浏览器和服务器逻辑中的差异来绕过Cookie防御。

有关可视化的演练,请参阅SteelCon直播录制:

使用Unicode空白字符覆盖Cookie

Cookie前缀在RFC 6265bis中引入,通过命名规则加强Cookie安全性:

  • 带有__Host-前缀的Cookie必须是主机唯一的,意味着不能在子域之间共享
  • 带有__Secure-前缀的Cookie必须从安全源设置

这些限制由浏览器强制执行,以防止Cookie投掷或会话固定等攻击。然而,浏览器和服务器处理Cookie编码和解析的方式不一致可能引入微妙但危险的缺陷。

根据原始RFC 6265,Cookie头被定义为一个八位字节序列,而不是字符。这意味着浏览器在网络上发送原始字节,而服务器负责将这些字节解码为字符串。如果浏览器和服务器对这些字节的解释不同,就会发生解析差异。

通过使用UTF-8编码,攻击者可以伪装受限制的Cookie - 例如以__Host-开头的Cookie - 以绕过浏览器保护。浏览器可能将其视为非限制性Cookie,但服务器可能以将其解码和规范化为受保护Cookie的方式处理。

这是一个演示此行为的最小概念验证:

1
document.cookie=`${String.fromCodePoint(0x2000)}__Host-name=injected; Domain=.example.com; Path=/;`

这个带空白前缀的Cookie被浏览器解释为非前缀、非限制性值,因此被发送到目标域范围内的所有子域。

在测试期间,我发现某些服务器端框架(如Django和ASP.NET)在处理之前对Cookie名称应用规范化和修剪。具体来说,当服务器将U+2000解释为空白字符时,会将其删除,导致Cookie名称变为等效于__Host-name

Django使用Python内置的.strip()方法来处理Cookie键和值。此方法删除了各种Unicode空白字符,包括[133, 160, 5760, 8192–8202, 8232, 8233, 8239, 8287, 12288],有效地将它们视为空格。

有趣的是,Safari处理这种情况的方式不同。它不支持Cookie名称中的多字节Unicode空白字符,这阻止了像U+2000这样的值被使用。但是,仍然允许单字节字符,如U+0085(NEL)和U+00A0(不换行空格)。

使用传统解析覆盖Cookie

除了Unicode技巧外,传统的Cookie解析行为也可被滥用以绕过前缀保护。如上一篇博客文章所示,如果Cookie头以$Version=1开头,一些基于Java的Web服务器(如Apache Tomcat和Jetty)会切换到传统解析模式。在此模式下,单个Cookie字符串可能被解释为多个独立的Cookie。例如,以下JavaScript设置了一个包含伪造__Host-对的Cookie:

1
document.cookie=`$Version=1,__Host-name=injected; Path=/somethingreallylong/; Domain=.example.com;`;

这使攻击者能够绕过浏览器的前缀检查,并从子域或不安全源注入高权限Cookie。

完整攻击场景

假设您发现一个XSS漏洞,其中Cookie值在没有适当转义的情况下被反射到网页中。应用程序使用__Host-前缀的Cookie,这通常由于浏览器强制限制而防止从不信任的子域覆盖。但是,使用前面描述的技术之一,您使用JavaScript注入伪造的__Host-name Cookie:

1
2
3
document.cookie=`${String.fromCodePoint(0x2000)}__Host-name=<img src=x onerror=alert(1)>; 
Domain=example.com; 
Path=/;`

浏览器不知道此Cookie等同于受保护的Cookie,接受它并在请求中包含原始和攻击者控制的Cookie。在网络上,浏览器发送以下头:

1
Cookie: __Host-name=Carlos;  __Host-name=<img src=x onerror=alert(1)>;

当此请求到达后端时,服务器解析Cookie头。如果存在多个同名Cookie,许多框架(包括Django)通过仅接受一个值来解决冲突,通常是最后一个出现的值。在这种情况下,攻击者控制的值优先。

如果应用程序在没有适当编码的情况下将此Cookie值反射到响应中,结果就是跨站脚本漏洞。或者,如果相同的Cookie用于CSRF保护或会话标识,此行为也可能导致会话固定或其他权限提升路径。

Django对我的漏洞报告作出回应:

官方Django文档警告不要允许来自不受信任子域的Cookie,因为这容易受到攻击:https://docs.djangoproject.com/en/5.0/topics/http/sessions/#topics-session-security。由于此攻击依赖于此,这不会被视为安全漏洞。

要点

同一个Cookie可以被浏览器和后端以不同的方式解释。这种不匹配可能悄悄地破坏Cookie机密性和完整性的保证,即使有最强的浏览器端保护。为了帮助测试这里讨论的问题,我为Burp Suite创建了一个轻量级自定义操作。

它可以快速检测后端可能容易受到Cookie前缀绕过攻击的条件。

这篇博客文章结束了我们对Cookie解析不一致性以及如何利用它们来绕过安全机制的探索。如果您还没有,请确保查看本系列的前一篇文章,我们在其中演示了如何使用Cookie三明治技术窃取HttpOnly Cookie。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计