File Upload Security
MediumTests file upload endpoints for security vulnerabilities.
What is File Upload Security?
Insecure file upload handling can allow attackers to upload malicious files, execute arbitrary code, or perform directory traversal attacks.
Security Impact
Severity: High to Critical
- Remote code execution
- Malware distribution
- Defacement
- Data exfiltration
- Server compromise
How to Fix
1. Validate File Types
public function upload(Request $request)
{
$request->validate([
'file' => 'required|file|mimes:jpg,jpeg,png,pdf|max:2048',
]);
// Process upload
}
2. Validate MIME Types (Server-Side)
use Illuminate\Support\Facades\Storage;
use Symfony\Component\Mime\MimeTypes;
public function upload(Request $request)
{
$file = $request->file('file');
// Get actual MIME type
$mimeType = $file->getMimeType();
$allowedMimes = ['image/jpeg', 'image/png', 'application/pdf'];
if (!in_array($mimeType, $allowedMimes)) {
return back()->withErrors(['file' => 'Invalid file type']);
}
// Store file
$path = $file->store('uploads', 'private');
}
3. Generate Unique Filenames
use Illuminate\Support\Str;
$filename = Str::uuid() . '.' . $file->getClientOriginalExtension();
$file->storeAs('uploads', $filename, 'private');
4. Store Files Outside Web Root
// config/filesystems.php
'disks' => [
'uploads' => [
'driver' => 'local',
'root' => storage_path('app/uploads'),
'visibility' => 'private',
],
],
// Store
$path = $file->store('', 'uploads');
// Download (through controller)
public function download($filename)
{
return Storage::disk('uploads')->download($filename);
}
5. Scan for Malware
// Using ClamAV
use Xenolope\Quahog\Client;
public function scanFile($filePath)
{
$quahog = new Client('unix:///var/run/clamav/clamd.ctl');
$result = $quahog->scanLocalFile($filePath);
if ($result['status'] === 'FOUND') {
unlink($filePath);
throw new \Exception('Malware detected');
}
}
6. Implement File Size Limits
// config/upload.php
'max_file_size' => 2048, // KB
// Validation
$request->validate([
'file' => 'required|file|max:' . config('upload.max_file_size'),
]);
7. Prevent Double Extensions
$allowedExtensions = ['jpg', 'png', 'pdf'];
$extension = strtolower($file->getClientOriginalExtension());
if (!in_array($extension, $allowedExtensions)) {
return back()->withErrors(['file' => 'Invalid file extension']);
}
8. Add Image Processing
use Intervention\Image\Facades\Image;
// Re-encode images to strip metadata and potential exploits
public function processImage($file)
{
$img = Image::make($file);
$img->encode('jpg', 85);
$filename = Str::uuid() . '.jpg';
Storage::disk('uploads')->put($filename, (string) $img);
return $filename;
}
Complete Example
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Storage;
class FileUploadController extends Controller
{
public function upload(Request $request)
{
// Validate
$request->validate([
'file' => [
'required',
'file',
'mimes:jpg,jpeg,png,pdf',
'max:2048',
function ($attribute, $value, $fail) {
// Additional MIME check
$mimeType = $value->getMimeType();
$allowed = ['image/jpeg', 'image/png', 'application/pdf'];
if (!in_array($mimeType, $allowed)) {
$fail('Invalid file type.');
}
},
],
]);
$file = $request->file('file');
// Generate secure filename
$filename = Str::uuid() . '.' . $file->getClientOriginalExtension();
// Store in private disk
$path = $file->storeAs('uploads', $filename, 'private');
// Save to database
auth()->user()->files()->create([
'filename' => $filename,
'original_name' => $file->getClientOriginalName(),
'mime_type' => $file->getMimeType(),
'size' => $file->getSize(),
'path' => $path,
]);
return back()->with('success', 'File uploaded successfully');
}
public function download($id)
{
$file = auth()->user()->files()->findOrFail($id);
return Storage::disk('private')->download(
$file->path,
$file->original_name
);
}
}
Verification Steps
- Try uploading executable file - should be rejected
- Try uploading file with double extension - should be handled
- Try uploading oversized file - should be rejected
- Verify files stored outside web root
- Test direct file access - should be blocked
Best Practices
- Always validate on server-side
- Never trust client-supplied filenames
- Store files outside web root
- Use private file systems
- Implement malware scanning
- Re-encode images
- Set file size limits
- Log all uploads
- Implement virus scanning
- Use Content-Disposition headers
Related Issues
- Cloud Storage Security
- CSRF Protection
- Directory Exposure
Automatically detect this issue
StackShield can automatically scan your Laravel application for this security issue and alert you when it's detected.
Start Free Trial