Cross-Site Scripting (XSS): The Complete Practical Guide

XSS Contexts: HTML, Attribute, JavaScript, and URL

Why the same XSS payload wins in one place and fails in another. Breaking out of HTML body, attribute, JavaScript-string, and URL contexts, step by step.

Published

The single most useful idea in XSS is that a payload is not universal. The exact same string is inert in one spot and dangerous in another, because what counts as a special character depends on where your input lands. Learning to read the context and pick the matching break-out is what separates guessing from exploiting.

Why the same payload wins or fails

When your input is reflected, it sits somewhere inside the page: between tags, inside a quoted attribute, inside a <script> block, or inside a URL. Each of those is parsed by different rules. A <script>alert(1)</script> dropped into the HTML body runs, but the same string placed inside an attribute value is just text, because the browser is not looking for new tags there. To execute, you first have to escape to a position where your characters mean something. So the question is never "what is a good payload," it is "what context am I in, and what breaks out of it."

HTML-body context

Your input lands between tags, as element content:

<div>YOUR_INPUT</div>

Here < starts a new tag, so you inject one:

<script>alert(1)</script>
<img src=x onerror=alert(1)>
<svg onload=alert(1)>

If < and > survive unencoded, this is the easiest context there is.

Attribute context

Your input lands inside a quoted attribute value:

<input value="YOUR_INPUT">

You have two ways out. Break out of the quotes and inject a tag:

"><svg onload=alert(1)>

Or, if you cannot break the tag but can add to it, inject an event handler that fires without breaking out at all:

" autofocus onfocus=alert(1) x="

The second trick matters when the app encodes < and > but leaves your quote intact, which is a common half-fix.

JavaScript-string context

Your input lands inside a string in a script:

<script>var q = 'YOUR_INPUT';</script>

Tags do nothing inside a script. Instead you close the string and the statement, run your code, and comment out the rest:

';alert(1)//
';alert(1);//

If the app backslash-escapes your quote, break the escape first (\';alert(1)//), or close the whole script element and open a new one:

</script><script>alert(1)</script>

URL / href context

Your input becomes a URL the page navigates to or links to:

<a href="YOUR_INPUT">click</a>

No tags or quotes needed. Supply a scripting scheme:

javascript:alert(document.domain)

This fires when the link is followed (or immediately, for some sinks). It is also the DOM-XSS payload for sinks like location.href = and element.src =.

Encoded contexts and double-decoding

Sometimes your characters are encoded once but decoded again before the sink. If the server HTML-encodes your input but the page then feeds it through a second decode (or a JavaScript sink that unescapes it), a payload that looked neutralized comes back to life. When a single layer of encoding is in your way, test whether a second decode happens downstream and encode to match. This shades directly into filter and WAF evasion.

Picking the right break-out

The workflow every time:

  1. Inject a marker and find it in the response or the DOM.
  2. Read the characters immediately around it. Quotes? Angle brackets? A <script> block? An href?
  3. Choose the one escape that fits that context, and test only the characters you need.

Get this reflex solid and most of XSS becomes mechanical. The cheat sheet has the break-out for every context in one place. Drill it on a free live target.

← Back to Cross-Site Scripting (XSS): The Complete Practical Guide