File Upload Security Testing (DAST)¶
Escape discovers every file upload endpoint your application exposes
and probes each one against the OWASP Top 10 categories that matter
for upload surfaces (A01:2021 access control, A03:2021 injection,
A04:2021 insecure design, A05:2021 misconfiguration, A10:2021
SSRF). The module runs automatically inside your existing DAST scan
with no separate profile or infrastructure.
The module hooks into the DAST scan types you already run:
| Surface | Scanner kind | What we look at |
|---|---|---|
| WebApp DAST | Web application | Live multipart traffic captured by the agentic crawler + JS |
| REST API DAST | REST API | Recorded API exchanges with Content-Disposition: filename= |
| GraphQL API DAST | GraphQL API | Same recorded exchanges, GraphQL multipart spec aware |
Coverage¶
Seven active checks plus an informational ISSUE_FILE_UPLOAD_DETECTED
per detected endpoint. Every check confirms on a deterministic signal
that customers can audit from the event log.
| Check | Issue | Severity | Compliance | How we confirm it |
|---|---|---|---|---|
| Unrestricted Upload | ISSUE_FILE_UPLOAD_UNRESTRICTED |
High | CWE-434 + OWASP A04:2021 | Canary file uploaded under a dangerous extension; confirms only on 2xx upload AND canary readable through the inferred URL. |
| Stored XSS via Upload | ISSUE_FILE_UPLOAD_STORED_XSS |
High | CWE-79 + OWASP A03:2021 | SVG / HTML / PDF / polyglot canaries; confirms only when retrieval Content-Type is image/svg+xml or text/html AND canary is reflected verbatim. |
| RCE Polyglot | ISSUE_FILE_UPLOAD_RCE |
High | CWE-94 + CWE-434 + A03 | Idempotent GIF+PHP / .htaccess / web.config / .user.ini / ZIP polyglot; confirms via canary token echo or OOB callback at ssrf.tools.escape.tech. |
| Path Traversal via Upload | ISSUE_FILE_UPLOAD_PATH_TRAVERSAL |
High | CWE-22 + OWASP A01:2021 | Canary-suffixed traversal filenames (../escape_canary_<uuid>.txt, null-byte, Windows backslash, RTLO); confirms only on canary read-back. |
| XXE via Upload | ISSUE_FILE_UPLOAD_XXE |
High | CWE-611 + OWASP A05:2021 | SVG / OOXML / xlsx-style XML with OOB-only external entities; confirms via tagged callback at the OOB collector. |
| SSRF via Upload | ISSUE_FILE_UPLOAD_SSRF |
High | CWE-918 + OWASP A10:2021 | SVG xlink:href / <image href> / XML SYSTEM references to the OOB collector; confirms via tagged callback with fu- prefix. |
| Zip Slip | ISSUE_FILE_UPLOAD_ZIP_SLIP |
High | CWE-22 | Archive entries with canary-suffixed traversal paths; ZIP bomb capped at 5x ratio and 1 MiB on the wire; confirms only on canary read-back. |
How tests are executed¶
flowchart LR
HAR[Live traffic / recorded exchanges / JS source] --> DET[Endpoint discovery]
DET --> Reduce[Endpoint consolidation]
Reduce --> Profile[Endpoint profiling]
Profile --> Checks[7 active checks in parallel]
Checks --> Issues[(SecurityIssues)]
Detection methodology¶
Escape's detection layer reads signals already collected during your DAST
scan and emits one FileUploadEndpointCandidate per upload-shaped
exchange. The reducer dedupes them by (transport, url, GraphQL operation, method), profiles each replayable endpoint, and emits
ISSUE_FILE_UPLOAD_DETECTED so you can see the surface even when no
active check confirms.
Network-layer signals (any one is enough to short-list a candidate):
Content-Type: multipart/form-data; boundary=...with at least one part headerContent-Disposition: form-data; name=...; filename="...".- GraphQL multipart spec:
operations+map+ numbered file parts (the standardapollo-upload-clientshape). - Presigned cloud upload:
POSTorPUTagainst a recognised S3 / GCS / R2 / Azure host carryingX-Amz-Signature=,X-Goog-Signature=, orsig=. - Two-step presigned negotiation: a JSON response that contains
uploadUrl,presignedUrl,signedUrl,upload_url, orurl. - Resumable
tusprotocol:Tus-Resumable/Upload-Lengthheaders. - AWS S3 multipart:
POST ?uploads,PUT ?partNumber=.
Path heuristics corroborate the above: /upload, /files,
/attachment, /media, /import, /avatar, /image, /photo,
/document, /asset, /picture, /profile/picture,
/api/Complaints. A path keyword on its own is never enough; a
multipart, presigned, or two-step signal must also be present.
JavaScript-source signals catch endpoints the crawler has not
exercised yet: FormData, <input type="file">, imports of
apollo-upload-client, react-dropzone, tus-js-client,
@uppy/core, filepond, and fineuploader. JS-only endpoints
produce ISSUE_FILE_UPLOAD_DETECTED from deterministic evidence and
are not actively probed.
When endpoint signals are ambiguous, Escape uses an AI-assisted classifier to decide whether the surface is likely to accept uploads. Confirmed upload endpoints are summarized in the inventory so you can review their role even when no vulnerability is found.
Customer safety¶
The default catalogues are designed to surface the vulnerability without polluting your environment:
- Every payload filename carries a per-scan
escape_canary_<uuid>suffix, so resolved paths cannot collide with real customer files. - Path traversal and zip slip variants assert on canary read-back
only. We never write to
/etc,~/.ssh, cron directories, or any real-looking filename. - ZIP bomb canaries are capped at 5x compression ratio and 1 MiB on the wire.
- XXE and SSRF default catalogues are OOB-only. Local file
exfiltration (
file:///etc/passwd, ...) is intentionally out of scope in the first release. - RCE polyglot canaries are idempotent echoes of the canary token. No shells, no filesystem writes, no privileged operations.
- Uploaded artefacts remain in your storage until your normal
retention sweeps. Cross-link to
35-out-of-band-testing.mdfor OOB collector behaviour shared with the LLM module's SSRF check.
Evidence¶
When a file upload issue is raised, the finding explains which endpoint was tested, which check confirmed the behavior, and the non-destructive canary evidence used for confirmation.
Opt-in¶
The module runs automatically as part of your DAST scan and requires no configuration. All seven active checks are enabled by default.