Back to guides

GUIDE / security

XSS Testing: Finding Cross-Site Scripting

Learn XSS testing to find reflected, stored, and DOM XSS with payloads, encoding checks, CSP tests, and a practical QA security checklist now.

By The Testing AcademyPublished July 9, 2026Updated July 9, 202618 min read

XSS testing is one of the highest-value security skills a QA engineer can build. Cross-site scripting lets an attacker run script in a victim's browser under your application's origin. That can steal sessions, rewrite pages, force actions, phish users, or open deeper attacks. You do not need to be a full-time penetration tester to find many XSS bugs. You need a method, a map of input and output, careful payloads, and a habit of checking every rendering context.

This guide is a practical XSS testing playbook for QA. You will learn how to distinguish reflected, stored, and DOM-based XSS, how to probe forms and URLs safely, how to use XSS test payloads for input fields, how to verify output encoding, how to test Content Security Policy, and how to write defects developers can fix. You will also get a DOM-based XSS testing checklist, comparison tables, worked examples, and the common mistakes that hide real issues.

If you want tool support while you practice, pair this guide with the Burp Suite tutorial for beginners. For access control bugs that often sit next to XSS, read authentication and authorization testing.

What Is XSS and Why QA Should Care

Cross-site scripting (XSS) happens when untrusted data is included in a web page without safe handling, and the browser treats part of that data as code. The classic example is a comment field that stores <script>alert(1)</script> and later shows it to every reader as live script. Modern apps are more complex: single-page applications, JSON APIs, template engines, rich text editors, and client-side routers create many places where data can become code.

XSS matters to QA for three reasons:

  1. User impact is direct. Victims are real users, support agents, or admins viewing attacker-controlled content.
  2. It is often easy to find. Many XSS bugs appear during ordinary feature testing if you deliberately use special characters.
  3. It is easy to miss with happy-path testing. Teams that only type names and emails never trigger the dangerous paths.

XSS is not only about stealing cookies. Modern browsers may mark cookies HttpOnly, which reduces cookie theft. Attackers still use XSS for:

  • Session or token theft from JavaScript-accessible storage.
  • Forced actions as the logged-in user.
  • Defacement and fake UI overlays.
  • Keylogging or form harvesting on sensitive pages.
  • Pivoting into admin tools when privileged users view malicious content.

The Three XSS Types You Must Separate

TypeWhere the payload livesWho gets hitTypical entry pointsWhy it is dangerous
Reflected XSSIn the request, echoed in the immediate responseThe user who clicks a crafted link or submits a crafted formSearch boxes, error messages, query params, headersEasy to weaponize via phishing links
Stored XSSIn the database or other persistent storeOther users who later view the contentComments, profiles, tickets, chat, file namesScales to many victims without extra clicks each time
DOM XSSIn client-side JavaScript sources and sinksUsers whose browser executes the unsafe client codeURL fragments, query params read by JS, postMessage, localStorageCan bypass some server-side filters entirely

Keep these labels in your bug reports. Developers fix each type differently. Reflected issues often need response encoding on a specific page. Stored issues need encoding at render time plus safer storage rules for rich text. DOM issues need safer JavaScript APIs and source-to-sink hardening.

Reflected XSS in Plain Language

The application takes input from the request and reflects it into the response without safe encoding. Example flow:

  1. Attacker crafts https://app.example/search?q=<payload>.
  2. Victim opens the link.
  3. The search page shows: Results for <payload>.
  4. The browser executes the payload.

Stored XSS in Plain Language

The application saves untrusted content, then later renders it to users. Example flow:

  1. Attacker posts a support ticket subject with a payload.
  2. Support agent opens the ticket list or ticket detail page.
  3. The subject renders as HTML or script.
  4. The agent browser executes attacker code while authenticated as support.

Stored XSS is often higher severity because privileged staff are common victims.

DOM XSS in Plain Language

The server may never echo the payload as HTML. Client-side code does the damage:

// Dangerous pattern
const name = new URLSearchParams(location.search).get("name");
document.getElementById("welcome").innerHTML = "Hello " + name;

If name is <img src=x onerror=alert(1)>, the page still becomes executable without a classic server reflection.

XSS Testing Mindset Before You Fire Payloads

Good XSS testing is not random script spam. Use a structured approach.

Step 1: Get Authorization and Scope

Only test systems you are allowed to test. Prefer dedicated staging. Confirm:

  • Target hosts and environments.
  • Accounts and roles you may use.
  • Whether automated scanners are allowed.
  • How to report findings.
  • What not to touch (production users, payment rails, live PII).

Step 2: Map Inputs and Outputs

Build a simple inventory:

  • Query parameters and path parameters.
  • Form fields and file uploads.
  • Headers that are reflected (User-Agent, Referer, custom headers).
  • JSON fields in APIs that later appear in UI.
  • WebSocket or chat messages.
  • Error pages and validation messages.
  • Emails, PDFs, exports, and admin reports generated from user data.

For each input, ask: where does this value reappear? Immediately? Later? In another role's UI? In logs shown in an admin console?

Step 3: Identify Context

Encoding that is safe in one context can be unsafe in another.

ContextDangerous if untrusted data is placed asSafer direction
HTML bodyRaw HTML/scriptHTML entity encode
HTML attributeQuote breakout + event handlerAttribute encode, strict quoting
JavaScript stringQuote breakout into codeJS encode, avoid building JS from strings
URLjavascript: or dangerous schemeURL encode, allowlist schemes
CSSExpression-like or URL injectionAvoid untrusted CSS, strict allowlists
Rich textAllowed tags/attributes abusedSanitize with a maintained library

Your job as a tester is to discover the context, then choose payloads that fit that context.

Step 4: Prove Impact Safely

In staging, a simple alert('XSS') or a DOM marker is enough for many teams. Prefer:

  • Unique canary strings first (qabattle_xss_12345).
  • Then minimal proof payloads.
  • Then impact notes: which cookie or token is accessible, which role is affected, whether the payload persists.

Never use real malware, cryptominers, or data exfiltration to third-party sites you do not control.

How to Test for Cross-Site Scripting Step by Step

1. Start With Reflection Detection

Before complex payloads, inject a unique marker:

qabattle_marker_91827

Submit it in each field and parameter. Search the response and the rendered DOM for that exact string. If it appears, note:

  • Exact location in HTML.
  • Whether quotes or brackets are preserved.
  • Whether the value appears multiple times.
  • Whether it appears only for the submitter or for other users later.

Reflection alone is not XSS. Reflection plus a context that can become code is the path to a finding.

2. Escalate With Context-Aware Payloads

Use XSS test payloads for input fields that match what you observed.

HTML body context probes:

<script>alert('xss')</script>
"><script>alert('xss')</script>
<img src=x onerror=alert('xss')>
<svg onload=alert('xss')>

Attribute context probes:

" onmouseover="alert('xss')
' autofocus onfocus=alert('xss') x='

URL or href context probes:

javascript:alert('xss')
"><a href="javascript:alert(1)">x</a>

Template or framework escape probes (examples only):

{{constructor.constructor('alert(1)')()}}
${alert('xss')}

Not every payload will apply to every stack. The point is controlled escalation, not a giant unreviewed list pasted into production.

3. Test Stored Paths With Two Accounts

Stored XSS needs dual-role testing:

  1. Account A submits the payload in a profile bio, comment, or ticket.
  2. Account B (or admin) views the content.
  3. Observe whether B's browser executes the payload.
  4. Check list views, detail views, notifications, search indexes, and export screens.

Many teams only re-open the page as the same user and miss cross-user impact.

4. Run a DOM-Based XSS Testing Checklist

Use this checklist whenever client-side rendering is involved.

CheckWhat to doPass signal
Source mapList untrusted sources: location, document.referrer, postMessage, storage, editable DOMInventory exists
Sink mapSearch for innerHTML, outerHTML, document.write, eval, setTimeout(string), dangerous jQuery APIsInventory exists
URL fragment testsPut markers and payloads after #No execution, safe text only
Query param JS readsPayload in params consumed only by frontend codeEncoded or assigned to textContent
postMessageSend untrusted messages to open handlersOrigin checks and schema validation
Client templatesInject into client-rendered componentsNo HTML interpretation of untrusted strings
Framework bypassesTest v-html, dangerouslySetInnerHTML, raw filtersNot used for untrusted data
Third-party widgetsChat, markdown previews, analytics callbacksIsolated and sanitized

A practical DOM XSS workflow:

1. Open DevTools Sources and search for innerHTML, document.write, eval.
2. Note functions that read location.search or location.hash.
3. Place a marker in the URL and breakpoint on the sink.
4. Confirm whether your marker reaches the sink unencoded.
5. Replace the marker with a minimal event-handler payload.
6. Record the exact source, sink, and reproduction URL.

5. Test File Names, Metadata, and Secondary Channels

XSS is not only web forms:

  • Upload "><img src=x onerror=alert(1)>.png and view the file list.
  • Put payloads in image EXIF fields if the app displays metadata.
  • Use payloads in CSV imports that later render in HTML tables.
  • Check PDF or HTML exports generated from user content.
  • Inspect email templates that include user-provided names.

Secondary channels are common in admin tools and back-office portals.

6. Test Encoding and Filters, Not Just Blocks

If the app strips <script>, do not stop. Try:

  • Case variation: <ScRiPt>.
  • Nested tags if a filter is naive: <scr<script>ipt>.
  • Event handlers without script tags.
  • SVG, MathML, or uncommon tags.
  • Encoded forms: HTML entities, URL encoding, double encoding.
  • Unicode tricks only when relevant and authorized.

Your goal is not to bypass every WAF for sport. Your goal is to show whether application-level output handling is robust.

Output Encoding: How Testers Verify the Fix

When a developer says "we encoded the output," retest like a skeptic.

What Good Looks Like

If you enter <script>alert(1)</script> into a comment, the page source or DOM should show encoded content such as:

&lt;script&gt;alert(1)&lt;/script&gt;

The user may still see angle brackets as text. The browser must not execute script.

Verification Steps

  1. Replay the original payload exactly.
  2. Replay nearby variants (attribute breakouts, event handlers).
  3. Confirm all render locations: list, detail, notification bell, email, export.
  4. Check both HTML source and runtime DOM after client rendering.
  5. Confirm the fix did not break legitimate content (for example, math formulas or intentional markdown).

Context Mistakes After Fixes

A common false fix is encoding for HTML body while the same value is also injected into a JavaScript string:

<script>
  var search = "USER_INPUT";
</script>

If USER_INPUT is "; alert(1);//, HTML entity encoding may not be the right control for that context. Report the context precisely.

Content Security Policy Testing for XSS

Content Security Policy (CSP) is a browser-enforced allowlist for resources such as scripts. It can reduce XSS impact when implemented well. It can also give false confidence when implemented poorly.

What QA Should Check

CSP questionWhy it matters
Is a CSP header or meta policy present on sensitive pages?Missing CSP removes a useful safety net
Does the policy include unsafe-inline for scripts?Inline attacker scripts may still run
Are nonces or hashes used correctly?Broken nonces can break apps or weaken policy
Is unsafe-eval present?Expands the attack surface for DOM XSS
Are wildcards or overly broad CDNs allowed?Script injection via trusted hosts becomes more likely
Is there a report-only policy first?Helps teams adopt CSP without silent breakage
Do legitimate features still work?Over-strict CSP can break analytics, editors, or payment widgets

Practical CSP Test Cases

  1. Load key pages and inspect Content-Security-Policy and Content-Security-Policy-Report-Only.
  2. Confirm whether inline script from a reflected payload is blocked.
  3. Confirm the application still loads its own scripts.
  4. Trigger a deliberate violation in staging and check reporting if enabled.
  5. Note pages that inherit no policy because of inconsistent middleware.

CSP is defense in depth. A strong CSP does not excuse missing output encoding. Weak CSP does not mean the app is doomed if encoding is solid. Test both.

XSS Testing With Common QA Tools

You can find many issues with browser DevTools alone. For request control and payload iteration, use an intercepting proxy.

High-value Burp (or similar) workflows for XSS:

  • Intercept a form submit and inject payloads into fields the UI hides.
  • Use Repeater to iterate encodings quickly.
  • Compare raw response body with rendered browser behavior.
  • Test headers and cookies that the UI cannot edit.

See the full Burp Suite tutorial for proxy setup, Repeater, and safe Intruder use.

For API-driven UIs, also test the API contract path:

  1. Send payloads in JSON fields.
  2. Confirm storage.
  3. Confirm UI rendering for every consumer of that field.

That pairs well with API authentication testing when tokens and role-specific views change what content is shown.

Worked Example: Search Box Reflected XSS

Requirement sketch:

The storefront search page shows the user's query in a heading: "Results for {query}".

Test Design

IDCaseDataExpected secure behavior
XSS-S-01Marker reflectionqabattle_markerMarker may appear as text
XSS-S-02Script tag<script>alert(1)</script>No execution; encoded or stripped safely
XSS-S-03Image handler<img src=x onerror=alert(1)>No execution
XSS-S-04Attribute breakout if query also appears in value attributes" onfocus=alert(1) x="No execution
XSS-S-05Encoded payload%3Cscript%3Ealert(1)%3C/script%3EDecoded safely as text, not script
XSS-S-06Long payload2k-4k mixed meta charactersNo crash, no execution, stable UI

Reproduction Notes for a Finding

If XSS-S-03 fires:

URL: https://staging.shop.example/search?q=<img src=x onerror=alert(1)>
Role: anonymous and authenticated buyer
Browser: latest Chromium
Result: alert executes when search results render
Impact: reflected XSS in search results heading
Suggested fix direction: HTML-encode query when rendering; avoid innerHTML for this value

Write the defect with clear evidence. If you need a reusable bug format, use the guide on how to write a bug report.

Worked Example: Stored XSS in Support Tickets

  1. Log in as a standard user.
  2. Open "Create ticket".
  3. Set subject to <img src=x onerror=alert('stored')>.
  4. Submit.
  5. Log in as support agent.
  6. Open the queue and the ticket detail page.
  7. Observe execution on list, detail, or browser tab title if subject is reflected there too.

Also check:

  • Email notifications to agents.
  • Slack or webhook previews if integrated in staging.
  • Search highlighting of the subject.

Stored XSS in internal tools is frequently critical because agents have elevated data access.

Automation and Regression for XSS

You cannot automate every XSS idea, but you can regress known sinks.

Ideas for automation:

  • Contract tests that assert API responses encode or sanitize fields used in HTML.
  • UI tests that submit known safe canaries and assert textContent rather than executable nodes.
  • Security unit tests around sanitizer helpers.
  • CSP header presence checks in synthetic monitors.

Keep a small curated payload pack for staging pipelines. Do not dump huge attack lists into shared environments without rate and data controls.

Severity and Risk Rating for QA

Not all XSS is equal. When you file a bug, include:

  • Type: reflected, stored, DOM.
  • Auth requirement: none, user, admin victim.
  • Self-XSS only vs cross-user.
  • Session impact: HttpOnly cookies, token in localStorage, CSRF pairing.
  • Blast radius: one page vs site-wide template.
  • User interaction: click required vs automatic on load.

Example ratings (adapt to your program):

ScenarioTypical severity
Stored XSS in admin ticket subject, executes on openCritical / High
Reflected XSS on public search, no authHigh
DOM XSS in forgotten fragment handler on marketing pageMedium / High
Self-XSS only in a profile preview that nobody else seesLow / Informational, still fix

Self-XSS can still matter if social engineering is realistic, but be honest in severity.

Common Mistakes in XSS Testing

1. Only Testing the Happy Field

Teams test the comment body and ignore title, display name, avatar URL, custom status text, and file name. Attackers do not ignore those fields.

2. Stopping at <script> Filters

If <script> is blocked, many apps still allow event handlers or dangerous URLs. Continue with context-appropriate probes.

3. Ignoring Client-Side Rendering

A response that looks encoded in "View Source" can still become dangerous after JavaScript inserts it with innerHTML. Always verify the live DOM.

4. Single-Role Testing for Stored Issues

Submitting and viewing as the same user underestimates impact. Use a second role.

5. No Context in the Bug Report

"XSS in profile" is weak. Name the parameter, the page, the DOM sink, the browser, and the payload that proves execution.

6. Testing Only Chromium

Most findings reproduce broadly, but markup parsing can differ. Spot-check Safari and Firefox for important apps.

7. Confusing Encoding Bugs With Feature Requests

Rich text editors may intentionally allow some HTML. In that case, test the sanitizer allowlist: scripts, handlers, javascript: URLs, and nested exploits. The product decision may allow bold text while still forbidding active content.

8. Running Aggressive Scanners on Shared Staging

Noisy scanners can wipe data, lock accounts, or pollute analytics. Coordinate and scope them.

9. Forgetting Downstream Renderers

Exports, PDFs, notification emails, and mobile WebViews may render the same data differently from the main web app.

10. Treating CSP as a Full Fix

CSP helps. Output encoding and safe JS patterns remain mandatory.

XSS Testing Checklist You Can Reuse

Scope and authorization confirmed
Input inventory complete (UI, API, headers, files, secondary channels)
Output inventory complete (who sees the data, where, when)
Marker reflection tested per input
Context identified (HTML, attribute, JS, URL, CSS, rich text)
Reflected XSS probes completed
Stored XSS dual-account tests completed
DOM source/sink review completed
Encoding verification after fixes
CSP presence and strength reviewed
Bug reports include payload, URL, role, impact, evidence
Regression cases added for confirmed sinks

How XSS Fits a Broader Security Testing Habit

XSS rarely lives alone. While you test injection points, you will often notice:

  • Missing authorization on the endpoint that stores content.
  • Session cookies without secure flags.
  • Admin pages without CSRF protections.
  • Verbose error messages that aid attacks.

Stay focused enough to finish XSS coverage, but log adjacent risks. Access control deserves its own method; start with authentication and authorization testing.

If you want deliberate practice with real scenarios, join a security-focused challenge in QABattle battles or create an account to track progress across testing tracks.

What Good Looks Like on a Team

Mature teams treat XSS testing as a normal quality activity:

  • Every user-generated content feature has explicit security test cases.
  • Sanitizer and encoding helpers have unit tests.
  • CI checks for dangerous JS APIs in new code where feasible.
  • Security findings use the same defect workflow as functional bugs, with clear severity.
  • Developers and QA share a short internal wiki of approved proof payloads for staging.

You do not need a huge program to start. Begin with one high-risk surface: search, comments, tickets, or profile fields. Expand from there.

Final Takeaway

XSS testing rewards curiosity and structure. Map inputs and outputs, identify context, prove impact with safe payloads, verify encoding and CSP, and write precise reports. Separate reflected, stored, and DOM cases so fixes land in the right layer. Avoid payload spam without observation, and avoid trusting a single filter message as proof of safety.

If you practice this method on every feature that accepts free text, you will catch issues that pure functional testing never sees. That is the real goal: not collecting alerts, but protecting users and privileged operators from untrusted data that pretends to be code.

FAQ

Questions testers ask

How do you test for cross-site scripting (XSS)?

Map every place user input is accepted, stored, or rendered. Inject safe proof-of-concept payloads into forms, URLs, headers, and APIs, then check whether script or markup executes in the browser. Separate reflected, stored, and DOM XSS, and verify encoding and CSP after fixes land.

What is the difference between stored, reflected, and DOM XSS?

Reflected XSS echoes attacker input in an immediate response. Stored XSS saves the payload and later serves it to other users. DOM XSS never needs a server-side reflection: client-side JavaScript reads an untrusted source and writes it into a dangerous sink such as innerHTML.

How do you verify output encoding fixes XSS?

Re-run the original payload and nearby variants after the fix. Confirm the page shows encoded characters instead of executable markup, and that the browser does not fire script. Also check alternate contexts: HTML body, attributes, JavaScript strings, URLs, and CSS where encoding rules differ.

What are good XSS test payloads for input fields?

Start with simple probes such as a unique marker string, then escalate to harmless script tags, event handlers, SVG or image onerror attributes, and context-specific breakouts. Prefer proof payloads that alert or change DOM state only in staging. Never run destructive scripts against production.

How does Content Security Policy help with XSS?

CSP can block inline scripts, restrict script sources, and report violations. Testing CSP means checking policy presence, unsafe-inline usage, nonce or hash correctness, report endpoints, and whether legitimate app scripts still load. CSP is defense in depth, not a substitute for output encoding.

Is XSS testing safe for QA to do without a security team?

Yes, when limited to authorized staging or test environments with safe payloads and clear rules of engagement. Avoid production, avoid destructive payloads, and escalate complex findings to security engineers. Document steps so developers can reproduce and verify fixes.