绕过Cookie防护:揭秘__Host和__Secure前缀的旁路攻击技术

本文详细探讨了如何利用浏览器与服务器在Cookie解析上的不一致性,通过Unicode空白字符和传统解析模式绕过__Host和__Secure安全前缀,实现Cookie注入与权限提升的技术细节。

概述

浏览器引入了Cookie前缀(如__Host-__Secure-)来加强会话安全性,防止攻击者设置恶意Cookie。本文展示了如何利用浏览器和服务器逻辑之间的差异来绕过这些Cookie防御机制。

通过Unicode空白字符覆盖Cookie

Cookie前缀在RFC 6265bis中定义,通过命名规则增强安全性:带有__Host-前缀的Cookie必须是仅限主机的(不能跨子域共享),而带有__Secure-前缀的Cookie必须来自安全源。这些限制由浏览器强制执行,旨在防止Cookie投掷或会话固定等攻击。

然而,浏览器和服务器在处理Cookie编码和解析时的不一致,可能引入微妙而危险的缺陷。根据原始的RFC 6265,Cookie头被定义为一个八位字节序列,而非字符。这意味着浏览器在网络上发送原始字节,而服务器负责将这些字节解码为字符串。如果浏览器和服务器对这些字节的解释不同,就会发生解析差异。

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

以下是一个展示此行为的最小化概念验证代码:

1
2

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
2

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

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

完整攻击场景

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

1
2
3
4
5
6

document.cookie=`${String.fromCodePoint(0x2000)}__Host-name=<img src=x onerror=alert(1)>; 

Domain=example.com; 

Path=/;`

浏览器并未意识到此Cookie等同于受保护的Cookie,因此接受了它,并在请求中同时包含了原始Cookie和攻击者控制的Cookie。在网络上,浏览器发送以下头信息:

1
2

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