Summary
The HTTP login endpoints (POST /login and POST /signalk/v1/auth/login) are protected by express-rate-limit (default: 100 attempts per 10-minute window, configurable via HTTP_RATE_LIMITS). The WebSocket login path — sending {login: {username, password}} messages over an established WebSocket connection — calls app.securityStrategy.login() directly without any rate limiting.
An attacker can bypass HTTP rate limiting entirely by opening a WebSocket connection and attempting unlimited password guesses at the speed bcrypt allows (~20 attempts/sec with 10 salt rounds).
Details
Vulnerable code: src/interfaces/ws.ts, function processLoginRequest (lines 753-780)
The function directly calls app.securityStrategy.login(msg.login.username, msg.login.password) with no throttling or attempt tracking.
Rate-limited HTTP path for comparison: src/tokensecurity.ts lines 609-617 apply loginLimiter middleware to the HTTP login routes at line 637.
Steps to Reproduce
- Start Signal K server with security enabled
- Open a WebSocket connection to
ws://server:3000/signalk/v1/stream?subscribe=none
- Wait for the hello message
- Send login attempts in rapid succession:
{"requestId": "1", "login": {"username": "admin", "password": "guess1"}}
{"requestId": "2", "login": {"username": "admin", "password": "guess2"}}
- Observe that all attempts are processed without any 429 response or throttling
- For comparison, send 100+ HTTP POST requests to
/signalk/v1/auth/login — the 101st returns 429
A POC script is available that demonstrates both the HTTP rate limiting working correctly and the WebSocket path accepting unlimited attempts.
Impact
- Credential brute-forcing via the WebSocket protocol at ~20 attempts/sec (bcrypt-limited)
- Complete bypass of the HTTP rate limiting defense
- A single WebSocket connection is sufficient for unlimited attempts
- With multiple parallel connections, throughput multiplies
- A 10,000-word dictionary attack completes in ~8 minutes over a single connection
Signal K servers are commonly deployed on boat networks where they may be accessible to other devices on the same LAN.
CWE
CWE-307: Improper Restriction of Excessive Authentication Attempts
Suggested Fix
Track failed login attempts per remote IP in a shared store (or reuse the existing express-rate-limit store) that is checked in both the HTTP login middleware and the processLoginRequest WebSocket handler.
Context
Found while building an open source maritime security scanner. Happy to submit a PR with a fix if helpful. Verified on v2.24.0 (current master).
— Mark Curphey
Summary
The HTTP login endpoints (
POST /loginandPOST /signalk/v1/auth/login) are protected byexpress-rate-limit(default: 100 attempts per 10-minute window, configurable viaHTTP_RATE_LIMITS). The WebSocket login path — sending{login: {username, password}}messages over an established WebSocket connection — callsapp.securityStrategy.login()directly without any rate limiting.An attacker can bypass HTTP rate limiting entirely by opening a WebSocket connection and attempting unlimited password guesses at the speed bcrypt allows (~20 attempts/sec with 10 salt rounds).
Details
Vulnerable code:
src/interfaces/ws.ts, functionprocessLoginRequest(lines 753-780)The function directly calls
app.securityStrategy.login(msg.login.username, msg.login.password)with no throttling or attempt tracking.Rate-limited HTTP path for comparison:
src/tokensecurity.tslines 609-617 applyloginLimitermiddleware to the HTTP login routes at line 637.Steps to Reproduce
ws://server:3000/signalk/v1/stream?subscribe=none{"requestId": "1", "login": {"username": "admin", "password": "guess1"}} {"requestId": "2", "login": {"username": "admin", "password": "guess2"}}/signalk/v1/auth/login— the 101st returns 429A POC script is available that demonstrates both the HTTP rate limiting working correctly and the WebSocket path accepting unlimited attempts.
Impact
Signal K servers are commonly deployed on boat networks where they may be accessible to other devices on the same LAN.
CWE
CWE-307: Improper Restriction of Excessive Authentication Attempts
Suggested Fix
Track failed login attempts per remote IP in a shared store (or reuse the existing express-rate-limit store) that is checked in both the HTTP login middleware and the processLoginRequest WebSocket handler.
Context
Found while building an open source maritime security scanner. Happy to submit a PR with a fix if helpful. Verified on v2.24.0 (current master).
— Mark Curphey