Bug Mandiri e-Money Isi Ulang (November 2015)

Jika Anda belum membaca, sebaiknya baca dulu pengantar seri ini: Mencari dan melaporkan bug security. Perlu dicatat: bug ini sudah dilaporkan (akhir November 2015), sudah (sebagian besar) diperbaiki. Posting ini hanya untuk pelajaran bersama.

Kartu mandiri e-money adalah stored value card dengan teknologi NFC. Tadinya proses isi ulang tidak bisa dilakukan dari ponsel, tapi sejak sekitar Februari 2015, aplikasi isi ulang diluncurkan untuk ponsel Android dengan NFC.

NFC

Saya sudah agak lama memiliki beberapa kartu mandiri e-money dengan saldo sedikit sekali dari adik saya, tapi belum sempat mengeksplore kartu ini (dulu saya pernah minta diberikan berbagai kartu prabayar Indonesia). Pada hari saya menemukan bug ini, ada dua hal yang terjadi: adik saya mengirimkan ADB log menanyakan bug aplikasi Android yang ditulisnya, dan kebetulan saya mendapat pekerjaan checking implementasi NFC bank lain dan menemukan blog ini.

Saya tidak tahu apakah ada yang sudah menemukan bug ini sebelumnya, dari blog yang saya sebutkan sebelumnya, sepertinya dia tidak menemukan bug (karena APDU tidak bisa diplayback).

Daftar bug yang saya temukan:

  1. Bisa isi ulang kartu mandiri e-money berkali-kali (bisa mengisi kartu mana saja), tanpa mengurangi saldo Kartu Debit/Kredit kita (sudah diperbaiki)
  2. Kita bisa membuat kartu seseorang tidak bisa diisi ulang lagi dengan menuliskan data yang salah ke kartu tersebut (ketika bug pertama masih ada, ini bisa dilakukan dengan gratis, tapi sekarang perlu membuang uang untuk transaksi)
  3. Nomor kartu kredit, expiration date, CVV masuk ke log Android, user dan password login juga (belum diperbaiki)

Bug ini sudah dilaporkan 27 November 2015, ke berbagai pihak. Saya mengirimkan email ke beberapa orang. Email pertama ke developer tidak dibalas sama sekali, email kedua ke bagian fraud hanya mendapat balasan “akan kami cek” (dan tidak ada kabar 2 minggu kemudian), saya cek bugnya masih ada. Email berikutnya ke developer lain (yang katanya lebih terlibat dengan bagian ini): “akan kami cek”, tapi tidak ada follow up juga hingga sekarang. Tawaran saya memberikan script exploit juga tidak ditanggapi.

Hasil ADB log yang dikirimkan adik saya berisi data yang lengkap untuk melakukan replay message untuk isi ulang mandiri. Bahkan adik saya tidak tahu kalau saya berhasil mengisi ulang berkali-kali. Setelah email pertama saya kirimkan ke Mandiri, saya tidak mendapatkan balasan dari Mandiri. Beberapa hari kemudian, ketika saya mencoba apakah eksploit saya masih jalan, adik saya mengirimkan pesan ke saya, bahwa dia mendapatkan email bahwa dia telah mengisi ulang (dengan tanggal transaksi beberapa hari sebelumnya). Jadi sepertinya pihak mandiri telah mendapatkan email saya, dan mengubah sistemnya supaya mengirimkan email setelah transaksi.

Saat ini saya mendapati bug ini sudah diperbaiki setelah mengetes bahwa exploit saya sudah tidak jalan. Sebenarnya saya mengharapkan ada diskusi lebih lanjut mengenai ini, misalnya: apakah sistem transaksi kartu sudah menggunakan secure receipt. Dalam kasus isi ulang, ini tidak digunakan: server tidak peduli apakah APDU DD dikirim ke kartu, dan apakah kartu membalas dengan benar. Artinya jika ada gangguan di langkah terakhir: server menganggap transaksi sudah selesai, sudah berhasil, jadi data di kartu tidak sama dengan di server. Untuk kasus isi ulang, ini tidak apa-apa, yang rugi hanya customer (dan customer bisa datang ke bank untuk meminta saldonya dikoreksi). Bagimana jika ini terjadi juga di proses pembayaran? Eksploitasi ini tidak mudah, tapi juga tidak terlalu sulit bagi orang yang mengerti teknologi NFC.

Cara kerja isi ulang mandiri kira-kira seperti ini:

  1. Transaksi kartu kredit dilakukan menggunakan API veritrans
  2. API Veritrans akan menghasilkan ID Order (order_no), order_no ini akan dipakai di langkah berikutnya untuk melakukan isi ulang dengan API mandiri
  3. Setelah order_no didapat, akan dilakukan pemanggilan API mandiri untuk isi ulang, ini bukan satu langkah saja, ada banyak pertukaran data antara client dan server

Bug utamanya adalah ini: order_no tidak dicek apakah sudah dipakai atau belum sebelumnya. Dengan memiliki satu order id yang valid, kita bisa mengisi ulang kartu apa saja berkali-kali. Tidak ada asosiasi antara order id dan nomor kartu, kartu yang berbeda bisa diisi ulang dengan order id yang sama.

Proses pertukaran data antara client dan server dilakukan seperti ini:

  1. Di langkah pertama, Aplikasi Android akan mengirimkan ID kartu ke server
  2. Server mengirimkan APDU untuk dikirim ke kartu, app Android mengirimkan APDU ini ke kartu
  3. Balasan APDU dari kartu akan dikirimkan kembali ke server
  4. Langkah 2-3 diulangi sampai transaksi selesai (ada 12 kali pertukaran data)

Bagi saya APDU ini sifatnya blackbox, karena mereka mengimplementasikan protokol yang custom. Beberapa langkah pertama adalah cek kartu dan saldo kartu. Langkah-langkah berikutnya adalah memilih aplikasi di kartu, lalu melakukan serangkaian challenge dan response untuk membuka akses tulis ke kartu.

Langkah 11 adalah mengeset data yang akan dituliskan ke kartu, dan langkah terakhir (APDU 00DD000000) akan menuliskan data ke kartu (melakukan commit).

Contoh data langkah 11 seperti ini (dari server):

00D60010108011091450C30025111503DA446CFFFF

Jika kita melakukan intercept data di langkah 11, dan mengubahnya, kemungkinan besar data akan corrupt, dan kartu tidak bisa diisi ulang (server mengecek ini di langkah awal). Saya sudah mencoba-coba supaya data ini valid, tapi tidak berhasil.

Karena sifat data di APDU D6 yang tidak sepenuhnya dicek oleh kartu, maka kita bisa membuat kartu orang lain menjadi corrupt, kemungkinan selain tidak bisa diisi ulang, juga tidak bisa dipakai untuk transaksi lain. Tapi karena saya tidak bisa mengecek ini, mungkin saja saya salah, mungkin kartu tetap bisa transaksi. Pemeriksaan yang dilakukan oleh terminal POS (point of sales) mungkin lebih longgar dibandingkan oleh server.

NFC setup

Untuk menemukan bug ini, saya membuat aplikasi Python/C dengan libnfc, mengimplementasikan ulang protokol isi ulang Mandiri. NFC reader yang saya pakai harganya murah sekali (kurang dari 12 USD) saya hubungkan ke Raspberry Pi. Sebenarnya bisa saja saya membuat app Android untuk mengeksploitasi ini, tapi eksperimen dengan command line lebih mudah dilakukan.

Sebagai catatan:

  1. Saya tidak mempergunakan semua kartu yang saya isi.
  2. Untuk bug no 2: saya cukup yakin hanya sedikit orang yang mau membayar 50rb untuk merusak kartu orang lain (saldo maksimum kartu adalah 1 juta)
  3. Untuk bug no 3: berhati-hatilah menginstall aplikasi dengan akses root, karena aplikasi tersebut bisa mengakses log Android. Atau amannya: jangan root HP Anda jika Anda tidak mengerti konsekuensinya.

Saya berharap di masa depan Mandiri bisa lebih tanggap terhadap security bug report. Untungnya bug kali ini hanya akan merugikan bank (kita tidak mencuri uang orang lain), dan untungnya saya bukan penjahat yang mencoba mencari keuntungan dari ini. Untungnya juga saya bukan orang anarkis yang langsung menyebar app android untuk isi ulang kartu Anda sendiri dan menyebarkan aplikasi itu di Internet.

Edit: tambahan untuk risiko bug no 3 (karena ada yang bertanya di comment dan via jalur lain):

Meskipun nomor kartu kredit tidak disimpan oleh aplikasi isi ulang, tapi info ini muncul di log. Contoh kasus di mana ini bisa jadi masalah begini: HP Anda hilang, dan tidak dipassword/tidak dilock atau password/locknya bisa ditebak. Maka yang nemu HP nya bisa melakukan ini:

  1. Mengaktifkan developer mode
  2. Membaca log (dapat nomor kartu kredit, cvv, expiration date)
  3. Karena punya akses ke HP Anda, kalo ada sms verifikasi juga bisa dipakai oleh yang nemu HP tersebut.

Harapan saya: semoga di masa depan, bank dan lembaga penting lainnya memberikan bug bounty, atau minimal cara yang mudah untuk melaporkan bug.

Pelajaran penting lainnya: hati-hati menginstall aplikasi yang butuh akses root, ini hanya satu contoh kecil di mana aplikasi membocorkan data via log.

27 thoughts on “Bug Mandiri e-Money Isi Ulang (November 2015)”

  1. Menarik, klo boleh tahu beli readernya dari mana ya mas? Dan boleh donk sharing ilmu prihal reader dan rasberry pi serta rangkaiann IOnya.

    1. Beli readernya saya link di posting ini. Mengenai rangkaiannya, ada banyak kok di Internet.

  2. Berarti apdu di langkah 11 tidak diprotek dengan session key ya , karena bisa dilakukan replay attack.
    Sepertinya proses challenge respon hanya untuk membuka akses kartu, komunikasi selanjutnya masih tetap plain. Seharusnya flaw ini sudah ketahuan sejak awal melakukan desain protokol apdu.

    1. Nggak bisa replay attack.

      Jadi kira-kira flownya adalah:
      – verifikasi kartu
      – buka akses write ke kartu
      – kirimkan data untuk ditulis ke kartu
      – commit

      Hanya saja datanya nggak dicek oleh kartu apakah valid atau nggak.

      1. Oh iya,setelah baca ulang, replay nya dilakukan dari tahap order_no , jadi bug nya dari server mandiri yang tidak memeriksa apakah order_no sudah dipakai . Jadi patch-nya juga cukup di server saja.
        Apakah seperti itu juga analisa bapak ?

        1. Iya betul. Masalah utamanya hanya perlu patch di server saja.

          Masalah Android log, perlu update client.

          Masalah kartu yang menerima data apa saja untuk dituliskan ke kartu menurut saya risikonya rendah (sedangkan ini perlu diperbaiki di level kartu).

  3. Oh iya, menurut saya bisa merusak kartu dengan mengirimkan apdu D6 acak belum tentu merupakan bug. Bisa jadi merupakan design decision dari vendor/mandiri.

    payload dari apdu D6 kemungkinan adalah amount yang akan dikreditkan disertai semacam MAC kemudian di encrypt dengan session key. Jika dibuat random, maka setelah didecrypt oleh kartu akan terdeteksi MAC nya tidak cocok, kemudian kartu mendisable diri sendiri. Bisa jadi merupakan upaya pencegahan brute force.

    1. Memang isinya amount dan tanggal transaksi plus mac. Tapi design ini salah: harusnya server mengirimkan data yang dienkrip dengan key + hmac, dan d6 bisa mereply dengan sukses atau gagal.

  4. wow baru tahu saya
    kalau ada orang sehebat ini

    terima kasih pak infonya atas penggunaan e-mandiri.
    tapi pak yang mau saya tanyakan

    sampai komentar ini saya tulis di blog bapak
    apakah bug itu telah di perbaiki da ntidak terdapat bug yang mirip dengan kasus di atas

    terima kasih

    1. Bug yang mempengaruhi pengguna adalah: nomor kartu kredit, user, password di log di Android log.

      Jika Android tidak diroot, maka harusnya cukup aman, karena defaultnya log ini tidak terbaca. Sebaiknya proteksi juga Android Anda dengan password agar aman ketika hilang.

      Jadi meskipun nomor kartu kredit tidak disimpan oleh aplikasi itu sendiri, tapi muncul di log. Contoh kasus di mana ini bisa jadi masalah begini: HP Anda hilang, dan tidak dipassword/tidak dilock atau password/locknya bisa ditebak. Maka yang nemu HP nya bisa melakukan ini:

      – Mengaktifkan developer mode
      – Membaca log (dapat nomor kartu kredit, cvv, expiration date)
      – Karena punya akses ke HP Anda, kalo ada sms verifikasi juga bisa dipakai oleh yang nemu HP tersebut.

      1. Di android log dicatat cvv kartu kredit juga ya? Pelanggaran nih 😀
        Anyway, nice artikel juga. Awalnya sih nyari artikel buat top up prabayar flazz/e-money pake iOS. Tiba2 kesasar kesini :D.

Leave a Reply

Your email address will not be published. Required fields are marked *