Publicações

dCTF 2021: Very Secure Website

Resolvendo um desafio de comparação fraca em PHP com um magic hash.

Writeup original do desafio: Very Secure Website.md

Pasted image 20210516145338

O código-fonte fornecido era:

 <?php
    if (isset($_GET['username']) and isset($_GET['password'])) {
        if (hash("tiger128,4", $_GET['username']) != "51c3f5f5d8a8830bc5d8b7ebcb5717df") {
            echo "Invalid username";
        }
        else if (hash("tiger128,4", $_GET['password']) == "0e132798983807237937411964085731") {
            $flag = fopen("flag.txt", "r") or die("Cannot open file");
            echo fread($flag, filesize("flag.txt"));
            fclose($flag);
        }
        else {
            echo "Try harder";
        }
    }
    else {
        echo "Invalid parameters";
    }
?> 

Pesquisar o hash do username mostrou resultados online dizendo que ele já tinha sido quebrado 1000 vezes. O valor era admin.

Esse foi difícil, especialmente porque foi o primeiro desafio que tentei e eu não sabia PHP.

Fiquei encarando o source pelo que pareceu uns 20 minutos. A checagem do username era direta, e eu não conseguia encontrar nada para injetar que fizesse o branch da senha avaliar como true.

O hash da senha era a única parte estranha.

Em uma string tão longa, ver apenas uma letra chamava muita atenção.

Solução

Eventualmente encontrei o problema: comparação fraca em PHP com ==.

A checagem da senha usava == em vez de ===:

hash("tiger128,4", $_GET['password']) == "0e132798983807237937411964085731"

Com comparação fraca, o PHP trata 0e132798983807237937411964085731 como notação científica. Quando compara esse valor numericamente, ele avalia para 0.

Então o objetivo era fornecer uma senha cujo hash também fosse tratado pelo PHP como 0.

Por sorte, outras pessoas já tinham coletado magic hashes para tiger128,4:

https://github.com/spaze/hashes/blob/master/tiger128%2C4.md

Um valor que funcionava era LnFwjYqB.

Isso fazia a checagem ficar assim:

else if (hash("tiger128,4", "LnFwjYqB") == "0e132798983807237937411964085731")

O hash se torna:

"0e087005190940152635463034029558" == "0e132798983807237937411964085731"

O PHP então compara ambos como números:

"0" == "0"

Isso avalia como true.

Usar admin como username e LnFwjYqB como password deu a flag:

dctf{It's_magic._I_ain't_gotta_explain_shit.}