Client-Side Path Traversal chains in bug bounty targets enable stored XSS, account takeover and account deletion
Researcher Pedro Cruz (gankd) documents two Client-Side Path Traversal (CSPT) chains found in production bug bounty programs. The first turns CSPT into Stored XSS and account takeover on a payments platform's Angular POS single-page application. The second, found with Felipe Torres, triggers unauthorized account deletion on one of the largest e-commerce platforms in Latin America. Both targets are redacted and no active exploitation is reported. The write-up argues that CSPT is invisible to blackbox scanners and AI tools because the traversal happens in browser JavaScript, so the server never sees it.
Technical Details
- Finding 1 (read-then-render): The Angular route parameter
alphanumIdis decoded byDefaultUrlSerializer(%2fbecomes/) and concatenated into theprintPublic()API URL. The traversal (..%2f..%2f..%2fv1%2foffice%2f801) redirects the request to/v1/office/{id}, which holds an attacker-stored HTML payload in thenamefield. An unconditional HTTP interceptor (posInterceptor) attaches the victim's token to every request. The response is injected throughRenderer2.setProperty('innerHTML'), which bypassesDomSanitizer, so the XSS fires. The print endpoints HTML-encode output, but the traversal sidesteps them. - Impact of Finding 1: The XSS reads
localStorageand steals a JWT with a 417-day expiry. The same primitive can reach other authenticated endpoints (sales, contacts, payments, subscriptions). The research was enabled by source maps exposed in the staging environment. - Finding 2 (write primitive): The
checkout_flowparameter of/checkout/finisheris used to build an internal API request. CloudFront blocked plain../. Felipe Torres bypassed it by interleaving tab characters (%09) with double-encoded dots (%252e), which the frontend JavaScript decodes into../. Six levels up, the request reaches the account cancellation API (create-cancellation-request) and is sent with the victim's cookies.%26keeps the query parameters inside thecheckout_flowvalue. - The write-up ties the pattern to Doyensec's CSPT2CSRF research and notes that framework routers (Angular, React Router) decode encoded path separators.
Remediation
- Whitelist destinations in HTTP interceptors so tokens are attached only to the API origin and authenticated path prefixes.
- Never render API responses with
innerHTMLorRenderer2.setProperty('innerHTML'). Use sanitized template binding instead. - Validate route and query parameters, rejecting
/,.and%where an ID is expected. - Do not ship source maps to production.
- Add JavaScript data-flow analysis (manual review, AI-assisted review, Semgrep rules) to the testing workflow.
Linha do tempo
-
Client-Side Path Traversal chains in bug bounty targets enable stored XSS, account takeover and account deletion
ler a análise desta fonte ocultar
Researcher Pedro Cruz (gankd) documents two Client-Side Path Traversal (CSPT) chains found in production bug bounty programs. The first turns CSPT into Stored XSS and account takeover on a payments platform's Angular POS single-page application. The second, found with Felipe Torres, triggers unauthorized account deletion on one of the largest e-commerce platforms in Latin America. Both targets are redacted and no active exploitation is reported. The write-up argues that CSPT is invisible to blackbox scanners and AI tools because the traversal happens in browser JavaScript, so the server never sees it.
Technical Details
- Finding 1 (read-then-render): The Angular route parameter
alphanumIdis decoded byDefaultUrlSerializer(%2fbecomes/) and concatenated into theprintPublic()API URL. The traversal (..%2f..%2f..%2fv1%2foffice%2f801) redirects the request to/v1/office/{id}, which holds an attacker-stored HTML payload in thenamefield. An unconditional HTTP interceptor (posInterceptor) attaches the victim's token to every request. The response is injected throughRenderer2.setProperty('innerHTML'), which bypassesDomSanitizer, so the XSS fires. The print endpoints HTML-encode output, but the traversal sidesteps them. - Impact of Finding 1: The XSS reads
localStorageand steals a JWT with a 417-day expiry. The same primitive can reach other authenticated endpoints (sales, contacts, payments, subscriptions). The research was enabled by source maps exposed in the staging environment. - Finding 2 (write primitive): The
checkout_flowparameter of/checkout/finisheris used to build an internal API request. CloudFront blocked plain../. Felipe Torres bypassed it by interleaving tab characters (%09) with double-encoded dots (%252e), which the frontend JavaScript decodes into../. Six levels up, the request reaches the account cancellation API (create-cancellation-request) and is sent with the victim's cookies.%26keeps the query parameters inside thecheckout_flowvalue. - The write-up ties the pattern to Doyensec's CSPT2CSRF research and notes that framework routers (Angular, React Router) decode encoded path separators.
Remediation
- Whitelist destinations in HTTP interceptors so tokens are attached only to the API origin and authenticated path prefixes.
- Never render API responses with
innerHTMLorRenderer2.setProperty('innerHTML'). Use sanitized template binding instead. - Validate route and query parameters, rejecting
/,.and%where an ID is expected. - Do not ship source maps to production.
- Add JavaScript data-flow analysis (manual review, AI-assisted review, Semgrep rules) to the testing workflow.
- Finding 1 (read-then-render): The Angular route parameter