Skip to content

Commit 65455e0

Browse files
committed
Compliant SAML Response destination check (#31175)
Make SAML Response Destination check compliant Only validate the Destination element of an incoming SAML Response if Destination is present and the SAML Response is signed. The standard [1] - 3.5.5.2 and [2] - 3.2.2 does mention that the Destination element is optional and should only be verified when the SAML Response is signed. Some Identity Provider implementations are known to not set a Destination XML Attribute in their SAML responses when those are not signed, so this change also aims to enhance interoperability. [1] https://docs.oasis-open.org/security/saml/v2.0/saml-bindings-2.0-os.pdf [2] https://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf
1 parent 00b5e8c commit 65455e0

File tree

2 files changed

+50
-2
lines changed

2 files changed

+50
-2
lines changed

x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/saml/SamlAuthenticator.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,8 +151,10 @@ private String getSessionIndex(Assertion assertion) {
151151
private void checkResponseDestination(Response response) {
152152
final String asc = getSpConfiguration().getAscUrl();
153153
if (asc.equals(response.getDestination()) == false) {
154-
throw samlException("SAML response " + response.getID() + " is for destination " + response.getDestination()
154+
if (response.isSigned() || Strings.hasText(response.getDestination())) {
155+
throw samlException("SAML response " + response.getID() + " is for destination " + response.getDestination()
155156
+ " but this realm uses " + asc);
157+
}
156158
}
157159
}
158160

x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/saml/SamlAuthenticatorTests.java

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -523,13 +523,59 @@ public void testIncorrectDestinationIsRejected() throws Exception {
523523
"</assert:Attribute></assert:AttributeStatement>" +
524524
"</assert:Assertion>" +
525525
"</proto:Response>";
526-
SamlToken token = token(signDoc(xml));
526+
SamlToken token = randomBoolean() ? token(signDoc(xml)) : token(signAssertions(xml, idpSigningCertificatePair));
527527
final ElasticsearchSecurityException exception = expectSamlException(() -> authenticator.authenticate(token));
528528
assertThat(exception.getMessage(), containsString("destination"));
529529
assertThat(exception.getCause(), nullValue());
530530
assertThat(SamlUtils.isSamlException(exception), is(true));
531531
}
532532

533+
public void testMissingDestinationIsNotRejectedForNotSignedResponse() throws Exception {
534+
Instant now = clock.instant();
535+
Instant validUntil = now.plusSeconds(30);
536+
String sessionindex = randomId();
537+
final String xml = "<?xml version='1.0' encoding='UTF-8'?>\n" +
538+
"<proto:Response ID='" + randomId() + "' InResponseTo='" + requestId +
539+
"' IssueInstant='" + now + "' Version='2.0'" +
540+
" xmlns:proto='urn:oasis:names:tc:SAML:2.0:protocol'" +
541+
" xmlns:assert='urn:oasis:names:tc:SAML:2.0:assertion'" +
542+
" xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'" +
543+
" xmlns:xs='http://www.w3.org/2001/XMLSchema'" +
544+
" xmlns:ds='http://www.w3.org/2000/09/xmldsig#' >" +
545+
"<assert:Issuer>" + IDP_ENTITY_ID + "</assert:Issuer>" +
546+
"<proto:Status><proto:StatusCode Value='urn:oasis:names:tc:SAML:2.0:status:Success'/></proto:Status>" +
547+
"<assert:Assertion ID='" + sessionindex + "' IssueInstant='" + now + "' Version='2.0'>" +
548+
"<assert:Issuer>" + IDP_ENTITY_ID + "</assert:Issuer>" +
549+
"<assert:Subject>" +
550+
"<assert:NameID SPNameQualifier='" + SP_ENTITY_ID + "' Format='" + TRANSIENT + "'>randomopaquestring</assert:NameID>" +
551+
"<assert:SubjectConfirmation Method='" + METHOD_BEARER + "'>" +
552+
"<assert:SubjectConfirmationData NotOnOrAfter='" + validUntil + "' Recipient='" + SP_ACS_URL + "' " +
553+
" InResponseTo='" + requestId + "'/>" +
554+
"</assert:SubjectConfirmation>" +
555+
"</assert:Subject>" +
556+
"<assert:AuthnStatement AuthnInstant='" + now + "' SessionNotOnOrAfter='" + validUntil +
557+
"' SessionIndex='" + sessionindex + "'>" +
558+
"<assert:AuthnContext>" +
559+
"<assert:AuthnContextClassRef>" + PASSWORD_AUTHN_CTX + "</assert:AuthnContextClassRef>" +
560+
"</assert:AuthnContext>" +
561+
"</assert:AuthnStatement>" +
562+
"<assert:AttributeStatement><assert:Attribute " +
563+
" NameFormat='urn:oasis:names:tc:SAML:2.0:attrname-format:uri' Name='urn:oid:0.9.2342.19200300.100.1.1'>" +
564+
"<assert:AttributeValue xsi:type='xs:string'>daredevil</assert:AttributeValue>" +
565+
"</assert:Attribute></assert:AttributeStatement>" +
566+
"</assert:Assertion>" +
567+
"</proto:Response>";
568+
SamlToken token = token(signAssertions(xml, idpSigningCertificatePair));
569+
final SamlAttributes attributes = authenticator.authenticate(token);
570+
assertThat(attributes, notNullValue());
571+
assertThat(attributes.attributes(), iterableWithSize(1));
572+
final List<String> uid = attributes.getAttributeValues("urn:oid:0.9.2342.19200300.100.1.1");
573+
assertThat(uid, contains("daredevil"));
574+
assertThat(uid, iterableWithSize(1));
575+
assertThat(attributes.name(), notNullValue());
576+
assertThat(attributes.name().format, equalTo(TRANSIENT));
577+
}
578+
533579
public void testIncorrectRequestIdIsRejected() throws Exception {
534580
Instant now = clock.instant();
535581
Instant validUntil = now.plusSeconds(30);

0 commit comments

Comments
 (0)