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
maxLengthon strings so oversized values are rejected - Set
minOccursandmaxOccursto 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-declto true, and disableexternal-general-entitiesandexternal-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.maxElementDepthas one of the JAXP processing limits that are on by default; on a current JDK the default is 100, independent ofFEATURE_SECURE_PROCESSING- measured on JDK 26, a 100-level document parses and a 101-level one is rejected withJAXP00010006on a stockDocumentBuilderFactorywith nothing configured. Set it explicitly anyway if your documents have a known shallower bound, either by that name or ashttp://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 andXML_PARSE_HUGEraises 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 - soXmlReaderSettings.MaxCharactersInDocumentis 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 issetSchema(compiledSchema)(or theschemaLanguagefactory attribute), or a standalonejavax.xml.validation.Validator. MITRE's own demonstrative example for CWE-112 isfactory.setValidating(false), so a reader who "fixes" it by flipping that flag lands in the trap. - .NET:
XmlReaderSettings.ValidationType = ValidationType.DTDonly works withDtdProcessing = DtdProcessing.Parse. UseValidationType.Schemawithsettings.Schemas.Add(...)and leaveDtdProcessingatProhibit.
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."