File upload security on IIS and Nginx: stop upload-to-RCE
Validate uploads, store them outside the executable root, block script execution in upload paths, and defend against path traversal.
- Build
- 26100.33650
- Edition
- Standard / Datacenter
- Architecture
- x64
- Support
- Documentation (E1)
- Evidence
- E1Official documentation
- Verified
- 2026-10-29
- Page type
- Task
The short answer
Section titled “The short answer”An upload is dangerous when three things line up:
- the file lands in a path IIS/Nginx serves;
- the server executes that extension (
.php,.asp,.aspx,.cgi); - the attacker controls the name/content.
Break at least two. Best: store outside the web root and serve through a controlled handler.
Validation checklist
Section titled “Validation checklist”- Allowlist extensions and MIME types; do not trust the
Content-Typealone. - Enforce a size limit at proxy + app.
- Generate a random stored name; keep the original only as metadata.
- Re-encode images if the app can; strip EXIF.
- Scan with AV/EDR if the business allows.
- Store uploads outside the site root, or in a directory with no script handlers.
- Serve downloads with
Content-Disposition: attachmentand a safe content type.
Path traversal
Section titled “Path traversal”Reject/normalize:
../,..\, encoded%2e%2e%2f, double encoding;- absolute paths (
C:\,\\server\share); - NUL bytes and ADS (
file.php::$DATA).
Do not build a path from user input and Path.Combine alone; resolve the canonical path and verify it stays under the upload root.
Put uploads in a directory with no managed handler:
<location path="uploads"> <system.webServer> <handlers> <clear /> </handlers> <staticContent> <clientCache cacheControlMode="NoControl" /> </staticContent> </system.webServer></location>If you <clear />, the directory serves nothing; usually you want to remove *.php/*.asp handlers, not all static. Test with a real file.
Also use request filtering to deny executable extensions in the upload path.
location ^~ /uploads/ { add_header Content-Disposition "attachment"; try_files $uri =404;}
location ~ \.php$ { try_files $uri =404; include fastcgi_params; fastcgi_pass 127.0.0.1:9000;}^~ prevents the regex PHP location from matching files under /uploads/. Without it, shell.php in uploads can be executed. try_files blocks PATH_INFO tricks.
Verify
Section titled “Verify”- Upload a
.php/.aspxfile; request it; expect 404/403, not execution. - Upload
..%2fweb.config; expect rejection. - Request a valid image; expect 200 with the right type.
- Check the app pool identity cannot write the site root, only uploads.
Lab boundary
Section titled “Lab boundary”E1. The lab used PHP FastCGI and IIS but did not run an upload/RCE test; the controls are the production checklist for Server 2025.