Friday, May 31, 2019

Always Secure Your localhost Servers

Recently I was surprised to learn that web browsers allow any site you visit to make requests to resources on localhost (and that they will happily allow unreported mixed-content). If you'd like to test this out, run an HTTP server on port 8080 (for instance with python -m http.server 8080) and then visit this page.

You should see "Found: HTTP (8080)" listed and that's because the Javascript on that page made an HTTP GET request to your local server to determine that it was running. Chances are it detected other services as well (for instance if you run Tor or Keybase locally).

There are two implications from this that follow:
  1. Website owners could potentially use this to collect information about what popular services are running on your local network.
  2. Malicious actors could use this to exploit vulnerabilities in those services.
Requests made this way are limited in certain ways since they're considered opaque, meaning that the web page isn't able to read the response body or headers. But even with these restrictions a request can do all kinds of nasty things unless the local server is properly secured.

For instance, at one point Redis was vulnerable to these types of attacks because its protocol is text-over-TCP (just like HTTP) so any web page you visit could craft an HTTP POST request that mimics normal Redis commands. There's an excellent write-up on this vulnerability here (which has since been fixed).

Similarly, if you run the popular gaming platform Steam on Linux, at the time of writing this the main application opens a TCP listener on port 57343. I have no idea what that port is for but I do know that visiting this web page while Steam is open will cause the entire UI to freeze, as well as most games, until the tab is closed: [WARNING: don't blame me if it crashes your game]

This works because the favicon on that page is actually an HTTP GET request to the TCP server which never closes, thus blocking their listener. It may even be possible to attack the server in other ways with specifically crafted requests (I was able to crash the application using a large enough payload).

These types of vulnerabilities are widespread mostly because application developers assume that the server will only be locally-accessible and not available to every website the user visits. Hopefully this is enough proof to justify why better security measures need to be taken with local servers even if you don't intend to forward the ports you're using to the outside world.

So, as developers what can we do to prevent this kind of attack?

For HTTP and WebSocket servers you can add authentication, CSRF protection, and restrict access based on the page origin (which browsers should include in the request headers).

For TCP servers it could be harder depending on your application. You can detect HTTP-like request headers and block the connection (this seems to be what Redis is doing now). Or require some sort of handshake/authentication that the browser won't perform and reject connections based on that.

As far as preventing fingerprinting and service discovery this way... I'm not entirely sure what the best way to prevent this would be other than ungracefully failing the requests (which a lot of HTTP frameworks probably don't make easy). If anyone has any ideas here feel free to drop me a tweet @davywtf.