4

Are there any pre-existing methods in .NET to detect/prevent an xpath injection attack?

I can forsee 2 examples but there are likely many more.

e.g.

"/Some/XPath/" + UntrustedNodeName

If UntrustedNodeName is "DoesNotExist | /Some/Other/XPath" then this could be an attack.

"/Some/XPath[" + UntrustedFilter + "]"

If UntrustedFilter is "1 = 1" then this could also be an attack.

I make no assumption that I have covered all cases here!

I am guessing that the 2 situations need to be tested separately with different logic.

For other types of attacks there are encoding methods and parameterised classes to mitigate the risks. For XPath I can't find anything similar.

(Except - I did find this: http://www.tkachenko.com/blog/archives/000385.html but the installer didn't work)

DJL
  • 2,060
  • 3
  • 20
  • 39

3 Answers3

3

As Michael Kay says, the right approach here is to use XPath variables, but this is a bit tricky using built-in .NET APIs. I'll provide an example below of a (very bare bones) class that allows you to define XPath variables. Once you have that, you can make use of it like this:

VariableContext context = 
               new VariableContext { { "hello", 4 }, { "goodbye", "adios" } };

// node is a System.Xml.XmlNode object
XmlNodeList result = 
               node.SelectNodes("/my/field[. = $hello or . = $goodbye]", context);

This same class should also work with XmlNode.SelectSingleNode(), XPathNavigator.Select(), XPathNavigator.SelectSingleNode(), XPathNavigator.Evaluate(), and the XPath methods in XElement.

This provides a safe way to incorporate user-provided data values into your XPath, but as with Tomalak's answer, it does not address the issue of how to allow your user to provide entire pieces of XPath. I don't think there is a way to determine whether a piece of XPath is objectively safe or not, so if you're worried about that being a security risk, then the only solution is to not do it.

Here is the class. If you want to have it handle namespaces and stuff like that, that would need to be added.

class VariableContext : XsltContext
{
    private Dictionary<string, object> m_values;

    public VariableContext()
    {
        m_values = new Dictionary<string, object>();
    }

    public void Add(string name, object value)
    {
        m_values[name] = value;
    }

    public override IXsltContextVariable ResolveVariable(string prefix, string name)
    {
        return new XPathVariable(m_values[name]);
    }

    public override int CompareDocument(string baseUri, string nextbaseUri)
    {
        throw new NotImplementedException();
    }

    public override bool PreserveWhitespace(XPathNavigator node)
    {
        throw new NotImplementedException();
    }

    public override IXsltContextFunction ResolveFunction(string prefix, string name, 
                                                         XPathResultType[] ArgTypes)
    {
        throw new NotImplementedException();
    }

    public override bool Whitespace
    {
        get { throw new NotImplementedException(); }
    }

    private class XPathVariable : IXsltContextVariable
    {
        private object m_value;

        internal XPathVariable(object value)
        {
            m_value = value;
        }

        public object Evaluate(XsltContext xsltContext)
        {
            return m_value;
        }

        public bool IsLocal
        {
            get { throw new NotImplementedException(); }
        }

        public bool IsParam
        {
            get { throw new NotImplementedException(); }
        }

        public XPathResultType VariableType
        {
            get { throw new NotImplementedException(); }
        }
    }

}
JLRishe
  • 99,490
  • 19
  • 131
  • 169
0

Just avoid building XPath expressions using string concatenation. Use parameters/variables instead.

Michael Kay
  • 156,231
  • 11
  • 92
  • 164
  • Not always possible - think of `.SelectSingleNode()` or other DOM API functions that take an XPath string parameter. If one wanted to incorporate user input into that string, injection attacks would be a threat. – Tomalak Oct 22 '13 at 08:42
  • 1
    If your XPath API doesn't allow use of variables, then it's time to use a different API. – Michael Kay Oct 22 '13 at 11:42
  • Which .NET-compatible XPath API does, given the fact that built-ins don't? – Tomalak Oct 22 '13 at 11:56
  • @MichaelKay I prefer to avoid using 3rd party libraries where possible. However - if there is no alternative. Can you make use Saxon an answer please and I will accept it. – DJL Oct 23 '13 at 08:24
  • @DJL Of course you can also use LINQ to XML instead of XPath, like shown in [this answer](http://stackoverflow.com/a/19700521/18771). – Tomalak Oct 31 '13 at 07:27
  • 1
    @Tomalak Both `XmlDocument` and `XPathNavigator` allow using XPath variables, but the necessary setup is a pain in the neck. You basically need to create your own XsltContext class (or find one) to handle the variables, and pass that to the selection method you're using. I'll add an answer here with a rudimentary example. – JLRishe Oct 31 '13 at 10:19
0

I think you could do the following.

  • For input that should represent node names, throw out all characters that are invalid in a node name (compare the "names" definition). This can be done via regex.
  • For input that should represent strings it comes in handy that there are no escape sequences in XPath.

    • Either throw out all single quotes from the user input and only work with single quoted strings in your expression templates. You are safe from injection because there is no way to escape from a single quoted string other than a single quote.

      var xpathTmpl = "/this/is/the/expression[@value = '{0}']";
      
      var input = "asd'asd";
      var safeInput = input.Replace("'", "");
      
      var xpath = String.Format(xpathTmpl, safeInput);
      // -> "/this/is/the/expression[@value = 'asdasd']"
      
    • Or the other way around. Same effect, more backslashes (in C# at least).

      var xpathTmpl = "/this/is/the/expression[@value = \"{0}\"]";
      
      var input = "asd\"asd";
      var safeInput = input.Replace("\"", "");
      
      var xpath = String.Format(xpathTmpl, safeInput);
      // -> "/this/is/the/expression[@value = "asdasd"]"
      

      …of course that's not 100% nice because you change the user's input.

    • If you want to represent the user input verbatim, you must split it into sections at the XPath string delimiter you chose (say, the single quote) and use XPath's concat() function, like this:

      var xpathTmpl = "/this/is/the/expression[@value = {0}]";
      
      var input = "asd'asd";
      var inputParts = input.Split('\'');
      var safeInput = "concat('" + String.Join("', \"'\", '", inputParts) + "')";
      
      var xpath = String.Format(xpathTmpl, safeInput);
      // -> "/this/is/the/expression[@value = concat('asd', "'", 'asd')]"
      

      Wrap that in a utility function and dynamic XPath building becomes manageable.

  • Numbers are easy via a sanity check (float.Parse()) and String.Format().
  • Booleans are relatively easy, insert them either as XPath-native Booleans (true() or false()) or as numeric values (i.e. 1 or 0), which are then coerced to Booleans automatically by XPath when used in a Boolean context.
  • If you want your users to be able to enter whole sub-expressions... Well. Don't.
Tomalak
  • 332,285
  • 67
  • 532
  • 628
  • On a security course I did recently it was hammered into us that black lists are a bad idea - instead you should use a whitelist. That said - I like your concat() idea - I wouldn't have thought of that. It still doesn't feel particularly safe to me though. – DJL Oct 23 '13 at 08:23
  • I understand your concern, but rest assured the concat variant is absolutely safe. There is absolutely no way to escape from this method. Regarding blacklists vs whitelists - if you have a node name whitelist, go for it. But removing all illegal characters is not a "token blacklist" (those are unsafe), but a grammar level blacklist. There is no way to break that. In fact it *is* a whitelist - only certain characters are allowed. – Tomalak Oct 23 '13 at 08:35
  • Actually, the `concat()` method is the only way to get a single quote into a single quoted string within XPath. – Tomalak Oct 23 '13 at 09:23