Unrestricted file upload (CWE-434) allows attackers to upload malicious files including web shells, scripts, and exploits. The fix requires layered validation: extension allowlisting, MIME type checking, magic byte verification, and safe storage. This guide covers every layer.
An unrestricted file upload vulnerability occurs when an application accepts user-uploaded files without properly validating their type, content, or name. The most dangerous consequence is remote code execution: an attacker uploads a web shell or script and then accesses it directly through the web server.
Even without direct execution, malicious uploads can exploit server-side file processors (image libraries, PDF parsers, XML handlers) or be used for phishing by hosting malicious content on a trusted domain.
| Layer | Risky approach | Safe approach | Note |
|---|---|---|---|
| Extension allowlist | No extension check | Allow only .jpg, .png, .pdf etc. | First line of defense — easy to implement |
| MIME type check | Trust Content-Type header | Verify python-magic / file-type | Content-Type can be spoofed by attacker |
| Magic byte check | Only check extension | Read first bytes, verify signature | Catches renamed files (shell.php.jpg) |
| Filename sanitization | Use original filename | Generate random UUID filename | Prevents path traversal via filename |
| Storage location | Store in web root | Store outside web root | Prevents direct execution of uploaded files |
Vulnerable
@app.route("/upload", methods=["POST"])
def upload():
f = request.files["file"]
# Dangerous: no validation, stores original name
f.save(f"uploads/{f.filename}")
return "ok"Secure
import uuid, magic
ALLOWED = {".jpg", ".png", ".pdf"}
@app.route("/upload", methods=["POST"])
def upload():
f = request.files["file"]
ext = os.path.splitext(f.filename)[1].lower()
if ext not in ALLOWED:
abort(400)
mime = magic.from_buffer(f.read(2048), mime=True)
if mime not in {"image/jpeg","image/png","application/pdf"}:
abort(400)
f.seek(0)
# Safe: random name, no original filename
name = f"{uuid.uuid4()}{ext}"
f.save(f"/var/uploads/{name}")
return "ok"Vulnerable
const upload = multer({ dest: "uploads/" });
// Dangerous: no fileFilter, any type accepted
app.post("/upload", upload.single("file"), (req, res) => {
res.send("uploaded");
});Secure
const ALLOWED_MIMES = new Set(["image/jpeg","image/png","application/pdf"]);
const upload = multer({
dest: "/var/uploads/",
fileFilter: (req, file, cb) => {
cb(null, ALLOWED_MIMES.has(file.mimetype));
},
limits: { fileSize: 5 * 1024 * 1024 }, // 5 MB
});
app.post("/upload", upload.single("file"), (req, res) => {
res.send("uploaded");
});# Semgrep: Flask upload handler with no extension check
rules:
- id: flask-unrestricted-upload
patterns:
- pattern: $F.save(...)
- pattern-not-inside: |
if $EXT not in $ALLOWED:
...
message: File upload handler may lack extension validation (CWE-434)
severity: WARNING
languages: [python]Orbis AppSec identifies file upload handlers that lack validation and opens automated fix pull requests adding extension allowlisting and MIME type checks.
Want Orbis AppSec to find and fix file upload vulnerabilities in your GitHub repositories?
Try Orbis AppSecBrowse posts where Orbis AppSec detected and fixed file upload vulnerabilities in real open-source repositories:
View all security case studiesAn unrestricted file upload (CWE-434) occurs when an application accepts uploaded files without validating their type, content, or name. Attackers can upload web shells, malicious scripts, or files that exploit server-side processors.
CWE-434: Unrestricted Upload of File with Dangerous Type.
No. Attackers can rename files to bypass extension checks (e.g., shell.php.jpg). Defense in depth requires extension allowlisting, MIME type verification, and magic byte validation.
Use an extension allowlist, verify MIME type with python-magic, read and check magic bytes, generate a UUID for the stored filename, and store files outside the web root.
Yes. Semgrep rules can flag file upload handlers that lack extension or MIME type checks, and CodeQL can trace file data flows to storage sinks.
The upload directory should not be executable and should not be directly accessible by the web server as a static file root. Serve uploaded files through application logic that sets a safe Content-Type.