Information Security Stack Exchange is a question and answer site for information security professionals. Join them; it only takes a minute:

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

I'm working on a website — right now it's in early stages of testing, not yet launched and just has test data - thank goodness. I have some concerns, perhaps someone can help me out.

First of all, a hacker figured out the password to log onto the websites 'administration' pages*. I think they used a key logger on a friend's computer who logged into the site to give me feedback.

Secondly they used a picture upload box to upload a PHP file. I have put in strict checking so that only .jpg and .png files are accepted — everything else should have been rejected. Surely there is no way to upload a .jpg file and then change the extension once the file is stored?

Thankfully I also generate new file names when a file is sent to me, so I don't think they were able to locate the file to execute the code.

I just can't seem to figure out how the website let a php file through. What's wrong with my security? The validation function code is below:

function ValidateChange_Logo(theForm)
{
   var regexp;
   if (theForm.FileUpload1.value == "")
   {
      alert("You have not choosen a new logo file, or your file is not supported.");
      theForm.FileUpload1.focus();
      return false;
   }
   var extension = theForm.FileUpload1.value.substr(theForm.FileUpload1.value.lastIndexOf('.'));
   if ((extension.toLowerCase() != ".jpg") &&
       (extension.toLowerCase() != ".png"))
   {
      alert("You have not choosen a new logo file, or your file is not supported.");
      theForm.FileUpload1.focus();
      return false;
   }
   return true;
}

Once the file gets to the server, I use the following code to retain the extension, and generate a new random name. A bit messy but it works well.

// Process and Retain File Extension
$fileExt = $_FILES[logo][name];
$reversed = strrev($fileExt);
$extension0 = substr($reversed, 0, 1);
$extension1 = substr($reversed, 1, 1);
$extension2 = substr($reversed, 2, 1);
$fileExtension = ".".$extension2.$extension1.$extension0;
$newName = rand(1000000, 9999999) . $fileExtension;

I've just tested with a name such as logo.php;.jpg and although the picture cannot be opened by the website, it correctly changed the name to 123456.jpg. As for logo.php/.jpg, Windows doesn't allow such a file name.


* Protected pages on the website that allow simple functions: like uploading a picture that then becomes a new logo for the website. FTP details are completely different to the password used to log onto the protected pages on the website. As are Database and Cpanel credentials. I've ensured that people can't even view the folder and file structure of the site. There is literally no way I can think of to rename a .jpg, or .png extension to .php on this site if you don't have FTP details.

share|improve this question
8  
Do you know what happens when filename is foo.php/.jpg or foo.php;.jpg or something similar? Have you read owasp.org/index.php/Unrestricted_File_Upload ? – domen yesterday
3  
How do you know the picture upload was the vulnerability here? Is there any other vector that could've been exploited from the obtained password? Is this password reused for other components of your system? FTP? DB? – JᴀʏMᴇᴇ yesterday
37  
You are validating client-side with Javascript code, which can be bypassed easily by the attacker by disabling Javascript/deleting that JS-code by pressing F12. Also, any chance you're running a Nginx server? If misconfigured .png images can be interpreted as PHP-code. – O'Niel yesterday
26  
I'd also highlight it is probably pretty unlikely that a hacker accessed the page via a keylogger on a friend's computer. That would be a remarkably specific attack and your test site is unlikely to be a significant target. It is much more likely that there is another security issue that allowed access and a random vulnerability scanner found it and exploited it to post compromising code. These things literally scan the internet looking for places to put compromising code and exploit whatever they find, often completely automated. My web server has a few thousand such attempts a day. – AJ Henderson yesterday
2  
there's no reason why you can't rename a file to change the extension, which is not a special part of the file at all but rather just some convention we've all come up with to determine what type of file it is based on just the name. in other words, I can write some PHP code in a text file and save it as "cooldog.jpg" but that doesn't mean it's not a text file that contains PHP code. – 2rs2ts yesterday
up vote 164 down vote accepted

Client side validation

The validation code you have provided is in JavaScript. That suggests it is code that you use to do the validation on the client.

Rule number one of securing webapps is to never trust the client. The client is under the full control of the user - or in this case, the attacker. You can not be sure that any code you send to the client is used for anything, and no blocks you put in place on the client has any security value what so ever. Validation on the client is just for providing a smooth user experience, not to actually enforce any security relevant constraints.

An attacker could just change that piece of JavaScript in their browser, or turn scripts off completely, or just not send the file from a browser but instead craft their own POST request with a tool like curl.

You need to revalidate everything on the server. That means that your PHP must check that the files are of the right type, something your current code doesn't.

How to do that is a broad issue that I think is outside the scope of your question, but this question are good places to start reading. You might want to take a look at your .htaccess files as well.

Getting the extension

Not a security issue maybe, but this is a better way to do it:

$ext = pathinfo($filename, PATHINFO_EXTENSION);

Magic PHP functions

When I store data from edit boxes, I use all three of PHP's functions to clean it:

$cleanedName = strip_tags($_POST[name]); // Remove HTML tags
$cleanedName = htmlspecialchars($cleanedName); // Allow special chars, but store them safely. 
$cleanedName = mysqli_real_escape_string($connectionName, $cleanedName);

This is not a good strategy. The fact that you mention this in the context of validating file extensions makes me worried that you are just throwing a couple of security related functions at your input hoping it will solve the problem.

For instance, you should be using prepared statements and not escaping to protect against SQLi. Escaping of HTML tags and special characters to prevent XSS needs to be done after the data is retrieved from the database, and how to do it depends on where you insert that data. And so on, and so on.

Conclusion

I am not saying this to be mean, but you seem to be doing a lot of mistakes here. I would not be surprised if there are other vulnerabilities. If your app handles any sensitive information I would highly recommend that you let someone with security experience have a look at it before you take it into production.

share|improve this answer
49  
To take a simple example: Open your website in Chrome. Press f12 to open the developer tools. Select the sources tab. You'll see all your JavaScripts, lined up and nicely editable. Find your validation function. Delete it, and replace with return true; – superluminary yesterday
42  
/\ Not only in Chrome – Marc.2377 yesterday
    
Comments are not for extended discussion; this conversation has been moved to chat. – Rory Alsop 15 hours ago
5  
One thing that important to emphasize is "revalidate everything" -- just because you are validating on the server doesn't mean you shouldn't in the browser. Just don't use it for security. This way your users can know things like "oh I put an A in my phone number, whoops" before sending the form across. – Captain Man 11 hours ago
    
Right, validation on the user's side should just be used to decrease the amount of requests and as such increase speed. – Stephan Bijzitter 6 hours ago

First off - if someone exploited a file upload function, ensure that you're verifying the file on both the client AND server. Bypassing client side JS protection is trivial.

Second - ensure that you go through and implement these ideas from OWASP.

That should cover most of your problems. Also ensure that you don't have any other unpatched flaws on your server (eg outdated plugins, exploitable servers etc.)

share|improve this answer
1  
Can you elaborate on the "OWASP" ideas, sending people away from here is not necessarily a good thing. Even bringing some of the content or ideas here would greatly improve this answer. – Mooz 5 hours ago

It is super-important to note here that PHP was designed to be embedded in other content. Specifically, it was designed to be embedded within HTML pages, complementing a largely static page with minor bits of dynamically updated data.

And, quite simply, it doesn't care what it's embedded in. The PHP interpreter will scan through any arbitrary file type, and execute any PHP content that's properly marked with script tags.

The historic problem that this has caused is that, yes, image uploads can contain PHP. And if the web server is willing to filter those files through the PHP interpreter, then that code will execute. See the Wordpress "Tim Thumb" plugin fiasco.

The proper solution is to make sure that your web server never, ever treats untrusted content as something it should run through PHP. Attempting to filter the input is one way to go about it, but as you've found, limited and error prone.

Much better to verify that both file location and file extension are used to define what PHP will process, and to segregate uploads and other untrusted content into locations and extensions that won't be processed. (How you do so varies from server to server, and suffers from the "making things work usually means loosening security in ways you're not aware" problem.)

share|improve this answer

You are only checking the extension, this doesn't necessarily correlate to what the actual file type is. Server side you could use something like exif_imagetype to verify the file is an image. exif image type usage: http://php.net/manual/en/function.exif-imagetype.php

share|improve this answer
4  
This might be a good idea, but remember that (a) it might be possible to create a file that is both valid as image and PHP, and (b) a PHP file with a .png extension doesn't do much harm on your server, since it will not be executed. – Anders yesterday
3  
@Anders but it is still malicious code, and all it takes for it to break things is someone/something telling it to run foo.png as a php file. While it's unlikely, I'd rather stay away from keeping pocket bombs in my pocket, even if I'm the only one with a detonator. – Delioth yesterday
1  
@Anders Unless your server is configured to interpret files based on their content and not the extension, of course. Security is hard... – Luaan yesterday
    
@Delioth Agreed, I was a bit to categorical. I should have said is that it is not as dangerous, not that it is without risk. – Anders yesterday

You can disable PHP in your upload directory by .htaccess so your server won't execute any php code in the directory and in his subdirectories.

Create a .htaccess file inside your upload directory with this rule:

php_flag engine off

Please note that the .htaccess rule will work only if your PHP server is running in mod_php mode.

share|improve this answer

One possible part of a solution here is to keep the image, but also to make a resized copy with PHP http://php.net/manual/en/function.imagecopyresized.php such that you can ensure that you are getting an image, and reduce the size of the original image. This has several positive effects

  • never lets the original image be used for display so its more secure
  • saves space on your server (which may or may not be an issue)
  • better load time for site visitors as they would not be waiting for some 10Mb image to load. Instead they have a 300Kb image at a reasonable size loading

The main point is that you can't trust user data, ever. Validate everything, file uploads, inputs, cookies to ensure that the data can't cause problems.

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.