Cookie前缀绕过技术:揭秘__Host和__Secure防护机制的安全漏洞

本文详细分析了浏览器Cookie前缀安全机制存在的漏洞,探讨了如何利用Unicode空白字符和传统解析差异绕过__Host和__Secure前缀保护,并提供了具体的攻击场景和防护建议。

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

浏览器引入了Cookie前缀来保护会话安全,防止攻击者设置恶意Cookie。本文将展示如何利用浏览器和服务器逻辑差异来绕过Cookie防护机制。

使用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 设计