Skip to content

PHP Tutorial PHP Security

File Upload Security

Lesson 295 of 369 2 min read By Deepak Paswan Updated Oct 5, 2026

Uploads are one of the riskiest features. A mistake can give an attacker control of your server. Here is a deliberately unsafe script, why it is dangerous, and the fix.

The vulnerable script

index.php
PHP
<?php
// DANGEROUS: do not write this
$target = "uploads/" . $_FILES["file"]["name"];

if ($_FILES["file"]["type"] === "image/png") {
    move_uploaded_file($_FILES["file"]["tmp_name"], $target);
    echo "Uploaded!";
}

What is wrong

  1. It trusts type, which is just text sent by the visitor. An attacker sends a PHP script with the header Content-Type: image/png, and the check passes.
  2. It uses the visitor’s file name. The name shell.php is saved as uploads/shell.php. The name ../index.php may overwrite your own files. A name like x.php.png can trick some server setups.
  3. It stores the file inside the web folder, where the server can run it.

The attacker then opens https://yoursite.example/uploads/shell.php. The server runs their code. This is a web shell: the attacker can now read your database password, steal data and attack other sites from your server.

The fixes

index.php
PHP
<?php
function checkUpload(string $tmpFile): ?string
{
    $mime = (new finfo(FILEINFO_MIME_TYPE))->file($tmpFile);

    $allowed = ["image/png" => "png", "image/jpeg" => "jpg", "image/webp" => "webp"];

    if (!isset($allowed[$mime]) || getimagesize($tmpFile) === false) {
        return null;     // not an allowed image
    }

    return $allowed[$mime];   // the extension WE choose
}

$png = base64_decode("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==");
file_put_contents("real.tmp", $png);
file_put_contents("shell.tmp", "<?php system(\$_GET['cmd']);");

var_dump(checkUpload("real.tmp"));
var_dump(checkUpload("shell.tmp"));
Output
OUTPUT
string(3) "png"
NULL

The script disguised as an image is rejected, because the content is checked, not the name or the browser’s claim.

The safe save

index.php
PHP
<?php
$extension = checkUpload($_FILES["file"]["tmp_name"] ?? "");

if ($extension === null) {
    exit("Only PNG, JPEG and WebP images are allowed.");
}

$name = bin2hex(random_bytes(16)) . "." . $extension;                // our name, our extension
$target = dirname(__DIR__) . "/storage/uploads/" . $name;           // outside the public folder

if (!move_uploaded_file($_FILES["file"]["tmp_name"], $target)) {
    exit("Could not save the file.");
}

The full checklist

RuleWhy
Check UPLOAD_ERR_OKThe upload may have failed
Limit the size (PHP and server settings)Stops disk and memory abuse
Check the content type with finfoThe type and name are attacker-controlled
Allow-list types and extensionsNever “block bad types”
Random file name, extension chosen by youNo overwrite, no .php tricks
Store outside the web rootPHP files cannot be run from a URL
If it must be public, disable script execution in that folderDefence in depth
For images, re-encode with GDRemoves hidden payloads
Serve with the right Content-Type and X-Content-Type-Options: nosniffStops the browser from guessing
Limit who can upload, and how manyAbuse control
Scan with antivirus when files are shared with othersMalware

Never allow uploads of .php, .phtml, .phar, .html, .svg (SVG can contain scripts) or .htaccess unless you really know what you are doing.

lightbulb
The full version
Secure File Uploads has the complete, tested validation function and the serving code.