Avoiding XXE vulnerabilities in .NET

Applications processing Extensible Markup Language (XML) input data from untrusted sources without proper validation or sanitization are vulnerable to XML external entity (XXE) attacks. An attacker using XXE can wreak havoc by inserting malicious XML code into a production system. XXE attacks can expose confidential information and cause adverse effects such as server-side request forgery (SSRF), remote code execution (RCE), and port scanning from the perspective of the parser’s host machine.

In this hands-on tutorial simulates an XXE attack on a .NET application and explore how to protect yourself from such attacks. To follow along, you’ll need Visual Studio installed in your development environment with .NET version 4.5.2+.

Identifying and patching XXE vulnerabilities in .NET applications

First, open Visual Studio. Select Create a new project to create a new C# project:

The Visual Studio 2022 panel Fig. 1: The Visual Studio 2022 panel

Create a C# console app and name it “XXEAttacks”:

Creating a C# console app in Visual Studio 2022 Fig. 2: Creating a C# console app in Visual Studio 2022

You’ll add your code to the XXEAttacks/program.cs file in the solution explorer.

The XXE vulnerability occurs when an application uses an XML parser to process user-input XML containing external entities. External entities are references to external resources, including paths to sensitive files, commands for remote code execution, and URLs to private websites and APIs. Failing to configure the XML parser or sanitize the input correctly means a malicious actor could inject a malicious external entity. This external entity could then read sensitive data or execute arbitrary code on the server.

For example, your e-commerce app allows users to update product reviews by submitting XML to the server:

<review product="<<PRODUCT_ID>>"> 
<comment>An amazing product</comment>
</review>

An attacker uploads the following XML:

<!DOCTYPE review [ <!ENTITY xxe SYSTEM "file:///path/to/file"> ]> 
<review product="<<PRODUCT_ID>>">
<comment>&xxe;</comment>
</review>

The above XML creates an internal variable called xxe that stores the local file’s contents. Then, it sets the product’s comment to the variable’s value. The product’s comment now contains the system’s passwords, effectively leaking them.

Vulnerable C# code

Malicious actors can use unvalidated user input in a vulnerable function that parses XML documents.

For example, this C# function uses an XML parser but isn’t configured to avoid XXE attacks:

async Task TestReader(String xml) 
{
XmlReaderSettings settings = new XmlReaderSettings();
settings.Async = true;
settings.DtdProcessing = DtdProcessing.Parse;
settings.XmlResolver = new XmlUrlResolver();
using (XmlReader reader = XmlReader.Create(new StringReader(xml), settings))
{
while (await reader.ReadAsync())
{
Console.WriteLine(reader.Value);
}
}
}

The TestReader function defined above parses XML documents and processes their Document-Type-Definition (DTD) without proper security configurations, making it vulnerable to XXE attacks.

To see what this looks like, create a file called secure.txt with the text string, “This is a secure file.” That’s your sensitive information. Save it on your development machine and note the absolute path. Add a sample XML string and pass it to the TestReader function:

string xml = @"<?xml version=""1.0"" encoding=""ISO-8859-1""?> 
<!DOCTYPE foo [ <!ENTITY xxe SYSTEM ""file:///C:/secure.txt""> ]>
<foo> &xxe;</foo>
";
await TestReader(xml);

Click the Run button in the Visual Studio toolbar. This opens the following console window:

The Microsoft Visual Studio Debug Console Fig. 3: The Microsoft Visual Studio Debug Console

The console outputs the XML file’s content and the external entity file’s contents. As illustrated, this reproduces the sensitive contents of secure.txt. This means a malicious actor has read the contents of sensitive security files on the host machine via an external entity.

Identify vulnerabilities in existing .NET code

As long as you’ve declared them in the document's DOCTYPE, the XML standard permits internal or external entities. However, when parsing an XML file, the system obtains data within external entities from external storage sources, like the file system or network. Unless you implement safeguards, this could expose arbitrary files or result in SSRF vulnerabilities. That’s where a URL to a back-end system defines an external entity.

You can manually review the XML parser code to identify vulnerabilities that may harm your application’s security. In .NET, you can use XmlDocument, XmlReader, or XmlSerializer to parse XML documents.

The XmlDocument class is usually unsafe when its XmlResolver property is assigned to an XmlUrlResolver, as shown below. Because XmlUrlResolver might allow the loading of unverified external entities, it’s better assigned to a null value:

XmlDocument parser = new XmlDocument(); 
parser.XmlResolver = new XmlUrlResolver();
parser.LoadXml("xxe.xml");

The XmlReader class is usually safe with its default settings. However, two conditions can make it unsafe:

  • Setting its XmlResolver property to anything other than null
  • Setting its DtdProcessing to Parse, allowing it to parse the DTD
XmlReaderSettings settings = new XmlReaderSettings(); 
settings.DtdProcessing = DtdProcessing.Parse;
settings.XmlResolver = new XmlUrlResolver();
XmlReader reader = XmlReader.Create("xxe.xml", settings);

Finally, the code may use the XmlSerializer class to deserialize the XML input. Instantiating the XML input without an XmlReader object can make your app vulnerable to XXE:

XmlSerializer s = new XmlSerializer(typeof(TextReader)); 
s.Deserialize(new FileStream("path/to/file", FileMode.Open));

Exploiting vulnerable .NET code

To identify a vulnerable .NET application, an attacker typically must only confirm that it parses XML data when inputs are submitted. Malicious actors can exploit applications by crafting malicious XML documents and exploiting a vulnerability in .NET code. This allows them to access sensitive data, execute arbitrary code on servers, or otherwise compromise the security of the application or system through SSRF attacks.

How do hackers expose sensitive information stored on files in the environment hosting the application? If a Linux server using .NET Core is hosting the application, malicious actors can gain access to system passwords through XML files. They do this via the SYSTEM identifier and by providing the path of the external file you want to load, as shown:

<?xml version="1.0" encoding="ISO-8859-1"?> 
<!DOCTYPE review [ <!ENTITY xxe SYSTEM "file:///etc/passwd"> ]>
<review>
<comment>&xxe;</comment>
</review>

The attacker can also target other sensitive files, like /etc/login.defs or /etc/shadow.

In a Windows environment, a malicious actor can use the following malicious XML payload. This checks the boot options of the computer hosting the application to compromise the system:

<?xml version="1.0" encoding="ISO-8859-1"?> 
<!DOCTYPE review [ <!ENTITY xxe SYSTEM "file:///c:/boot.ini"> ]>
<review>
<comment>&xxe;</comment>
</review>

Hackers may also access internal sites the production environment hosts, potentially exposing confidential information:

<?xml version="1.0" encoding="ISO-8859-1"?> 
<!DOCTYPE review [ <!ENTITY xxe SYSTEM "http://myinternalwebsite.com"> ]>
<review>
<comment>&xxe;</comment>
</review>

If the production environment runs on PHP and has the expect module installed, hackers could access Remote Code Execution (RCE). They could then remotely execute commands in the production environment, leading to system downtime:

<?xml version="1.0" encoding="ISO-8859-1"?> 
<!DOCTYPE review [ <!ENTITY xxe SYSTEM "expect://id"> ]>
<review>
<comment>&xxe;</comment>
</review>

The XML file above can trigger RCE using the expect PHP wrapper.

Patching vulnerable .NET code

To patch XXE vulnerabilities, you must ensure applications properly validate and sanitize all user-supplied XML input before processing. These precautions reduce the risk of XXE vulnerabilities and protect sensitive data from unauthorized access or manipulation.

Disabling external entities

To prevent entities from being parsed, you must assign a value of null to the XmlResolver, especially if you’re using XmlDocument to parse the XML files:

XmlDocument parser = new XmlDocument(); 
parser.XmlResolver = null; // Compliant
parser.LoadXml("xxe.xml");

You can also skip processing DTDs if you’re using XmlReader class to parse the XML files. You can do this via the XmlReaderSettings object used to instantiate the XmlReader class:

XmlReaderSettings settings = new XmlReaderSettings(); 
settings.DtdProcessing = DtdProcessing.Prohibit;
settings.XmlResolver = null;
XmlReader reader = XmlReader.Create("xxe.xml", settings)

When using the XmlSerializer class, instantiate the Deserializer using the safe XmlReader you created above:

XmlSerializer s = new XmlSerializer(typeof(TextReader)); 
s.Deserialize(reader);

Securing XML resolvers

The .NET framework provides the XmlSecureResolver class, which secures the XmlResolver object by restricting access to only external entities with approved URLs:

myResolver = new XmlSecureResolver(new XmlUrlResolver(), "http://www.example.com/"); 
settings = new XmlReaderSettings();
settings.XmlResolver = myResolver;

Validating XML schemas

In .NET, validating XML documents against a schema that describes their structure ensures the system only submits valid XML inputs to your app.

The example below displays a generated instance of XmlDocument. This includes an XML Schema Definition (XSD) schema associated with it through XmlReaderSettings and XmlReader objects:

static void ValidationEventHandler(object sender, ValidationEventArgs e) 
{
switch (e.Severity)
{
case XmlSeverityType.Error:
Console.WriteLine("Error: {0}", e.Message);
break;
case XmlSeverityType.Warning:
Console.WriteLine("Warning {0}", e.Message);
break;
}
}
XmlReaderSettings settings = new XmlReaderSettings();
settings.Schemas.Add("http://www.example.com/books", "example.xsd");
settings.ValidationType = ValidationType.Schema;

XmlReader reader = XmlReader.Create("example.xml", settings);
XmlDocument document = new XmlDocument();
document.Load(reader);
ValidationEventHandler eventHandler = new ValidationEventHandler(ValidationEventHandler);

document.Validate(eventHandler);

By employing the XPathNavigator class, a malicious actor can alter the typed value of an element in this XML document, causing a schema validation error called InvalidCastException. InvalidCastException is usually thrown if XML documents are modified by the XPathNavigator class. You’ll need to handle this error to maintain security.

Conclusion

This article reviewed XXE vulnerabilities in .NET applications, which can allow attackers to access sensitive data or execute arbitrary code on the server. The tutorial highlighted how to identify XXE vulnerabilities in a .NET application by paying attention to XmlReader, XmlSerializer, and XmlDocument classes. It also covered potentially malicious XML formats and how they can harm your application.

Then, it detailed how to avoid XXE vulnerabilities in .NET applications, such as configuring XML parsers to prohibit loading external entities. You now know how

Preventing XXE vulnerabilities is critical for ensuring the security of .NET applications. All developers must validate and sanitize user input and use secure parsing libraries to prevent XXE attacks.

Was this article helpful?

Related Articles

Write For Us

Write for Site24x7 is a special writing program that supports writers who create content for Site24x7 "Learn" portal. Get paid for your writing.

Write For Us

Write for Site24x7 is a special writing program that supports writers who create content for Site24x7 “Learn” portal. Get paid for your writing.

Apply Now
Write For Us