这是封装 XML 签名的弱点吗?

信息安全 密码学 电子签名 。网 xml
2021-08-19 23:41:28

许多基于 XML 签名验证的应用程序中可能存在问题(当然,前提是我没有错)。

让我们有一个带有封装 XML 签名的简单 XML 消息:

<?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>

根据这篇MSDN文章,您应该使用类验证这样的 XML 文档SignedXml

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
}

我认为有一个问题:该CheckSignature方法验证签名值是否正确,但不验证签名数据是否真的是预期要签名的数据。

一个邪恶的家伙可以这样修改消息:

<?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>

该消息被正确验证,因为签名元素和签名仍然相同。但是,结果数据字符串包含“FAKE DATA!!”。

有几种方法可以避免这种攻击——对 XSD 使用模式验证、检查id受信任元素的属性等。消除这种风险的推荐方法是什么?是否应该改进 MSDN 文章?是否有任何参考实现可以正确处理此问题?

4个回答

如前所述,这就是设计的方式。可能 MSDN 文章可以(或应该)对此更明确。您提取“签名”数据的方式是错误的。XMLDSig 文档对此很清楚。您可能想阅读此链接的第 8.1 节https://www.w3.org/TR/xmldsig-core/

就像用户应该只签署他或她“看到的东西”一样,基于有效签名信任已转换文档有效性的人员和自动化机制应该对已转换(包括规范化)和签名的数据进行操作,而不是原始的预转换数据。

请注意,Canonical XML [XML-C14N] 的使用确保所有内部实体和 XML 名称空间在被签名的内容中扩展。所有实体都被它们的定义替换,规范形式明确表示元素将继承的命名空间。不规范化 XML 内容(尤其是 SignedInfo 元素)的应用程序不应该使用内部实体,并且应该在被签名的内容中明确表示命名空间,因为它们不能依赖规范化来为它们执行此操作。此外,关心与正在签名的 XML 实例相关联的元素类型定义的完整性的用户可能希望也对这些定义进行签名(即,模式、DTD 或与名称空间/标识符相关联的自然语言描述)。

其次,包含签名信息的信封不受签名保护。例如,当加密的信封包含签名时,签名不保护未签名的信封头及其密文形式的真实性或完整性,它只保护实际签名的明文。

有关如何正确验证签名的更多详细信息,请参见此处:https ://www.w3.org/TR/xmldsig-core/#sec-CoreValidation

第一个 Transform 的输入是取消引用 Reference 元素的 URI 属性的结果。

应该只信任从 SignerInfo 取消引用引用的结果。所有其他数据不应被视为已签名,也不应提供给用户。

PS:是的,XMLDSig 非常复杂。实现很容易出错。

对我来说,这似乎不是协议的错误,而是错误使用。

您使用了 XML 的一部分,Doc.SelectSingleNode("/message/msgenvelope")但您从未检查过这"/message/msgenvelope"实际上是 XML 的签名部分!

您必须将 XML 更像是一个具有不同文件的文件系统,而不是一个实体。像这样想:您有三个文件:a.exeb.exesecure.signature您检查secure.signature并告诉您: 文件a.exe由 Trusted Guy 签名并且没有更改。如果你然后执行b.exe并认为这是安全的,这不是签名的安全问题。

这基本上就是您在示例中所做的!您询问签名是否有效并返回它是否有效。但它也指出<Reference URI="#SIGNED_DATA">这意味着签名对#SIGNED_DATA完全有效,仅此而已。

然后,您要求获取由/message/msgenvelope标识的部分,并将其视为具有有效签名!

但是/message/msgenvelope显然与#SIGNED_DATA 不同它可能会识别相同的元素,也可能不会。但是您肯定从未明确检查过/message/msgenvelope的签名!

XML 签名容易受到太多攻击。这是由于定义 XML 作为文件格式的方式以及它是如何由(解析)框架实现的。

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

我将在此处添加此内容,因为我还没有看到此案例的具体答案。XML 签名和相关技术在 .NET 4.5 中得到了显着改进。添加了对任何密钥长度和 SHA256 签名的支持。我之前已经成功使用过这段代码。

主要问题在于您签名的参考标签:

您明确告诉 SignedXml 类只查看该元素。要签署整个文档,Uri 属性应该为空。以下是 SHA-256 签名的 XML 文档的示例:

<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>

本文档使用以下代码签署:

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;
}

使用这种方法,对整个文档进行签名,而不仅仅是单个元素。

您可以像这样验证签名。更改签名文件中的任何字节都会使签名无效:

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.");
    }
}

另请注意,一份文件可以有多个签名。例如,您可以将签名保留在那里,专门针对该元素,然后为整个文档添加另一个签名。上面的代码假定找到的第一个签名元素。