Skip to content

Context first: the part of XSS testing people still rush

Payload choice is less important than injection context. HTML body, attributes, URLs, scripts, and DOM sinks all fail differently.

Published on 3 min read

Most bad XSS testing starts with a payload list.

That sounds useful until the target reflects your input inside a quoted attribute, or inside a JavaScript string, or after a sanitizer has moved half the markup around. Then the list becomes noise. You paste twenty strings, the app blocks nineteen, one reflects as text, and nobody learned much.

Start with the context.

The same payload is not the same test

This input:

"><svg onload=alert(1)>

means one thing in an HTML attribute. It means something else in a JSON response. It means almost nothing if the app inserts the value through textContent. A payload is not just characters. It is a hypothesis about how the browser will parse those characters after the application, framework, sanitizer, and browser have all touched them.

That is why a builder should make context visible. HTML body. Attribute. Script block. URL. DOM sink. These categories are imperfect, but they force the operator to stop guessing.

Attribute context has annoying edges

Attribute injection is where a lot of tests get lazy. People throw onerror=alert(1) at everything and move on.

But the details matter. Is the attribute quoted? Single or double quote? Does the framework HTML-escape quotes but not backticks? Is the value inside href, src, style, data-*, or an event handler already? Does the app strip spaces but allow tabs or newlines?

This is a real difference:

<input value="USER_INPUT">
<a href="USER_INPUT">
<div data-name="USER_INPUT">

The escape strategy is different for each. In the href case, javascript: may be relevant, but only if URL sanitization and browser rules permit it. In data-name, execution may require a second bug: JavaScript reading the attribute and assigning it to an unsafe sink.

DOM context needs dataflow, not vibes

DOM XSS is worse because the vulnerable rendering step may not be near the source. The input comes from location.hash, postMessage, query parameters, local storage, or a JSON response. Later, a component writes it into innerHTML, insertAdjacentHTML, document.write, or a framework escape hatch.

Testing that manually is painful. You need to track the path.

const q = new URLSearchParams(location.search).get("q");
preview.innerHTML = q;

That is simple. Production code usually is not.

A practical workflow

Paste surrounding markup. Mark the injection point. Identify the parse context before selecting a payload. Then apply mutations and encodings only when they match a real transformation in the target.

This is slower than spraying payloads.

It also produces better findings. A report that says "the value escapes a double-quoted attribute and reaches an event handler" gives a developer something to fix. A report that says "XSS possible with payload from list" does not.

Related articles

A useful XSS report explains context, execution path, impact, and remediation without dumping a payload and hoping the team understands it.
URL encoding, HTML entities, Base64, and JavaScript escaping change meaning depending on order. This article explains the operational mistakes.
Responsible XSS testing depends on scope, evidence discipline, data minimization, and knowing when a proof of concept has gone far enough.