REST and XSRF, Part One
Hi everyone. In case you missed my talk at Black Hat, “REST for the Wicked”, I wanted to give you the Cliffs Notes version here. This will be a two-part post; the first will deal with attack techniques and the second will describe appropriate design and implementation mitigations for the attacks.
The SOAP vs. REST debate has been raging (or at least simmering) for years now. While I don’t want to offer this blog as another battleground in this war, I do want to talk about some of the security issues affecting REST. Specifically, I want to talk about REST and its susceptibility to Cross-Site Request Forgery (XSRF) attacks.
Let’s look at a sample RESTful web service that provides information on movies and actors and TV shows – let’s call it BryMDB for Bryan’s Movie Database. If a user wanted to retrieve all of the available data for the movie Wanted, she would just make an HTTP GET request like this:
GET /movies/Wanted HTTP/1.1
If she noticed that no one had created an entry for the new James Bond movie and she wanted to add one, she would make a POST request with the movie data in the message body:
POST /movies/Quantum_Of_Solace HTTP/1.1
Similarly, the HTTP method POST is used to update existing data and DELETE is used to delete data. Essentially we have a mapping of data CRUD (Create/Read/Update/Delete) to HTTP methods POST, GET, PUT and DELETE. The problem with this is that unless preventative measures are taken, the service will be extremely vulnerable to XSRF attacks. For example, Evil Eve might write a page with the following HTML and JavaScript code:
<body onload=javascript:document.evil.submit()> <form name="evil" method="POST" action="https://www.brymdb.com/movies/Iron_Man" enctype="text/plain"> <input name="{review:'This movie is horrible! I want my money back!', rating:1}//" value=""/> </form></body>
Now any visitor to this page will automatically post a negative review of Iron Man, whether they liked the movie or not (or even whether they’ve seen the movie or not)! And Eve doesn’t have to host this page on the brymdb.com domain. She can host this page anywhere and the attack will still be successful.
In terms of REST, the “C” in CRUD as accessed through the POST method is actually the easiest to attack. However, R, U and D are all very much vulnerable as well. We’ll save “R” for last and focus on attacking update and delete functionality now.
It’s more difficult for an attacker to forge PUT and DELETE requests since you can’t specify these methods as the “method” attribute of a <form> element – <form> only allows GET or POST. However, you can specify PUT or DELETE as the HTTP method used for XMLHttpRequest, like this:
var xhr = new XMLHttpRequest(); xhr.open("PUT", "https://www.brymdb.com/movies/Kung_Fu_Panda", true); xhr.send("{review:'This movie rocks!', rating:10}");
The problem with this (if you’re an attacker) is that you can’t normally make cross-domain requests with XMLHttpRequest. There are two takeaways from this. The first is that this should really highlight the importance of preventing XSS in your applications. Once an attacker can add script of her choice to your pages, she can use XMLHttpRequest to send PUT and DELETE attacks against any REST services on the domain. (Actually at this point, attacks against your REST services are probably the least of your concerns.) The second takeaway is that this also shows the danger of the W3C cross-domain XHR proposal that I’ve previously written about here.
At this point, we’ve addressed means by which an attacker can attack POST, PUT, and DELETE methods, but we haven’t looked any ways to attack the most common HTTP method: GET. At first glance, attacking GET would seem trivial. There are literally dozens of ways to make browsers issue cross-domain GET requests. Here is a sampling of just a few of them:
<img src="https://www.brymdb.com/Movies/WallE"/> <iframe src="https://www.brymdb.com/Movies/WallE"> <object data="https://www.brymdb.com/Movies/WallE">
The problem with this (again from the attacker’s perspective) is that in a true RESTful architecture, GET is only used to read or retrieve data. It does no good to issue forged GET requests if the attacker can’t read the (presumably valuable and confidential) data being returned from the server. You can’t use <img src> or any of the techniques listed above to read data. You can use XHR to issue GET requests and read the responses, but again, you can’t normally make cross-domain XHR requests.
One possibility is to use a Rich Internet Application (RIA) to forge requests across domains. Both Flash and Silverlight allow you to issue cross-domain GET requests and read the responses, assuming that the target domain has an appropriate cross-domain access policy file in place. Silverlight code to perform a request forgery against a REST service would look like this:
void attack() { WebClient webClient = new WebClient(); webClient.DownloadStringCompleted += new DownloadStringCompletedEventHandler(callback); webClient.DownloadStringAsync(new Uri("https://www.brymdb.com/Movies/WallE")); } void callback(object sender, DownloadStringCompletedEventArgs e) { string stolenData = e.Result; // now send it home… }
For REST services that use JSONP as their response data format, you can also use script to redefine or “hijack” the JSONP callback function. Given this JSONP response body:
myCallbackFunction("{review:'I loved it! It was much better than Cats.', rating:9}");
An exploit page would look like this:
<script type="text/javascript"> function myCallbackFunction(data) { var stolenReview = data.review; var stolenRating = data.rating; // send the stolen data home… } </script> <script src="https://www.brymdb.com/Movies/WallE"/>
Notice the second <script> tag at the bottom of the page that calls the target REST service. We can use this trick since the service is returning JSONP, and JSONP is valid JavaScript. The cross-domain limitation we had with XHR and the RIAs does not apply here; you can use <script src> to call any domain on the internet, regardless of whether that domain has defined a cross-domain access policy.
A variation of the JSONP hijacking attack shown above is to redefine the built-in JavaScript object constructor instead of the JSONP callback function. This attack will work on straight JSON as well as JSONP. However, most browsers (including IE) do not allow you to redefine the object constructor, which greatly reduces the potential pool of victims that could be affected by this attack.
Well, we’ve talked a lot about possible XSRF attack vectors against REST services. Next time, we’ll shatter the myth that XSRF is indefensible and present some mitigations for these attacks.