PHP
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Functions
-
Section 4: Object-Oriented PHP
-
Section 5: Working with Data
-
Section 6: Modern PHP (PHP 8)
-
Section 7: Working with Files and Networking
-
Section 8: Common PHP Frameworks Overview
-
Section 9: Tooling and Ecosystem
-
Section 10: Practical Projects
-
Section 11: Interview Practice
-
Section 12: More Standard Library
-
Section 13: Data Structures and Algorithms in PHP
-
Section 14: More Practice Exercises
-
Section 15: More Security and Best Practices
-
Section 16: More Testing and Tooling
-
Section 17: WordPress-Style CMS Concepts
-
Section 18: Advanced OOP Practice
-
Section 19: More Web Fundamentals
-
Section 20: Database Practice
-
Section 21: PHP Manual: Array Functions
-
Section 22: PHP Manual: Date and Calendar Functions
-
Section 23: PHP Manual: Filesystem and Directory Functions
-
Section 24: PHP Manual: Filter and Var Handling
-
Section 25: PHP Manual: Math Functions
-
Section 26: PHP Manual: JSON and XML
-
Section 27: PHP Manual: Network and Stream Functions
-
Section 28: PHP Manual: Error and Exception Handling
-
Section 29: PHP Manual: Output Control and Misc
-
Section 30: PHP Manual: FTP, Zip, and Mail
-
Section 31: Modern PHP Frameworks Deep Dive
-
Section 32: PHP Design Patterns
-
Section 33: More Practice Exercises
-
Section 34: PHP Performance and Deployment
-
Section 35: More Interview Practice
-
Section 36: More PHP Standard Library
-
Section 37: PHP Concurrency and Async
-
Section 38: More Web Development Practice
-
Section 39: PHP Testing Deep Dive
-
Section 40: Composer and Package Development
-
Section 41: PHP Security Deep Dive
-
Section 42: More Practical Projects
-
Section 43: Legacy PHP Maintenance
-
Section 44: More Algorithm Practice
-
Section 45: Final Practice and Review
-
Section 46: PHP for E-Commerce Patterns
-
Section 47: PHP API Design Deep Dive
-
Section 48: PHP Caching Strategies
-
Section 49: PHP Queue and Background Jobs
-
Section 50: PHP Multi-Tenancy Patterns
-
Section 51: PHP Real-Time Features
-
Section 52: PHP CMS and Content Modeling
-
Section 53: PHP Internationalization
-
Section 54: More Framework-Specific Practice
-
Section 55: PHP Legacy Code Refactoring
-
Section 56: More Practice Projects Round 2
-
Section 57: PHP Command-Line Applications
-
Section 58: PHP and Microservices
-
Section 59: More Interview and Review Round 2
68: Secure File Upload Handling
I want to show you a snippet of code that looks perfectly reasonable at first glance. I've seen this exact pattern in countless junior developers' portfolios, and it's one of the most dangerous mistakes you can make in a PHP application.
// The "Naive" Upload Script
$target_dir = "uploads/";
$filename = $_FILES["user_file"]["name"];
$target_file = $target_dir . basename($filename);
if (move_uploaded_file($_FILES["user_file"]["tmp_name"], $target_file)) {
echo "The file has been uploaded!";
} else {
echo "Sorry, there was an error uploading your file.";
}
If you're thinking, "Wait, isn't basename() enough to stop path traversal?" you're halfway there, but you're missing the bigger picture. The problem here isn't just where the file goes—it's what the file is. By trusting $_FILES["user_file"]["name"], you are letting the user decide what the file is called and, more importantly, what extension it has.
The Danger of Trusting User-Provided Filenames
Imagine I'm an attacker. I don't upload vacation_photo.jpg. Instead, I upload a file called cmd.php containing a simple payload: . Because your script blindly accepts the filename and moves it into a web-accessible directory, I now have a "web shell" on your server. I can navigate to yourdomain.com/uploads/cmd.php?cmd=ls -la and see every file in your directory. I've just gone from a visitor to having remote code execution (RCE).
Even if you try to check the extension with something like pathinfo(), attackers can bypass that with double extensions (like image.jpg.php) or null byte injections in older PHP versions. You simply cannot trust the client-side metadata.
Sanitizing and Renaming for Safety
The first rule of secure uploads is: Never use the original filename. I don't care if the user wants their file to be named "My Resume.pdf"; you should rename it to something the system generates. This kills two birds with one stone: it prevents RCE by stripping the extension and it stops users from accidentally overwriting each other's files.
// A much safer approach to naming
$target_dir = "uploads/";
$extension = pathinfo($_FILES["user_file"]["name"], PATHINFO_EXTENSION);
// Generate a cryptographically secure random name
$new_filename = bin2hex(random_bytes(16)) . '.' . $extension;
$target_file = $target_dir . $new_filename;
Now, we've solved the naming problem, but we still have a problem: I could still upload a PHP file and just rename it to randomstring.php. We need to validate the actual content of the file.
Validating Content, Not Just Extensions
Checking the extension is like checking the label on a soup can without opening the can. To actually know what's inside, we use the finfo (File Information) extension. This looks at the "magic bytes" at the beginning of the file to determine its actual MIME type.
$allowed_types = ['image/jpeg', 'image/png', 'application/pdf'];
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mime_type = $finfo->file($_FILES["user_file"]["tmp_name"]);
if (!in_array($mime_type, $allowed_types)) {
die("Error: Invalid file type. Please upload a JPG, PNG, or PDF.");
}
// Only now do we move the file
if (move_uploaded_file($_FILES["user_file"]["tmp_name"], $target_file)) {
echo "File uploaded securely.";
}
I'll give you one last pro tip: if you can, store your uploads outside the web root (e.g., one level above public_html). If the files aren't in a folder the web server is configured to serve, they can't be executed as scripts even if an attacker manages to sneak a .php file past your filters. You can then use a separate PHP script to read the file and stream it to the user using readfile().
📋 Practical Task
Build a Secure Profile Picture Uploader
Create a PHP script that allows a user to upload a profile picture. Your implementation must satisfy the following security requirements:
- Strict MIME Validation: Use
finfoto ensure the file is specifically animage/jpegorimage/png. Reject all other types. - Filename Randomization: The file must be saved using a randomly generated string (e.g., via
bin2hex(random_bytes())) so the original filename is completely discarded. - Size Limitation: Check
$_FILES["user_file"]["size"]and reject any file larger than 2MB. - Directory Protection: Create an
uploads/folder and include a.htaccessfile inside it that disables PHP execution (php_flag engine off) as an extra layer of defense.
Test your script by attempting to upload a .txt file renamed to .jpg to see if your finfo check catches the deception.
There are no comments for now.