11

There might be a problem in many applications based on XML signature verification (provided I am not wrong, of course).

Let's have a simple XML message with an enveloped XML signature:

<?xml version="1.0" encoding="UTF-8"?>
<message>

<msgenvelope id="SIGNED_DATA">some signed data</msgenvelope>

<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
 <SignedInfo>
  ...
  <Reference URI="#SIGNED_DATA">
   ...
  </Reference>
 </SignedInfo>
 <SignatureValue>naUY...+xZbEA=</SignatureValue>
</Signature>
</message>

According to this MSDN article, you should verify such a XML document with the SignedXml class:

SignedXml signedXml = new SignedXml(Doc); //Doc is my message
XmlNodeList nodeList = Doc.GetElementsByTagName("Signature");
signedXml.LoadXml((XmlElement)nodeList[0]);
bool ok = signedXml.CheckSignature(Key);
if (ok) {
    string signedData = Doc.SelectSingleNode("/message/msgenvelope").InnerText;
    //do something with the signed data
} else {
    //throw error or something
}

I think there is a problem: the CheckSignature method verifies if the signature value is correct, but not if the signed data is really the data which are expected to be signed.

An evil guy could modify the message this way:

<?xml version="1.0" encoding="UTF-8"?>
<message>

<msgenvelope id="anotherid">FAKE DATA!!</msgenvelope>

<evil_envelope>
 <msgenvelope id="SIGNED_DATA">some signed data</msgenvelope>
</evil_envelope>

<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
 <SignedInfo>
  ...
  <Reference URI="#SIGNED_DATA">
   ...
  </Reference>
 </SignedInfo>
 <SignatureValue>naUY...+xZbEA=</SignatureValue>
</Signature>
</message>

This message is verified correctly, because the signed element and the signature are still the same. However, resulting data string contains "FAKE DATA!!".

There are a few ways to avoid this attack - using schema verification against XSD, checking the id attribute of the trusted element etc. What is the recommended approach to get rid of this risk? Should the MSDN article be improved? Is there any reference implementation that handles this problem correctly?

vojta
  • 356
  • 4
  • 16
  • 6
    XML signatures are a typical example where the apparently useful flexibility of signing only part of the document resulted in a broken design. See [All Your Clouds are Belong to us – Security Analysis of Cloud Management Interfaces](http://www.nds.rub.de/media/nds/veroeffentlichungen/2011/10/22/AmazonSignatureWrapping.pdf) from 2011 how this could be used in practical attacks. – Steffen Ullrich Jan 10 '16 at 13:42
  • @SteffenUllrich Wow, that is amazing! I have never heard XML signature has been so vulnerable in so many ways. Is there any official recommendation how to handle these problems? Or should I use another signature technique? I need to use XML signature as a part of my project, but developing my own technique could be even more dangerous than using this broken one... – vojta Jan 10 '16 at 13:47
  • 3
    If you are required to use XML signatures then you "just" have to be very careful, i.e. do not trust the data if there is some signature inside but check which part of the document was actually signed. If you are not required to use XML signatures then you could also use PGP or similar and sign the full document. – Steffen Ullrich Jan 10 '16 at 14:46
  • @SteffenUllrich Thanks a lot! I am trying to find any official document by W3C suggesting how to avoid this problem, but I have not found any. This article (http://www.w3.org/TR/xmldsig-core/#sec-CoreValidation) does not cover the topic. Did they publish any statement? It is a serious security issue after all and I cannot just follow your advice from SO (although I appreciate it very much) - I could face some problems during security certification of my project... – vojta Jan 11 '16 at 11:45
  • Unfortunately I know of no official response. But you will also find more information at [OWASP](https://www.google.com/search?q=owasp+xml+signature). For me it is simply a broken design so one need to be careful. Insofar nothing special, i.e. the web is full of broken designs causing issues (CSRF, XSS...) – Steffen Ullrich Jan 11 '16 at 11:56
  • If you remove the reference from the Reference URI attribute (e.g. ), the entire XML document is signed, not just the element. The algorithm is not really broken, your use of it is explicitly limited to one element. It used to be pretty bad, but just because it was limited to SHA1 and 1024-bits keys. .NET 4.5 removes that limitation. – Drunken Code Monkey Jan 23 '17 at 03:40
  • This is pretty old but I was perusing over it and I realized you could simply add a strong hash of the executable file you are verifying the signature for as the ID of the signature you add to your file. When verifying, select the signature element with the correct ID by hashing the executing assembly at runtime instead of verifying the first signature you find. It effectively adds tamper protection to the signature verification mechanism. – Drunken Code Monkey Jan 05 '18 at 04:35

4 Answers4

2

To me, this seems not really as a error of the protocol but as wrongly used.

You get a part of the XML using Doc.SelectSingleNode("/message/msgenvelope") but you never checked that "/message/msgenvelope" is actually the signed part of the XML!

You have to treat the XML more like a file system with different files and not like one entity. Think like this: you have three files: a.exe, b.exe and secure.signature You check secure.signature and it tells you: File a.exe was signed by Trusted Guy and is not changed. If you then execute b.exe and think this is secure, it's not a security problem of the signature.

This is basically what you do in the example! You ask if the Signature is valid and get back that it is valid. But it also states <Reference URI="#SIGNED_DATA"> which means that the signature is valid exactly for #SIGNED_DATA and nothing else.

You then ask to get the part identified by /message/msgenvelope and treat it like it has a valid signature!

But /message/msgenvelope is clearly not the same as #SIGNED_DATA. It might identify the same element, it might not. But you most certainly never checked the signature of /message/msgenvelope explicitly!

Josef
  • 5,903
  • 25
  • 33
  • I understand the concept very well - "checking the id attribute of the trusted element" is exactly what I mentioned in my post. However, this is not a problem of the protocol at all, this is a problem of how it is documented. XML signature is a security issue - there must be a step-by-step normative document which developers could follow without taking care of possible caveats like this one. MSDN example is wrong, apparently. Provide a link to such a document and I will accept your answer. – vojta Jan 20 '16 at 14:40
  • @vojta I don't see where the MSDN example is wrong. The wrong part of the code is the `string signedData = Doc.SelectSingleNode("/message/msgenvelope").InnerText;` and I don't see that in the example. It is maybe incomplete because it doesn't show you how to extract the validated part of the XML, but it is not wrong. In the [W3C Recommendation](https://www.w3.org/TR/xmldsig-core/#sec-Security) seems to be an paragraph about what is and isn't signed. – Josef Jan 20 '16 at 14:50
  • Well, it does not check the `` at all. They teach you how to sign your whole document in the [first tutorial](https://msdn.microsoft.com/en-us/library/ms229745%28v=vs.110%29.aspx). After verification they conclude "The XML signature is valid." in [the second part](https://msdn.microsoft.com/en-us/library/ms229950%28v=vs.110%29.aspx). Do you really think they want to say "The signature is valid, but you still cannot trust your document" by words "The XML signature is valid"? – vojta Jan 20 '16 at 15:03
  • I am not sure what you mean exactly. Of course they need to check the because otherwise it is impossible to check the signature. And yes, "The XML signature is valid." does mean exactly that. The checked Signature (**you** selected the signature using `Doc.GetElementsByTagName("Signature");`) is valid. This means the signature is valid for exactly the reference given in the signature. If you change the signature to contain `` it will not be valid in the second example. – Josef Jan 20 '16 at 15:13
  • Also the W3C Recommendation states: "only what is signed is secure" "an envelope containing signed information is not secured by the signature" "[...] it only secures the plaintext actually signed" I am not sure how this can be made any clearer. – Josef Jan 20 '16 at 15:15
2

As already stated, this is that way by design. Probably MSDN article could (or should) be more explicit about that. The way you extracted the "signed" data is wrong. The XMLDSig documentation is clear about it. You may want to read section 8.1 of this link https://www.w3.org/TR/xmldsig-core/

Just as a user should only sign what he or she "sees," persons and automated mechanism that trust the validity of a transformed document on the basis of a valid signature should operate over the data that was transformed (including canonicalization) and signed, not the original pre-transformed data.

and

Note that the use of Canonical XML [XML-C14N] ensures that all internal entities and XML namespaces are expanded within the content being signed. All entities are replaced with their definitions and the canonical form explicitly represents the namespace that an element would otherwise inherit. Applications that do not canonicalize XML content (especially the SignedInfo element) SHOULD NOT use internal entities and SHOULD represent the namespace explicitly within the content being signed since they can not rely upon canonicalization to do this for them. Also, users concerned with the integrity of the element type definitions associated with the XML instance being signed may wish to sign those definitions as well (i.e., the schema, DTD, or natural language description associated with the namespace/identifier).

Second, an envelope containing signed information is not secured by the signature. For instance, when an encrypted envelope contains a signature, the signature does not protect the authenticity or integrity of unsigned envelope headers nor its ciphertext form, it only secures the plaintext actually signed.

More details on how to correctly validate a signature are found here: https://www.w3.org/TR/xmldsig-core/#sec-CoreValidation

The input to the first Transform is the result of dereferencing the URI attribute of the Reference element.

One should only trust on the result of dereferencing References from SignerInfo. All other data should not be considered signed and should not be presented to the user.

PS: Yes, XMLDSig is very complex. And implementations can easily get it wrong.

CristianTM
  • 2,532
  • 15
  • 20
1

XML Signature are prone too many attacks. That is due to the way XML as a file format is defined and how it is implemented by (parsing) frameworks.

https://www.owasp.org/images/5/5a/07A_Breaking_XML_Signature_and_Encryption_-_Juraj_Somorovsky.pdf

https://lists.w3.org/Archives/Public/public-xmlsec/2009Nov/att-0019/Camera-Ready.pdf

1

I will add this here because I have not seen the specific answer for this case. XML signing and related technologies were significantly improved in .NET 4.5. The support for any key length, and for SHA256 signatures was added. I have used this code successfully before.

The main problem is with your signature's Reference tag:

You explicitly tell the SignedXml class to look ONLY at that element. To sign the entire document, the Uri attribute should be empty. Here is an example of a SHA-256 signed XML document:

<License xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <Id>243</Id>
    <CustId>4365</CustId>
    <CustName>Joe Blow</CustName>
    ...

    <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/2001/04/xmldsig-more#rsa-sha256" />
            <Reference URI="">
                <Transforms>
                    <Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
                    <Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
                </Transforms>
                <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256" />
                <DigestValue>gNyvSh639wV7wHa4UYGPG524pjQ8JZBgaHhEiAm541k=</DigestValue>
            </Reference>
        </SignedInfo>
        <SignatureValue>ntQaT+PMZIS6eke81Vu0uRy8JJDhDfPic5e9Er34tDm00oprQ4qAFVJ1reuXSt+GIf/8XZAV0vR9RLqbB6R5K26lfQc5FCUotLYYjAYexFxwFzJqFV2hrYjhNxYHnXZRs37wY9iVbZlrG7fmEvqg7uN5cb1/K5a3VTFPoZvcUYkswfbzgxmdMdFDdOJCLLLA5oQEI3E60G32FABTJi11Sn9vCSnyePEJdi8yhJCUU9897bD7t2vkoyfbl7Ud5UyEPXUuKDBuX1uIUlU1WatlvH4qghaeV/LfQk8RSP7wHrtrB6T281ko+1+CdebnjTg5FTjo8vwknBXgDK8CRSQVm6DxNf0zeE+IGOhGXFRMCfFOsS9/jnKLT0wMIIqxPMKBX5cXDTX/4udHw6hLEc9H9X/vQLCyTl76ew8gdpgtZZKt8T/Tms8GUrAcIqZYIsUO399LS17lPtOJ2rXlzhDZSjRdVzHnQmGOWxDMtRF9Jb6b13Gr9JuXtPOmrJTl9kCsr+Dv81/h1aCa6xuwIkJtKS2n233+E6zsuSXj/eQJH56lsOJq9ijyXPtRV8LPXkY1Dta5vBwV2EeBA2LAzVOqU6SmM0B99XMCV90PcRLw71OnpdmMs/iUBQNyzn3Awk68hcJy5H3StZD5kl41RObYHQLvVU8/U6bFuwUiY1MAizM=</SignatureValue>
    </Signature>
</License>

This document was signed using this code:

public static XmlDocument SignXml(XmlDocument xmlDoc, RSACryptoServiceProvider rsaKey)
{
    try {
        SignedXml signedXml = new SignedXml(xmlDoc);
        signedXml.SigningKey = rsaKey;
        signedXml.SignedInfo.SignatureMethod = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256";

        Reference reference = new Reference();
        reference.Uri = "";
        reference.AddTransform(new XmlDsigEnvelopedSignatureTransform());
        reference.AddTransform(new XmlDsigExcC14NTransform());
        reference.DigestMethod = "http://www.w3.org/2001/04/xmlenc#sha256";

        signedXml.AddReference(reference);
        signedXml.ComputeSignature();

        XmlElement xmlDigitalSignature = signedXml.GetXml();
        xmlDoc.DocumentElement.AppendChild(xmlDoc.ImportNode(xmlDigitalSignature, true));
        if (xmlDoc.FirstChild.GetType() == typeof(XmlDeclaration))
            xmlDoc.RemoveChild(xmlDoc.FirstChild);

        return xmlDoc;
    } catch (Exception ex) {
        xmlDoc = null;
    }
    return xmlDoc;
}

Using this method, the entire document is signed, not just a single element.

You can verify the signature like this. Changing any byte in the signed file will invalidate the signature:

public static void VerifySignedXml(XmlDocument xmlDoc, RSACryptoServiceProvider rsaKey)
{
    SignedXml signedXml = new SignedXml(xmlDoc);
    XmlNodeList nodeList = xmlDoc.GetElementsByTagName("Signature");

    if (nodeList.Count > 0) {
        signedXml.LoadXml((XmlElement)nodeList(0));
    } else {
        throw new Exception("Signed XML verification failed: No Signature was found in the document.");
    }

    if (!signedXml.CheckSignature(rsaKey)) {
        throw new Exception("Signed XML verification failed: Document signature did not match.");
    }
}

Also please note that a document can have multiple signatures. For example you could keep the signature you have up there, specifically for that element, and then add another signature for the entire document. The code above assumes the first signature element found.