Blog

Popping alert(1) in Flash

A lesser known cross-site scripting (XSS) vector is injecting JavaScript into Flash URL parameters. We discuss how to review ActionScript code for security issues and discuss the effectiveness of the input sanitisation functions provided, as well as looking at some real-world examples of bugs previously found in Flash video players run by Facebook.

ActionScript: An Introduction

Originally introduced by Macromedia Inc. in 1998, and currently developed by Adobe Systems, ActionScript (AS) is an object-oriented language primarily used for the development of software for Adobe Flash Player, including Rich Internet Applications (RIAs). ActionScript is a dialect of ECMAScript, meaning its syntax and semantics are similar to those seen in JavaScript.

A plug-in version of Flash Player exists for modern browsers, allowing embedded SWF (Small Web Format) files, which may contain ActionScript, to execute in the browser. SWF files are compiled and cannot be edited with Adobe Flash, though the SWF format can easily be decompiled back to FLA source using third-party decompilers.

With Flash being commonly used in the browser, inherent functionality exists within the ActionScript library to facilitate the communication between ActionScript contained in a SWF file and JavaScript used on a web page, allowing for seamless integration between the Flash application and the web application. As of February 2015, AS 3.0 remains the latest version of the language, with its initial appearance taking place in June 2006; however, use of AS 2.0 is still seen across the web.

Code Review

As with every other language, ActionScript suffers from implementation patterns which could cause security vulnerabilities to exist within the created project. This section, to remain true to the title, will only cover ActionScript functionality which can be abused to achieve cross-site scripting (XSS).

Since SWFs can be decompiled, code review is a great method for discovering XSS in Flash projects.

Flash allows for parameters (FlashVars) to be dynamically set by a web page, or passed via query string directly to the SWF file. The FlashVars can then be accessed within ActionScript using global variables _root, _level0, or _global (AS 2.0) or with the LoaderInfo class (AS 3.0). Both AS 2.0 and 3.0 can suffer vulnerabilities when FlashVars are passed directly to functions, with no validation.

ActionScript to JavaScript

As previously mentioned, ActionScript provides functionality for communication with JavaScript; this is achieved via the ExternalInterface class. ExternalInterface offers a call() method which allows JavaScript functions to be invoked.

The following code snippets demonstrate two ways in which common implementations of ExternalInterface.call() can be exploited:

ZeroClipboard

public function ZeroClipboard()
{
...
    flashvars = LoaderInfo(this.root.loaderInfo).parameters;
    this.domId = flashvars.id;
...
    addEventListener(MouseEvent.MOUSE_OVER, function() 
    {
        ExternalInterface.call("ZeroClipboard.dispatch", domId, "mouseOver", null);
        return;
    });
...
}

Video.js

if(loaderInfo.parameters.readyFunction != undefined){
    try{
        ExternalInterface.call(loaderInfo.parameters.readyFunction, ExternalInterface.objectID);
    }
    catch(e:Error){
        if (loaderInfo.parameters.debug != undefined && loaderInfo.parameters.debug == "true") {
            throw new Error(e.message);
        }
    }
}

These two examples are both calling ExternalInterface.call() insecurely, but are exploitable in different ways. When call() is fired, a JavaScript try/catch block is created. With the ZeroClipboard code from example 1, this will look something like this:

try { __flash__toXML(ZeroClipboard.dispatch("")); } catch (e) { "<undefined/>"; }

Since ZeroClipboard is allowing the domId parameter to be user-controllable, it is possible to close off the try and supply a new catch, containing arbitrary JavaScript to execute. This can be achieved by setting the id FlashVar to \"))}catch(e){alert(document.domain);}//.

The second example, Video.js, simply allows arbitrary JavaScript functions to be executed right out of the box. To explain the code block, if the FlashVar readyFunction is set, an attempt will be made to execute the contents of readyFunction via ExternalInterface.call(). This means exploitation is as simple as setting readyFunction to alert(document.domain).

Abusing URI Schemes

ActionScript permits the navigation to external locations via getURL() and navigateToURL() functions in 2.0 and 3.0, respectively, as well as through HTML anchor tags inside a text field with .htmlText. The snippets of code below are commonly seen in Flash banner advertisements:

AS 2.0

on (release) { getURL(_root.clickTAG, '_blank'); }

AS 3.0

click_btn.addEventListener(MouseEvent.MOUSE_UP, onClick);
function onClick(e:MouseEvent):void
{
    var click_url:String = root.loaderInfo.parameters.clickTAG;
    if(click_url)
    {
        navigateToURL(new URLRequest(click_url), '_blank');
    }
}

Both pieces of code perform the same action; upon user interaction, the browser will navigate to URL specified in the clickTAG parameter in a new window. Since no validation is performed to ensure the URL begins with http:// or https://, this can be exploited by supplying a javascript: or data: URI.

The code below demonstrates a text field being created in ActionScript 2.0, which greets the user based on the supplied name in the username parameter:

_root.createTextField("tf",0,100,100,640,480);
_root.tf.html = true;
_root.tf.htmlText = "Hello, " + _root.username + "!";

Using a similar approach to the above, this could be exploited by setting the parameter to an anchor tag with the href set to a javascript: or data: URI, such as:

<a href='javascript:alert(document.domain)' target='_blank'>MWRLabs</a> 

Remote Content Loading

To fulfil media playing capabilities, it is necessary to allow external content to be loaded by Flash applications. Functions such as loadMovie() in AS 2.0 can be used to fetch and load local or remote SWF, JPEG, GIF, or PNG files into a clip. In AS 3.0, the Loader class is used to perform these activities:

var myLoader:Loader = new Loader();
var url:URLRequest = new URLRequest("second_movie.swf");
myLoader.load(url);
addChild(myLoader);

Issues can arise when remote SWF files can be loaded in without validation of origin. If the parameter in the URLRequest method was user-controllable, an attacker could host a SWF file which has been purposely created to execute JavaScript via ExternalInterface and have it loaded via the user-controllable parameter.

As well as loading external multimedia files, Flash also inherently supports the loading of remote XML documents. These XML files are typically used to store configuration properties, which are then parsed in ActionScript after the document is successfully loaded. For example, the following piece of ActionScript from JW Player loads an XML document if the config parameter is set:

public Getter function xmlConfig() : String
{
    return RootReference.root.loaderInfo.parameters["config"];
}
public function loadConfig()
{
    this.loadCookies();
    if(this.xmlConfig)
    {
        this.loadXML(this.xmlConfig);
    }
    else
    {
        this.loadFlashvars(RootReference.root.loaderInfo.parameters);
    }
    return;
}
...
private static function send(arg0:String)
{
...
    if(ExternalInterface.available)
        {
            ExternalInterface.call(this._config.debug, text);
        }
...
}

Usually, the code above would be exploitable by supplying arbitrary JavaScript in the debug parameter in the query string of the URL. In a scenario where server-side restrictions prohibit an attacker from setting certain parameters, or perhaps block certain keywords useful for cross-site scripting, remote loading of XML can often be a novel way of successfully bypassing these filters and gaining execution of JavaScript. Since the loadConfig() method above does not validate the origin of this XML file, this can be achieved by hosting an XML file containing the following code, and forcing the Flash application to load it via the config parameter in the URL:

<?xml version='1.0' encoding='UTF-8' ?>
<config>
    <debug>(function(){alert(document.domain)})()</debug>
</config>

Real-World Examples

To demonstrate the exploitability of some of the techniques described above in custom-built Flash applications, two real-world cases will be discussed below.

The following lines of code have been taken from Facebook’s video player (Feb. 2013, CNGlbLZqQw6.swf). Note that the code below has been heavily redacted.

package videoplayer.ui 
{
    public class VidControls extends facebook.display.FBSprite
{
        internal function onGotoButtonClick(arg1:flash.events.MouseEvent):void
        {
            flash.net.navigateToURL(new flash.net.URLRequest(this._permalinkURL));
            return;
        }
        public function set permalinkURL(arg1:String):void
        {
            this._permalinkURL = arg1;
            return;
        }
    }
}
package videoplayer 
{
    public class VideoPlayer extends facebook.display.FBApp
    {
        internal function init(arg1:flash.events.Event):void
        {
            this._params = com.adobe.serialization.json.JSON.decode(getParameter("params"));
            this._controls.permalinkURL = this._params["permalink_url"];
        }
    }
}

When the Flash embedded parameter is set to true, the video player will display a “Go to Video” button once the playback has completed, this activity is directly tied to the onGotoButtonClick() event handler, and will cause the browser to navigate to the URL supplied in permalink_url. The purpose of this was to allow for externally embedded Facebook videos to navigate back to the permanent link for the video. Since permalink_url is not validated, it is possible to set the parameter to an arbitrary value, including a data: or javascript: URI. The following parameters can be set to exploit this vulnerability:

{"sd_src":
"https://fbcdn-video-a.akamaihd.net/cfs-ak-snc6/v/433881/909/10152308357575602_58669.mp4",
"hd_src":"https://fbcdn-video-a.akamaihd.net/cfs-ak-prn1/v/681389/909/
10152308357575602_2627.mp4", "default_hd":true, "embedded":true, "permalink_url":
"javascript:alert(document.domain)", "autoplay":false, "video_id":"10152308357575602",
"thumbnail_src":"https://fbcdn-vthumb-a.akamaihd.net/hvthumb-ak-prn1/
158797_10152308368980602_10152308357575602_28204_1151_b.jpg",
"video_duration":23, "rotation":0, "source":"permalink", "dtsg":"[redacted]"}

Roughly six months after patching the issue shown above, another vulnerability in the video player was discovered, allowing for cross-site scripting by a completely different method.

The function below executes when the Flash parameter is_hds is set to true, and hds_plugin_url is supplied:

internal function loadHDSPlugin():void
{
    var loc1:*=new org.osmf.media.URLResource(this._params["hds_plugin_url"]);
    var loc2:*=this._playerSprite.mediaFactory;
    loc2.addEventListener(org.osmf.events.MediaFactoryEvent.PLUGIN_LOAD,
        this.onHDSPluginLoadSuccess);
    loc2.addEventListener(org.osmf.events.MediaFactoryEvent.PLUGIN_LOAD_ERROR,
        this.onHDSPluginLoadError);
    loc2.loadPlugin(loc1);
    return;
}

The first line in this function attempts to create a media resource from the user-supplied hds_plugin_url parameter, note that no validation is performed on this parameter. Towards the end of the function, the loadPlugin() method is executed and the resource is fetched and loaded. Due to the lack of validation, it is possible to force the video player to load a malicious SWF. As mentioned previously, ActionScript can bridge to JavaScript using the ExternalInterface class. This means a SWF can be created just for the purpose of executing JavaScript. To exploit the scenario above, the file “xssproject.swf” is loaded from a remote domain, using the js parameter, arbitrary JavaScript can be supplied and will execute in context of “facebook.com”. The SWF is requested on page load, meaning the JavaScript executes with no user interaction, unlike the previous example:

cb-alert1-pic1

The Future for Flash

Flash Player’s death has been predicted numerous times; 2007 saw the introduction of Apple’s iPhone which had no support for Flash, and 2011 saw Adobe cancel all development plans for mobile browsers, shortly followed by the withdrawal of Flash Player from the Google Play Store. True enough, these have all contributed to reducing its reach, but none have caused Flash to disappear entirely.

More recently, in January 2015, YouTube decided it would default to HTML5 video instead of Flash, but still offer Flash as an option to its users. Again, many have claimed this act is the final nail in Flash’s coffin. Similarly, a large number of freeware and commercial HTML5 media players, such as JW Player, Flowplayer and Video.js, offer a Flash fallback for browsers which do not support HTML5 audio/video.

Of course, it is not only media players which make use of Flash. Enterprise software products, such as SAP, are often packaged with Flash-based Rich Internet Applications, or make use of Flash in some way.

Looking at Flash from a security perspective, from the beginning of 2015 to the time of writing, 5th February 2015, thirteen CVEs have already been assigned, eleven of which have been rated as critical.

Looking at previous years, this is not uncommon. 2014 saw similar results, with roughly 67% of vulnerabilities also regarded as critical. Despite these depressing statistics, Flash continues to live on.

So, while the mobile version of Flash is near enough non-existent (Flash Player 11.1 can still be manually installed on Android, but will not receive updates), it looks to be unlikely that the desktop version will be going away any time soon.

Bibliography / Further Information