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

本文详细介绍了一种创新的XSS漏洞检测方法——通过钩子alert()函数构建"金丝雀"系统,实时捕获攻击数据并发送到回调服务器,帮助网站管理员及时发现和响应跨站脚本攻击。

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

引言

作为一名长期从事Web应用渗透测试的安全研究员,我在职业生涯中发现了数百个跨站脚本(XSS)漏洞。XSS是一个 notoriously难以解决的问题,其检测受到Web服务器对客户端攻击缺乏可见性的阻碍。

于是,我产生了一个疯狂的想法:“能否构建一个’金丝雀’,当网站任何地方发生XSS漏洞利用时,它能通知网站所有者?”

让我们来探索这个想法…

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

选择金丝雀

金丝雀是一个微妙的指示器,用于信号特定条件或事件的存在,通常作为潜在问题的早期预警系统。

当安全研究人员在网站上寻找XSS漏洞时,他们通常以执行alert() JavaScript函数作为概念验证。这是因为alert()函数提供了明确无误的利用指示,暂停网页执行,让黑客快速识别和验证XSS漏洞的存在。因此,alert()已成为在线XSS单词列表的主要内容。

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

构建XSS金丝雀

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

  1. 保存原始alert()函数的副本,保留其原始行为
  2. 捕获关键上下文信息以提供对攻击的全面理解
  3. 将关键上下文信息发送到回调服务器,以便分析和用于安全决策
  4. 调用保存的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('发送xss报告失败:', error);
    });

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

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

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

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

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

将XSS金丝雀插入您的网站

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

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

1
2
https://xsscanary.com/canary.js?domain=example.com
https://xsscanary.com/canary_no_dom.js?domain=example.com

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

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

1
https://www.srihash.org/

生成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的跨站脚本漏洞,可以通过向URL栏添加有效负载?id="><svg%20onload=alert(“BHIS”)>来利用,如下所示(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 设计