CodeBlanks Logo Icon CodeBlanks

Secure file upload

PHP course · Expert · Lesson 34/54

Learn MIME checks, random names and storage separation for secure file upload.

Secure file upload principles

Secure upload rests on three principles: MIME check, random name, storage separation. The finfo class (FILEINFO_MIME_TYPE) reads the MIME from the file's real content; $_FILES['x']['type'] is an untrusted browser hint. The uploaded file gets a random name via bin2hex(random_bytes(16)); the original name is not kept.

The file lives outside the web root; downloads go through a script. The extension is bounded by an allowlist (png, jpg, pdf); in_array is called in strict mode (third argument true). move_uploaded_file moves the temporary file to the target.

Random filename and storage

The original filename is user controlled; it may contain a path traversal (../../etc/passwd). So you generate a 32-character random name with bin2hex(random_bytes(16)) and append the allowlist extension. The file is stored outside the web root, e.g. /var/www/uploads; downloads go through a controller script.

The allowlist is kept as ['png', 'jpg', 'pdf']; strict check uses in_array($ext, $allowed, true). move_uploaded_file moves the temporary file to the target and blocks path traversal. As output you should print only the name's length or a status code; never print the random name itself.

Exercise Write a generateUploadName function

Define generateUploadName(string $ext, array $allowed): string. Return an empty string when in_array($ext, $allowed, true) is false. If true, return bin2hex(random_bytes(8)) . "." . $ext. Define $allowed = ["png", "jpg", "pdf"]. Call generateUploadName("png", $allowed) and generateUploadName("exe", $allowed); print 1/0 for whether the first is 20 long and the second is empty.

Show example solution
string { if (!in_array($ext, $allowed, true)) { return ""; } return bin2hex(random_bytes(8)) . "." . $ext; }. "png" is valid → bin2hex(random_bytes(8)) = 16 hex + ".png" = 20 characters → strlen 20 = true (1). "exe" is invalid → empty string → === "" true (1).
Example solution:
<?php
function generateUploadName(string $ext, array $allowed): string {
    if (!in_array($ext, $allowed, true)) {
        return "";
    }
    return bin2hex(random_bytes(8)) . "." . $ext;
}
$allowed = ["png", "jpg", "pdf"];
$name = generateUploadName("png", $allowed);
$bad = generateUploadName("exe", $allowed);
echo (strlen($name) === 20 ? "1" : "0") . "\n";
echo ($bad === "" ? "1" : "0") . "\n";

Test yourself

  1. Match the upload security concepts

    finfo
    Reads real MIME
    random_bytes
    Generates random name
    bin2hex
    Converts to hex string
    allowlist
    Extension check
  2. How do you read the real MIME of an uploaded file?

    • With $_FILES['file']['type']
    • With pathinfo()
    • With basename()
    • With the finfo class
    Show answer

    With the finfo class — Correct. finfo reads the MIME from the file's content; the untrusted hint is not relied on.

  3. Generate a name with random_bytes

    $name = bin2hex(____(16)) . ".png";
    Show answer
    $name = bin2hex(random_bytes(16)) . ".png";

All lessons in Expert