Sejumlah ilmuwan dengan didukung kekuatan komputasi Google berhasil melakukan serangan SHA1 collision terhadap dokumen PDF. Mereka menciptakan dua file PDF dengan nilai SHA1 hash yang identik walaupun isi filenya berbeda.
Lalu kenapa kalau ada 2 file SHA1-nya sama?
Hash seperti MD5 atau SHA1 sering digunakan sebagai fingerprint untuk mengidentifikasi sebuah file. Mirip seperti sidik jari kita yang karena keunikannya bisa digunakan untuk mengidentifikasi seseorang dengan tepat. Bayangkan bila ada 2 orang yang sidik jarinya sama, tentu akan berbahaya dampaknya kalau sampai tertukar identitasnya. Masih banyak bahaya lain dari hash collision, silakan baca tulisan saya sebelumnya tentang bahaya memakai MD5.
Salah satu bahaya dari collision untuk PDF adalah pemalsuan dokumen, dengan kata lain akan mudah memalsukan dokumen PDF walaupun sudah dilindungi dengan tandatangan digital. Dokumen penting seperti perjanjian bisnis, surat kontrak, akta jual beli, sangat rawan untuk dipalsukan.
Bisakah collision SHA1 dilakukan untuk file selain PDF? Tentu bisa, dalam tulisan ini saya akan menunjukkan cara membuat SHA1 collision untuk file PHP dan HTML/Javascript. Mari kita mulai dengan memahami cara kerja SHA1.
Cara kerja SHA1
SHA1 mengubah input data sepanjang berapapun, menjadi string singkat dan padat sebesar 20 bytes (40 byte dalam hexstring). Bagaimana caranya?
Seperti kita makan, kalau makanannya sangat besar tentu sulit untuk menelan sekaligus semuanya, makanya biasanya kita memotong-motong makanan jadi potongan kecil-kecil yang cukup untuk masuk ke mulut. Begitu pula dengan SHA1, dia bekerja dengan memotong input dalam blok-blok berukuran 64 byte dan memprosesnya satu per satu dari blok pertama sampai blok terakhir.
SHA1 menggunakan compression function, yang menerima 2 input yaitu 20 byte hash awal (input hash) dan 64 byte blok data untuk menghasilkan 20 byte hash keluaran (output hash). Karena fungsi ini menerima hash dan menghasilkan hash juga, maka hash keluaran dari fungsi ini bisa dipakai sebagai hash masukan untuk memanggil fungsi ini lagi untuk blok berikutnya.
Khusus untuk blok pertama, karena tidak ada blok sebelumnya, maka perlu ada nilai hash awal (initial hash) yang sudah ditetapkan yaitu 0x67452301EFCDAB8998BADCFE10325476C3D2E1F0. Jadi semua SHA1 untuk input data apapun selalu berawal dari nilai hash yang sama, kemudian untuk setiap blok, hash awal ini berevolusi menjadi hash yang berbeda. Hash yang dikeluarkan dari blok terakhir menjadi nilai hash final dari keseluruhan input data.
Analisa shattered-1.pdf dan shattered-2.pdf
Mari kita bandingkan shattered-1.pdf dan shattered-2.pdf. Ternyata perbedaannya hanya dari offset 192 ke 320 (128 bytes), sedangkan di luar range tersebut isinya sama.
Karena SHA1 bekerja menggunakan block berukuran 64 byte, maka bisa kita lihat 2 file tersebut diawali dengan prefix yang sama sebesar 3 blok (common prefix), diikuti dengan 2 blok yang berbeda ( collision block), kemudian setelah itu diikuti dengan byte yang sama sampai akhir file.
Kalau dilihat lebih detil lagi sebenarnya dalam collision block tidak semua byte berbeda, hanya 62 byte saja yang benar-benar berbeda. Perbedaan byte-byte pada posisi tertentu tersebut, terkait dengan format PDF dan sudah diatur sedemikian rupa sehingga bisa menghasilkan gambar yang berbeda ketika dilihat dengan PDF viewer.
Dengan implementasi sha-1 yang dimodifikasi, kita bisa melihat intermediate hash yang dihasilkan setiap blok. Mari kita lihat hash yang dihasilkan oleh 5 blok pertama dari shattered-1.pdf dan shattered-2.pdf pada gambar di bawah ini.
Dari 5 blok pertama shattered-1 dan shattered-2, perbedaan hanya ada pada blok ke-4 dan blok ke-5, blok ke-1 hingga ke-3 sama persis (common prefix) sehingga bisa kita lihat 3 hash pertama yang dihasilkan juga sama. Hash yang dihasilkan dari 3 blok common prefix ini adalah 0x4ea9…bf45. Kita bisa melanjutkan rangkaian rantai compression function ini dengan blok ke-4 dan ke-5 (collision block).
Selanjutnya hash dari common prefix, 0x4ea9…bf45, akan menjadi input untuk memproses blok-4. Karena isi blok-4 berbeda, maka kedua file menghasilkan 2 hash yang berbeda. Menariknya perbedaan tersebut hanya 6 byte saja.
Selanjutnya, 2 hash yang berbeda (keluaran dari blok-4) menjadi input untuk memproses blok-5. Ada yang ajaib disini, kedua file menghasilkan hash yang sama walaupun input hash dan isi blok ke-5 kedua file tersebut tidak sama. Perhatikan ilustrasi di bawah ini, perbedaan dimulai dari blok-4 namun blok-5 menyatukan perbedaan itu seolah tidak ada yang berbeda dari kedua file karena hash untuk 5 blok pertama keduanya sama, 1eac…8ac5.
Membuat collision SHA1 untuk file PHP
Mari kita mulai membuat 2 file PHP yang isinya berbeda dan melakukan hal yang berbeda (good.php dan evil.php), namun SHA1 dari kedua file tersebut sama. Bagaimana caranya? Apakah perlu super komputer? Tidak perlu, kita hanya perlu mengeksploitasi cara kerja SHA1 yang memproses hash blok demi blok.
Ingat bahwa hash keluaran dari blok ke-5 kedua file tersebut sama. Karena output hash dari blok ke-5 akan menjadi input hash untuk memproses blok ke-6, maka bila isi blok ke-6 kedua file tersebut sama, maka bisa dipastikan hash keluaran blok ke-6 keduanya juga sama. Dengan kata lain, selama kedua file memiliki common suffix (isi yang sama untuk blok ke-6 dan seterusnya), maka hash akhir keduanya akan sama.
Bila diketahui M1 dan M2 adalah dua blok yang berbeda, namun memenuhi
SHA1(common prefix+M1) = SHA1(common prefix+M2)
maka menambahkan common suffix setelahnya akan membuat SHA1 keduanya tetap sama.
SHA1(common prefix+M1+common suffix) = SHA1(common prefix+M2+common suffix)
Eksploitasi ini biasa disebut dengan hash length extension, yang ada pada fungsi hash yang menggunakan struktur Merkle-Damgard seperti MD5 dan SHA. Silakan baca tulisan saya sebelumnya tentang serangan ini hash length extension attack
Common suffix, payload PHP
Pada gambar di bawah ini, terlihat bahwa 320 byte (5 blok) pertama good.php berasal dari 5 blok pertama shattered-1, sedangkan 320 byte pertama evil.php berasal dari 5 blok pertama shattered-2. Selanjutnya diikuti dengan common suffix berupa payload php yang sama pada kedua file.
Walaupun code php pada payload sama, namun code tersebut harus melakukan hal yang jahat dalam evil.php dan tidak melakukan apa-apa pada good.php, bagaimanakah caranya? Tentu tidak bisa hanya sekedar melihat filename apakah evil/good, karena filename bisa diubah-ubah.
Caranya adalah payload code tersebut perlu membaca 320 byte pertama dirinya sendiri, kemudian menentukan bila 320 byte tersebut berasal dari shattered-1, maka itu artinya good.php, sebaliknya bila berasal dari shattered-2, maka itu artinya evil.php.
Pada shattered-2 terdapat string “x0Z!Vd” yang tidak ada di shattered-1, sehingga string ini bisa dijadikan indikator untuk shattered-2. Bila ditemukan string “x0Z!Vd” dalam 320 byte pertama, maka itu artinya adalah evil.php karena 320 byte pertama dari evil.php berasal dari shattered-2. Kurang lebih payload code yang menjadi common suffix seperti di bawah ini.
[code language=”php” light=”true”]
<?php
$body = mb_strcut(file_get_contents(__FILE__), 0, 320);
$position = strpos($body, "x0Z!Vd");
if ($position > -1) {
// evil.php, do something bad, rm -rf /
} else {
// good.php, do nothing
}
?>
[/code]
Berikut ini adalah contoh evil.php dan good.php dengan payload seperti contoh di atas. Dengan diff terlihat dua file tersebut sebenarnya berbeda, namun shasum menghasilkan SHA1 yang sama persis.
[code language=”text” light=”true”]
$ diff -q good.php evil.php
Files good.php and evil.php differ
$ shasum good.php evil.php
9f98b1abdb660db00ce4b0d06576bca8988565e0 good.php
9f98b1abdb660db00ce4b0d06576bca8988565e0 evil.php
[/code]
Ini artinya kita sukses membuat SHA1 collision untuk file php. Namun bagaimana dengan eksekusinya? Apakah script php ini bisa dieksekusi?
Kita telah berhasil mengeksekusi evil.php dan good.php, namun apa yang sebenarnya terjadi disini, kenapa ada karakter-karakter yang merupakan bagian dari header PDF terlihat di awal, sebelum kode payload dieksekusi? Mari kita lihat isi file good.php berikut ini.
Dari hexdump tersebut kita melihat bahwa 320 byte pertama file good.php berasal dari 320 byte pertama shattered-1.pdf, kemudian diikuti dengan kode php sebagai common suffix.
PHP memiliki keistimewaan yang memungkinkan SHA1 collision ini bisa dilakukan. PHP interpreter hanya akan mengeksekusi kode php di antara tag “<?php” dan “?>”. Dengan begini, maka header PDF di atas kode PHP tidak akan menjadi masalah, kode php dalam payload akan tetap bisa dieksekusi dengan sukses. Kita tidak bisa melakukan ini untuk python file misalkan, karena interpreter python akan komplain syntax error akibat adanya header PDF di awal file.
Membuat collision SHA1 untuk file HTML
Dengan cara yang sama kita juga bisa membuat SHA1 collision untuk jenis file html yang mengandung javascript di dalamnya. Kita bisa membuat evil.html dan good.html yang berbeda, namun memiliki SHA1 yang sama. Berikut ini adalah payload yang menjadi common suffix. Disini kita menggunakan javascript untuk mendeteksi apakah terdapat string “x0z!vd” yang menandakan ini adalah file evil.html yang berasal dari shattered-2.pdf.
[code language=”html” light=”true”]
<html> <body>
<script>
var text = window.document.body.innerHTML.substr(0,320);
var pos= text.toLowerCase().indexOf("x0z!vd");
if (pos > -1) {
document.body.innerHTML = "<h1>I AM so evil, doing evil things…</h1>";
} else {
document.body.innerHTML = "<h1>I AM GOOD!</h1>";
}
</script>
</body></html>
[/code]
Berikut ini adalah contoh good.html dan evil.html dengan nilai hash yang sama, namun isi filenya berbeda.
[code language=”text” light=”tru”]
$ shasum good.html evil.html
4487d29d4ce7262c3cdb10edf4172ca7a3f2bf30 good.html
4487d29d4ce7262c3cdb10edf4172ca7a3f2bf30 evil.html
$ diff -q good.html evil.html
Files good.html and evil.html differ
[/code]
Gambar di bawah ini menunjukkan tampilan di browser untuk good.html dan evil.html. Tidak seperti file PHP sebelumnya yang masih terlihat header PDF di awal, disini kita bisa dengan mudah “menyembunyikan” header PDF tersebut dengan menulis ulang document.body.innerHTML.
Dengan cara yang sama sebenarnya kita juga bisa menyembunyikan header PDF untuk file php bila php script tersebut dijalankan di web server dan dibuka di browser, tapi karena dijalankan di console maka header tersebut tidak bisa disembunyikan.
Salah satu website dan penulis yang selalu ditunggu tulisan-tulisan terbarunya. Sangarrrr