XXE and .Net
XXE, or XML External Entity, is an attack against applications that parse XML. It occurs when XML input contains a reference to an external entity that it wasn’t expected to have access to. Through this article, I will discuss how .Net handles XML for certain objects and how to properly configure these objects to block XXE attacks. It is important to understand that the different versions of the .Net framework handle this differently. I will point out the differences for each object.
I will cover the XmlReader, XmlTextReader, and XMLDocument. Here is a quick summary regarding the default settings:
Object | Safe by Default? |
---|---|
XmlReader | |
Prior to 4.0 | Yes |
4.0 + | Yes |
XmlTextReader | |
Prior to 4.0 | No |
4.0 + | No |
XmlDocument | |
4.5 and Earlier | No |
4.6 | Yes |
XMLReader
Prior to 4.0
The ProhibitDtd property is used to determine if a DTD will be parsed.
- True (default) – throws an exception if a DTD is identified. (See Figure 1)
- False – Allows parsing the DTD. (Potentially Vulnerable)
Code that throws an exception when a DTD is processed: – By default, ProhibitDtd is set to true and will throw an exception when an Entity is referenced.
static void Reader() { string xml = "<?xml version=\"1.0\" ?><!DOCTYPE doc [<!ENTITY win SYSTEM \"file:///C:/Users/user/Documents/testdata2.txt\">] ><doc>&win;</doc>"; XmlReader myReader = XmlReader.Create(new StringReader(xml)); while (myReader.Read()) { Console.WriteLine(myReader.Value); } Console.ReadLine(); }
Exception when executed:
[Figure 1]
Code that allows a DTD to be processed: – Using the XmlReaderSettings object, it is possible to allow the parsing of the entity. This could make your application vulnerable to XXE.
static void Reader() { string xml = "<?xml version=\"1.0\" ?><!DOCTYPE doc [<!ENTITY win SYSTEM \"file:///C:/Users/user/Documents/testdata2.txt\">] ><doc>&win;</doc>"; XmlReaderSettings rs = new XmlReaderSettings(); rs.ProhibitDtd = false; XmlReader myReader = XmlReader.Create(new StringReader(xml),rs); while (myReader.Read()) { Console.WriteLine(myReader.Value); } Console.ReadLine(); }
Output when executed showing injected text:
[Figure 2]
.Net 4.0+
In .Net 4.0, they made a change from using the ProhibitDtD property to the new DtdProcessing enumeration. There are now three (3) options:
- Prohibit (default) – Throws an exception if a DTD is identified.
- Ignore – Ignores any DTD specifications in the document, skipping over them and continues processing the document.
- Parse – Will parse any DTD specifications in the document. (Potentially Vulnerable)
Code that throws an exception when a DTD is processed: – By default, the DtdProcessing is set to Prohibit, blocking any external entities and creating safe code.
static void Reader() { string xml = "<?xml version=\"1.0\" ?><!DOCTYPE doc [<!ENTITY win SYSTEM \"file:///C:/Users/user/Documents/testdata2.txt\">] ><doc>&win;</doc>"; XmlReader myReader = XmlReader.Create(new StringReader(xml)); while (myReader.Read()) { Console.WriteLine(myReader.Value); } Console.ReadLine(); }
Exception when executed:
[Figure 3]
Code that ignores DTDs and continues processing: – Using the XmlReaderSettings object, setting DtdProcessing to Ignore will skip processing any entities. In this case, it threw an exception because there was a reference to the entirety that was skipped.
static void Reader() { string xml = "<?xml version=\"1.0\" ?><!DOCTYPE doc [<!ENTITY win SYSTEM \"file:///C:/Users/user/Documents/testdata2.txt\">] ><doc>&win;</doc>"; XmlReaderSettings rs = new XmlReaderSettings(); rs.DtdProcessing = DtdProcessing.Ignore; XmlReader myReader = XmlReader.Create(new StringReader(xml),rs); while (myReader.Read()) { Console.WriteLine(myReader.Value); } Console.ReadLine(); }
Output when executed ignoring the DTD (Exception due to trying to use the unprocessed entity):
[Figure 4]
Code that allows a DTD to be processed: Using the XmlReaderSettings object, setting DtdProcessing to Parse will allow processing the entities. This potentially makes your code vulnerable.
static void Reader() { string xml = "<?xml version=\"1.0\" ?><!DOCTYPE doc [<!ENTITY win SYSTEM \"file:///C:/Users/user/Documents/testdata2.txt\">] ><doc>&win;</doc>"; XmlReaderSettings rs = new XmlReaderSettings(); rs.DtdProcessing = DtdProcessing.Parse; XmlReader myReader = XmlReader.Create(new StringReader(xml),rs); while (myReader.Read()) { Console.WriteLine(myReader.Value); } Console.ReadLine(); }
Output when executed showing injected text:
[Figure 5]
XmlTextReader
The XmlTextReader uses the same properties as the XmlReader object, however there is one big difference. The XmlTextReader defaults to parsing XML Entities so you need to explicitly tell it not too.
Prior to 4.0
The ProhibitDtd property is used to determine if a DTD will be parsed.
- True – throws an exception if a DTD is identified. (See Figure 1)
- False (Default) – Allows parsing the DTD. (Potentially Vulnerable)
Code that allows a Dtd to be processed: (Potentially Vulnerable) – By default, the XMLTextReader sets the ProhibitDtd property to False, allowing entities to be parsed and the code to potentially be vulnerable.
static void TextReader() { string xml = "<?xml version=\"1.0\" ?><!DOCTYPE doc [<!ENTITY win SYSTEM \"file:///C:/Users/user/Documents/testdata2.txt\">] ><doc>&win;</doc>"; XmlTextReader myReader = new XmlTextReader(new StringReader(xml)); while (myReader.Read()) { if (myReader.NodeType == XmlNodeType.Element) { Console.WriteLine(myReader.ReadElementContentAsString()); } } Console.ReadLine(); }
Code that blocks the Dtd from being parsed and throws an exception: – Setting the ProhibitDtd property to true (explicitly) will block Dtds from being processed making the code safe from XXE. Notice how the XmlTextReader has the ProhibitDtd property directly, it doesn’t have to use the XmlReaderSettings object.
static void TextReader() { string xml = "<?xml version=\"1.0\" ?><!DOCTYPE doc [<!ENTITY win SYSTEM \"file:///C:/Users/user/Documents/testdata2.txt\">] ><doc>&win;</doc>"; XmlTextReader myReader = new XmlTextReader(new StringReader(xml)); myReader.ProhibitDtd = true; while (myReader.Read()) { if (myReader.NodeType == XmlNodeType.Element) { Console.WriteLine(myReader.ReadElementContentAsString()); } } Console.ReadLine(); }
4.0+
In .Net 4.0, they made a change from using the ProhibitDtD property to the new DtdProcessing enumeration. There are now three (3) options:
- Prohibit – Throws an exception if a DTD is identified.
- Ignore – Ignores any DTD specifications in the document, skipping over them and continues processing the document.
- Parse (Default) – Will parse any DTD specifications in the document. (Potentially Vulnerable)
Code that allows a DTD to be processed: (Vulnerable) – By default, the XMLTextReader sets the DtdProcessing to Parse, making the code potentially vulnerable to XXE.
static void TextReader() { string xml = "<?xml version=\"1.0\" ?><!DOCTYPE doc [<!ENTITY win SYSTEM \"file:///C:/Users/user/Documents/testdata2.txt\">] ><doc>&win;</doc>"; XmlTextReader myReader = new XmlTextReader(new StringReader(xml)); while (myReader.Read()) { if (myReader.NodeType == XmlNodeType.Element) { Console.WriteLine(myReader.ReadElementContentAsString()); } } Console.ReadLine(); }
Code that blocks the Dtd from being parsed: – To block entities from being parsed, you must explicitly set the DtdProcessing property to Prohibit or Ignore. Note that this is set directly on the XmlTextReader and not through the XmlReaderSettings object.
static void TextReader() { string xml = "<?xml version=\"1.0\" ?><!DOCTYPE doc [<!ENTITY win SYSTEM \"file:///C:/Users/user/Documents/testdata2.txt\">] ><doc>&win;</doc>"; XmlTextReader myReader = new XmlTextReader(new StringReader(xml)); myReader.DtdProcessing = DtdProcessing.Prohibit; while (myReader.Read()) { if (myReader.NodeType == XmlNodeType.Element) { Console.WriteLine(myReader.ReadElementContentAsString()); } } Console.ReadLine(); }
Output when Dtd is prohibited:
[Figure 6]
XMLDocument
For the XMLDocument, you need to change the default XMLResolver object to prohibit a Dtd from being parsed.
.Net 4.5 and Earlier
By default, the XMLDocument sets the URLResolver which will parse Dtds included in the XML document. To prohibit this, set the XmlResolver = null.
Code that does not set the XmlResolver properly (potentially vulnerable) – The default XMLResolver will parse entities, making the following code potentially vulnerable.
static void Load() { string fileName = @"C:\Users\user\Documents\test.xml"; XmlDocument xmlDoc = new XmlDocument(); xmlDoc.Load(fileName); Console.WriteLine(xmlDoc.InnerText); Console.ReadLine(); }
Code that does set the XmlResolver to null, blocking any Dtds from executing: – To block entities from being parsed, you must explicitly set the XmlResolver to null. This example uses LoadXml instead of Load, but they both work the same in this case.
static void LoadXML() { string xml = "<?xml version=\"1.0\" ?><!DOCTYPE doc [<!ENTITY win SYSTEM \"file:///C:/Users/user/Documents/testdata2.txt\">] ><doc>&win;</doc>"; XmlDocument xmlDoc = new XmlDocument(); xmlDoc.XmlResolver = null; xmlDoc.LoadXml(xml); Console.WriteLine(xmlDoc.InnerText); Console.ReadLine(); }
.Net 4.6
It appears that in .Net 4.6, the XMLResolver is defaulted to Null, making the XmlDocument safe. However, you can still set the XmlResolver in a similar way as prior to 4.6 (see previous code snippet).
.Net EnableHeaderChecking
How often do you take untrusted input and insert it into response headers? This could be in a custom header or in the value of a cookie. Untrusted user data is always a concern when it comes to the security side of application development and response headers are no exception. This is referred to as Response Splitting or HTTP Header Injection.
Like Cross Site Scripting (XSS), HTTP Header Injection is an attack that results from untrusted data being used in a response. In this case, it is in a response header which means that the context is what we need to focus on. Remember that in XSS, context is very important as it defines the characters that are potentially dangerous. In the HTML context, characters like < and > are very dangerous. In Header Injection the greatest concern is over the carriage return (%0D or \r) and new line (%0A or \n) characters, or CRLF. Response headers are separated by CRLF, indicating that if you can insert a CRLF then you can start creating your own headers or even page content.
Manipulating the headers may allow you to redirect the user to a different page, perform cross-site scripting attacks, or even rewrite the page. While commonly overlooked, this is a very dangerous flaw.
ASP.Net has a built in defense mechanism that is enabled by default called EnableHeaderChecking. When EnableHeaderChecking is enabled, CRLF characters are converted to %0D%0A and the browser does not recognize it as a new line. Instead, it is just displayed as characters, not actually creating a line break. The following code snippet was created to show how the response headers look when adding CRLF into a header.
public partial class _Default : Page { protected void Page_Load(object sender, EventArgs e) { Response.AppendHeader("test", "tes%0D%0At\r\ntest2"); Response.Cookies.Add(new HttpCookie("Test-Cookie", "Another%0D%0ATest\r\nCookie")); } }
When the application runs, it will properly encode the CRLF as shown in the image below.
My next step was to disable EnableHeaderChecking in the web.config file.
<httpRuntime targetFramework="4.6" enableHeaderChecking="false"/>
My expectation was that I would get a response that allowed the CRLF and would show me a line break in the response headers. To my surprise, I got the error message below:
So why the error? After doing a little Googling I found an article about ASP.Net 2.0 Breaking Changes on IIS 7.0. The item of interest is “13. IIS always rejects new lines in response headers (even if ASP.NET enableHeaderChecking is set to false)”
I didn’t realize this change had been implemented, but apparently, if using IIS 7.0 or above, ASP.Net won’t allow newline characters in a response header. This is actually good news as there is very little reason to allow new lines in a response header when if that was required, just create a new header. This is a great mitigation and with the default configuration helps protect ASP.Net applications from Response Splitting and HTTP Header Injection attacks.
Understanding the framework that you use and the server it runs on is critical in fully understanding your security risks. These built in features can really help protect an application from security risks. Happy and Secure coding!
I did a podcast on this topic which you can find on the DevelopSec podcast.
ASP.Net Insecure Redirect
Filed under: Development, Security
It was recently discovered that there was a vulnerability within the ASP.Net Forms Authentication process that could allow an attacker to force a user to visit a malicious web site upon success authentication. Until this vulnerability was found, it was thought that the only way to allow the Forms Authentication redirect (managed by the ReturnUrl parameter) to redirect outside of the host application’s domain was to set enableCrossAppRedirects to true in the web.config file. If this property was set to false, the default value, then redirection could only occur within the current site.
So how does it work? When a user navigates to a page that requires authentication, and they are not currently authenticated, ASP.Net redirects them to the login page. This redirection appends a querystring value onto the url that, once successful login occurs, will allow the user to be redirected to the originally requested page. Here is an example (Assuming that EnableCrossAppRedirects=”False” ):
- User requests the following page: http://www.jardinesoftware.com/useredit.aspx
- The framework recognizes that the user is not authenticated and redirects the user to http://www.jardinesoftware.com/logon.aspx?ReturnUrl=useredit.aspx
- The user enters their login information and successfully authenticates.
- The application then analyzes the ReturnUrl and attempts to determine if the Url references a location with the current domain, or if it is outside of the domain. To do this, it uses the Uri object to determine if the Url is an Absolute path or not.
- If it is determined to NOT be an absolute path, by using the Uri object, then the redirect is accepted.
- If it is determined to be an absolute path, additional checks are made to determine if the hosts match for both the current site and the redirect site. If they do, a redirect is allowed. Otherwise, the user is redirected to the default page.
The details are not fully known at this time as to what the malicious link may look like to trick ASP.Net into believing that the Url is part of the same domain, so unfortunately I don’t have an example of that. If I get one, I will be sure to post it. It does appear that some WAF’s are starting to include some signatures to try and detect this, but that should be part of defense in depth, not the sole solution. Apply the patch. The bulletin for MS11-100 describes the patch that is available to remediate this vulnerability.
It is important to note that this is an issue in the FormsAuthentication.RedirectFromLogin method and not the Response.Redirect method. If you are doing the latter, through a custom login function, you would be required to validate the ReturnUrl. If you use the default implementation to redirect a user after credentials have been verified or use the built in asp:Login controls, you will want to make sure this patch gets applied.
This issue requires that a legitimate user clicks on a link that already has a malicious return url included and then successfully logs in to the site. This could be done through some sort of phishing scheme or by posting the link somewhere where someone would click on it. It is always recommended that when users go to a site that requires authentication that they type in the address to the site, rather than follow a link someone sent them.
This issue doesn’t have any impact on the site that requires authentication, it is strictly a way for attackers to get users to visit a malicious site of their own.
For more information you can read Microsoft’s Security Bulletin here: http://technet.microsoft.com/en-us/security/bulletin/ms11-100.
The information provided in this post is provided as-is and is for educational purposes only. It is imperative that developers understand the vulnerabilities that exist within the frameworks/platforms that they work with. Although there is not much you can really do when the vulnerability is found within the framework, understanding the possible workarounds and the risks associate with them help determine proper remediation efforts.