CWE-611: XML External Entity (XXE) Injection - C# / .NET
Overview
XXE vulnerabilities in .NET occur when XML parsers process external entity references in untrusted XML. While modern .NET versions have safer defaults, older code and misconfigured parsers remain vulnerable.
Primary Defence: Set DtdProcessing = DtdProcessing.Prohibit in XmlReaderSettings (.NET Framework 4.5.2+/.NET Core 2.0+), or use XDocument.Parse() (.NET Framework 4.5.2+) which is safe by default.
Common Vulnerable Patterns
XmlDocument with Default Settings
// VULNERABLE - XmlDocument allows DTDs by default (.NET Framework)
using System.Xml;
public void ParseXml(string xml)
{
var doc = new XmlDocument();
doc.LoadXml(xml); // DANGEROUS in .NET Framework!
// Attacker can read files:
// <?xml version="1.0"?>
// <!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///C:/Windows/win.ini">]>
// <root>&xxe;</root>
}
Why this is vulnerable:
- DTDs and external entities are processed by default in .NET Framework.
- Enables file disclosure, SSRF, and entity expansion DoS.
XmlTextReader Without ProhibitDtd
// VULNERABLE - XmlTextReader in .NET Framework
public void ReadXml(string xml)
{
using (var reader = new XmlTextReader(new StringReader(xml)))
{
// DTD processing enabled by default
while (reader.Read())
{
// Process XML
}
}
}
Why this is vulnerable:
- DTDs are enabled by default in .NET Framework.
- Enables file disclosure, SSRF, and DoS.
XmlReader with Unsafe Settings
// VULNERABLE - DtdProcessing.Parse allows DTDs
var settings = new XmlReaderSettings
{
DtdProcessing = DtdProcessing.Parse // DANGEROUS!
};
using (var reader = XmlReader.Create(new StringReader(xml), settings))
{
// Can process external entities
}
Why this is vulnerable:
- Explicitly enables DTD and external entity processing.
- Reintroduces file disclosure and SSRF risk.
DataContractSerializer with Unsafe Resolver
// VULNERABLE - Custom XmlResolver
var serializer = new DataContractSerializer(typeof(User));
var settings = new XmlReaderSettings
{
XmlResolver = new XmlUrlResolver() // Allows external resources!
};
using (var reader = XmlReader.Create(new StringReader(xml), settings))
{
var user = (User)serializer.ReadObject(reader);
}
Why this is vulnerable:
- XmlUrlResolver allows external resource resolution.
- Enables file disclosure and SSRF through XML payloads.
Secure Patterns
XmlDocument with Secure Settings (.NET Core/5+)
// SECURE - .NET Core 3.0+ has safe defaults
using System.Xml;
public void ParseXmlSecure(string xml)
{
var doc = new XmlDocument();
// .NET Core defaults: DTD processing disabled
// But explicitly set for clarity and backwards compatibility
doc.XmlResolver = null; // Disable external entity resolution
doc.LoadXml(xml);
// Process safely
var name = doc.SelectSingleNode("//name")?.InnerText;
}
Why this works:
XmlResolver = nullblocks external entity resolution.- Explicit setting makes intent clear and preserves .NET Framework safety.
XmlReader with DtdProcessing.Prohibit
// SECURE - Explicitly prohibit DTDs
using System.Xml;
public void ReadXmlSecure(string xml)
{
var settings = new XmlReaderSettings
{
DtdProcessing = DtdProcessing.Prohibit, // Reject DTDs entirely
XmlResolver = null, // No external entity resolution
MaxCharactersFromEntities = 0 // No entity expansion
};
using (var reader = XmlReader.Create(new StringReader(xml), settings))
{
while (reader.Read())
{
if (reader.NodeType == XmlNodeType.Element && reader.Name == "name")
{
string name = reader.ReadElementContentAsString();
// Process safely
}
}
}
}
Why this works:
DtdProcessing.Prohibitrejects DOCTYPE outright.XmlResolver = nullblocks external fetches even if misconfigured.MaxCharactersFromEntities = 0prevents entity expansion DoS.
XDocument with Secure Settings
// SECURE - XDocument with safe XmlReader
using System.Xml.Linq;
using System.Xml;
public XDocument ParseXmlToXDocument(string xml)
{
var settings = new XmlReaderSettings
{
DtdProcessing = DtdProcessing.Prohibit,
XmlResolver = null
};
using (var reader = XmlReader.Create(new StringReader(xml), settings))
{
return XDocument.Load(reader);
}
}
// Usage:
var doc = ParseXmlToXDocument(untrustedXml);
var name = doc.Root?.Element("name")?.Value;
Why this works:
- XDocument inherits the secure XmlReader settings.
- Prohibiting DTDs and nulling XmlResolver prevents XXE.
XmlSerializer with Secure Reader
// SECURE - XmlSerializer with safe XmlReader
using System.Xml.Serialization;
public T DeserializeXml<T>(string xml)
{
var serializer = new XmlSerializer(typeof(T));
var settings = new XmlReaderSettings
{
DtdProcessing = DtdProcessing.Prohibit,
XmlResolver = null,
MaxCharactersFromEntities = 0
};
using (var reader = XmlReader.Create(new StringReader(xml), settings))
{
return (T)serializer.Deserialize(reader);
}
}
// Usage:
var user = DeserializeXml<User>(xmlString);
Why this works:
- A hardened XmlReader prevents DTDs and external entities.
- Works for any type without relying on default reader settings.
Framework-Specific Guidance
ASP.NET Core
// SECURE - ASP.NET Core API endpoint
using Microsoft.AspNetCore.Mvc;
using System.Xml;
[ApiController]
[Route("api/[controller]")]
public class DataController : ControllerBase
{
[HttpPost]
[Consumes("application/xml")]
public IActionResult ProcessXml([FromBody] string xml)
{
try
{
var settings = new XmlReaderSettings
{
DtdProcessing = DtdProcessing.Prohibit,
XmlResolver = null,
MaxCharactersFromEntities = 0
};
using (var reader = XmlReader.Create(new StringReader(xml), settings))
{
var doc = new XmlDocument { XmlResolver = null };
doc.Load(reader);
// Process XML
var result = ProcessDocument(doc);
return Ok(result);
}
}
catch (XmlException ex)
{
return BadRequest($"Invalid XML: {ex.Message}");
}
}
}
// Configure model binding for XML (Startup.cs / Program.cs)
builder.Services.AddControllers()
.AddXmlSerializerFormatters(options =>
{
options.XmlReaderSettings = new XmlReaderSettings
{
DtdProcessing = DtdProcessing.Prohibit,
XmlResolver = null
};
});
WCF Services
// SECURE - WCF with DataContractSerializer
using System.ServiceModel;
using System.Runtime.Serialization;
[ServiceContract]
public interface IUserService
{
[OperationContract]
User GetUser(int id);
}
[DataContract]
public class User
{
[DataMember]
public string Name { get; set; }
[DataMember]
public string Email { get; set; }
}
// web.config - Secure XML reader quotas
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="SecureBinding">
<readerQuotas maxDepth="32"
maxStringContentLength="8192"
maxArrayLength="16384"
maxBytesPerRead="4096"
maxNameTableCharCount="16384" />
<security mode="Transport" />
</binding>
</basicHttpBinding>
</bindings>
</system.serviceModel>
XML Configuration Files
// SECURE - Reading app configuration XML
using System.Configuration;
public class ConfigManager
{
public static string GetSetting(string key)
{
// ConfigurationManager uses secure defaults in .NET Core
return ConfigurationManager.AppSettings[key];
}
// For custom XML config files:
public static void LoadCustomConfig(string configPath)
{
var settings = new XmlReaderSettings
{
DtdProcessing = DtdProcessing.Prohibit,
XmlResolver = null
};
using (var reader = XmlReader.Create(configPath, settings))
{
var doc = new XmlDocument { XmlResolver = null };
doc.Load(reader);
// Process configuration
}
}
}
Reusable Secure XML Utility
// Utility class for secure XML operations
public static class SecureXmlHelper
{
/// <summary>
/// Creates secure XmlReaderSettings
/// </summary>
public static XmlReaderSettings GetSecureSettings()
{
return new XmlReaderSettings
{
DtdProcessing = DtdProcessing.Prohibit,
XmlResolver = null,
MaxCharactersFromEntities = 0,
MaxCharactersInDocument = 10000000 // 10MB limit
};
}
/// <summary>
/// Parse XML string to XmlDocument securely
/// </summary>
public static XmlDocument ParseXml(string xml)
{
var settings = GetSecureSettings();
using (var reader = XmlReader.Create(new StringReader(xml), settings))
{
var doc = new XmlDocument { XmlResolver = null };
doc.Load(reader);
return doc;
}
}
/// <summary>
/// Parse XML string to XDocument securely
/// </summary>
public static XDocument ParseToXDocument(string xml)
{
var settings = GetSecureSettings();
using (var reader = XmlReader.Create(new StringReader(xml), settings))
{
return XDocument.Load(reader);
}
}
/// <summary>
/// Deserialize XML to object securely
/// </summary>
public static T Deserialize<T>(string xml)
{
var serializer = new XmlSerializer(typeof(T));
var settings = GetSecureSettings();
using (var reader = XmlReader.Create(new StringReader(xml), settings))
{
return (T)serializer.Deserialize(reader);
}
}
}
// Usage:
var doc = SecureXmlHelper.ParseXml(untrustedXml);
var xdoc = SecureXmlHelper.ParseToXDocument(untrustedXml);
var user = SecureXmlHelper.Deserialize<User>(xmlString);
Input Validation
// Validate XML content after parsing
using System.Xml.Schema;
public class XmlValidator
{
public static bool ValidateAgainstSchema(string xml, string xsdPath)
{
var settings = new XmlReaderSettings
{
DtdProcessing = DtdProcessing.Prohibit,
XmlResolver = null,
ValidationType = ValidationType.Schema
};
// Load schema
settings.Schemas.Add(null, xsdPath);
settings.ValidationEventHandler += (sender, args) =>
{
throw new XmlSchemaValidationException(args.Message);
};
using (var reader = XmlReader.Create(new StringReader(xml), settings))
{
while (reader.Read()) { }
return true;
}
}
public static void ValidateContent(XmlDocument doc)
{
// Validate expected structure
var root = doc.DocumentElement;
if (root == null || root.Name != "user")
{
throw new InvalidOperationException("Invalid XML structure");
}
var name = root.SelectSingleNode("name")?.InnerText;
if (string.IsNullOrWhiteSpace(name) || name.Length > 100)
{
throw new InvalidOperationException("Invalid name");
}
var email = root.SelectSingleNode("email")?.InnerText;
if (string.IsNullOrWhiteSpace(email) || !email.Contains("@"))
{
throw new InvalidOperationException("Invalid email");
}
}
}
Verification
After implementing the recommended secure patterns, verify the fix through multiple approaches:
- Manual testing: Submit malicious payloads relevant to this vulnerability and confirm they're handled safely without executing unintended operations
- Code review: Confirm all instances use the secure pattern (parameterized queries, safe APIs, proper encoding) with no string concatenation or unsafe operations
- Static analysis: Use security scanners to verify no new vulnerabilities exist and the original finding is resolved
- Regression testing: Ensure legitimate user inputs and application workflows continue to function correctly
- Edge case validation: Test with special characters, boundary conditions, and unusual inputs to verify proper handling
- Framework verification: If using a framework or library, confirm the recommended APIs are used correctly according to documentation
- Authentication/session testing: Verify security controls remain effective and cannot be bypassed (if applicable to the vulnerability type)
- Rescan: Run the security scanner again to confirm the finding is resolved and no new issues were introduced
.NET Version Differences
// .NET Framework 4.5 and earlier: UNSAFE defaults
// - DTD processing enabled
// - External entities allowed
// .NET Framework 4.5.2+: Safer with XmlReaderSettings.ProhibitDtd
// - But still need explicit configuration
// .NET Core 3.0+ / .NET 5+: SAFE defaults
// - DTD processing disabled by default
// - XmlResolver is null by default
// - Still good practice to explicitly set for clarity
#if NETFRAMEWORK
// Extra caution needed for .NET Framework
var settings = new XmlReaderSettings
{
ProhibitDtd = true, // Old API
XmlResolver = null
};
#else
// .NET Core / .NET 5+
var settings = new XmlReaderSettings
{
DtdProcessing = DtdProcessing.Prohibit, // Modern API
XmlResolver = null
};
#endif