Problem
HTTPServer::BindAndStartListening() unconditionally enables SO_REUSEADDR before binding the RPC listener. On Windows, a reuse-enabled listener does not reserve the port exclusively: another local process can request SO_REUSEADDR and bind to the same port (see https://learn.microsoft.com/en-us/windows/win32/winsock/using-so-reuseaddr-and-so-exclusiveaddruse).
If the competing socket receives a new connection, it can capture the HTTP Basic Authorization header (including the cookie credential) and proxy or issue privileged RPC calls as the victim. This crosses a local-user boundary and can expose wallet-controlling RPC credentials.
Fix
Have Windows use SO_EXCLUSIVEADDRUSE instead which makes the port exclusive to the process which first requests it, while retaining the restart-friendly behavior which SO_REUSEADDR enabled. Abort if another process is already bound to the port.
Further context & rationale
This issue is new in our homegrown HTTP server implementation, since libevent had a guard against setting SO_REUSEADDR on Windows, see evutil_make_listen_socket_reuseable() https://github.com/libevent/libevent/blob/d82464a277d0f42703702c4dfd9af6af38595a83/evutil.c#L483. libevent does not reference SO_EXCLUSIVEADDRUSE.
Why should we not just avoid SO_REUSEADDR on Windows and skip SO_EXCLUSIVEADDRUSE like the libevent approach?
Because setting either option makes the process less prone to failing to bind to a port after having been restarted. Not sure why this wasn't an issue before, maybe the node startup was usually slow enough to time out the port before we tried to re-bind it on Windows.
Discovered by Project Loupe.