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.
Basic case
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?
This is an all too common of an assumption.
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 127.0.0.0:8080/run/do_evil
!
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.
JavaScript execution isn’t even always necessary for these attacks, as long as we are happy with being limited to sending “normal” HTML-induced GETs and <form> POSTs.
WebSockets
In a similar way, unsecured websockets can provide an even easier avenue for exploitation.
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.
Password protection
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
Anyway, so all these browsers and HTTPs and JavaScripts and what have you clearly suck.
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”
ports.
Applications can attempt to identify cross-protocol requests (for instance
by detecting
the
Host:
string) and terminate the connection before damage occurs.
DNS Rebinding
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.
The malicious JavaScript has no problem with the same-origin policy. For all intents and purposes the code comes from the same origin as the response.
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 --stop-dns-rebind
).
Checking the Host:
request header server-side and refusing to serve
unexpected domains goes a long way in preventing this.
Final observation
TCP was never meant as a mechanism for communication between processes on the same machine. Caution should be exercised when implementing localhost-only applications.