Skip to content

CWE-112: Missing XML Validation

Overview

Missing XML validation occurs when applications parse XML without validating it against a defined schema (XSD, RELAX NG, or a DTD), so business logic acts on documents whose shape was never checked: missing required elements, extra elements that are silently ignored, wrong data types, oversized values, and out-of-range numbers. Entity-based attacks such as XXE and billion laughs are a separate problem with a separate fix - see CWE-611.

Relationship to Other CWEs

CWE-112 is ChildOf CWE-1286 (Improper Validation of Syntactic Correctness of Input) and CWE-20 (Improper Input Validation) - it is the general "the parser accepts XML it shouldn't" weakness. It is not formally linked by MITRE to CWE-611 (Improper Restriction of XML External Entity Reference), but the two frequently show up together in scanner findings and share a remediation step: disabling DTD/external-entity processing on the parser closes off CWE-611, while adding schema validation on top of that closes off CWE-112. A finding that only reports "XXE" is CWE-611's territory; a finding about structurally invalid, oversized, or unexpected XML being accepted is this page's.

OWASP Classification

A05:2025 - Injection

Risk

High: Without schema validation, an attacker controls the structure of the document your code reads, not just its values. Unexpected or omitted elements produce type confusion and null-handling failures downstream; an injected element the code reads without question (<role>admin</role>) becomes a logic bypass; oversized or deeply nested structures exhaust memory and CPU; and out-of-range values propagate into the database and any system fed from it.

What schema validation does not fix: entities are expanded while the document is parsed, before a validator ever sees the resulting infoset, so an XXE document is schema-valid and the file it read is already in the output. XXE and billion-laughs expansion are closed by parser configuration (CWE-611), not by the schema.

Remediation Steps

Core Principle: Never process untrusted XML without validating it against a strict, application-defined schema; reject any XML that does not conform exactly to the expected structure.

Define Strict XML Schema

Write an XSD (XML Schema Definition) that describes exactly what a valid document looks like:

  • Declare every allowed element, its type, and whether it is required or optional
  • Use XSD types (string, int, date and so on) to enforce data types
  • Set maxLength on strings so oversized values are rejected
  • Set minOccurs and maxOccurs to bound how many times an element can appear
  • Restrict attribute values with enumerations or patterns
  • Prevent unbounded nesting. XSD has no depth facet, so this is not a schema switch you turn on. Depth follows from the schema's own shape: a complex type that refers to itself, directly or through another type, is what permits arbitrary nesting in the first place. Where the data does not genuinely recurse, spell the levels out instead of defining a recursive type. Where it does, the bound has to come from the parser - see the element depth limit below

Configure Secure XML Parser

Turn on schema validation and turn off the parser features that XXE and entity-expansion attacks rely on:

  • Set the compiled XSD on the parser factory so every document is validated
  • Disable external entities: set disallow-doctype-decl to true, and disable external-general-entities and external-parameter-entities
  • Disable DTD processing entirely if DTDs are not needed
  • Limit how many times entities can be expanded, which prevents billion laughs
  • Set an element depth limit. The parser, not the schema, is where a recursive document gets bounded, and the default varies enough between runtimes that it is worth measuring rather than assuming. Java applies jdk.xml.maxElementDepth as one of the JAXP processing limits that are on by default; on a current JDK the default is 100, independent of FEATURE_SECURE_PROCESSING - measured on JDK 26, a 100-level document parses and a 101-level one is rejected with JAXP00010006 on a stock DocumentBuilderFactory with nothing configured. Set it explicitly anyway if your documents have a known shallower bound, either by that name or as http://www.oracle.com/xml/jaxp/properties/maxElementDepth, and check the value on the JDK you actually ship - these limits were introduced and tightened across releases. libxml2 caps element nesting at 256 by default and XML_PARSE_HUGE raises that to 2048, so leave that option off. .NET has no depth setting and no default depth limit - measured on .NET 10, a 50,000-level document parses without complaint - so XmlReaderSettings.MaxCharactersInDocument is the ceiling available to you, blunter but effective
  • Disable XInclude processing so a document cannot pull in external files
  • Keep the XML parser library patched

Validate With an XSD, Not a DTD

XSD validation and DOCTYPE rejection compose. The schema comes from the application, so the parser can refuse every <!DOCTYPE> in the incoming document and still validate it. DTD validation and DOCTYPE rejection do not compose: DTD validation requires the document's DOCTYPE declaration to be processed, and frequently an external DTD to be fetched, which is precisely the machinery the bullets above switch off. With disallow-doctype-decl set, a DTD-validated document is rejected outright before validation begins - the two controls are mutually exclusive, not alternatives.

The trap is that the obvious "turn validation on" switch enables DTD validation, not XSD:

  • Java: DocumentBuilderFactory.setValidating(true) is DTD validation. XSD validation is setSchema(compiledSchema) (or the schemaLanguage factory attribute), or a standalone javax.xml.validation.Validator. MITRE's own demonstrative example for CWE-112 is factory.setValidating(false), so a reader who "fixes" it by flipping that flag lands in the trap.
  • .NET: XmlReaderSettings.ValidationType = ValidationType.DTD only works with DtdProcessing = DtdProcessing.Parse. Use ValidationType.Schema with settings.Schemas.Add(...) and leave DtdProcessing at Prohibit.

If a finding really must be closed with a DTD, it has to be a local, application-supplied grammar attached by the parser configuration, with external-entity and external-DTD resolution disabled - never a DTD named by the incoming document.

Validate XML Against Schema Before Processing

Parse with validation enabled and reject anything that fails:

  • Attach the compiled schema to the parser configuration so every parse call is validated
  • Turn on namespace-aware parsing, which schema validation requires
  • Install an error handler that fails on any validation error rather than logging a warning and carrying on
  • When validation fails, return an error to the caller and do not process the document
  • Log validation failures so they are visible to security monitoring

Implement Business Logic Validation

A schema-valid document can still carry data the application should refuse. Once the structure has been checked:

  • Validate formats the schema left as plain strings, such as email addresses and URLs
  • Check that numeric values fall within the ranges the business allows
  • Check that referenced IDs exist and that relationships between records hold
  • Check that the user has permission to submit the data they sent
  • Validate enumerated fields such as status, country and category against a known-good list

Apply Defense in Depth

Schema validation, parser hardening and business-rule checks are separate layers; keep all three. Values read out of a valid document are still untrusted once they leave the parser:

  • Use prepared statements when XML values go into a database, so they cannot become SQL injection
  • Encode XML values when they are rendered in HTML, so they cannot become XSS
  • Alert on validation failures, malformed XML and XXE attempts

Test with Malicious XML Payloads

Confirm the parser rejects each of these:

  • Unexpected elements and missing required fields
  • An external entity payload such as <!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
  • Recursive entity expansion (billion laughs)
  • Extremely long strings and deeply nested structures
  • Malformed XML: invalid syntax, unclosed tags, encoding issues

Common Vulnerable Patterns

// VULNERABLE - pseudo-code
parser = create_xml_parser()
document = parser.parse(xml_input)     // no schema attached - any structure is accepted
users = document.find_elements("user")
process(users)
// Attack: attacker submits XML with extra/unexpected elements, oversized field
// values, deeply nested structures, or entity payloads - none of it is rejected
// because nothing defines what "valid" XML looks like for this document

Why this is vulnerable: Nothing tells the parser what a valid document looks like, so any structure, any element depth or count, and any data type is accepted and handed straight to the application logic.

Secure Patterns

// SECURE - pseudo-code
schema = load_schema("user-schema.xsd")   // elements, types, lengths, min/maxOccurs
parser = create_xml_parser()
parser.set_schema(schema)                 // reject anything that doesn't conform
parser.disable_doctype()                  // also closes off XXE (CWE-611)
parser.disable_external_entities()
parser.set_error_handler(fail_on_any_error)

function process_xml(xml_input):
    try:
        document = parser.parse(xml_input)
    catch SchemaValidationError as e:
        reject("XML does not conform to schema: " + e.message)
        return

    // Schema-valid XML can still violate business rules - check those too
    for user in document.find_elements("user"):
        if not is_valid_email(user.get("email")):
            reject("Invalid email format")
            return

    process(document)

Why this works: Schema validation enforces a strict XML structure (elements, types, lengths, cardinality) before any processing occurs, so only expected data is accepted. The parser is separately configured to disable DTDs and external entities, closing the XXE path regardless of what the schema allows. Business-logic validation adds a layer beyond structure, since a schema can't express rules like "email must be a real address" or "this ID must exist."

Additional Resources