Files
exploitarium/lunar-modrinth-chain-poc
2026-06-23 00:13:35 -05:00
..
2026-06-23 00:13:35 -05:00
2026-06-23 00:13:35 -05:00
2026-06-23 00:13:35 -05:00
2026-06-23 00:13:35 -05:00
2026-06-23 00:13:35 -05:00

Lunar Client Modrinth Explore Chain PoC

Proof package for a high-severity Lunar Client chain observed in the June 2026 unpacked Electron application.

This repository documents a practical RCE-style chain and includes a benign cross-platform calc-pop proof for the final "drop local launcher and open it via the OS shell" primitive.

Status

High-confidence critical candidate, not yet a fully packaged public Modrinth-to-Lunar end-to-end exploit.

The chain is severe because it joins several individually dangerous behaviors:

  • Modrinth project content is rendered in Lunar Explore as raw HTML.
  • The Explore renderer has access to privileged Lunar preload APIs.
  • The renderer can reach raw IPC/Redux state synchronization into main.
  • Main accepts forged profile state and materializes provider profiles.
  • Modrinth override extraction can write root-level override files into a profile-controlled game directory.
  • The unverified modpack warning path only scans mods, resourcepacks, and shaderpacks, not root overrides.
  • openExternalLink can reach shell.openExternal for non-HTTP URLs when the initiator is not one of the two restricted user-input initiators.
  • Opening shell-dispatched local launcher files can execute code. On Windows, the reviewed chain uses .lnk; the included proof also has macOS/Linux branches for environments where Windows is unavailable.

If the live Modrinth delivery leg is confirmed end-to-end through Lunar's production cache, the likely impact is arbitrary code execution as the victim's desktop user after the victim views or clicks a malicious Modrinth project in Lunar Explore. This path does not require launching Minecraft, having a JRE ready, or having a selected Minecraft account.

Impact

Estimated severity: critical.

Tentative CVSS v3.1: AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H = 9.6.

This score assumes the attacker can publish or otherwise get attacker-controlled Modrinth project/changelog content rendered by Lunar Explore, and that the raw HTML delivery path executes script-capable content in the packaged renderer.

Chain Summary

  1. Attacker-controlled Modrinth Markdown is fetched by Lunar Explore.
  2. Lunar renders that Markdown through ReactMarkdown with rehypeRaw, without an observed sanitizer or HTML allowlist.
  3. A raw HTML payload can execute renderer JavaScript through an iframe-style delivery primitive.
  4. Renderer JavaScript can use exposed preload APIs and raw IPC/Redux sync.
  5. The renderer forges or creates a Modrinth provider profile whose overrides.gameDirectory points at a writable attacker-chosen directory.
  6. The renderer asks main to install the forged Modrinth profile.
  7. Main downloads the .mrpack, saves the profile, and extracts root-level overrides/* entries to getEffectiveGameDirectory(profile).
  8. A root override such as a benign launcher file is written to the chosen directory and is not covered by the unverified-file warning scanner.
  9. The renderer calls the external-link API on a file:///.../<launcher> URL using a non-restricted initiator.
  10. Main reaches shell.openExternal(url). The operating system dispatches the local launcher.

Evidence From Local Review

The following paths refer to extracted source-map sources from the reviewed Lunar Client build.

  • Raw HTML Markdown sink: src/renderer/impl/app/pages/explore/project/components/markdown.tsx imports ReactMarkdown, uses rehypeRaw, and does not apply a sanitizer.
  • Modrinth content flow: src/renderer/impl/app/pages/explore/project/utils/fetch.ts maps project body and version changelog into renderer state.
  • Main install handler: src/electron/module/modrinth/index.ts exposes installModpack.
  • Profile source of truth: src/electron/module/modrinth/install/modpack/index.ts reads the target profile from launcherRedux.store.getState().profiles.profiles.
  • Profile reducer: src/shared/store/profiles/index.ts accepts profiles/addOrUpdateProfile and inserts or replaces the supplied profile object.
  • Profile persistence: src/electron/module/profiles/index.ts preserves profile.overrides when saving a virtual profile.
  • Effective game directory: src/electron/module/profiles/paths.ts returns profile.overrides.gameDirectory when present.
  • Override extraction: src/electron/module/profiles/handlers/extract-overrides/utils.ts maps non-content-dir overrides/* entries to the effective game directory.
  • Warning coverage: src/electron/module/profiles/handlers/unverified-modpack-files/consts.ts scans only overrides/mods/, overrides/resourcepacks/, and overrides/shaderpacks/.
  • External open sink: src/electron/window/preload/impl/misc.ts blocks non-HTTP protocols only for selected initiators, then calls shell.openExternal(url).
  • Redux bridge: src/electron/redux/index.ts enables stateSyncEnhancer() with no observed application-level action allowlist.

Calc-Pop PoC

Run this only on a local test machine. It does not interact with Lunar Client or any live Modrinth project. It validates the final execution primitive by:

  1. writing a marker file,
  2. creating a local platform-appropriate launcher file,
  3. asking the OS shell to open that launcher, and
  4. popping the Calculator app where available.
npm run poc

Expected output:

marker: calc-pop-attempted
opened: <launcher path>

Platform behavior:

  • Windows: creates and opens a .lnk pointing to calc.exe.
  • macOS: creates and opens a .command launcher that runs Calculator.
  • Linux: creates and opens a .desktop launcher for the first available calculator binary from a small allowlist.

In the original audit environment, the Windows shortcut primitive also wrote lnk-executed to a marker file when the shortcut was opened.

What Is Intentionally Not Included

This repository does not include:

  • A live malicious Modrinth project.
  • A weaponized iframe or renderer payload.
  • A .mrpack containing an executable launcher.
  • A script that drives Lunar Client against real users.

See poc/renderer-chain-skeleton.md for a non-executable outline of the renderer-side chain.

Fix Guidance

Recommended fixes should be layered:

  • Disable raw HTML in Modrinth Markdown, or sanitize with a strict allowlist.
  • Forbid script-capable embedded content in Explore project descriptions and changelogs.
  • Remove generic renderer access to raw IPC sendMessage, or enforce a strict channel allowlist in preload.
  • Disable or constrain Electron Redux state sync from untrusted renderers.
  • Validate profile objects at every IPC/main boundary.
  • Do not accept arbitrary renderer-supplied gameDirectory paths without a real user gesture and path policy.
  • Treat every overrides/* archive entry as potentially dangerous, including root-level files.
  • Block file:, ms-*, and other non-web protocols in openExternalLink unless there is a narrow, explicit allowlist.
  • Refuse to open executable file types such as .lnk, .exe, .bat, .cmd, .ps1, .vbs, and similar from renderer-controlled URLs.

Disclosure Note

This is intended for authorized validation and coordinated disclosure. Keep the repository private until the vendor has acknowledged and remediated the issue.