Pairing: global rate limit lets anyone lock out the code, and the code path hands out the permanent token #2
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Problem
Three weaknesses in
handleHello()(ControlServer.kt:227-263) compound each other.1. The rate limiter is global, not per client.
codeAttemptsis a single shared deque (ControlServer.kt:59). Worse,codeAttemptAllowed()is only reached when the message carries a non-emptycode— a wrongtokennever consumes budget, but boguscodeattempts do.2. No Origin check.
openWebSocket()never inspectsOrigin/Host, and NanoWSD does not either (confirmed: the class contains noOriginreference at all). WebSockets are not subject to same-origin policy, so any web page open in any browser on the LAN can connect tows://<tv-ip>:8765/from JavaScript.3. The four-digit path yields the permanent token. The
welcomepayload always includespairingToken(ControlServer.kt:254), regardless of whether authentication used the high-entropy token or the guessable code. The token never rotates (Pairing.kt).Failure scenarios
hello{code:…}per minute forever. The global budget is permanently starved, so the owner can never pair with the correct code. Trivially automated, needs only LAN access.Suggested fix
helloregardless of which field was wrongpairingTokeninwelcomewhen authentication used the token; code-based sessions get a session-scoped credentialOriginis present and not the server's own addressFound by multi-agent code review; verified against current
ControlServer.kt.Fixed in
dd612fc, shipped in v0.9.0. All three weaknesses are addressed:hello, whether it carried a token or a code (attemptAllowed(remoteAddress)runs before any credential is inspected). One hostile client can no longer starve the owner's budget; the tracking map is pruned so it cannot grow without bound.serve()rejects any request whoseOriginheader is present and does not match the server's own host, with 403. A page on another origin can still open a socket at the TCP level but never gets past the HTTP upgrade.Pairing.newSessionToken), stored server-side in a capped list and revocable independently. The QR token is only echoed back to clients that already presented it.Constant-time comparison (
MessageDigest.isEqual) is used for both token and code, and "Kopplung zurücksetzen" in the settings invalidates everything at once (see #3).