published on in HowTo Security
tags: php security

Image upload? Watch out!

Some days ago I saw a vulnerable website. ImageShare site of my friend. I don’t use websites like this. I just checked it and I found a bug. After my discovery, I looked into that matter. That was deeper than I thought.

<h1>Upload a new image</h1>
<form action="?menu=upload" method="post" enctype="multipart/form-data">
  <input name="my_str" type="text" value="TestString">
  <input name="my_file" type="file">
  <input type="submit" value="Send">
</form>
</pre>

And the handler on server-side:

<?php
if(isset($_FILES) && array_key_exists('my_file', $_FILES)) {
  if (strtolower($_FILES['my_file']['type']) === 'image/jpeg') {
    $name = 'testfiles/'.time().'_'.md5($_FILES['my_file']['name']).'.jpeg';
    move_uploaded_file($_FILES['my_file']['tmp_name'], $name);
    echo "<a href="{$name}">{$name}</a> uploaded...";
  } else {
    echo 'only jpeg!';
  }
} else {
  echo 'Upload error!';
}

Ok… What exactly happened?

Client:

  1. load the upload form
  2. select a file
  3. add title
  4. click on the submit button
  5. send request to the server

Server:

  1. gets the request
  2. calls php file
  3. does something before the highlighted code
  4. checks existence of the $_FILES array
  5. checks my_file key exists in $_FILES array
  6. if (4) or (5) is false: prints out an error message and “exit”
  7. checks MIME-type of the file
  8. if MIME-type is not an image/jpeg: prints an error message and “exit”
  9. generates a target filename from current timestamp, md5 hash of the filename and adds the jpeg extension
  10. moves temporary file to the target place
  11. prints a link of the file

Where is the bug?

What do you think, is the MIME-type filled up by the server? Or is it sent by the client? Yes, of course it is sent by the client =) Are you surprised? Client (?) sends the content, name and type of the file. Client or Attacker…

What is a request to upload files like?

POST /php_upload_vul/upload.php HTTP/1.1
Host: www.target_domain.tld
Origin: hxxp://www.target_domain.tld
Content-Length: {CONTENT_LENGTH_OF_THE_REQUEST}
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; en-us)
Content-Type: multipart/form-data; boundary=----WebKitFormBoundarytdQDXALfOP7FtIa6
Accept: text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
Referer: hxxp://www.target_domain.tld/php_upload_vul/index.php
Accept-Language: en-us
Accept-Encoding: gzip, deflate
Connection: keep-alive

------WebKitFormBoundarytdQDXALfOP7FtIa6
Content-Disposition: form-data; name="my_str"

This is the value of the `my_str` input field
------WebKitFormBoundarytdQDXALfOP7FtIa6
Content-Disposition: form-data; name="my_file"; filename="Name of the file.jpeg"
Content-Type: image/jpeg

{CONTENT_OF_THE_FILE}
------WebKitFormBoundarytdQDXALfOP7FtIa6--

What does an attacker do?

POST /php_upload_vul/upload.php HTTP/1.1
Host: www.target_domain.tld
Origin: hxxp://www.target_domain.tld
Content-Length: {CONTENT_LENGTH_OF_THE_REQUEST}
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; en-us)
Content-Type: multipart/form-data; boundary=----WebKitFormBoundarytdQDXALfOP7FtIa6
Accept: text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
Referer: hxxp://www.target_domain.tld/php_upload_vul/index.php
Accept-Language: en-us
Accept-Encoding: gzip, deflate
Connection: keep-alive

------WebKitFormBoundarytdQDXALfOP7FtIa6
Content-Disposition: form-data; name="my_str"

Photo about my ass
------WebKitFormBoundarytdQDXALfOP7FtIa6
Content-Disposition: form-data; name="my_file"; filename="very_funny_photo.jpeg"
Content-Type: image/jpeg

<!--?php phpinfo(); /* or some evil code */ ?-->
------WebKitFormBoundarytdQDXALfOP7FtIa6--

Yes… You can put a php code into the content of the file if you want. Now if I am able to make the system to require this file… I will be the puppet master.

Conclusion

  1. Never forget to check the extension of the file.
  2. Never forget to check the MIME-type of the file.
  3. Never forget to check EXIF (or similar) datas of the file (eg: width, height). (Oh yeah… This can be tricked.)
  4. If you are paranoid then try to load images with GD.
  5. Never forget to check any of the uploaded images can’t be required.
  6. Never forget to check configuration of you web-server (apache, nginx).
  7. If you are more paranoid than (4) … …
    • DO NOT make a website.
    • DO NOT host any website.
    • DO NOT use any website.
    • DO NOT breathe and DO NOT blow it out.