How to protect against XML vulnerabilities in .NET

  • .NET
  • Security

This post is part of the series 'Vulnerabilities'. Be sure to check out the rest of the blog posts of the series!

Lots of applications use XML files. For instance, this blog has RSS and Atom feeds that are XML documents. Application can communicate using SOAP. You can use XML serialization. But most the applications use only a subset of the XML features. When you start exploring the rest of the specification, you can find some possible vulnerabilities when dealing with XML documents! Let's explore some of them and see how to prevent them in a .NET application.

#Deny of service (DOS) using custom XML entities

There are well-known entities in XML such as &lt; witch is translated to <. But, you can also define custom entities:

<?xml version="1.0" ?>
<!DOCTYPE samples [
  <!ENTITY name "value">
]>
<test>&name;</test> <!-- Evaluated to <test>value</test> -->

But you can also define entities that evaluate recursively:

<?xml version="1.0" ?>
<!DOCTYPE samples [
  <!ENTITY name "value">
  <!ENTITY name2 "&name; &name;">
]>
<test>&name2;</test> <!-- Evaluated to <test>value value</test> -->

Now, let's consider this small document (< 1kB), also known as the billion laughs attack. When evaluating this document, you'll need about 3GB of memory to store the expanded string. Indeed, &lol9; expands to a string composed of 1 billion "lol".

<?xml version="1.0"?>
<!DOCTYPE lolz [
  <!ENTITY lol "lol">
  <!ENTITY lol2 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;">
  <!ENTITY lol3 "&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;">
  <!ENTITY lol4 "&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;">
  <!ENTITY lol5 "&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;">
  <!ENTITY lol6 "&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;">
  <!ENTITY lol7 "&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;">
  <!ENTITY lol8 "&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;">
  <!ENTITY lol9 "&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;">
]>
<lolz>&lol9;</lolz>

Another variation of the billion laughs attack that does work is the Quadratic Blowup attack. While not as effective as the previous attack, it does defeat parsers that don't allow recursive expansion. Instead of defining multiple small, deeply nested entities, it defines one very large entity and refers to it many times. A document of about 200kB can expand up to 2.5GB.

<?xml version="1.0"?>
<!DOCTYPE QuadraticBlowup [
  <!ENTITY a "aaaaaaaaaaaaaaaaaa...">
]>
<QuadraticBlowup>&a;&a;&a;&a;&a;&a;&a;&a;&a;...</QuadraticBlowup>

#Information disclosure with custom XML entities

Entity expansion also allows you to access local or remote data. An attacker could use this to get the content of a local file or an internal URL.

<!DOCTYPE doc [
    <!ENTITY localfile SYSTEM "c:\test.txt">
    <!ENTITY remotefile SYSTEM "https://sample/">
]>
<doc>&localfile;</doc>

#Remote code execution using msxsl

XSLT is a language to transform an XML into another XML document. XSLT syntax allows you ta do lots of things depending on the underlying engine. For instance, the msxsl engine on Windows allows you to execute any C# or JavaScript code during the transformation. This means you can access local or remote resources.

<!-- full code (XML + C#): https://gist.github.com/meziantou/2c377432b178ebab17a6802f189adce7 -->
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:msxsl="urn:schemas-microsoft-com:xslt"
    xmlns:user="http://dummy/ns">
    <msxsl:script language="C#" implements-prefix="user">
        <![CDATA[
        public string CustomCode()
        {
            return DateTime.Now.ToString();
        }
        ]]>
    </msxsl:script>
    <xsl:template match="/">
        <xsl:value-of select="user:CustomCode()"/>
    </xsl:template>
</xsl:stylesheet>

#Information disclosure with XSLT

You can also copy the content of an existing XML file using document(...):

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:template match="/">
        <xsl:copy-of select="document('file.xml')" />
    </xsl:template>
</xsl:stylesheet>

XSLT 2.0 introduced unparsed-text. It allows to read a non-xml file and returns its content as string. So, once again you can read the content of a file:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:template match="/">
        <xsl:copy-of select="unparsed-text('file.txt')" />
    </xsl:template>
</xsl:stylesheet>

#Detect vulnerable XSLT engines

XSLT also allows you to use system-properties such as <xsl:value-of select="system-property('xsl:product-name')" />. Using this, you can gather information about the tool that processes the file and maybe detect a vulnerable engine.

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:template match="/">
        <xsl:value-of select="system-property('xsl:version')" />
        <xsl:value-of select="system-property('xsl:vendor')" />
        <xsl:value-of select="system-property('xsl:vendor-url')" />
        <xsl:value-of select="system-property('xsl:product-name')" />
        <xsl:value-of select="system-property('xsl:product-version')" />
    </xsl:template>
</xsl:stylesheet>

#How to prevent XML vulnerabilities in .NET

To sum up, by parsing XML/XSLT files you are potentially exposed to:

  • Deny of service
  • Information disclosure
  • Remote code execution

In .NET there are many ways to read an XML document: XmlReader, XmlDocument, XDocument, XslTransform.Most of them are safe by default since .NET 4 but the default values can change in the future. So, I strongly advise you to test your code against the different attacks.

Here're some settings to protect your code:

var readerSettings = new XmlReaderSettings()
{
    DtdProcessing = DtdProcessing.Prohibit, // Prohibit or Ignore
    XmlResolver = null, // Do not allow to open external resources
};
var reader = XmlReader.Create("file.xml", readerSettings);
var settings = new XsltSettings()
{
    EnableScript = false, // Disallow execution of scripts
};
var xslTransform = new XslCompiledTransform(enableDebug: true);
xslTransform.Load(xsl.CreateReader(), settings, stylesheetResolver: null);

Additional resources:

Do you have a question or a suggestion about this post? Contact me!

Follow me:
Enjoy this blog?Buy Me A Coffee