Vulnerability in .NET SignedXml

.NET’s SignedXML class has had a risky implementation for lookup of XML elements by id in GetIdElement() when resolving signed xml references. The lookup validated only the first element if there are several with the same id. This opens up for XML Signature Wrapping attacks in any library that is using the default implementation without taking necessary precautions. For SAML2 libraries signature wrapping is a well known attack class with very severe implications.

I reported this privately to Microsoft on December 3rd 2015. They responded (as promised within 24 hours) that they would investigate. The vulnerability was assigned ids CVE-2016-0132 and MS16-035. A fix was released on “patch Tuesday” in March 2016 (and yes, I’m proud to be listed in the acknowledgement section). The fix also contains a number of related breaking changes.

This is an example of a signed XML document with data that might be incorrectly trusted.

<r>
   <b Id="q">
      <data>Valid data</data>
      <Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
         <SignedInfo>
            <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315" />
            <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" />
            <Reference URI="#q">
               <Transforms>
                  <Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
               </Transforms>
               <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
               <DigestValue>Drhn/EC2O7JBZwj9lS/kdS8RYis=</DigestValue>
            </Reference>
         </SignedInfo>
         <SignatureValue>T2hBKCNuonADXznJ2IT/cIH2ZB/8/WvLpywVH3ebWwN9EDKt5T4n4NC7/rhWTFMX3pacGNzS0oDcEe7iYBW05eJou2XGzX+GXD+I8nPE7nXOQzVYZDnN+1tGfn35L1z86iZHyUXZsTwJ1FA9VZk3ph6zCAn5YmBYg495fg2chFI=</SignatureValue>
      </Signature>
   </b>
   <b Id="q">
      <data>Some false data</data>
   </b>
</r>

The document demonstrates how two elements have the same id. The unpatched SignedXml.GetIdElement() method will only find and validate the first occurrence of the id, but code that loops all nodes and checks that the id is present in the signature’s references will trust both <b> nodes.

This is some typical validation code that will process untrusted data.

// Check signature
var signedXml = new SignedXml(xmlDoc);
signedXml.LoadXml(signatureNode);
Console.WriteLine("Signature is correct: " + signedXml.CheckSignature(key));
 
Console.Write("\n** Checking elements with name b **");
// Loop nodes and check against reference id.
foreach (var e in xmlDoc.SelectNodes("//b").Cast<XmlElement>())
{
  Console.WriteLine("\nMatching reference found in signedinfo: " 
    + signedXml.SignedInfo.References.Cast<Reference>()
      .Any(r => r.Uri == "#" + e.GetAttribute("Id")));
  Console.WriteLine("Contents of element: " + e["data"].InnerText);
}

When run on an unpatched system, it gives the following output:

Signature is correct: True
 
** Checking elements with name b **
Matching reference found in signedinfo: True
Contents of element: Valid data
 
Matching reference found in signedinfo: True
Contents of element: Some false data

On a patched system, a CryptograhpicException is thrown with the message “Malformed reference element.”. It is possible to disable the checking and revert to the old unsafe behaviour by adding a DWORD value under HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework\Security named SignedXmlAllowAmbiguousReferenceTargets with value 1.

The Vulnerable Code

The code that lies behind this vulernability is in the SignedXml.GetIdElement() method. This is the listing of the method in the reference source (retrieved 2016-03-10, .NET 4.6.1; it is still the old code and not the updated of the patch).

public virtual XmlElement GetIdElement (XmlDocument document, string idValue) {
  if (document == null)
    return null;
 
  // Get the element with idValue
  XmlElement elem = document.GetElementById(idValue);
  if (elem != null)
    return elem;
  elem = document.SelectSingleNode("//*[@Id=\"" + idValue + "\"]") as XmlElement;
  if (elem != null)
    return elem;
  elem = document.SelectSingleNode("//*[@id=\"" + idValue + "\"]") as XmlElement;
  if (elem != null)
    return elem;
  elem = document.SelectSingleNode("//*[@ID=\"" + idValue + "\"]") as XmlElement;
    return elem;
}

The problem is the usage of SelectSingleNode, which is what opens up for the duplicate id attack. For anyone being used to LINQ it has a deceiving name. Following the LINQ conventions it should be named SelectFirstNodeOrDefault. The signature will validate the first matching node and completely ignore if there are other nodes with the same id in the document.

Mitigating Factors

The ability to successfully exploit the vulnerability depends entirely on if the document format allows duplicate ids to be inserted. In the SAML2 case the risk is reduced by the fact that an enveloped signature is used and the id to be signed is the one of the enclosing element. As long as the signed element is the root of the document, the attack is not possible. If several assertions are allowed, a secondary assertion might be injected, with a signature copied from the first assertion. Or the assertion validated by the signature could be placed in an extensions element before the assertion. My assessment is that an attack is possible for many implementations of SAML2 and other protocols. By sheer luck, Kentor.AuthServices has never been vulnerable to this attack, as the XML of each assertion was extracted to an own XmlDocument before signature validation. The signature validation code was updated in 0.16.0 and by then, I had found this vulnerability and applied a workaround.

Workarounds

With the old implementation of SignedXml.GetIdElement() two workarounds are suggested.

Resolve References with SignedXml.GetIdElement()

The first workaround is to use SignedXml.GetIdElement() to resolve the references in the calling code. This guarantees that only exactly the same data as validated by the signature will be processed. This is what Kentor.AuthServices 0.16.0 uses.

foreach (var r in signedXml.SignedInfo.References.Cast<Reference>())
{
  if (r.Uri[0] == '#')
  {
    var e = x.GetIdElement(xmlDoc, r.Uri.Substring(1));
    Console.WriteLine("Contents of element: " + e.InnerText);
  }
}

While using this approach is safe, there are drawbacks as the context of the signed element is lost. The returned element is for sure the one that is signed, but it could be anywhere in the enclosing XML document.

Overload SignedXml.GetIdElement()

Another alternative is to overload the SignedXml.GetIdElement() method with a better implementation that checks if there are multiple elements with the same id. This is what the patched version from Microsoft does.

6 comments

  1. The registry fix appears to have no effect.

    HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework\Security named SignedXmlAllowAmbiguousReferenceTargets with value 1.

    This does not activate the original behaviour. Only removing the updates seems to work.

    1. Did you set it in the right part of the registry? There is a 32-bit and a 64-bit instance of those registry keys. For a 32-bit process you need to set it in the 32-bit part.

      1. I have created a DWORD as detailed here:

        HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework\Security

        name SignedXmlAllowAmbiguousReferenceTargets
        value 1

        Have restarted, but still get the error. I can see there is a QWORD option, that is 64-bit. Is that what you mean? If not, where can I find 32/64 bit option?

        I’ve switched the app pool between 32 and 64 bit, but still same error. Only way I’ve got it to work on a server is to remove the windows update relating to this, but now I’m reluctant to put new .NET patches on, because I know at some point MS is probably going to just force the changed code in.

        Does the registry hack work for you? Is your site .NET 2 or 4.x? Are you running a 32 or 64 bit app pool? I am on 4.x, but am free to use 32 or 64 bit for the site if I can get one of them to work locally.

        My server is Win 2008 R2, my local machine is Windows 10 64 bit. I can’t find the updates on Windows 10 to remove, hence I am stuck with the error locally. If I can get the registry hack to work, I can do that on the server and then feel safer to install .NET 4.6 which I need on there now.

      2. That’s the 64-bit version of the registry. for 32-bit applications you need to set it at HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\.NETFramework\Security name SignedXmlAllowAmbiguousReferenceTargets

      3. Set both of those up, restarted, switched the app pool between 32 bit and 64 bit, but I still get the new behaviour so our attempt to validate XML docs which was working is broken locally, but works fine on the server where I removed the windows update. The registry hack did not work there either; I tried that first before removing the patch.

        Have you actually tried the registry hack yourself and verified it works, or are you relying on what Microsoft told you? I’m pretty sure I am not doing anything stupid, I’ve tried all the combinations I can think of locally for the site but still cannot get it working.

      4. Yes, I tried this back when I wrote this post, or actually when I wrote the breaking changes post. I think this comment thread should have been on that post and not on this one. I’d suggest to move this question over to Stack Overflow where others can chime in. Leave a comment here with a link to the SO question once you’ve posted it and I’ll have a look. Also please state clearly in that question what exception message you are seeing. As per the breaking changes post, there are actually several security enhancements in the same patch and several corresponding settings to disable them.

Leave a comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.