Secure XSL Transformations in Microsoft .NET

 

Prajakta Joshi
Microsoft Corporation

July 28, 2003

Summary: Guest author Prajakta Joshi discusses secure programming practices while transforming XML data with the .NET XslTransform class. Topics include resolving imported/included stylesheets, resolving document() function references, using stylesheet script, and extension objects. (9 printed pages)

Note All discussion and code samples in this article refer to XslTransform implementation in .NET Framework 1.1 SDK.

Security Risks with XSLT

XSLT 1.0 operates on the same data model as XPath 1.0. An XSLT transformation describes rules for transforming a source tree into a result tree. The language offers powerful constructs such as template rules, recursion, conditional processing, and much more.

The core language has features such as import/include/document, where the processor needs to resolve URI references. This is the first category of security risks involved that we will discuss in this article.

The second category arises from use of XSLT extensions. Similar to XPath 1.0, powerful data manipulation using XSLT necessitates the use of extension elements and functions. W3C XSLT 1.0 recommendation allows processors to support such extensions. The most popular extension mechanisms are stylesheet script blocks and extension objects. Using such extensions allows an XSLT processor to run code on the user's behalf.

Stylesheets, Script, and Safety

At first glance, XSLT 1.0 seems to be a safe language for transforming XML documents into other XML documents. One might think that if their stylesheet does not use any script block or extension objects, then it must be safe.

Well if you think so, here are a couple of scenarios to ponder:

  • If your XSLT application is running in full trust on the server that is accepting stylesheets from untrusted origins, a malicious stylesheet can cause a DOS type of attack by executing an infinite loop and exhausting system resources.
  • Besides the source XML and stylesheet, an XSLT processor needs to access other resources such as imported stylesheets, included stylesheets, or other XML files referred by the document() function. Accessing these resources may require permissions for cross-zone or cross-domain access.

In this article, we will review how one can securely write XSL transformations with the XslTransform class. Let us begin our discussion with the security risks involved in resolution of external resources.

Resolution of External Resources

A single, huge stylesheet with complex processing logic is just as difficult to maintain as a single, non-modular program 10,000 lines long. Moreover, sometimes the business problem at hand inherently favors writing different stylesheets for different modules. XSLT 1.0 provides two mechanisms to combine stylesheets:

  • Include
  • Import

Both xsl:include and xsl:import elements have an href attribute whose value is a URI reference identifying the stylesheet to be included/imported. XslTransform uses an XmlResolver to resolve these relative/absolute URI references.

The document() function is used to access XML documents other than the main source document during transformation. The primary difference between resolution of import/include and document() is when they are resolved. The former is resolved when the stylesheet is compiled using the XslTransform.Load() method, while the later is resolved when the stylesheet transformation is being executed using the XslTransform.Transform() method.

Role of XmlResolver

The XmlResolver class is used by System.Xml APIs to resolve external URI references, which can be XML, imported/included stylesheets, external entities, DTDs, or schemas. The Credentials property of XmlResolver can be set to an ICredential object. The GetEntity() method on this class actually returns the underlying object as Stream.

XslTransform uses XmlResolver in two cases:

  • For import/include resolution where you need to pass an XmlResolver to the XslTransform.Load() method.
  • For document() function resolution where you need to pass an XmlResolver to the XslTransform.Transform() method.

Note that the XslTransform class compiles the main stylesheet along with any included/imported stylesheets when a call to XslTransform.Load() is made. For overloads of XslTransform.Load() that do not take a resolver parameter, a default XmlUrlResolver with no credentials is created for the user by XslTransform.

(XmlUrlResolver is an implementation of XmlResolver and is the default resolver for all classes in the System.Xml namespace.)

Scenario 1: Import/Include Resolution

Consider a scenario in which your server-side application is accepting a stylesheet from a trusted partner site, such as http://www.example.com, to run a transformation. What happens if that trusted stylesheet imports bad.xsl from another untrusted Web site?

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:include href="http://www.badexample.com/bad.xsl">
<xsl:template match="/">
   ...
</xsl:template>
</xsl:stylesheet>

Solution to Scenario 1: XmlSecureResolver

Can you make Scenario 1 secure by using an XmlUrlResolver? The answer is no. What you need is the ability to specify a set of Web permissions for resolving URIs from only trusted sites. This can be done using the XmlSecureResolver class introduced in version 1.1 of .NET Framework SDK.

The XmlSecureResolver class wraps an XmlResolver with a permission set. Its constructor takes an XmlResolver and one of Evidence, PermissionSet, or String URL objects. Note that a PermissionSet can be computed from Evidence or String objects. The CreateEvidenceForUrl() method can be used to compute Evidence from a String object. XmlSecureResolver calls PermitOnly() on the permission set before calling the GetEntity method on the underlying XmlResolver. So, an attempt to resolve an insecure URI fails with a security exception.

Thus, if you trust the stylesheets from www.example.com, you can ensure that XslTransform will only resolve imported/included stylesheets from this site with the following code snippet. It uses the System.Net.WebPermission class that controls rights to access HTTP Internet resources.

// 1. Generate the default permission set for the stylesheet URL
Evidence evidence = XmlSecureResolver.CreateEvidenceForUrl(stylesheetURL);
PermissionSet myPermissions = SecurityManager.ResolvePolicy(evidence);

// 2. Modify the permission set to only allow access to trusted site
WebPermission myWebPermission = new WebPermission(NetworkAccess.Connect, 
"http://www.example.com");
myPermissions.SetPermission(myWebPermission);

// 3. Wrap using XmlSecureResolver
XmlUrlResolver resolver = new XmlUrlResolver();
XmlSecureResolver sResolver = new XmlSecureResolver(resolver, 
myPermissions);

// 4. Load stylesheet in XslTransform
XslTransform xslt = new XslTransform();
xslt.Load("http://www.example.com/stylesheet.xsl", sResolver);

To summarize, passing XmlSecureResolver to XslTransform.Load() gives developers the ability to control safe resolution of import/include URIs.

Resolving Document() Function

The document() function in XSLT 1.0 allows transformation to work with multiple source documents. Here is an example of a stylesheet that illustrates the use of this function.

Stylesheet

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:template match="/root">
   <xsl:for-each select="order">
      <xsl:call-template name="processOrders" select="document(.)"/>
   </xsl:for-each>
   <xsl:for-each select="customer">
      <xsl:call-template name="processCustomers" select="document(.)"/>
   </xsl:for-each>
</xsl:template>

<xsl:template name="processOrders">
   <!-- Process order documents here -->
</xsl:template>

<xsl:template name="processCustomers">
   <!-- Process customer documents here -->
</xsl:template>

</xsl:stylesheet>

XML

<root>
   <order>order1.xml</order>
   <order>order2.xml</order>
   <customer>cust1.xml</customer>
   <customer>cust2.xml</customer>
</root>

The general risks and issues are similar to those discussed in the import/include section. As I mentioned earlier, the document() function URI references are resolved during execution. The XmlResolver passed to Transform() method is used for resolution. You should pass XmlSecureResolver to ensure secure cross-zone resolution.

There are a couple of points to note:

  • If the code executing transformation does not have FullTrust permission set, the document() function is disabled (along with scripts).
  • When the attempt to resolve document URI fails, transformation does not throw an error and terminate. Instead, an empty node set is returned for document(). Many users have asked on public newsgroups how this behavior could be overridden. If you want to terminate transformation, you can use a custom resolver and throw an exception if GetEntity() fails.

Stylesheet Script

Scripting is probably the most popular extension feature of XSLT. The XslTransform class supports the <msxsl:script> extension element, which allows scripting languages like C#, JScript®, and Visual Basic®. When a stylesheet is compiled, the script code blocks are compiled to one or more in-memory assemblies using code generation classes in the System.CodeDom.Compiler namespace.

Script in stylesheets raises the most security concerns among XSLT developers. Malicious script can access protected/system resources on your machine. For example, read a file from your hard drive or perform privileged operations that it does not have permissions to perform. So, the questions you might ask are:

  • Can I control the permissions stylesheet script has?
  • Can I disable scripts altogether in a stylesheet?

The answer to both questions is yes.

Scenario 2: Scripting

Consider a scenario in which you are designing a Web client application that executes XSL transformations in the browser. Here is an example stylesheet with script that needs FileIOPermission to delete a file on your computer.

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
xmlns:myScript="http://my.com">

<xsl:template match="/">
   <xsl:value-of select="myScript:DeleteFile()"/>
</xsl:template>

<msxsl:script language="C#" implements-prefix="myScript">
   <![CDATA[
      public void DeleteFile()
      {
         if (System.IO.File.Exists('c:\temp.txt'))
            System.IO.File.Delete('c:\temp.txt');
      }
   ]]>
</msxsl:script>
</xsl:stylesheet>

You want to grant permissions to the XSL transformation depending on the origin of the stylesheet. If the stylesheet originates from the Web site of a trusted business partner, you want to grant the transformation a permission set that includes FileIO. Otherwise, you would like to run the transformation under default Internet zone permissions.

Evidence Based Scripting Security

In version 1.1 of the SDK, the XslTransform class implementation uses a scripting security model similar to CLR Code Access Security model. You can pass an Evidence object to XslTransform.Load(), and this evidence controls the permissions of the script assembly.

The Evidence class is actually a container for a set of objects that represent code evidence. It holds two sets that correspond to host and assembly evidence:

  • Host evidence can be provided by hosts that have ControlEvidence permission. Typically, this evidence provides information about the origin of the code and is comprised of Url, Site, and Zone objects. You can create an empty evidence object and add host evidence objects to it using the Evidence.AddHost() method.
  • Assembly evidence is part of a CLR assembly itself and is added to it at the time of its generation before the assembly is signed. Developers can attach custom evidence to an assembly.

You can refer .NET SDK conceptual documentation at Code Access Security for more details.

Note that the caller must have ControlEvidence permission in order to supply evidence and UnmanagedCode permission to compile script assembly. This means that if the code-executing transformation does not have a FullTrust permission set, you can not run scripts. Even though this might seem very restrictive, it is a good security practice that prevents luring attacks.

Solution to Scenario 2

Let's get back to the problem described in Scenario 2. In you client side application, you can examine the source URI of the stylesheet.

  • If the URI is not a trusted partner site, pass the following evidence to XslTransform.Load().

    Evidence evidence = new Evidence();
    evidence.AddHost(new Zone(SecurityZone.Internet));
    

    This will give the script assembly the same permissions as specified by the SecuityManager policy for Internet zone. By default this does not include FileIO permission.

  • If the URI is from the trusted partner site, you need to create a custom permission set for that site. You can use the .NET Framework Configuration Tool or the Code Access Security Policy Tool for this purpose.

Secure Scripting Guidelines

Passing the correct evidence to XslTransform.Load() controls the security of your XslTransform application with respect to stylesheet script. Here are some general guidelines that you can follow:

  1. If the stylesheet comes from an application you trust, use the evidence from that assembly for scripts. Every CLR Assembly has an evidence property that supplies the evidence object for that assembly:

    xslt.Load(reader, resolver, assembly.Evidence);
    
  2. If you trust the URL the stylesheet comes from, create evidence from the URL and pass that to XslTransform.Load(). Note that Load(string) does this internally.

    xslt.Load(reader, resolver, 
    XmlSecureResolver.CreateEvidenceForUrl(stylesheetUrl));
    
  3. If you cannot verify the source of the stylesheet, you should disable execution of stylesheet scripts. Use overloads of Load() that take evidence object and pass NULL as evidence.

  4. Remember that overloads of Load() that do not take an Evidence parameter execute scripts with a FullTrust permission set. This is to preserve compatibility with version 1.0 behavior. When XSL transformation code is running in FullTrust permission set, it is the programmer's responsibility to override this behavior.

Extension Objects

Similar to scripts, extension objects add programming capabilities to XSL transformations in the .NET XslTransform class. Internally, the XslTransform class uses reflection to invoke extension object methods.

Whether to use script or extension objects is the XML developer's choice. However, there are a few advantages to using extension objects:

  • Using extension objects provides better encapsulation and reuse. You can identify the common extension functions that your application needs, implement them in a class library, and reuse it. Dare Obasanjo's last article on EXSLT uses extension objects.
  • Using stylesheet script makes them lengthy, unreadable (unless you document your script), and difficult to maintain.
  • Stylesheet script supports only a subset of .NET SDK namespaces. If you want to use classes outside of these namespaces, the class names must be fully qualified.

What is a possible security hole here? If semi-trusted code running an XSL transformation adds an extension object from a fully-trusted assembly, there is an elevation of permissions for the original application. Therefore, extension objects are allowed only when your code is running with a FullTrust permission set. The AddExtensionObject() method on XsltArgumentList throws a security exception if the caller does not have a FullTrust permission set. This ensures that elevation of permissions does not happen when extension object code is executed.

Conclusion

In this article, we reviewed the security risks involved in some features of the XSLT 1.0 transformation language and Microsoft specific extensions. We also identified best practices to follow while writing secure code using the XslTransform class. In general, it is when your XSLT application is running in a fully trusted environment that you should carefully examine security threats. In semi-trusted environment, XslTransform disallows script, extension objects, and the document() function to prevent security attacks.

Acknowledgements

I would like to thank Dare Obasanjo and members of Microsoft WebData XML team for their help and valuable inputs in reviewing this article.

Prajakta Joshi is a member of Microsoft's WebData XML team and works on the XPath/XSLT components within the System.Xml namespace of the .NET Framework.

Feel free to post any questions or comments about this article on Extreme XML Message Board on GotDotNet.