Image uploads without surprises
The extension means nothing, and the MIME type the browser sends means even less. What is left is re-encoding.
The upload form is the most common way into a PHP site. Not because PHP is fragile, but because it is easy to trust what the browser sends.
What is not a check
- The extension in the file name. The attacker picks it.
$_FILES['x']['type']. The browser sends it, so the attacker sends it.
What is
$info = @getimagesize($tmp);
if ($info === false) {
throw new RuntimeException('That file is not an image.');
}
And, more importantly, re-encoding through GD. A file with PHP code hidden in an EXIF comment does not survive a round trip through imagecreatefromjpeg and imagejpeg.
The last safety net
Even if everything above failed, the upload folder must never execute anything:
<IfModule mod_php.c>
php_flag engine off
</IfModule>
RemoveHandler .php .phtml .php5
The IfModule matters: on hosting where PHP runs as FastCGI, a bare php_flag returns a 500 for the whole site.