DEV3 Technologies home

DEV3 Developer Forum

How do I validate and store uploaded files securely in PHP?

PHP helpSolved0 views
dev3 ·

My image upload checks $_FILES['file']['type'] == 'image/jpeg' and saves to /uploads with the original filename. A security scan said it allows remote code execution. How should I handle uploads safely?

#php#upload#security

0

1 reply

dev3 · Accepted answer

$_FILES['file']['type'] and the original name are supplied by the client, so they can be anything. An attacker uploads shell.php with a fake image/jpeg type and requests it.

Safe handling

function saveImage(array $f): string
{
    if ($f['error'] !== UPLOAD_ERR_OK) throw new RuntimeException('Upload failed');
    if (!is_uploaded_file($f['tmp_name'])) throw new RuntimeException('Bad upload');
    if ($f['size'] > 5 * 1024 * 1024) throw new RuntimeException('Too large');

    // detect the real type from content
    $mime = (new finfo(FILEINFO_MIME_TYPE))->file($f['tmp_name']);
    $map = ['image/jpeg' => 'jpg', 'image/png' => 'png', 'image/webp' => 'webp'];
    if (!isset($map[$mime])) throw new RuntimeException('Type not allowed');

    // ensure it really is an image
    if (@getimagesize($f['tmp_name']) === false) throw new RuntimeException('Not an image');

    // generate our own name, never use the client's
    $name = bin2hex(random_bytes(16)) . '.' . $map[$mime];
    $dest = '/var/www/storage/uploads/' . $name;
    if (!move_uploaded_file($f['tmp_name'], $dest)) throw new RuntimeException('Cannot save');
    return $name;
}

Rules that matter

  1. Choose the extension from the detected MIME type via an allow list, not from the user filename.
  2. Store uploads outside the web root and serve through a script (readfile with proper Content-Type and Content-Disposition), or at least disable PHP execution in the upload directory.
  3. Apache .htaccess in the upload folder (when it must be public):
<FilesMatch "\.(php|phtml|phar|php\d)$">
  Require all denied
</FilesMatch>

Nginx: do not pass the upload location to PHP-FPM. 4. Re-encode images with GD or Imagick (imagecreatefromjpeg then imagejpeg) to strip embedded payloads and metadata. 5. Limit size in both php.ini and in code; limit counts and rate. 6. SVG is XML and can contain scripts: sanitize it or do not accept it. 7. Check the PDF/ZIP type with finfo as well, and scan with ClamAV if users share files.

Pitfalls

  • A double extension image.php.jpg can run on misconfigured Apache with AddHandler. Renaming files prevents that.
  • mime_content_type and finfo need the fileinfo extension.
  • Do not trust getimagesize alone; polyglot files pass. That is why you also keep files non-executable.

The MIME type lookup tool helps when building the allow list.

0

Your reply

Every post is reviewed by DEV3 before it appears. Usually reviewed within 24 hours.

Markdown supported. Use ``` for code blocks. Images are not allowed.