利用幻影$Version Cookie绕过WAF的技术解析

本文探讨了现代Cookie解析器中鲜为人知但危险的特性,特别是利用RFC2109标准的$Version属性,通过引号编码、Cookie分割等技术绕过Web应用防火墙(WAF)的检测机制,并提供了Burp Suite扩展实现自动化攻击的示例。

利用幻影$Version Cookie绕过WAF

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

通过$Version降级Cookie解析器

HTTP Cookie的标准化尝试很多,始于第一个官方标准:RFC2109。尽管现代浏览器不支持旧的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,该版本以以下方式处理Cookie头:

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

另一个很好的例子是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 设计