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

本文深入探讨了HTTP Cookie解析中的RFC2109遗留标准漏洞,展示了如何利用$Version属性、引号编码和Cookie分割等技术绕过Web应用防火墙的检测机制,并提供了具体的Burp Suite扩展实现方法。

利用幻影$Version Cookie绕过WAF

HTTP Cookie通常控制着网站的关键功能,但其漫长而复杂的历史使其容易受到解析差异漏洞的影响。本文将探讨现代Cookie解析器一些危险且鲜为人知的功能,并展示如何滥用这些功能来绕过Web应用防火墙。这是关于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
2
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 设计