Repository navigation
v2.15.0
Security release
2.15.0 fixes a batch of security issues in signature verification, response validation and message construction. All users should upgrade. Several checks are now on by default, so read the behaviour changes below before you deploy.
Fixed vulnerabilities
| Advisory | Severity | Summary |
|---|---|---|
| GHSA-w8r9-gp23-wcxp | Critical | XML signatures were verified with a certificate from the message when the IdP metadata had no X.509 signing certificate (for example RSAKeyValue only). Only metadata-declared certificates are trusted now, and KeyDescriptor use is honoured. |
| GHSA-wxh7-j2vx-w63f | High | Login responses issued for another service provider were accepted. Audience, Destination and Recipient are now checked. |
| GHSA-wr2x-fh6q-q243 | High | On the Redirect and SimpleSign bindings, the detached signature was not bound to the message being parsed. The signed octet string must now carry the parsed message, SigAlg and RelayState. |
| GHSA-wj54-2vfp-35f2 | High | Missing InResponseTo and replay checks; an invalid message signature was ignored when the assertion signature verified; wantMessageSigned was not enforced. The bearer SubjectConfirmationData validity window was not enforced either. |
| GHSA-3427-gh8x-vv2c | High | Unbounded DEFLATE inflation on the Redirect binding (denial of service). Inflated messages are capped at 1 MB. |
| GHSA-963m-r92r-qm7c | High | XML comments could split extracted values, so samlify read a different value than the one signed. Element text is now read whole. |
| GHSA-fq9x-v53h-7w5p | Medium | XML injection through attribute metadata (name, nameFormat, valueXsiType, valueXmlnsXs, valueXmlnsXsi) in generated assertions. Template rendering is hardened, and a strict renderer is added. An encryption certificate from SP metadata is now validated before it is copied into the response. |
| GHSA-63ch-w8pw-4mf2 | Medium | Signature wrapping on signed LogoutRequest / AuthnRequest: the signature of an embedded assertion could authenticate the message, and a genuine signed message hidden in <Extensions> of a forged root was accepted. Requests and logout messages now need their own root signature, and every signature must cover the element that contains it (saml-core §5.4.2), with or without schema validation. |
| GHSA-3gg4-m4mq-vcxc | Medium | Assertions encrypted with RSA-1.5 key transport or TripleDES were decrypted. They are now refused before the private key is used. |
Duplicate reports: GHSA-4g4q-xgjf-jcx5, GHSA-6gg9-3vvg-c3p4 and GHSA-p9hp-pg94-7847 (fixed with GHSA-wxh7-j2vx-w63f); GHSA-qwg3-7m52-ghg5 (fixed with GHSA-fq9x-v53h-7w5p).
Hardening
allowedSignatureAlgorithms(GHSA-59q7-p59f-fx9m):requestSignatureAlgorithmonly selects the algorithm an entity signs with; it never restricted what it accepts. The new SP / IdP setting restricts accepted signature and digest algorithms on every binding, for example to refuse SHA-1. This was not exploitable: the sender chooses the algorithm, and changing it does not allow a forgery. Unset keeps today's behaviour.- Signature wrapping for encrypted assertions (GHSA-rq37-f3qr-mr59): we found no bypass on either decryption path. The new signature-placement and fail-on-invalid-signature checks harden both paths further.
Also update the schema validator.
@authenio/xsd-schema-validator0.7.4 (used by@authenio/samlify-xsd-schema-validator) fixes a crash: input the JVM rejected early could terminate the Node process with an unhandledEPIPE. Reinstall or update so that 0.7.4 or later is resolved (npm ls @authenio/xsd-schema-validator).
⚠️ Behaviour changes
| Change | Error | Opt-out |
|---|---|---|
Audience, Destination and Recipient are checked against the SP |
ERR_AUDIENCE_MISMATCH, ERR_RECIPIENT_MISMATCH |
wantAudienceCheck: false |
| Peer metadata must declare an X.509 signing certificate; a certificate in the message must match it | NO_SELECTED_CERTIFICATE, ERROR_UNMATCH_CERTIFICATE_DECLARATION_IN_METADATA |
— |
KeyDescriptor use is honoured; a descriptor without use serves both and logs a warning |
— | — |
Response@InResponseTo must agree with the signed SubjectConfirmationData@InResponseTo |
ERR_INRESPONSETO_MISMATCH |
— |
A signature must be enveloped in the element it signs; signatures beside the element or covering only a child (such as <Issuer>) are rejected. Signatures made by samlify since 2.5.0 are not affected. |
ERR_POTENTIAL_WRAPPING_ATTACK |
— |
Any invalid signature fails the message; wantMessageSigned: true requires a signed <Response> |
FAILED_TO_VERIFY_SIGNATURE, ERR_MISSING_MESSAGE_SIGNATURE |
— |
| Requests and logout messages must carry a signature on their root element | FAILED_TO_VERIFY_SIGNATURE |
— |
| Element text that contains comments or CDATA is read whole | — | — |
| RSA-1.5 and TripleDES are refused on decryption | ERR_INSECURE_ENCRYPTION_ALGORITHM |
allowInsecureDecryptionAlgorithms: true |
Redirect / SimpleSign: octetString is required when the signature is checked, and must match the parsed message |
ERR_MISSING_OCTET_STRING, ERR_OCTET_STRING_MISMATCH |
— |
Bearer SubjectConfirmation required; every bearer SubjectConfirmationData must be within its NotBefore / NotOnOrAfter window; an assertion with no NotOnOrAfter at all is rejected; every SessionNotOnOrAfter is checked |
ERR_SUBJECT_UNCONFIRMED, ERR_EXPIRED_SESSION |
— |
inResponseTo passed as undefined / null (no pending request) rejects a response that answers a request; on POST an unsigned outer InResponseTo without a signed counterpart is rejected |
ERR_INRESPONSETO_MISMATCH |
omit the option |
Metadata certificates must be base64; tagPrefix values must be XML names |
ERR_INVALID_CERTIFICATE, ERR_INVALID_TAG_PREFIX |
— |
| A message whose root element does not match the parser is rejected | ERR_UNEXPECTED_MESSAGE_TYPE |
— |
A LogoutRequest must come from the expected issuer; LogoutRequest / LogoutResponse Destination must be one of the receiver's SingleLogoutService locations; an expired LogoutRequest is rejected; with replayCache, a LogoutRequest is accepted once |
ERR_UNMATCH_ISSUER, ERR_RECIPIENT_MISMATCH, ERR_EXPIRED_REQUEST, ERR_REPLAYED_REQUEST |
wantAudienceCheck: false (Destination) |
| Input that is not well-formed XML, contains a DTD, or characters outside the XML range is rejected before schema validation | ERR_INVALID_XML |
— |
| Redirect-binding messages are limited to 1 MB after inflation | ERR_INFLATED_MESSAGE_TOO_LARGE |
— |
{ and } in template values are written as { / } (the same text once parsed) |
— | — |
New features
sp.parseLoginResponse(idp, binding, request, { inResponseTo })checks that the response answers a request you issued (one ID or an array).allowUnsolicitedResponse: falseon the SP rejects IdP-initiated responses (ERR_UNSOLICITED_RESPONSE).replayCache: { saveIfAbsent(key, expiresAt) }on the SP rejects an assertion that was already accepted (ERR_REPLAYED_ASSERTION). samlify ships no store; plug in Redis, a database, or similar. (#639)templateRenderer: 'strict'on the SP / IdP, andSamlLib.renderTemplateforcustomTagReplacementcallbacks: a context-aware renderer that rejects placeholders in names, comments and other unsafe positions. See Migrating to the strict template renderer.allowedSignatureAlgorithmson the SP / IdP.- Redirect / SimpleSign parse results include
relayState, theRelayStatecovered by the signature. Detached signatures verify against every signing certificate in the peer's metadata (key rollover). - The extract now exposes
assertionID, plus the signedSubjectConfirmationDataandAudienceRestriction.
Deprecations
-
wantLogoutRequestSignedandwantLogoutResponseSigneddefault tofalse. Accepting an unsigned logout message now logs a warning. Both becometrueby default in v3; set them now if your peer signs logout messages. -
SamlLib.replaceTagsByValueis hardened but deprecated, and will be removed in v3. UseSamlLib.renderTemplate. The strict renderer becomes the default in v3. -
SignatureVerifierOptions.signatureAlgorithmis ignored. UseallowedSignatureAlgorithms.
Acknowledgements
Thank you to the security researchers who reported these issues and worked with us through coordinated disclosure:
- @0xLordMahesh: GHSA-wxh7-j2vx-w63f, GHSA-wj54-2vfp-35f2, GHSA-3gg4-m4mq-vcxc, GHSA-59q7-p59f-fx9m
- @ericchiang: GHSA-63ch-w8pw-4mf2
- @hkmj19: GHSA-w8r9-gp23-wcxp
- @EQSTLab: GHSA-3427-gh8x-vv2c
- @cbev0x: GHSA-963m-r92r-qm7c
- @SnailSploit and @0xShemesh: GHSA-fq9x-v53h-7w5p
- @hetmehta-kee: GHSA-qwg3-7m52-ghg5
- @oss-security-shopify: GHSA-4g4q-xgjf-jcx5
- @subhash00: GHSA-6gg9-3vvg-c3p4
- @ksanjeev284 (Sanjeev Kumar): GHSA-p9hp-pg94-7847
- @ahacker1-securesaml: GHSA-rq37-f3qr-mr59
GHSA-wr2x-fh6q-q243, the <Extensions> variant of GHSA-63ch-w8pw-4mf2, and the issues listed under behaviour changes that are not tied to an advisory were found while we validated these reports.
Fixes, tests and documentation by @tngan.
Full Changelog: v2.14.0...v2.15.0