CVE-2022-39300: Improper Verification of Cryptographic Signature (CWE-347)
value: node SAML is a SAML 2.0 library based on the SAML implementation of passport-saml. A remote attacker may be able to bypass SAML authentication on a website using passport-saml. A successful attack requires that the attacker is in possession of an arbitrary IDP signed XML element. Depending on the IDP used, fully unauthenticated attacks (e.g without access to a valid user) might also be feasible if generation of a signed message can be triggered. Users should upgrade to node-saml version 4.0.0-beta5 or newer. Disabling SAML authentication may be done as a workaround.
CWE description: The product does not verify, or incorrectly verifies, the cryptographic signature for data.
CVSS v3 base score: 8.1
Published: 2022-10-13T22:15Z
Repository: https://github.com/node-saml/node-saml
Language: TypeScript
Files changed: src/saml.ts, test/static/signatures/invalid/response.root-signed.multiple-root-elements.xml, test/test-signatures.spec.ts
Commit date: 2022-10-11 13:21:18 -0400
Commit message: Merge pull request from GHSA-5p8w-2mvw-38pv


Diff: diff --git a/src/saml.ts b/src/saml.ts
index 022d6a7..4fbeb8d 100644
--- a/src/saml.ts
+++ b/src/saml.ts
@@ -686,13 +686,18 @@ class SAML {
         inResponseTo = inResponseToNodes.length ? inResponseToNodes[0].nodeValue : null;
 
         await this.validateInResponseTo(inResponseTo);
       }
       const certs = await this.certsToCheck();
-      // Check if this document has a valid top-level signature
+      // Check if this document has a valid top-level signature which applies to the entire XML document
       let validSignature = false;
-      if (validateSignature(xml, doc.documentElement, certs)) {
+      if (
+        validateSignature(xml, doc.documentElement, certs) &&
+        Array.from(doc.childNodes as NodeListOf<Element>).filter(
+          (n) => n.tagName != null && n.childNodes != null
+        ).length === 1
+      ) {
         validSignature = true;
       }
 
       if (this.options.wantAuthnResponseSigned === true && validSignature === false) {
         throw new Error("Invalid document signature");
diff --git a/test/static/signatures/invalid/response.root-signed.multiple-root-elements.xml b/test/static/signatures/invalid/response.root-signed.multiple-root-elements.xml
new file mode 100644
index 0000000..07c0706
--- /dev/null
+++ b/test/static/signatures/invalid/response.root-signed.multiple-root-elements.xml
@@ -0,0 +1,90 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<samlp:Response xmlns="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="IDVALUE" Version="2.0" IssueInstant="2004-10-08T14:38:05Z">
+  <samlp:Status>
+    <samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Responder">
+      <samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:NoPassive">
+        <samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:PartialLogout">
+        </samlp:StatusCode>
+      </samlp:StatusCode>
+    </samlp:StatusCode>
+    <samlp:StatusMessage>Random Error</samlp:StatusMessage>
+  </samlp:Status>
+  <Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
+  <SignedInfo>
+    <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
+    <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
+    <Reference URI="#IDVALUE">
+      <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/2000/09/xmldsig#sha1"/>
+      <DigestValue>sy2JL707GPx1uvsGKrALB+MVPek=</DigestValue>
+    </Reference>
+    </SignedInfo>
+  <SignatureValue>N4Pvot/wDRMmSdtp9XWJpK5krnSr9SZP7Ejeal8HaqZcjGXkYd35RJEiM69lHOI+
+80vrtr1pKokvHHh/iAmZr5daqKofmy70RAzt2SfxWyjkT46nkJpJ4R2MraJvrEjR
+qqwWKLwuPl6V64STUwId4DRpZyDt3u1+aaw0i0RaiQV6nKSXj1ODs3/OTehxtBbs
+Ok6kr03Z7lDu0Wv8qmJhwyMg1G+usW+hFdJZkpjzucSyGP2eVgJT7JvayVHlF/Se
+eT65266iWLE2kImImPpcw0HSVWKdOGR1EQNzGGtmYk/PjbyVmBfHZodvQm/EqT8q
+8Gxd+AmAINfG0Uvrm7p6dw==</SignatureValue>
+  <KeyInfo>
+    <X509Data>
+<X509Certificate>MIIDtTCCAp2gAwIBAgIJAKg4VeVcIDz1MA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV
+BAYTAlVTMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX
+aWRnaXRzIFB0eSBMdGQwHhcNMTUwODEzMDE1NDIwWhcNMTUwOTEyMDE1NDIwWjBF
+MQswCQYDVQQGEwJVUzETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50
+ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
+CgKCAQEAxG3ouM7U+fXbJt69X1H6d4UNg/uRr06pFuU9RkfIwNC+yaXyptqB3ynX
+KsL7BFt4DCd0fflRvJAx3feJIDp16wN9GDVHcufWMYPhh2j5HcTW/j9JoIJzGhJy
+vO00YKBt+hHy83iN1SdChKv5y0iSyiPP5GnqFw+ayyHoM6hSO0PqBou1Xb0ZSIE+
+DHosBnvVna5w2AiPY4xrJl9yZHZ4Q7DfMiYTgstjETio4bX+6oLiBnYktn7DjdEs
+lqhffVme4PuBxNojI+uCeg/sn4QVLd/iogMJfDWNuLD8326Mi/FE9cCRvFlvAiMS
+aebMI3zPaySsxTK7Zgj5TpEbmbHI9wIDAQABo4GnMIGkMB0GA1UdDgQWBBSVGgvo
+W4MhMuzBGce29PY8vSzHFzB1BgNVHSMEbjBsgBSVGgvoW4MhMuzBGce29PY8vSzH
+F6FJpEcwRTELMAkGA1UEBhMCVVMxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNV
+BAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZIIJAKg4VeVcIDz1MAwGA1UdEwQF
+MAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAJu1rqs+anD74dbdwgd3CnqnQsQDJiEX
+mBhG2leaGt3ve9b/9gKaJg2pyb2NyppDe1uLqh6nNXDuzg1oNZrPz5pJL/eCXPl7
+FhxhMUi04TtLf8LeNTCIWYZiFuO4pmhohHcv8kRvYR1+6SkLTC8j/TZerm7qvesS
+iTQFNapa1eNdVQ8nFwVkEtWl+JzKEM1BlRcn42sjJkijeFp7DpI7pU+PnYeiaXpR
+v5pJo8ogM1iFxN+SnfEs0EuQ7fhKIG9aHKi7bKZ7L6SyX7MDIGLeulEU6lf5D9Bf
+XNmcMambiS0pXhL2QXajt96UBq8FT2KNXY8XNtR4y6MyyCzhaiZZcc8=</X509Certificate>
+</X509Data>
+  </KeyInfo>
+</Signature>
+</samlp:Response>
+<Response>
+<saml:Assertion ID="_bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" IssueInstant="2020-09-25T16:00:00+00:00" Version="2.0">
+        <saml:Issuer>https://evil-corp.com</saml:Issuer>
+        <saml:Subject>
+            <saml:NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress">vincent.vega@evil-corp.com
+            </saml:NameID>
+            <saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
+                <saml:SubjectConfirmationData InResponseTo="_e8df3fe5f04237d25670" NotOnOrAfter="2020-09-25T17:00:00+00:00" Recipient="https://evil-corp.madness.com/sso/callback"/>
+            </saml:SubjectConfirmation>
+        </saml:Subject>
+        <saml:Conditions NotBefore="2020-09-25T16:00:00+00:00" NotOnOrAfter="2020-09-25T17:00:00+00:00"/>
+        <saml:AuthnStatement AuthnInstant="2020-09-25T16:00:00+00:00" SessionIndex="_9e315bdf7b1b6732be33c377cf6f5c4f">
+            <saml:AuthnContext>
+                <saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport
+                </saml:AuthnContextClassRef>
+            </saml:AuthnContext>
+        </saml:AuthnStatement>
+        <saml:AttributeStatement>
+            <saml:Attribute Name="evil-corp.egroupid">
+                <saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">
+                    vincent.vega@evil-corp.com
+                </saml:AttributeValue>
+            </saml:Attribute>
+            <saml:Attribute Name="evilcorp.givenname">
+                <saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">Vincent
+                </saml:AttributeValue>
+            </saml:Attribute>
+            <saml:Attribute Name="evilcorp.sn">
+                <saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">VEGA
+                </saml:AttributeValue>
+            </saml:Attribute>
+        </saml:AttributeStatement>
+    </saml:Assertion>
+  </Response>
\ No newline at end of file
diff --git a/test/test-signatures.spec.ts b/test/test-signatures.spec.ts
index 168801c..c0f346f 100644
--- a/test/test-signatures.spec.ts
+++ b/test/test-signatures.spec.ts
@@ -65,10 +65,21 @@ describe("Signatures", function () {
         amountOfSignaturesChecks,
         options
       );
   };
 
+  describe("Signatures - multiple roots are considered invalid", () => {
+    it(
+      "multiple roots => invalid",
+      testOneResponse(
+        "/invalid/response.root-signed.multiple-root-elements.xml",
+        INVALID_DOCUMENT_SIGNATURE,
+        1
+      )
+    );
+  });
+
   describe("Signatures on saml:Response - Only 1 saml:Assertion", () => {
     let fakeClock: sinon.SinonFakeTimers;
 
     beforeEach(function () {
       fakeClock = sinon.useFakeTimers(Date.parse("2020-09-25T16:59:00Z"));

Vulnerable code:       // Check if this document has a valid top-level signature
      if (validateSignature(xml, doc.documentElement, certs)) {
Fixed code:       // Check if this document has a valid top-level signature which applies to the entire XML document
      if (
        validateSignature(xml, doc.documentElement, certs) &&
        Array.from(doc.childNodes as NodeListOf<Element>).filter(
          (n) => n.tagName != null && n.childNodes != null
        ).length === 1
      ) {
<?xml version="1.0" encoding="UTF-8"?>
<samlp:Response xmlns="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="IDVALUE" Version="2.0" IssueInstant="2004-10-08T14:38:05Z">
  <samlp:Status>
    <samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Responder">
      <samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:NoPassive">
        <samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:PartialLogout">
        </samlp:StatusCode>
      </samlp:StatusCode>
    </samlp:StatusCode>
    <samlp:StatusMessage>Random Error</samlp:StatusMessage>
  </samlp:Status>
  <Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
  <SignedInfo>
    <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
    <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
    <Reference URI="#IDVALUE">
      <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/2000/09/xmldsig#sha1"/>
      <DigestValue>sy2JL707GPx1uvsGKrALB+MVPek=</DigestValue>
    </Reference>
    </SignedInfo>
  <SignatureValue>N4Pvot/wDRMmSdtp9XWJpK5krnSr9SZP7Ejeal8HaqZcjGXkYd35RJEiM69lHOI+
80vrtr1pKokvHHh/iAmZr5daqKofmy70RAzt2SfxWyjkT46nkJpJ4R2MraJvrEjR
qqwWKLwuPl6V64STUwId4DRpZyDt3u1+aaw0i0RaiQV6nKSXj1ODs3/OTehxtBbs
Ok6kr03Z7lDu0Wv8qmJhwyMg1G+usW+hFdJZkpjzucSyGP2eVgJT7JvayVHlF/Se
eT65266iWLE2kImImPpcw0HSVWKdOGR1EQNzGGtmYk/PjbyVmBfHZodvQm/EqT8q
8Gxd+AmAINfG0Uvrm7p6dw==</SignatureValue>
  <KeyInfo>
    <X509Data>
<X509Certificate>MIIDtTCCAp2gAwIBAgIJAKg4VeVcIDz1MA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV
BAYTAlVTMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX
aWRnaXRzIFB0eSBMdGQwHhcNMTUwODEzMDE1NDIwWhcNMTUwOTEyMDE1NDIwWjBF
MQswCQYDVQQGEwJVUzETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50
ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
CgKCAQEAxG3ouM7U+fXbJt69X1H6d4UNg/uRr06pFuU9RkfIwNC+yaXyptqB3ynX
KsL7BFt4DCd0fflRvJAx3feJIDp16wN9GDVHcufWMYPhh2j5HcTW/j9JoIJzGhJy
vO00YKBt+hHy83iN1SdChKv5y0iSyiPP5GnqFw+ayyHoM6hSO0PqBou1Xb0ZSIE+
DHosBnvVna5w2AiPY4xrJl9yZHZ4Q7DfMiYTgstjETio4bX+6oLiBnYktn7DjdEs
lqhffVme4PuBxNojI+uCeg/sn4QVLd/iogMJfDWNuLD8326Mi/FE9cCRvFlvAiMS
aebMI3zPaySsxTK7Zgj5TpEbmbHI9wIDAQABo4GnMIGkMB0GA1UdDgQWBBSVGgvo
W4MhMuzBGce29PY8vSzHFzB1BgNVHSMEbjBsgBSVGgvoW4MhMuzBGce29PY8vSzH
F6FJpEcwRTELMAkGA1UEBhMCVVMxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNV
BAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZIIJAKg4VeVcIDz1MAwGA1UdEwQF
MAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAJu1rqs+anD74dbdwgd3CnqnQsQDJiEX
mBhG2leaGt3ve9b/9gKaJg2pyb2NyppDe1uLqh6nNXDuzg1oNZrPz5pJL/eCXPl7
FhxhMUi04TtLf8LeNTCIWYZiFuO4pmhohHcv8kRvYR1+6SkLTC8j/TZerm7qvesS
iTQFNapa1eNdVQ8nFwVkEtWl+JzKEM1BlRcn42sjJkijeFp7DpI7pU+PnYeiaXpR
v5pJo8ogM1iFxN+SnfEs0EuQ7fhKIG9aHKi7bKZ7L6SyX7MDIGLeulEU6lf5D9Bf
XNmcMambiS0pXhL2QXajt96UBq8FT2KNXY8XNtR4y6MyyCzhaiZZcc8=</X509Certificate>
</X509Data>
  </KeyInfo>
</Signature>
</samlp:Response>
<Response>
<saml:Assertion ID="_bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" IssueInstant="2020-09-25T16:00:00+00:00" Version="2.0">
        <saml:Issuer>https://evil-corp.com</saml:Issuer>
        <saml:Subject>
            <saml:NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress">vincent.vega@evil-corp.com
            </saml:NameID>
            <saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
                <saml:SubjectConfirmationData InResponseTo="_e8df3fe5f04237d25670" NotOnOrAfter="2020-09-25T17:00:00+00:00" Recipient="https://evil-corp.madness.com/sso/callback"/>
            </saml:SubjectConfirmation>
        </saml:Subject>
        <saml:Conditions NotBefore="2020-09-25T16:00:00+00:00" NotOnOrAfter="2020-09-25T17:00:00+00:00"/>
        <saml:AuthnStatement AuthnInstant="2020-09-25T16:00:00+00:00" SessionIndex="_9e315bdf7b1b6732be33c377cf6f5c4f">
            <saml:AuthnContext>
                <saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport
                </saml:AuthnContextClassRef>
            </saml:AuthnContext>
        </saml:AuthnStatement>
        <saml:AttributeStatement>
            <saml:Attribute Name="evil-corp.egroupid">
                <saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">
                    vincent.vega@evil-corp.com
                </saml:AttributeValue>
            </saml:Attribute>
            <saml:Attribute Name="evilcorp.givenname">
                <saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">Vincent
                </saml:AttributeValue>
            </saml:Attribute>
            <saml:Attribute Name="evilcorp.sn">
                <saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">VEGA
                </saml:AttributeValue>
            </saml:Attribute>
        </saml:AttributeStatement>
    </saml:Assertion>
  </Response>
  describe("Signatures - multiple roots are considered invalid", () => {
    it(
      "multiple roots => invalid",
      testOneResponse(
        "/invalid/response.root-signed.multiple-root-elements.xml",
        INVALID_DOCUMENT_SIGNATURE,
        1
      )
    );
  });