利用幻影$Version Cookie绕过WAF防护

本文深入探讨现代Cookie解析器的危险特性,揭示如何通过RFC2109遗留规范、引用字符串编码和Cookie分割等技术绕过Web应用防火墙的检测机制,涵盖Spring、Python等框架的解析差异及实际利用方法。

利用幻影$Version Cookie绕过WAF防护

HTTP Cookie通常控制着网站的关键功能,但其漫长而复杂的历史使其容易受到解析差异漏洞的影响。本文将探讨现代Cookie解析器一些危险且鲜为人知的特性,并展示如何滥用这些特性来绕过Web应用防火墙。这是关于Cookie解析系列博客文章的第一部分。

降级Cookie解析器:$Version的妙用

尽管现代浏览器不再支持旧的RFC标准,但许多Web服务器仍然支持。以下是一个有效的Cookie标头示例:

1
Cookie: $Version=1; foo="bar"; $Path="/"; $Domain=abc;

$Version是一个必需属性,用于标识Cookie符合的状态管理规范版本。其他有趣的属性包括$Domain$Path,我们将在后面讨论。

根据标准,如果Cookie值包含空格、分号和等号等特殊字符,必须用双引号括起来:

许多HTTP/1.1标头字段值由LWS(线性空白)或特殊字符分隔的单词组成。这些特殊字符必须放在引号字符串中才能在参数值中使用。- RFC 2068

现代框架分析该标头的方式如下:

  • Flask: {"foo":"bar","$Version":"1","$Path":"/","$Domain":"abc"}
  • Django: {"foo":"bar","$Version":"1","$Path":"/","$Domain":"abc"}
  • PHP: {"foo":"\"bar\"","$Version":"1","$Path":"\"\/\"","$Domain":"abc"}
  • Ruby: {"foo":"\"bar\"","$Version":"1","$Path":"\"\/\"","$Domain":"abc"}
  • Spring: { "foo": "\"bar\""}
  • SimpleCookie: { "foo": "bar"}

可以看到,结果非常混乱。这种混乱给我们寻找安全弱点的机会。

Spring Boot Starter Web 2.x.x的解析特性

默认使用Apache Tomcat v.9.0.83的Spring Boot Starter Web 2.x.x以下列方式处理Cookie标头:

  • 同时处理RFC6265和RFC2109标准,如果字符串以特殊的$Version属性开头,则默认使用旧版解析逻辑
  • 还支持$Path$Domain属性,如果在响应前未正确检查,可能允许用户更改反射的Cookie属性
  • 解析器还将取消转义任何以反斜杠(\)开头的字符,如下例所示:
1
2
Cookie: $Version=1; foo="\b\a\r"; $Path=/abc; $Domain=example.com
=> Set-Cookie: foo="bar"; Path=/abc; Domain=example.com

Python SimpleCookie解析器的特性

Python SimpleCookie解析器在跟随键值对时支持旧版Cookie请求属性。这允许以与前面演示的相同方式注入恶意Cookie属性。所有基于Python的框架(Flask、Django等)都允许引用的Cookie值,但不识别魔术字符串(如$Version),将其视为普通的Cookie名称。它们还会自动解码引用字符串中的八进制转义序列:

任何非文本字符都被翻译成一个4字符序列:一个正斜杠后跟该字符的三位八进制等效值。- Cookies.py

例如:

  • "\012"\n
  • "\015"\r
  • "\073";

绕过Web应用防火墙(WAF)

许多WAF无法检测上述技术,允许恶意负载隐藏在引用的字符串中。

通过引用字符串编码绕过值分析

此外,引用的Cookie可以促进注入漏洞,如SQL注入或命令注入。这些类型的攻击通常使用特殊命令分隔符,如分号(;)、逗号(,)、换行符(\n)和反斜杠(\)。虽然这些通常在Cookie值中受限制,但有时可以被操纵以触发漏洞。

使用Burp Suite扩展的HttpHandler接口可以轻松实现这种类型的引用Cookie编码:

1
2
3
4
5
6
7
8
def handleHttpRequestToBeSent(requestToBeSent):
    result = "$Version=1; "
    for param in requestToBeSent.parameters:
        result += f"{param.name}=\""
        for char in param.value:
            result += f"\\{char}"
        result += "\"; "
    return continueWith(requestToBeSent.withAddedHeader("Cookie",result))

例如,Amazon Web Services WAF会阻止包含任何不允许函数内参数的请求:

  • eval() => 允许
  • eval('test') => 禁止
  • "\e\v\a\l\(\'\t\e\s\t\'\)" => 允许
  • "\145\166\141\154\050\047\164\145\163\164\047\051" => 允许

绕过Cookie名称阻止列表

RFC2109的另一个关键方面:服务器还应接受逗号(,)作为Cookie值之间的分隔符。这可以被利用来绕过简单的WAF签名,这些签名可能不会预料到Cookie名称隐藏在值中。此外,规范允许在注入的属性-值对中的等号前后有任何数量的空格或制表符,这也可能被用于避免检测。

考虑以下Cookie标头示例:

1
$Version=1; foo=bar, abc = qux => "abc": "qux"

通过Cookie分割绕过值分析

与许多其他HTTP标头一样,Cookie标头可以在单个请求中多次发送。服务器处理多个相同标头的方式可能有所不同。例如,我发送了以下GET请求:

1
2
3
4
GET / HTTP/1.1
Host: example.com
Cookie: param1=value1;
Cookie: param2=value2;

并得到以下返回:

  • Flask: { "param1": "value1", ",param2": "value2"}
  • Django: { "param1": "value1", ",param2": "value2"}
  • PHP: { "param1": "value1", ",_param2": "value2"}
  • Ruby: { "param1": "value1", ", param2": "value2"}
  • Spring: { "param1": "value1", "param2": "value2"}

如我们所见,Ruby、PHP以及Python框架Django和Flask将标头组合成单个逗号分隔的字符串(参数之间可选空格)。引用的Cookie值也受支持,这允许通过使用Cookie标头作为多行标头 continuation 来隐藏恶意负载。

不幸的是,引用字符串技术不适用于PHP和Ruby。要绕过上述AWS签名,可以使用以下请求:

1
2
3
Cookie: name=eval('test') => 禁止
Cookie: name=eval('test//
Cookie: comment')

结果Cookie:name=eval('test//, comment') => 允许

使用Burp扩展自动化

我们已在Param Miner中为您实现了这些技术的最佳部分。

预防漏洞

您可以采取一系列步骤来防止Cookie中的解析差异漏洞:

  • 确保在Web服务器上禁用对RFC2109的旧版支持,除非明确需要
  • 严格验证所有用户输入,以识别和减轻潜在危险数据。这有助于确保输入在应用程序内处理或与其他系统组件交互时是安全的
  • 避免依赖关于用户输入中特定字符存在或不存在的假设,以减少意外行为的风险

了解更多

这篇博客文章只是我们探索Cookie解析逻辑的第一部分。要了解这些技术如何在实际场景中应用以升级漏洞,请务必查看《使用Cookie三明治技术窃取HttpOnly Cookie》。

有关我们最新的博客文章和安全见解,请在X(前身为Twitter)和Bluesky上关注我们,并加入官方PortSwigger Discord。

如果您对引用Cookie的更多信息感兴趣,请查看我早期关于Pylibmc的Memcached命令注入的研究。

如果您对Cookie标头中的无效字符感到好奇,我推荐April King的《处理Cookie是一个雷区》研究。

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