Safe File Uploads and Storage

An upload form is the most direct path to running an attacker's code on your server. If a naive handler saves the file under its original name into the web root, an attacker uploads shell.php, requests it, and Apache 129 's mod_php executes it. The receiving side of $_FILES is in Subsections 4.15.8-4.15.9; here is the security layer.

A naive handler (for learning) and a safe onePHP
move_uploaded_file($f['tmp_name'], __DIR__ . '/up/' . $f['name']);   // VULNERABLE: client name
// SAFE: check the real bytes, rename randomly, store outside the web root
const TYPES = ['image/jpeg' => 'jpg', 'image/png' => 'png', 'image/webp' => 'webp'];
$type = (new finfo(FILEINFO_MIME_TYPE))->file($f['tmp_name']);
if (!isset(TYPES[$type])) exit("rejected: bytes say $type\n");
$name = bin2hex(random_bytes(8)) . '.' . TYPES[$type];
move_uploaded_file($f['tmp_name'], '/srv/ch04-17/private/' . $name);

A curl 3,008 -F upload of shell.php (holding <?php echo shell_exec("id");) to the naive endpoint, a request for the stored file, then the same .php and a real PNG to the safe endpoint, gives:

Output of 184
saved as up/shell.php
RCE: uid=33(www-data) gid=33(www-data) groups=33(www-data)
rejected: bytes say text/x-php
stored privately as 334036d3cce8ccf6.png (image/png)

The uploaded PHP ran as the web-server user and returned id: remote code execution from one form. Four measures close it: verify the type from the file's magic bytes with finfo, never the client name or type; assign a random name with a safe extension so a .php can never be requested; store uploads outside the document root and stream them back through a PHP script; and cap the size (upload_max_filesize), re-encoding images through GD to strip any payload (Email, Images and Documents).