Unsupported MIME Type Accepted
A file upload endpoint that restricts acceptable file types to image/jpeg, image/png, and application/pdf accepts a file whose MIME type is application/octet-stream (an executable). The upload succeeds and the file is stored, because validation is enforced only in the frontend — the backend API does not re-validate the Content-Type or inspect the file's magic bytes.
HighIntermediateSecurity testingAPI testingManual testingNegative testing
// UNDERSTAND
// Symptoms
- Uploading executable.exe to POST /api/documents/upload returns 200 OK and a file URL
- Files with a .exe, .sh, or .php extension are accepted and stored
- The frontend shows an 'Invalid file type' error when a disallowed file is selected via the file picker, but the same file is accepted when sent directly to the API
- No MIME type validation error appears in the API response for disallowed types
- Files with a renamed extension (e.g. malware.jpg that is actually an executable) are accepted and stored
// Root Cause
- MIME type validation is implemented only in the frontend file-picker's accept attribute and a JavaScript check before upload. The backend API handler accepts any Content-Type header value without checking it against an allowed list, so a request constructed without using the UI bypasses the restriction entirely.
- The backend checks the file extension from the filename string (e.g. .jpg) but does not validate the actual MIME type from the Content-Type header or read the file's magic bytes — an attacker can rename any file to an allowed extension and bypass the extension check.
// Where It Appears
- Document management systems where users upload PDFs, Word documents, or images
- Profile photo upload endpoints that should accept only image types
- Support ticket attachment endpoints
- Any upload endpoint where the allowed file types are documented but enforced only client-side
// REPRODUCE & TEST
// How to Reproduce
- 01Authenticate as a standard user and obtain the bearer token from any authenticated request in DevTools
- 02Locate the file upload endpoint from the API docs or DevTools: POST /api/documents/upload
- 03Create a test file named executable.exe (a plain text file with .exe extension is sufficient to test MIME type validation)
- 04Send a multipart/form-data POST request to /api/documents/upload with the file attached, using the bearer token; set the Content-Type of the file part to application/octet-stream
- 05Observe the HTTP response status and body
- 06If the response is 200 OK with a file URL, the unsupported MIME type was accepted
// Test Data Needed
- A valid user account with permission to upload documents
- A test file with a disallowed MIME type (application/octet-stream) or extension (.exe, .sh, .php)
- A way to send a raw multipart/form-data request with a controlled Content-Type header (Postman, curl, or Python requests)
// Manual Testing Ideas
- Use the file picker in the UI and select an .exe file — confirm the frontend shows an error and does not submit the upload
- Bypass the frontend by sending the same file directly to POST /api/documents/upload using Postman with application/octet-stream as the file part's Content-Type — confirm the API rejects it with a 415 Unsupported Media Type or 422
- Rename a known-disallowed file (e.g. executable.exe → executive_report.jpg) and upload via the API; confirm the backend validates actual MIME type, not just the filename extension
- Upload a valid JPEG file with its Content-Type header changed to application/pdf — confirm the backend validates the actual content, not only the declared type
- Test all documented allowed types (image/jpeg, image/png, application/pdf) via the API to confirm legitimate files are still accepted after fixing the validation
// API Testing Ideas
- Authenticate as a standard user; capture the bearer token from DevTools
- Send POST /api/documents/upload with a valid image/jpeg file; assert the response is 200 OK — this confirms allowed types still work
- Send POST /api/documents/upload with a file whose Content-Type is application/octet-stream
- Assert the response is 415 Unsupported Media Type or 422 Unprocessable Entity — not 200
- Send POST /api/documents/upload with a file named malware.jpg but with Content-Type: application/octet-stream
- Assert the response is 415 or 422 — confirming the backend validates the Content-Type header, not only the filename extension
- Send POST /api/documents/upload with a file whose magic bytes are that of a ZIP (PK\x03\x04) even if Content-Type is image/jpeg — this tests whether the backend inspects file content beyond just the header
// Automation Idea
Parameterize a test suite over disallowed MIME types: application/octet-stream, application/x-sh, text/x-script, application/x-php. For each type, send POST /api/documents/upload with a file of that Content-Type and assert the HTTP status is 415 or 422. Then repeat with each allowed type (image/jpeg, image/png, application/pdf) and assert the status is 200 OK. If any disallowed type returns 200, mark the test as a failure.
// Expected Result
POST /api/documents/upload rejects files with MIME types not in the allowed list (image/jpeg, image/png, application/pdf) with HTTP 415 Unsupported Media Type or 422 Unprocessable Entity, regardless of whether the request was sent via the UI or directly to the API.
// Actual Result (Example)
POST /api/documents/upload with a file named executable.exe and Content-Type: application/octet-stream returns 200 OK with body { "url": "https://storage.example.com/docs/executable.exe" }. The file is stored and publicly accessible via the returned URL.
// REPORT IT
Example Bug Report
- Title
- POST /api/documents/upload accepts application/octet-stream (executable) file without error
- Severity
- High
- Environment
- Staging environment Postman Valid bearer token Standard user account
- Steps to Reproduce
- 01Authenticate as a standard user and copy the bearer token from any authenticated request in DevTools
- 02Open Postman and create a POST request to /api/documents/upload
- 03Add the Authorization header with the bearer token
- 04In the Body tab, select form-data; add a key named 'file' of type File, and select executable.exe; manually set the Content-Type of the file part to application/octet-stream
- 05Send the request and observe the response status and body
- Expected Result
- The API returns 415 Unsupported Media Type or 422 Unprocessable Entity with a message indicating the file type is not allowed.
- Actual Result
- The API returns 200 OK with body { "url": "https://storage.example.com/docs/executable.exe" }. The file is stored at the returned URL and downloadable.
- Impact
- Malicious files can be uploaded and stored in application storage. If stored files are publicly accessible, an attacker can host malware or exploit payloads through the platform's own domain. This creates a significant security and reputational risk.