Skip to main content

Command Palette

Search for a command to run...

How to Protect ASP.NET Core APIs Against XXE Injection

Updated
โ€ข12 min read
How to Protect ASP.NET Core APIs Against XXE Injection

XML External Entity (XXE) injection remains one of the more dangerous vulnerabilities in ASP.NET Core APIs that accept or process XML โ€” and it is frequently misunderstood. Most .NET developers assume their XML parsers are safe by default, but that assumption is only partially true. The safety of your XML processing depends entirely on which parser class you use, how you configure it, and whether you have applied defence-in-depth controls around it. The gap between "safe parser selected" and "fully protected API" is wide enough for an attacker to walk through. If you want to go beyond parser settings and see how XXE prevention fits into a production-ready, security-hardened API, the complete implementation โ€” including input validation middleware, XML policy enforcement, and integration tests โ€” is available on Patreon, alongside the source code that enterprise teams actually ship.

XXE attacks exploit the XML standard's support for external entity references โ€” a feature that allows an XML document to pull in content from external URLs or local file paths. When an attacker controls the XML input and the parser resolves those references, they can exfiltrate local files (including /etc/passwd, appsettings.json, or secrets files), probe internal network services, or trigger denial-of-service conditions through recursive entity expansion (the "Billion Laughs" attack). This vulnerability consistently appears on the OWASP Top 10, and its impact in cloud-hosted APIs is amplified: a compromised instance metadata service URL (like AWS's http://169.254.169.254) can expose IAM credentials and lead to full account compromise. Understanding ASP.NET Core global error handling is also important here โ€” ensuring your error responses never leak parser internals when an XXE attempt is detected, a topic covered in depth in this article on global exception handling patterns.

The Threat Landscape: What XXE Injection Actually Enables

To understand why XXE demands serious attention, consider what an attacker can accomplish when they control an XML payload processed by a misconfigured parser.

File exfiltration is the most well-known attack vector. An XML document with an external entity that references file:///C:/inetpub/wwwroot/appsettings.json will cause a vulnerable parser to read that file and embed its contents in the response โ€” including connection strings, API keys, and environment variables.

Server-side request forgery via XML is less commonly discussed but equally dangerous. If the entity references an internal URL โ€” such as the Kubernetes API server or an internal microservice not exposed to the internet โ€” the parser becomes an unwitting HTTP client, pivoting the attacker into your internal network.

Denial of service through entity recursion (the Billion Laughs attack) uses deeply nested entity references to exponentially expand memory consumption. A 1 KB XML payload can expand to gigabytes of in-memory content, crashing your API pod in seconds.

Each of these attack variants shares a common root cause: the parser resolves external references when it should not.

How .NET XML Parsers Behave by Default

The ASP.NET Core ecosystem exposes several XML processing classes, and their default security posture varies significantly.

XmlReader created via XmlReader.Create() with .NET Core and later is safe by default โ€” external entity resolution is disabled and DTD processing is prohibited unless you explicitly configure XmlReaderSettings. This is the correct baseline, but it is not a guarantee. Developers who copy legacy code or use third-party libraries that wrap XmlReader with permissive settings can inadvertently re-enable the vulnerability.

XmlDocument has a more complicated history. In .NET Framework versions prior to 4.5.2, it was vulnerable by default. In .NET Core and later versions, it resolves to a safe default โ€” but only when no custom XmlResolver is assigned. Assigning xmlDocument.XmlResolver = new XmlUrlResolver() silently restores the dangerous behaviour.

XDocument and LINQ to XML are safe from XXE injection by default in modern .NET because they use XmlReader internally with safe defaults. However, they are not immune to Billion Laughs attacks if you process very large XML inputs without setting size limits.

XmlSerializer and DataContractSerializer โ€” used by ASP.NET Core's XML formatters โ€” are also safe from classic XXE in modern .NET, but their safe status depends on the underlying XmlReader settings being preserved end-to-end through the formatter pipeline.

The pattern that creates real-world vulnerabilities is not ignorance of these classes โ€” it is the assumption that "we use XmlReader so we're fine" without verifying what settings are active in the exact code path that processes attacker-controlled input.

What a Vulnerable Pattern Looks Like

The most common XXE vulnerability in ASP.NET Core APIs arises from legacy code migrated from .NET Framework, or from developers configuring XML readers for convenience without understanding the security implications.

var settings = new XmlReaderSettings
{
    DtdProcessing = DtdProcessing.Parse, // โš ๏ธ enables DTD โ€” dangerous
    XmlResolver = new XmlUrlResolver()   // โš ๏ธ resolves external entities โ€” dangerous
};

Both of those settings together create a textbook XXE condition. Individually, either one can be enough to introduce risk depending on the attack vector.

The secure baseline is explicit and deliberate:

var settings = new XmlReaderSettings
{
    DtdProcessing = DtdProcessing.Prohibit,
    XmlResolver = null,
    MaxCharactersFromEntities = 1024,
    MaxCharactersInDocument = 1_000_000
};

DtdProcessing.Prohibit throws an exception if the input document contains a DTD declaration โ€” which is exactly the right behaviour for an API that has no legitimate use for DTDs. MaxCharactersFromEntities and MaxCharactersInDocument are your defence against Billion Laughs attacks, and they are frequently omitted even in security-conscious codebases.

Securing the ASP.NET Core XML Input Formatter

If your API accepts application/xml requests, the built-in XML input formatter is your first line of defence โ€” and it deserves explicit configuration.

By default, ASP.NET Core's XmlSerializerInputFormatter and XmlDataContractSerializerInputFormatter use safe reader settings in modern .NET. But you should verify this explicitly in your Program.cs, particularly when upgrading from older framework versions or when using third-party formatter packages.

builder.Services.AddControllers(options =>
{
    // Explicitly replace the XML formatter with a hardened configuration
    var xmlFormatter = options.InputFormatters
        .OfType<XmlSerializerInputFormatter>()
        .FirstOrDefault();

    if (xmlFormatter != null)
    {
        xmlFormatter.XmlDictionaryReaderQuotas.MaxStringContentLength = 1024 * 100; // 100 KB limit
    }
});

Explicitly setting content length limits prevents an attacker from sending an oversized XML payload designed to exhaust memory before the parser even begins processing entities.

Content-Type Validation: Reject Unexpected XML

A significant proportion of XXE vulnerabilities in ASP.NET Core APIs occur not through the official XML content type, but through unexpected XML injection into endpoints that were designed to accept JSON โ€” and occasionally do accept XML if the client provides the right Content-Type header and the API has broad formatter support configured.

If your API does not need to accept XML, configure it explicitly:

builder.Services.AddControllers()
    .AddJsonOptions(options => { /* your JSON settings */ });
// Do NOT call .AddXmlSerializerFormatters() or .AddXmlDataContractSerializerFormatters()
// unless XML processing is a documented, reviewed requirement

When XML support is genuinely needed, scope it to the specific endpoints that require it rather than enabling it globally. The attack surface reduction is immediate and significant.

Defence-in-Depth: Beyond Parser Configuration

Parser hardening is necessary but not sufficient. A comprehensive defence requires additional layers.

Input size limits at the middleware level should be enforced before the request even reaches your XML parser. ASP.NET Core's built-in request size limits apply at the Kestrel and IIS integration layer:

builder.WebHost.ConfigureKestrel(options =>
{
    options.Limits.MaxRequestBodySize = 1_048_576; // 1 MB maximum
});

This ensures that even a crafted XXE payload designed to trigger Billion Laughs is bounded before any XML parsing begins.

Schema validation is a more advanced control that is appropriate when your API accepts XML from external partners or B2B integrations. Validating incoming XML against a strict XSD schema before any further processing ensures that documents with unexpected DTD declarations, entity references, or structural anomalies are rejected early โ€” before they reach business logic code.

Structured logging of XML parsing failures is critical for detection. When XmlException is thrown due to a prohibited DTD or an unexpected entity reference, that event should be logged at Warning level with the request path, client IP, and relevant request metadata โ€” but never the raw payload, which may itself contain sensitive data in an exfiltration attempt. Integrating this with your existing audit logging strategy gives security teams the visibility needed to detect XXE probing in real time.

Dependency scanning should be part of your CI/CD pipeline. Third-party NuGet packages that process XML โ€” report generators, document processors, serialisation libraries โ€” may use older XML APIs with permissive defaults. Tools like dotnet list package --vulnerable and SNYK integration catch these before they reach production.

When Your API Must Process External XML Sources

Some enterprise APIs legitimately need to process XML documents fetched from external URLs โ€” webhook payloads, EDI documents, SOAP responses from legacy systems. These scenarios require additional controls.

The critical rule is: never use the URL from the XML document itself to fetch content. The external source of XML and the resolution of references within that XML must be completely separated. Fetch the XML document using your own validated, allowlisted HTTP client. Then parse the fetched content with a fully locked-down XmlReader that resolves nothing.

For SOAP-based integrations, use WCF client with explicit security bindings, or wrap the HTTP communication yourself and feed the response to a secure parser. Never allow the XML content to control where the parser makes outbound connections.

The Anti-Patterns to Eliminate

Security audits of .NET codebases consistently surface the same patterns that introduce XXE vulnerabilities:

Copying parser code without reviewing settings. A developer copies a Stack Overflow snippet that uses DtdProcessing.Parse because the original question needed DTD support. The context is lost; the risk remains.

Re-enabling XmlResolver to fix a NullReferenceException. When a parser is configured with XmlResolver = null and code later throws because it tries to resolve something, the instinctive fix is to re-enable the resolver โ€” which re-opens the vulnerability.

Trusting that HTTPS prevents XXE. Transport encryption has nothing to do with what the parser does with the content. An HTTPS connection to your API does not prevent an authenticated attacker from submitting a malicious XML payload.

Assuming third-party libraries are safe. Libraries that wrap XML processing โ€” particularly older NuGet packages written against .NET Framework โ€” may use permissive XML reader defaults internally and expose no way to override them. When a library processes XML that comes from user input, its internal parser configuration matters just as much as your own.

โ˜• If this kind of security-first thinking resonates with you, buy us a coffee โ€” every bit helps keep the content coming!

FAQ

What is XXE injection in ASP.NET Core APIs?

XXE (XML External Entity) injection is a vulnerability that occurs when an XML parser processes user-controlled XML input that contains external entity references. In ASP.NET Core APIs, this typically happens when an endpoint accepts application/xml requests and the XML parser is configured to resolve external entities or process DTDs. A successful attack can lead to local file read, SSRF (server-side request forgery through XML), or denial of service through recursive entity expansion.

Is XmlReader in .NET 10 safe from XXE by default?

Yes โ€” XmlReader.Create() in modern .NET (Core and later) defaults to DtdProcessing.Prohibit and XmlResolver = null, making it safe from classic XXE injection. However, this safe default can be overridden by developer configuration. Always verify your XmlReaderSettings explicitly, particularly in code that has been migrated from .NET Framework or copied from older examples.

How do I prevent XXE in ASP.NET Core XML formatters?

Ensure that XmlSerializerInputFormatter and XmlDataContractSerializerInputFormatter are either not registered (if XML is not needed) or explicitly configured with safe reader settings. Additionally, enforce request body size limits at the Kestrel level to bound the maximum size of any XML payload before it reaches the formatter.

What is the Billion Laughs attack and how do I defend against it in .NET?

The Billion Laughs attack uses deeply nested XML entity references to cause exponential memory expansion. A small XML payload โ€” sometimes under 1 KB โ€” can expand to gigabytes of content when parsed, crashing your application. The defence in .NET is to set MaxCharactersFromEntities and MaxCharactersInDocument on your XmlReaderSettings, combined with enforcing a request body size limit at the Kestrel layer before parsing begins.

Should I disable XML support entirely if my API primarily uses JSON?

Yes, if your API endpoints have no documented requirement to accept XML, do not register XML input formatters. Not calling AddXmlSerializerFormatters() or AddXmlDataContractSerializerFormatters() in your Program.cs is the simplest and most effective way to eliminate XXE attack surface for JSON-first APIs. XML support should be an explicit, reviewed addition โ€” not an accidental default.

How does XXE relate to SSRF in ASP.NET Core?

XXE can be used to trigger SSRF by embedding an external entity that references an internal URL โ€” such as http://169.254.169.254/latest/meta-data/ on AWS or an internal Kubernetes API endpoint. The XML parser makes the outbound HTTP request as part of entity resolution, effectively turning the parser into a proxy for internal network access. Disabling external entity resolution (via XmlResolver = null) prevents this class of attack.

How should I log XXE attempt detection in ASP.NET Core?

When an XmlException is thrown due to a prohibited DTD declaration or entity reference, log it at Warning level using structured logging. Include the request path, HTTP method, client IP (from HttpContext.Connection.RemoteIpAddress), and a sanitised indicator of the exception type โ€” but never log the raw XML payload, which may contain data the attacker was trying to extract or sensitive business content.

More from this blog

C

Coding Droplets

235 posts

Coding Droplets is your go-to resource for .NET and ASP.NET Core development. Whether you're just starting out or building production systems, you'll find practical guides, real-world patterns, and clear explanations that actually make sense.

From beginner-friendly tutorials to advanced architecture decisions. We publish fresh .NET content every day to help you grow at every stage of your career.