Security

Dynamically controlling cross-domain permissions

What are crossdomain.xml files?

As any developer who has ever dipped their toes into the murky pond that is cross-domain configuration with Flash will tell you, it is quite a confusing and somewhat aggravating task to accomplish. In this post, I’m going to demonstrate how you can control access to your APIs from Flash applications by dynamically generating crossdomain.xml files that work the same as static crossdomain.xml files.

But before we go any further, what the hell is a crossdomain.xml file?

The definition of a cross-domain configuration file is (taken from http://www.senocular.com/pub/adobe/crossdomain/policyfiles.html):

A cross-domain policy file is a XML document that grants a web client, such as Adobe Flash Player, permission to handle data across multiple domains. When a client hosts a content from a particular source domain and that content makes requests directed towards a domain other than its own, the remote domain would need to host a cross-domain policy file that would grant access to the source domain allowing the client to continue with the transaction. Policy files grant read access to data as well as permit a client to include custom headers in cross-domain requests.

Essentially what this means is that if you have a Flash application and you need to pull data (other than images or video) from a domain which is not the domain of your hosted application, you will need to place a crossdomain.xml file on the server you are requesting data from.

The configuration file needs to be placed in the webroot of the server; you can have a look at some of the more popular sites’ crossdomain.xml files:

This post is not a “how-to” on crossdomain.xml files unfortunately: for that, you can visit http://www.adobe.com/devnet/flashplayer/articles/cross_domain_policy.html – a great article by Lucas Adamski.

The Problem

You would like to control access to your application’s API based on a database table, for instance:

You have an application that serves up a list of products like Amazon.com to be made available via AMF requests (could be a REST-based architecture, SOAP, etc). You have a service offering whereby Flash/Flex application developers can register with your company to be allowed to access this wonderful API. You charge a set price per year for your service and you keep a database of authorized subscribers. At this stage, you can either allow any domain to access your API by using the following crossdomain.xml file:

1
2
3
4
5
6
<!DOCTYPE cross-domain-policy SYSTEM "http://www.adobe.com/xml/dtds/cross-domain-policy.dtd">
 
<cross-domain-policy>
   <site-control permitted-cross-domain-policies="master-only"/>
   <allow-access-from domain="*"/>
</cross-domain-policy>

The problem with this configuration is that any domain can send requests to your API from their Flash applications, and for obvious reasons this is not such a great idea.

The other (more secure) option would be to manually stick in each subscriber’s domain name into the crossdomain.xml file as follows:

1
2
3
4
5
6
7
8
<!DOCTYPE cross-domain-policy SYSTEM "http://www.adobe.com/xml/dtds/cross-domain-policy.dtd">
 
<cross-domain-policy>
   <site-control permitted-cross-domain-policies="master-only"/>
   <allow-access-from domain="example1.com"/>
   <allow-access-from domain="example2.com"/>
   <allow-access-from domain="example3.com"/>
</cross-domain-policy>

With the above configuration, you will start running into major headaches your subscribers either start cancelling their accounts or fail to pay in full. You will manually have to go and remove their domains from the configuration each time.

The Solution

When Flash applies a security policy like this, it automatically looks for a crossdomain.xml file on the requested server in the webroot. We can use a nifty combination of Apache rewriting and a PHP script to dynamically generate a list of authorized domains.

Apache Configuration

We want Apache to listen for an incoming request for our crossdomain.xml file, but instead of serving the XML file, we want to process a PHP script called generate-config.php to generate our configuration options. This example is quite simplistic, and I encourage you to take this further with whatever rules your application may require.

We are going to create a .htaccess file in the webroot of our server with the following contents:

RewriteEngine On
RewriteCond %{REQUEST_URI} crossdomain.xml
RewriteRule ^crossdomain\.xml$ generate-config.php [L]
RewriteEngine On
RewriteCond %{REQUEST_URI} crossdomain.xml
RewriteRule ^crossdomain\.xml$ generate-config.php [L]

This will present the output of our generate-config.php file as the contents of the crossdomain.xml file – essentially using another file’s content in place of another.

PHP & MySQL

In our generate-config.php file, we are going to:

  1. Connect to a database
  2. Run a SQL query that will return a list of subscribers
  3. Iterate through our list and add them to our XML output
  4. Echo the XML output back to Apache

First, create a database called crossdomain_test and then run the following SQL query to create our subscribers table:

CREATE TABLE IF NOT EXISTS `subscribers` (
  `ID` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) NOT NULL,
  `domain` varchar(255) NOT NULL,
  `active` tinyint(1) NOT NULL DEFAULT '1',
  PRIMARY KEY (`ID`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;

Insert a couple of test rows:

INSERT INTO `subscribers` (`ID`, `name`, `domain`, `active`) VALUES
(1, 'YouTube', 'youtube.com', 1),
(2, 'Twitter', 'twitter.com', 1);

Now we’re going to create our generate-config.php file in the webroot:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?php
	// create our config skeleton according to the crossdomain.xml rules
	$xml = '<?xml version="1.0"?>
<!DOCTYPE cross-domain-policy SYSTEM "http://www.adobe.com/xml/dtds/cross-domain-policy.dtd">
<cross-domain-policy>
	<site-control permitted-cross-domain-policies="master-only"/>
</cross-domain-policy>';
 
	// create $config variable to hold our config data
	$config = new SimpleXMLElement($xml);
 
	// connect to MySQL and select database
	$conn = mysql_connect("localhost", "username", "password");
	mysql_select_db("crossdomain_test", $conn);
 
	// loop through all rows, and create xml nodes
	$res = mysql_query("SELECT domain FROM subscribers WHERE active = 1");
	while($subscriber = mysql_fetch_object($res))
	{
		$child = $config->addChild("allow-access-from", null);
		$child->addAttribute("domain", $subscriber->domain);
	}
 
	// echo back to Apache
	echo $config->asXML();
?>

If we navigate to the crossdomain.xml file in the browser, we should see:

1
2
3
4
5
6
7
<?xml version="1.0"?>
<!DOCTYPE cross-domain-policy SYSTEM "http://www.adobe.com/xml/dtds/cross-domain-policy.dtd">
<cross-domain-policy>
	<site-control permitted-cross-domain-policies="master-only"/>
	<allow-access-from domain="youtube.com"/>
	<allow-access-from domain="twitter.com"/>
</cross-domain-policy>

and if we delete one of the records out of the table (try deleting YouTube), and navigate to that page again, you’ll see:

1
2
3
4
5
6
<?xml version="1.0"?>
<!DOCTYPE cross-domain-policy SYSTEM "http://www.adobe.com/xml/dtds/cross-domain-policy.dtd">
<cross-domain-policy>
	<site-control permitted-cross-domain-policies="master-only"/>
	<allow-access-from domain="twitter.com"/>
</cross-domain-policy>

And that’s it! Not too difficult at all :)

A Workaround for all frustrated developers consuming REST services

As a side-note, if you ever encounter a site that you need to pull data from and they do not have a crossdomain.xml file, all you have to do is write a simple “proxy” file with PHP (or whichever language you choose) that will accept one parameter (the URL to be called) and send the results back:

1
2
3
4
5
<?php
 
	$url = $_GET["url"];
	echo file_get_contents($url);
?>

Make sure that this proxy file is sitting in your domain though, or a domain with a crossdomain.xml file present.

Protecting your SWFs from prying eyes

Ok. So you’ve made a Flex application… Now what? You’ve got to deploy your SWF on a server somewhere, and give access to the world. The only problem with that is……..

THE WORLD HAS ACCESS TO YOUR SWF

A SWF is a compiled file and can thus be decompiled using applications like Sothink SWF Decompiler. Applications like these can take your SWF file as decode the binary data, hence allowing you to see most of the source code contained therein. Flex apps are slightly more difficult to hack – as there is just so much more code than a plain old file made in Flash – but nonetheless, it can be done.

There a few ways to protect your SWF file and the data contained therein, but none of them are really hack-proof (and i’m sure my idea isn’t hack-proof either, but i see no easy way around it).

One highly obvious security tip is to keep all sensitive data out of your SWF. Rather store keys, codes and digests in your server-side scripts.

However, that only protects the data in your SWF, not the intellectual property (ActionScript logic, embedded images, etc). I had a brainfart last night about how to do this, and i figured it out this morning: let’s think about it… How do people access files on your server? They can access them if your files are in your webroot (public_html folder or /var/www). However, all files outside the webroot are not visible on the web.

So i got to thinking… How can i load a SWF at runtime from outside the webroot while fooling both the browser and the application into thinking that it is sitting in the webroot? Well, i managed this with PHP and got a few nice surprises. See the files below (i used this SWF in my last post and i was too lazy to make a new one):

Get Adobe Flash player

Get Adobe Flash player

The SWF on the left loads another SWF securely, while the one on the right loads a SWF as one normally would – via a relative or absolute link.

To actually see whether or not the SWF is being seen by the browser (i.e. via a GET request), try using the Net Monitor of Firebug. The SWF on the right – once clicked will force the browser into making a GET request while the one on the left will not. How’s this accomplished?

Well, it’s all about the PHP. See the script below:

1
2
3
<?php
	echo base64_encode(file_get_contents("/home/riacoder/etc/SWF/menu.swf"));
?>

If you navigate to http://ria-coder.com/blog/wp-content/uploads/2009/03/loader.php, you will see a bunch of nonsense being printed to the page. This is actually the base64 data of the menu.swf file, located outside my webroot, but there’s a copy of it here. As you can see in my PHP code, i have done is pointed PHP to the the menu.swf file located somewhere on my server (outside the accessible range) and i’m getting the file contents of the SWF (compiled binary data) and encoding it in base64 format.

In the ActionScript below, i am decoding the base64 data back into binary data and then returning those bytes.

1
2
3
4
5
6
private function loadSWF(base64String:String):ByteArray
{
	var decoder:Base64Decoder = new Base64Decoder();
	decoder.decode(base64String);
	return decoder.toByteArray();
}

You can download the files used in this tutorial here.

*** UPDATE: Serge Jespers (www.webkitchen.be) has informed me that this method is not infallable, as anybody could decompile the container SWF (the SWF from which you’re calling the PHP), find the link to your PHP script, decode the base64 data, and then write the bytes to a file – thus obtaining your SWF. Even though this method is not hack-proof, it’s still an extra deterrent and i’d recommend your using it nonetheless if you feel it’s worth the extra effort.