Don't just bind to localhost
This post is about a pet peeve of mine — local applications which expose various (not only) HTTP services by binding to the loopback interface without considering the “standard” attack vectors which web applications should be protected against.
Let’s say you have code approximating the following.
For an outside-facing application, this surely looks ridiculous. However, we are binding to the loopback interface, so nothing can possibly go wrong right?
If the user of the application is using a browser on the same machine (very
reasonable expectation in most cases),
any website they visit can execute a request to
Cross origin policy is going to prevent the requesting website from reading
the response contents, but as the damage has been already done when executing
the request, that’s not really relevant.
By default, any website can connect to any websocket and perform bidirectional communication. This “cross site websocket hijacking” is something to be aware of as it can leak information even if the attacker can’t cause damage on some another HTTP endpoint.
Methods like basic auth or standard session cookie based authentication aren’t sufficient for the same reason they aren’t sufficient for “normal” web services. Attacker-executed request will inherit the users credentials even if the request later fails because of the same-origin policy. Explicit protection against Cross Site Request Forgery is necessary.
Cross protocol scripting
Let’s just write a simple telnet interface for our application like it’s the nineties. Then none of this modern rubbish can do us any harm right?
Unfortunately, this assumption is also wrong.
Let’s see what happens if we send an HTTP request to the telnet interface of (an unpatched version of) OpenOCD (a popular open source tool for microcontroller debugging).
That’s right — nothing really happens. Line based protocols in general tend to be very fault-tolerant, especially if they are meant to be used manually.
This opens up an attack vector — we are forced by the constraint of running in a browser to send an HTTP requests with a bunch of headers we can’t really affect in any meaningful way. Body of the request is completely under our control though.
As all of the “invalid commands” get dropped anyway, all we need
is to include
exec do_evil\n (OpenOCD for “run this in a shell”) in our POST
request body and we are done.
This is a very insidious type of attack as all the potential cross-protocol paths might not be obvious. For example, the above described exploit can be slightly modified to use Gopher instead of HTTP. The modified version works with current OpenOCD and elinks.
Modern browsers provide some mitigation by refusing to connect to a list of what
are considered “unsafe”
Applications can attempt to identify cross-protocol requests (for instance
Host: string) and terminate the connection before damage occurs.
This is an awesome trick originally invented for bypassing same-origin policy on firewalled servers by bouncing off of a browser behind said firewall. It also works nicely for our localhost-bound services.
When poking around with this attack method, the public rbndr server comes in handy.
Some DNS recursors prevent this attack by refusing to resolve public TLDs to
private IP ranges (dnsmasq
Host: request header server-side and refusing to serve
unexpected domains goes a long way in preventing this.
TCP was never meant as a mechanism for communication between processes on the same machine. Caution should be exercised when implementing localhost-only applications.