Same Sh*t Different Android Browser


Overview

I have been researching Android web browsers quite a bit over the last year, and have made some interesting discoveries. One of those discoveries has been the complete lack of understanding on how to securely implement the use of the Intent URI scheme. Vulnerabilities that stem from insecurely parsing the Intent URI scheme are certainly nothing new at this point, and many Android web browsers that I have researched under this context, handle parsing operations without fault. However, there are quite a few that do not.

The following browsers as they exist today, are currently vulnerable to insecurely parsing the Intent URI scheme with their current number of downloads:

  • Cheetah Mobile Browser - 10,000,000 - 50,000,000
  • Maxthon Web Browser - 10,000,000 - 50,000,000
  • APUS Browser - 1,000,000 - 5,000,000
  • Mercury Browser for Android - 500,000 - 1,000,000
  • Jet Browser - 10,000 - 50,000

I have made multiple attempts to contact the developers for each of these browsers in order to address this issue, but alas no response. Let's dive into how we can detect this vulnerability, and how to determine whether they it is exploitable.

Detection

The majority of Android web browsers have the ability to parse different URI schemes that are commonly used inside HTML pages such as http://, https://, data://. You can usually determine this by inspecting the manifest declaration for an Activity that has an attached WebView. A straight forward way of attacking any Android web browser that parses the Intent URI scheme specifically is through a generic web page. Here is an example of HTML that Lobotomy uses for generic scheme parsing detection:

<html>  
            <head>
                <meta charset="utf-8" />
                <title>Trigger parseUri()</title>
            </head>
            <body>
                <script>  location.href="intent://#Intent;SEL;action=android.intent.action.VIEW;end";
                </script>
            </body>
        </html>

In order to leverage and build a new Intent object from the Intent URI scheme, the URI must be passed into parseUri(), which will return a new Intent based on the attributes within the URI itself. In order to understand whether the Android web browser in question supports the parsing of the Intent URI scheme, we can use the bowser module within Lobotomy.

parseUri

It is quite common to see Android web browsers leveraging shouldOverrideUrlLoading to handle and manipulate URIs before subsequently passing them into loadUrl. Lets jump into how the Jet Browser handles parsing of the Intent URI scheme.

public boolean shouldOverrideUrlLoading(WebView view, String url) {  
            if (LightningView.this.mBrowserController.isIncognito()) {
                return super.shouldOverrideUrlLoading(view, url);
            }
            if (url.startsWith("about:")) {
                return super.shouldOverrideUrlLoading(view, url);
            }
            if (url.contains("mailto:")) {
                MailTo mailTo = MailTo.parse(url);
                this.mActivity.startActivity(Utils.newEmailIntent(this.mActivity, mailTo.getTo(), mailTo.getSubject(), mailTo.getBody(), mailTo.getCc()));
                view.reload();
                return true;
            }
            if (url.startsWith("intent://")) {
                try {
                    Intent intent = Intent.parseUri(url, 1);
                    if (intent != null) {
                        try {
                            this.mActivity.startActivity(intent);
                            return true;
                        } catch (ActivityNotFoundException e) {
                            Log.e(Constants.TAG, "ActivityNotFoundException");
                            return true;
                        }
                    }
                } catch (URISyntaxException e2) {
                    return false;
                }

The code checks to see if the url starts with the Intent scheme - intent:// - passess that into parseUri(), and then calls startActivity() on the Intent returned from parseUri(). This essentially becomes an implicit Intent, and if the example HTML page was used from Lobotomy, because their wasn't a defined component, the ActivityManager would ask which application you would like to handle the Intent.

Exploitability

The exploit primitive here is based on a few things:

  • Private Activities
  • Private Activities with WebViews
  • Private Activities with WebViews that load URLs through Intent extras

This means we need to create an HTML page that will build a new Intent through the URI scheme, contains an extra which holds a URL, and pass that to a private Activity within the target Android web browser. Let's checkout the Apus Browser now to see if we can construct our primitive.

[2015-10-04 19:18:34.804388] Searching for loadUrl
1 Lcom/apusapps/browser/webview/ApusWebView;->loadUrl(Ljava/lang/String;)V (0x0) ---> Lcom/apusapps/browser/webview/SafeWebView;->loadUrl(Ljava/lang/String;)V  
1 Lcom/apusapps/browser/main/H5GameActivity;->onCreate(Landroid/os/Bundle;)V (0x92) ---> Lcom/apusapps/browser/webview/ApusWebView;->loadUrl(Ljava/lang/String;)V  
1 Lcom/apusapps/browser/main/b;->a(Ljava/lang/String;)V (0x68) ---> Lcom/apusapps/browser/webview/ApusWebView;->loadUrl(Ljava/lang/String;)V  
1 Lcom/apusapps/browser/main/b;->a(Ljava/lang/String; Ljava/util/Map;)V (0x80) ---> Lcom/apusapps/browser/webview/ApusWebView;->loadUrl(Ljava/lang/String; Ljava/util/Map;)V  
1 Lcom/apusapps/browser/main/b;->k()V (0x26) ---> Lcom/apusapps/browser/webview/ApusWebView;->loadUrl(Ljava/lang/String;)V  
1 Lcom/apusapps/browser/main/l;->a(Lcom/apusapps/browser/main/b; Z)V (0x2c) ---> Lcom/apusapps/browser/webview/ApusWebView;->loadUrl(Ljava/lang/String;)V  
1 Lcom/apusapps/libzurich/redirect/RedirectService$c;->handleMessage(Landroid/os/Message;)V (0x4e) ---> Lcom/apusapps/libzurich/utils/SafeWebView;->loadUrl(Ljava/lang/String;)V  
1 Lcom/apusapps/browser/main/l;->i(Ljava/lang/String;)Z (0x19c) ---> Landroid/webkit/WebView;->loadUrl(Ljava/lang/String;)V

From Lobotomy's output we can see that there is call to loadUrl() inside of the the H5GameActivity. We can also see that this Activity is not part of the Apus Browser's attack surface, meaning it is considered to be a private Activity.

lobotomy) attacksurface  
[2015-10-04 19:20:16.680861] ---------
[2015-10-04 19:20:16.680908] Activites
[2015-10-04 19:20:16.680921] ---------
[2015-10-04 19:20:16.681140] com.apusapps.browser.main.ApusBrowserActivity : Found Activity with launchMode!
[2015-10-04 19:20:16.681177] com.apusapps.browser.main.ApusBrowserActivity : launchMode : singleTask
[2015-10-04 19:20:16.681435] com.apusapps.browser.main.ApusBrowserActivity : Found Activity with schemes!
[2015-10-04 19:20:16.681457] com.apusapps.browser.main.ApusBrowserActivity : scheme : about
[2015-10-04 19:20:16.681480] com.apusapps.browser.main.ApusBrowserActivity : scheme : http
[2015-10-04 19:20:16.681492] com.apusapps.browser.main.ApusBrowserActivity : scheme : https
[2015-10-04 19:20:16.681504] com.apusapps.browser.main.ApusBrowserActivity : scheme : javascript
[2015-10-04 19:20:16.681515] com.apusapps.browser.main.ApusBrowserActivity : scheme : file
[2015-10-04 19:20:16.681526] com.apusapps.browser.main.ApusBrowserActivity : scheme : inline
[2015-10-04 19:20:16.682157] com.apusapps.browser.main.ApusBrowserActivity : action : android.intent.action.MAIN
[2015-10-04 19:20:16.682176] com.apusapps.browser.main.ApusBrowserActivity : action : android.intent.action.VIEW
[2015-10-04 19:20:16.682187] com.apusapps.browser.main.ApusBrowserActivity : action : android.intent.action.WEB_SEARCH
[2015-10-04 19:20:16.682197] com.apusapps.browser.main.ApusBrowserActivity : action : com.apusapps.browser.action.WEB_SEARCH
[2015-10-04 19:20:16.682207] com.apusapps.browser.main.ApusBrowserActivity : action : android.intent.action.MEDIA_SEARCH
[2015-10-04 19:20:16.682217] com.apusapps.browser.main.ApusBrowserActivity : action : android.intent.action.SEARCH
[2015-10-04 19:20:16.682227] com.apusapps.browser.main.ApusBrowserActivity : action : android.intent.action.SEARCH_LONG_PRESS
[2015-10-04 19:20:16.682237] com.apusapps.browser.main.ApusBrowserActivity : action : android.intent.action.ASSIST
[2015-10-04 19:20:16.682246] com.apusapps.browser.main.ApusBrowserActivity : action : com.apusapps.browser.CLIPBOARD_SEARCH
[2015-10-04 19:20:16.682257] com.apusapps.browser.main.ApusBrowserActivity : action : com.apusapps.browser.NEWS_PUSH
[2015-10-04 19:20:16.682268] com.apusapps.browser.main.ApusBrowserActivity : category : android.intent.category.LAUNCHER
[2015-10-04 19:20:16.682278] com.apusapps.browser.main.ApusBrowserActivity : category : android.intent.category.DEFAULT
[2015-10-04 19:20:16.682351] com.apusapps.browser.main.ApusBrowserActivity : category : android.intent.category.BROWSABLE
[2015-10-04 19:20:16.683010] com.apusapps.browser.bookmark.BookMarkAndHistoryActivity : Found Activity with launchMode!
[2015-10-04 19:20:16.683028] com.apusapps.browser.bookmark.BookMarkAndHistoryActivity : launchMode : singleTop
[2015-10-04 19:20:16.683431] com.apusapps.browser.download.DownloadListActivity : Found Activity with launchMode!
[2015-10-04 19:20:16.683446] com.apusapps.browser.download.DownloadListActivity : launchMode : singleTop
[2015-10-04 19:20:16.683846] com.apusapps.browser.crashcollector.UploadActivity : Found Activity with launchMode!
[2015-10-04 19:20:16.683861] com.apusapps.browser.crashcollector.UploadActivity : launchMode : singleTask
[2015-10-04 19:20:16.684261] com.apusapps.launcher.snsshare.SnsShareDialogActivity : Found Activity with launchMode!
[2015-10-04 19:20:16.684276] com.apusapps.launcher.snsshare.SnsShareDialogActivity : launchMode : singleTop
[2015-10-04 19:20:16.684675] com.apusapps.browser.settings.SettingsActivity : Found Activity with launchMode!
[2015-10-04 19:20:16.684689] com.apusapps.browser.settings.SettingsActivity : launchMode : singleTop
[2015-10-04 19:20:16.685088] com.apusapps.launcher.search.SearchEngineActivity : Found Activity with launchMode!
[2015-10-04 19:20:16.685102] com.apusapps.launcher.search.SearchEngineActivity : launchMode : singleTop
[2015-10-04 19:20:16.685487] com.apusapps.browser.settings.BrowserDataClearActivity : Found Activity with launchMode!
[2015-10-04 19:20:16.685500] com.apusapps.browser.settings.BrowserDataClearActivity : launchMode : singleTop
[2015-10-04 19:20:16.685862] com.apusapps.browser.settings.FontSizeSettingActivity : Found Activity with launchMode!
[2015-10-04 19:20:16.685875] com.apusapps.browser.settings.FontSizeSettingActivity : launchMode : singleTop
[2015-10-04 19:20:16.686246] com.apusapps.browser.homepage.manager.DisplaySettingActivity : Found Activity with launchMode!
[2015-10-04 19:20:16.686262] com.apusapps.browser.homepage.manager.DisplaySettingActivity : launchMode : singleTop
[2015-10-04 19:20:16.686745] com.apusapps.browser.homepage.HomeTopSiteAddActivity : Found Activity with launchMode!
[2015-10-04 19:20:16.686782] com.apusapps.browser.homepage.HomeTopSiteAddActivity : launchMode : singleTop

If we take a look at the onCreate() method for this Activity, we can see that we can control the URL being passed into loadUrl() through an Intent extra:

protected void onCreate(Bundle bundle) {  
        a.a(getWindow(), true);
        super.onCreate(bundle);
        setContentView(R.layout.h5_game_activity);
        this.a = (ApusWebView) findViewById(R.id.h5_game_wv);
        this.b = (BrowserProgressBar) findViewById(R.id.browser_progress_bar);
        this.c = (NetworkLinkErrorView) findViewById(R.id.network_error);
        a();
        Intent intent = getIntent();
        if (intent != null) {
            Object stringExtra = intent.getStringExtra("url");
            if (!TextUtils.isEmpty(stringExtra)) {
                this.a.loadUrl(stringExtra);
            }
        }
        g.a(getApplicationContext(), getString(R.string.game_mode_on), 1);
        b.a(getApplicationContext(), 11237);
    }

Once we have validated that the vulnerability is exploitable, there is the question of what to use for the actual exploit itself. For the most part if you research everything that has been covered around this issue in the past, you will see that turning this into a UXSS context is best suited for this situation. Here is a brief breakdown of things you can target in order to make this exploit a reality.

  • Injecting XSS into cookies, and pointing the URL with the file:// protocol at the cookie database within the browser's data directory. You will gain execution if the WebView tries to properly render the file. This technique was documented in the original paper, but I have never found this to be reliable.

  • Pre-download an HTML file containing code that targets UXSS in vulnerable versions of WebKit, and point the URL with the file:// protocol at the HTML page in the target download directory. This relies on all the work that has been documented here.

If a UXSS context is not possible, there are still numerous ways you can attack private Activities within these browsers, if decisions are made based on extras that can be controlled through the scheme.

Wrapping Up

I do not expect that this vulnerability will be going away anytime soon. There are constantly new Android browsers being added to Google Play, so be careful of which browser you choose for your Internet adventures.