An XML External Entity vulnerability (Or XXE for short) is a type of vulnerability that exploits weaknesses (Or more so features) in how external entities are loaded when parsing XML in code. Of course, OWASP has a great guide on it here, but in it’s most basic form, we can trick code into loading an external resource (Either a file on the target machine, or even a remote page on the same network) and giving us that information in some way.
For example, consider an ecommerce application allows you to update a production description by submitting the following XML to the server :
<product id="1"> <description>What a great product!</description> </product>
Then consider the following payload :
<!DOCTYPE foo [ <!ENTITY xxe SYSTEM "file:///etc/passwd"> ]> <product id="1"> <description>&xxe;</description> </product>
That may look confusing but essentially what we are doing is creating an internal variable called “xxe”, and storing the contents of the local password file (on linux) into it. Then we are setting the production description to that variable. Once completed, our production description will now leak all of the systems passwords.
It’s not just local files either, if a machine has access to internal only websites, then this could also be leveraged :
<!DOCTYPE foo [ <!ENTITY xxe SYSTEM "http://someinternalwebsite"> ]> <product id="1"> <description>&xxe;</description> </product>
Not many people realize that many XML parsers have the “feature” to reach out and load external entities and pull them into the XML, but very clearly, it’s a huge security risk. So much so that in 2020, XXE attacks were ranked number 4 in OWASP’s top 10 web application security list. Ouch!
Testing XXE In .NET Core
So it got me thinking for .NET Core, how could I test under what circumstances XXE can actually occur. After all, like SQL Injection, I always hear people say “Well that’s not relevant anymore, the framework protects you”. But does it really? And even if it does by default, how easy is it to shoot yourself in the foot?
My first step was to setup a testing rig to try out various pieces of code and see if they fit. It was actually rather simple. First I created a static class that allowed me to pass in a method that parses XML, and then I could validate whether that method was safe or not.
public static class AssertXXE { private static string _xml = "<!DOCTYPE foo [<!ENTITY xxe SYSTEM \"_EXTERNAL_FILE_\">]> <product id=\"1\"> <description>&xxe;</description></product>"; public static void IsXMLParserSafe(Func<string, string> xmlParser, bool expectedToBeSafe) { var externalFilePath = Path.GetFullPath("external.txt"); var xml = _xml.Replace("_EXTERNAL_FILE_", externalFilePath); var parsedXml = xmlParser(xml); var containsXXE = parsedXml.Contains("XXEVULNERABLE"); Assert.AreEqual(containsXXE, !expectedToBeSafe); } }
You may ask why I should pass in a boolean as to whether something is safe or not. I debated this. When I find an unsafe way of parsing XML, I didn’t want the test to “fail” per say. Because it became hard to figure out which were failing because they *should* fail, and which ones should fail because I made a simple syntax error. This way, once I found a vulnerable way of loading XML, I could then simply mark it that in future, I expect it to always be unsafe.
Onto the actual tests themselves, they were pretty simple like so :
[Test] public void XmlDocument_WithDefaults_Safe() { AssertXXE.IsXMLParserSafe((string xml) => { var xmlDocument = new XmlDocument(); xmlDocument.LoadXml(xml); return xmlDocument.InnerText; }, true); }
And so on. But onto the actual results…
Testing XmlDocument
The XmlDocument type in C# is “mostly” safe. Talking strictly .NET Framework 4.5.2 onwards (Including into .NET Core), the default setup of an XML Document was safe. So for example, this is not a vulnerable test :
[Test] public void XmlDocument_WithDefaults_Safe() { AssertXXE.IsXMLParserSafe((string xml) => { var xmlDocument = new XmlDocument(); xmlDocument.LoadXml(xml); return xmlDocument.InnerText; }, true); }
However, providing an XMLResolver to your XMLDocument made it eager to please and would download external entities. So this for example, would be unsafe :
var xmlDocument = new XmlDocument(); xmlDocument.XmlResolver = new XmlUrlResolver(); //<-- This! xmlDocument.LoadXml(xml); return xmlDocument.InnerText;
Remember how I mentioned that .NET Framework 4.5.2 > was safe? That’s because from that point, the XMLResolver was defaulted to null whereas earlier versions had a default resolver already set with the default XmlDocument constructor.
But for my use case, using XmlDocument in .NET Core with the defaults is not vulnerable to XXE.
Testing XmlReader
Next I took a look at XmlReader. Generally speaking, you can tie in an XmlReader to read a document, but then parse on any manipulation to a second class. So what I wanted to test was if I was using an XmlReader, and passing it to an XmlDocument class that was vulnerable, could the reader stop the disaster before it even got to the XmlDocument?
The answer was yes! Setting DtdProcessing to Prohibit would actually throw an exhibition when parsing the XML, and not allow processing to continue. Prohibit is also the default behaviour which was great!
XmlReaderSettings settings = new XmlReaderSettings(); settings.DtdProcessing = DtdProcessing.Prohibit; settings.MaxCharactersFromEntities = 6000; using (MemoryStream stream = new MemoryStream(Encoding.UTF8.GetBytes(xml))) { XmlReader reader = XmlReader.Create(stream, settings); var xmlDocument = new XmlDocument(); xmlDocument.XmlResolver = new XmlUrlResolver(); xmlDocument.Load(reader); return xmlDocument.InnerText; }
This also held true if I set DtdProcessing to ignore like so :
settings.DtdProcessing = DtdProcessing.Ignore;
Although I would get the following exception because instead of simply stopping parsing, it would still try and parse the document, but ignore all entity declarations.
Reference to undeclared entity 'xxe'.
Interestingly, to make XmlReader unsafe I had to do two things. First, I have to make DtdProcessing be set to “Parse” *and* I had to set a UrlResolver up :
XmlReaderSettings settings = new XmlReaderSettings(); settings.DtdProcessing = DtdProcessing.Parse; settings.XmlResolver = new XmlUrlResolver();
Without these settings on the reader, even if the resulting stream was passed to an XmlDocument with a Resolver setup, it was still not vulnerable.
Getting Involved
For my particular use cases, what I found was that the way in which I use XmlDocument in .NET Core was safe. I never manually set an XmlResolver up, so I was good to go. But maybe you’re using a different way to parse XML? Maybe you’re even using a third party library to work with XML?
For this, I’ve thrown up my code that I used to test my scenarios on Github. You can access it here : https://github.com/mindingdata/XXEDotNetCore
If you, or the company you work for parse XML a different way, I really encourage you to add a PR on whether it is safe or unsafe for XXE. Again, this harks back to what I said earlier that so many of these OWASP top 10 security issues, developers like to say “Oh, that’s an old thing, it’s not a problem anymore”. And maybe for the majority of use cases that’s true, but it really doesn’t hurt to rig up your code and actually prove that’s the case!