代码中的金丝雀:利用Alert()检测XSS漏洞

本文介绍了一种创新的XSS漏洞检测方法,通过钩子alert()函数构建金丝雀系统,实时捕获攻击数据并发送到回调服务器,帮助蓝队快速响应和修复漏洞。

代码中的金丝雀:利用Alert()检测XSS漏洞

| Jack Hyland

Jack Hyland自大学毕业以来一直从事信息安全领域工作,并利用业余时间深入学习新技术。他现在专注于创建和贡献开源项目,同时执行企业网络和基础设施的安全评估。

作为一名Web应用程序渗透测试员,多年来我发现了数百个跨站脚本(XSS)漏洞¹。跨站脚本是一个 notoriously 难以解决的问题,其检测受到Web服务器对客户端攻击缺乏可见性的阻碍。

一天,我有了一个疯狂的想法:“是否可能构建一个金丝雀,当XSS漏洞在网站任何地方被利用时,通知网站所有者?”让我们来探索一下……

警告: 本博客涉及一些技术内容,适合对Web开发、JavaScript和Linux有基本了解的读者。

选择金丝雀

金丝雀是一种微妙的指示器,用于信号特定条件或事件的存在,通常作为潜在问题的早期预警系统或警报。当安全研究人员在网站上寻找XSS漏洞时,他们通常旨在执行alert() JavaScript函数作为概念验证。这是因为alert()函数提供了清晰且明确的利用指示,停止网页的执行,并允许黑客快速识别和验证XSS漏洞的存在。因此,alert()已成为在线XSS单词列表中的主要内容²。

根据我的经验,现代网站很少使用alert()函数在浏览器中显示信息,使其在合法浏览中成为罕见事件。因此,当现代网页上触发警报框时,很可能是成功XSS利用的迹象。

构建XSS金丝雀

确定了alert()函数作为我们的触发器后,我们现在可以设计金丝雀的其余部分。我们的目标是信号XSS漏洞的存在,并向蓝队提供有价值的见解。为实现这一点,我们的金丝雀必须:

  • 保存原始alert()函数的副本,保留其原始行为。
  • 捕获关键上下文信息,以提供对攻击的全面理解。
  • 将关键上下文信息发送到回调服务器,以便分析和用于安全决策。
  • 调用保存的alert()函数,保持原始功能。

以下JavaScript示例演示了钩子alert()函数的基本实现,允许我们在继续原始行为之前执行自定义代码。在这种情况下,我们将在继续之前计算2+2。

1
2
3
4
5
const originalAlert = window.alert; // 保存alert()的副本
window.alert = function(...args) { // 钩子alert()
    console.log(2+2); // 在这里运行任何代码
    originalAlert.apply(window, args); // 调用原始alert()
};

钩子alert()函数后,我们现在可以将重点转向重建攻击序列,并更深入地理解我们的网站是如何被利用的。为实现这一点,我们将生成详细的堆栈跟踪,提供导致利用触发的执行路径的清晰可视化。我们还将收集警报消息、受损页面的URL、事件的时间戳和文档引用者。然后,我们将捕获利用时的DOM快照,其中应包含页面中反映的任何恶意代码。

1
2
3
4
5
6
7
8
const debugData = {
    alert_msg: args.join(' '), 	// 攻击者显示的消息
    stack: error.stack,		// 执行的完整堆栈跟踪
    url: window.location.href, 	// 当前URL及参数
    ref: document.referrer,	// 引用者域名
    dom: document.documentElement.outerHTML,	// DOM的副本
    timestamp: new Date().toISOString()	// 时间戳
};

最后一步是将收集的调试信息传输到网站所有者控制的服务器,使他们能够调查漏洞并采取行动。我们通过POST请求实现这一点。

1
2
3
4
5
6
7
8
9
// 通过POST请求将数据发送到回调服务器
fetch('https://example.com/xss', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
    },
    // 将调试数据转换为JSON
    body: JSON.stringify(debugData)
})

将所有内容整合在一起,我们得到了XSS金丝雀的最终代码。在下一节中,我们将概述设置金丝雀回调服务器的过程,该服务器将接收用于事件响应和分析的上下文信息。

 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
const originalAlert = window.alert;

window.alert = function(...args) {
    // 创建错误以捕获堆栈跟踪
    const error = new Error();

    // 收集调试信息
    const debugData = {
        alert_msg: args.join(' '),	// 警报消息
        stack: error.stack,        	// 堆栈跟踪
        url: window.location.href, 	// 当前URL
        ref: document.referrer,		// 网站引用者
        dom: document.documentElement.outerHTML, // DOM的副本
        timestamp: new Date().toISOString() 	 // 时间戳
    };

    // 通过POST请求将数据发送到金丝雀回调服务器
    fetch('https://example.com/xss', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
        },
        // 将调试数据转换为JSON
        body: JSON.stringify(debugData)
    })
    .catch((error) => {
        console.error('Failed to send xss report:', error);
    });

    // 调用原始alert函数以确保警报仍然有效
    originalAlert.apply(window, args);
};

XSS金丝雀回调Web服务器设置

在继续之前,请确保您有一个专用域名和一个专用的Linux虚拟私有服务器(VPS),运行Ubuntu或Debian,至少有两个核心和2GB RAM。设置一个A记录,从域名指向VPS的公共IP地址。以下步骤将通用地引用此域名为example.com。

要接收来自XSS金丝雀的日志,您需要设置回调服务器以在管理门户中查看收集的报告。随意修改服务器代码以通过电子邮件、Slack、Discord或您选择的任何其他消息平台发送通知。如果您开发了实时消息集成,请考虑与社区分享。

安装脚本

为了轻松在您的服务器上安装XSS金丝雀回调软件,我创建了一个安装脚本³。此脚本首先安装依赖项,然后创建一个系统守护进程,以低权限用户身份运行Web服务器。命令中的电子邮件用于Let’s Encrypt在SSL证书即将到期时通知您,尽管默认启用了自动续订。将curl管道到bash作为root通常是不建议的,因此请在专用VPS上执行以下命令之前阅读代码。

1
bash <(curl -s https://xsscanary.com/install) example.com your@email.com

以下视频显示了成功安装的样子。如果您遇到错误,请尝试再次运行脚本,或者如果您需要帮助进行故障排除,请在GitHub存储库上打开一个问题⁴。

我们刚刚安装的XSS金丝雀回调服务器是一个简单的概念验证,虽然我尽力确保其安全,但它没有速率限制或负载平衡,并使用基本身份验证而没有MFA。这是一个起点,我希望社区在此基础上构建以使其功能完整。

回调Web服务器有两个端点。首先,/xss端点接受包含来自XSS金丝雀的调试信息的POST请求。收到请求后,服务器将伴随的XSS金丝雀数据作为JSON对象存储在xss_canary.json文件中。其次,/dashboard端点返回一个密码保护的页面,供管理员查看传入的金丝雀。安装后,您可以在此处使用用户名admin和安装脚本输出中显示的密码登录。

将XSS金丝雀插入您的网站

现在我们已设置好回调服务器,我们将注意力转回金丝雀代码。

为了简化过程,我已将金丝雀代码在xsscanary.com上提供。域GET参数允许您替换您网站的回调服务器,从而轻松将金丝雀集成到您现有的基础设施中。脚本有两个版本,一个包含调试信息中的DOM,一个不包含。两者的链接如下:

然而,从您在互联网上找到的随机博客中包含JavaScript文件通常是不良实践,因为我可能某天轻松更改代码为恶意代码。相反,您应该首先独立阅读和验证代码,然后使用子资源完整性⁵(SRI)检查。这硬编码了脚本的SHA-384哈希,使得浏览器仅在哈希完全匹配时执行它。此安全措施使您无需信任我。

以下网站将为您创建一个安全的金丝雀脚本标签。我将在博客的其余部分使用test.xsscanary.com作为我的回调域。

生成XSS金丝雀SRI脚本标签

或者,您可以选择直接在您的网站上包含XSS金丝雀脚本,这消除了依赖外部源的需要,并提供了额外的控制和安全层。

测试金丝雀

要测试您的XSS金丝雀,请将以下HTML文件中的高亮脚本标签替换为由srihash.org生成的您自己的脚本标签。将更新的HTML文件保存到桌面,并在您首选的Web浏览器中打开它,以验证金丝雀是否按预期工作。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!DOCTYPE html>
<html lang="en">
<script src="https://xsscanary.com/canary.js?domain=test.xsscanary.com" integrity="sha384-sxhmxvuSR2mKLQjVnLSd0BjPODym8uvUotztbvITfsgmI2jtpgHv3Er2d5IikySU" crossorigin="anonymous"></script>
<head>
    <meta charset="UTF-8">
    
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>XSS Canary Test</title>
</head>
<body>
    <h1>Testing XSS Canary</h1>
    <p>Welcome to the vulnerable webpage. </br>Please check the URL's <strong>id</strong> parameter!</p>
    <script>
        // 从location.search检索'id'查询参数
        var urlParams = new URLSearchParams(location.search);
        // 仅使用查询字符串中的'id'参数
        var userId = urlParams.get('id'); 

        // 易受攻击:使用document.write而没有清理
        document.write("<p>User ID: " + userId + "</p>");
    </script>
</body>
</html>

一旦HTML文件打开,XSS金丝雀将自动加载,如下方开发者工具截图中的(1)和(2)所示。此网页在id GET参数中有一个基于DOM的跨站脚本漏洞,可以通过将有效载荷?id=”><svg%20onload=alert(“BHIS”)>添加到URL栏来利用,如下所示(3)。id参数值以不安全的方式写入网页,从而触发利用。因为我们钩子了alert()函数,金丝雀被激活(4)并将调试信息发送到我的回调服务器(在这种情况下是test.xsscanary.com)。最后,调用原始alert()函数,向用户显示警报(5),没有任何金丝雀已发送的视觉指示。

要查看回调服务器收集的XSS金丝雀,请在浏览器中打开https://example.com/dashboard,这将提示您输入用户名和密码。用户名是admin,您的密码是在安装期间生成的。如果您忘记了密码,请重新登录回调服务器并执行以下命令。此密码仅在您重新运行安装脚本时更改。

1
echo $DASHBOARD_PASSWORD

登录后,您应该看到以下信息。从金丝雀报告中,我们可以轻松识别id GET参数导致我们网站/example.html端点的XSS漏洞。现在坐下来,让攻击者完成所有工作,而您的监控系统捕获XSS 0-day。

一旦您验证了XSS金丝雀正常工作,您可以通过简单地将金丝雀代码添加到每个网页的顶部来将其集成到您的网站中。将金丝雀保持在每个网页的顶部确保我们的钩子脚本在任何其他代码之前运行。

如果您已经走到这一步,您已成功实现了一个强大的监控系统,检测并警报您网站上的反射、基于DOM和存储的跨站脚本(XSS)漏洞。这种主动方法将帮助您领先于威胁,使您能够迅速响应并最小化暴露窗口。

结束语和考虑事项

虽然XSS金丝雀是一个高度有效的工具,用于实时检测跨站脚本利用,但在您的网站上实施之前,必须考虑几个关键因素。

  • 如果您的网站频繁使用alert()函数,您可能会遇到大量的误报。
  • 您的回调服务器将是公共信息,因为它在每个网页上被引用。无法阻止用户手动提交虚假信息。
  • 我强烈建议您在专用域名和专用VPS上运行您的金丝雀回调服务器。
  • 如果您运行一个漏洞赏金计划并计划实施XSS金丝雀,考虑在修补漏洞之前等待漏洞被报告,允许研究人员因其努力而获得奖励。
  • 如果您的网站处理敏感的医疗或财务数据,您可能不应发送用户DOM的副本,因为它可能包括PII。相反,使用此脚本的替代版本,省略DOM信息xsscanary.com/canary_no_dom.js。
  • 本博客由安全研究人员撰写,而非法律或GDPR专家。如果您受隐私法规约束,请在向您的网站添加XSS金丝雀之前咨询法律专业人士。
  • 如果攻击者使用print()而不是alert(),如James Kettle⁶在2021年建议的,此XSS金丝雀不会触发。XSS金丝雀代码可以轻松修改以也钩子print(),但我将留给读者作为家庭作业。

如果攻击者长时间彻底审查您的网站,他们可能会调查“XSS金丝雀”并阻止域连接。然而,正如我们之前提到的,互联网上的大多数XSS单词列表使用alert()作为概念验证,并且有充分理由:攻击者和安全研究人员倾向于避免向他们的有效载荷添加复杂性,因为这可能导致利用失败。这些单词列表通常加载到扫描器中,并在没有额外解钩逻辑的情况下喷洒到网站参数中。

如果您担心您的金丝雀被绕过,考虑将JavaScript金丝雀的混淆版本直接包含到您的网站中,并修改回调端点。这种方法使在DNS级别阻止变得更加困难,同时也使使用静态签名检测它的尝试复杂化。

截至撰写本文时,我相信XSS金丝雀概念是原创的,但如果不是这种情况,请联系,我将乐意包含类似研究的参考文献。

参考文献

  1. https://owasp.org/www-community/attacks/xss/ ↩︎
  2. https://github.com/danielmiessler/SecLists/tree/master/Fuzzing/XSS/human-friendly ↩︎
  3. https://gist.github.com/ACK-J/9acef3f7d188de49d6ff7304328e168a ↩︎
  4. https://github.com/ACK-J/XSS-Canary-Callback ↩︎
  5. https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity ↩︎
  6. https://portswigger.net/research/alert-is-dead-long-live-print ↩︎
  7. https://discord.com/invite/bhis ↩︎
  8. https://github.com/ACK-J/XSS-Canary-Callback ↩︎

3/24更新 – 类似研究: https://www.trustwave.com/en-us/resources/blogs/spiderlabs-blog/detecting-successful-xss-testing-with-js-overrides/

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