Performance & Security

Secure File Uploads: Stopping Malicious Files on Your Site

How to make file uploads secure: allowlist file types, check content not just extensions, rename and store files safely, block execution, limit size and scan uploads, with WordPress-specific tips.

Quick answer: Secure file uploads depend on several layers working together. Allow only the file types you need, check the actual file content rather than trusting the extension or browser-reported type, give files new random names, store them outside the web root or in a folder where scripts cannot run, limit file size, and scan uploads for malware. Restrict who can upload, and serve files back in a way that cannot trick browsers into running code. Any one of these on its own is easy to bypass. Together they close most of the common attack routes.

Why upload forms are a favorite target

Any feature that lets visitors put a file on your server is an opportunity for an attacker. Common examples: job application forms with CV uploads, support forms with attachments, profile photo uploads, client portals and user-submitted content.

If an attacker can upload a script, such as a PHP file disguised as an image, and then request it through the browser, the server may run it. That gives them a web shell: the ability to read files, change content, add spam pages, steal data or use your server to attack others. A common route into hacked WordPress sites is an upload feature in an outdated plugin. Our guide to cleaning up a hacked WordPress site covers what happens next if it goes wrong.

Unrestricted file upload is a long-standing entry in OWASP guidance and ties into several risks on the OWASP Top 10, including injection, broken access control and security misconfiguration.

The main risks

  • Code execution: uploading a script that the server runs
  • Malware distribution: using your site to host infected files for others to download
  • Cross-site scripting (XSS): HTML or SVG files containing scripts that run in visitors’ browsers when opened from your domain
  • Overwriting files: using crafted file names to replace existing files
  • Path traversal: names like ../../config.php that try to write outside the upload folder
  • Denial of service: very large files or huge numbers of uploads filling the disk
  • Parser exploits: files crafted to exploit bugs in image or document processing libraries
  • Sensitive data exposure: uploaded private documents left publicly accessible

Layer 1: Allow only what you need

Use an allowlist, not a blocklist. Decide exactly which types each form accepts: for a CV upload, perhaps PDF and DOCX; for a profile photo, JPEG and PNG.

Blocklists (“block .php and .exe”) miss variants like .phtml, .php5, .phar, double extensions such as photo.php.jpg and server-specific quirks. An allowlist avoids that guessing game.

Layer 2: Check the real content

Attackers control the file name and the Content-Type header the browser sends. Do not trust either on its own.

  • Check the extension against the allowlist.
  • Check the file’s actual type by reading its signature (magic bytes), for example with PHP’s finfo functions.
  • Make sure the extension and the detected type agree.
  • For images, try to open and re-encode them with an image library. This strips most hidden payloads and metadata and confirms it really is an image.

In WordPress, wp_check_filetype_and_ext() performs extension and content checks, and the core media handling uses it. Custom upload code should use the core functions rather than moving files around directly.

Layer 3: Rename files

Never keep the user’s original file name as the stored name.

  • Generate a random name or ID for each file.
  • Set the extension yourself based on the validated type.
  • Store the original name in your database if you need to show it later, after sanitising it.

This prevents overwriting, path traversal and attacks that rely on predictable names.

Layer 4: Store files where they cannot run

The safest option is storing uploads outside the public web root and serving them through a script that checks permissions. If files must be in a public folder:

  • Disable script execution in that folder. On Apache, an .htaccess file can turn off the PHP engine and deny access to script extensions. On Nginx, add a location rule that refuses to pass files in the uploads folder to PHP.
  • Serve files from a separate domain or object storage (such as Amazon S3) so even a malicious HTML file cannot run scripts in your main site’s context.
  • Set correct file permissions so uploaded files are not executable.

Many WordPress security plugins can add the uploads folder execution block for you. Our guide to WordPress security covers this alongside other hardening steps.

Layer 5: Limit size and volume

  • Set maximum file sizes in your application and in server settings (upload_max_filesize and post_max_size in PHP, client_max_body_size in Nginx).
  • Limit the number of files per submission.
  • Rate limit upload endpoints to slow down automated abuse.
  • Monitor disk usage so a flood of uploads does not take the site down.

Layer 6: Scan for malware

For forms where the public uploads documents that staff will open, scan files before anyone touches them. ClamAV is a common open source scanner that can run on the server. Commercial scanning services and APIs are also available. Quarantine anything flagged and alert an administrator.

Remember that a clean scan does not prove a file is safe. Office documents with macros, for example, need staff training as well as scanning.

Layer 7: Control access

  • Require users to be logged in where possible.
  • Check permissions on every upload and every download. A user should only see their own files.
  • Use CSRF protection on upload forms.
  • In WordPress, respect capabilities: by default only trusted roles can upload, and the unfiltered_upload capability should stay disabled on most sites.

Serving files back safely

  • Send a correct Content-Type and add X-Content-Type-Options: nosniff so browsers do not guess types.
  • Use Content-Disposition: attachment for files that should download rather than open in the browser.
  • Do not allow uploads of HTML or SVG from untrusted users unless you sanitise them thoroughly. SVG files are XML and can contain scripts.

WordPress-specific tips

  • Keep WordPress core, themes and plugins updated. Upload vulnerabilities in plugins are a frequent cause of compromise. See plugin vulnerabilities for how to stay ahead.
  • Use the upload_mimes filter to remove types you do not need from the default list.
  • Be careful with plugins that add SVG support. Choose ones that sanitise SVG files.
  • Use well-maintained form plugins for public uploads, and check where they store files and whether those folders are protected.
  • Review user roles so only people who need to upload media can.

Test your upload forms

Try the attacks yourself on a staging site:

  • Upload a file with a double extension
  • Change the Content-Type in the request to image/jpeg for a script file
  • Upload an oversized file
  • Use a file name with ../
  • Upload an HTML or SVG file containing a script

If any of these succeed or produce a file you can request and run, fix it before an attacker finds it. For larger or high-risk applications, a professional penetration test is worth the cost.

FAQ

Is checking the file extension enough?

No. Extensions are easy to fake. Combine extension checks with content checks, renaming and blocking execution.

Are images safe to accept?

Mostly, when you validate and re-encode them. Re-encoding removes hidden content and confirms the file is a real image.

Should uploaded files be public?

Only if they are meant to be public, such as product images. Private documents like CVs and ID scans should be stored privately and served through permission checks.

Do I need antivirus scanning on a small site?

If the public uploads documents your staff will open, scanning is a sensible extra layer. For image-only uploads with re-encoding, it is less critical.

Close the door on malicious uploads

Secure file uploads come from layers: allowlists, content checks, renaming, safe storage, size limits, scanning and access control. If you want your forms and upload features reviewed or rebuilt safely, see how we help you turn visitors into customers on a site you can trust.

About the author

Imran M

Leave a Reply

Your email address will not be published. Required fields are marked *

Ready to improve your website?

Let's build a faster, higher performing website.