Moderate severityNVD Advisory· Published Nov 18, 2015· Updated May 6, 2026
CVE-2015-5253
CVE-2015-5253
Description
The SAML Web SSO module in Apache CXF before 2.7.18, 3.0.x before 3.0.7, and 3.1.x before 3.1.3 allows remote authenticated users to bypass authentication via a crafted SAML response with a valid signed assertion, related to a "wrapping attack."
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
org.apache.cxf:cxf-rt-rs-security-sso-samlMaven | < 2.7.18 | 2.7.18 |
org.apache.cxf:cxf-rt-rs-security-sso-samlMaven | >= 3.0.0, < 3.0.7 | 3.0.7 |
org.apache.cxf:cxf-rt-rs-security-sso-samlMaven | >= 3.1.0, < 3.1.3 | 3.1.3 |
Affected products
1Patches
3845eccb6484bAdding SAML SSO tests.
3 files changed · +226 −5
rt/rs/security/sso/saml/src/main/java/org/apache/cxf/rs/security/saml/sso/SAMLSSOResponseValidator.java+4 −5 modified@@ -92,7 +92,7 @@ public SSOValidatorResponse validateSamlResponse( } // Validate Assertions - boolean foundValidSubject = false; + org.opensaml.saml.saml2.core.Assertion validAssertion = null; Date sessionNotOnOrAfter = null; for (org.opensaml.saml.saml2.core.Assertion assertion : samlResponse.getAssertions()) { // Check the Issuer @@ -114,7 +114,7 @@ public SSOValidatorResponse validateSamlResponse( org.opensaml.saml.saml2.core.Subject subject = assertion.getSubject(); if (validateAuthenticationSubject(subject, assertion.getID(), postBinding)) { validateAudienceRestrictionCondition(assertion.getConditions()); - foundValidSubject = true; + validAssertion = assertion; // Store Session NotOnOrAfter for (AuthnStatement authnStatment : assertion.getAuthnStatements()) { if (authnStatment.getSessionNotOnOrAfter() != null) { @@ -123,10 +123,9 @@ public SSOValidatorResponse validateSamlResponse( } } } - } - if (!foundValidSubject) { + if (validAssertion == null) { LOG.fine("The Response did not contain any Authentication Statement that matched " + "the Subject Confirmation criteria"); throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity"); @@ -140,7 +139,7 @@ public SSOValidatorResponse validateSamlResponse( } // the assumption for now is that SAMLResponse will contain only a single assertion - Element assertionElement = samlResponse.getAssertions().get(0).getDOM(); + Element assertionElement = validAssertion.getDOM(); Element clonedAssertionElement = (Element)assertionElement.cloneNode(true); validatorResponse.setAssertionElement(clonedAssertionElement); validatorResponse.setAssertion(DOM2Writer.nodeToString(clonedAssertionElement));
rt/rs/security/sso/saml/src/test/java/org/apache/cxf/rs/security/saml/sso/AbstractSAMLCallbackHandler.java+4 −0 modified@@ -130,6 +130,10 @@ public void setSubjectLocality(String ipAddress, String dnsAddress) { this.subjectLocalityDnsAddress = dnsAddress; } + public void setSubjectName(String subjectName) { + this.subjectName = subjectName; + } + public void setResource(String resource) { this.resource = resource; }
rt/rs/security/sso/saml/src/test/java/org/apache/cxf/rs/security/saml/sso/CombinedValidatorTest.java+218 −0 added@@ -0,0 +1,218 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.cxf.rs.security.saml.sso; + +import java.io.InputStream; +import java.security.KeyStore; +import java.util.Collections; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import org.apache.wss4j.common.crypto.Crypto; +import org.apache.wss4j.common.crypto.Merlin; +import org.apache.wss4j.common.saml.OpenSAMLUtil; +import org.apache.wss4j.common.saml.SAMLCallback; +import org.apache.wss4j.common.saml.SAMLUtil; +import org.apache.wss4j.common.saml.SamlAssertionWrapper; +import org.apache.wss4j.common.saml.bean.AudienceRestrictionBean; +import org.apache.wss4j.common.saml.bean.ConditionsBean; +import org.apache.wss4j.common.saml.bean.SubjectConfirmationDataBean; +import org.apache.wss4j.common.saml.builder.SAML2Constants; +import org.apache.wss4j.common.util.Loader; +import org.apache.wss4j.dom.WSConstants; +import org.apache.wss4j.dom.WSSConfig; +import org.joda.time.DateTime; +import org.opensaml.saml.common.xml.SAMLConstants; +import org.opensaml.saml.saml2.core.Response; +import org.opensaml.saml.saml2.core.Status; + +/** + * Some unit tests for the SAMLProtocolResponseValidator and the SAMLSSOResponseValidator + */ +public class CombinedValidatorTest extends org.junit.Assert { + + static { + WSSConfig.init(); + OpenSAMLUtil.initSamlEngine(); + } + + @org.junit.Test + public void testSuccessfulValidation() throws Exception { + + Element responseElement = createResponse(); + Response marshalledResponse = (Response)OpenSAMLUtil.fromDom(responseElement); + + Crypto issuerCrypto = new Merlin(); + KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + ClassLoader loader = Loader.getClassLoader(CombinedValidatorTest.class); + InputStream input = Merlin.loadInputStream(loader, "alice.jks"); + keyStore.load(input, "password".toCharArray()); + ((Merlin)issuerCrypto).setKeyStore(keyStore); + + // Validate the Response + SAMLProtocolResponseValidator validator = new SAMLProtocolResponseValidator(); + validator.validateSamlResponse( + marshalledResponse, issuerCrypto, new KeystorePasswordCallback() + ); + + // Test SSO validation + SAMLSSOResponseValidator ssoValidator = new SAMLSSOResponseValidator(); + ssoValidator.setIssuerIDP("http://cxf.apache.org/issuer"); + ssoValidator.setAssertionConsumerURL("http://recipient.apache.org"); + ssoValidator.setClientAddress("http://apache.org"); + ssoValidator.setRequestId("12345"); + ssoValidator.setSpIdentifier("http://service.apache.org"); + + // Parse the response + SSOValidatorResponse ssoResponse = + ssoValidator.validateSamlResponse(marshalledResponse, false); + SamlAssertionWrapper parsedAssertion = + new SamlAssertionWrapper(ssoResponse.getAssertionElement()); + + assertEquals("alice", parsedAssertion.getSubjectName()); + } + + @org.junit.Test + public void testWrappingAttack3() throws Exception { + Element responseElement = createResponse(); + + // Get Assertion Element + Element assertionElement = + (Element)responseElement.getElementsByTagNameNS(SAMLConstants.SAML20_NS, "Assertion").item(0); + assertNotNull(assertionElement); + + // Clone it, strip the Signature, modify the Subject, change Subj Conf + Element clonedAssertion = (Element)assertionElement.cloneNode(true); + clonedAssertion.setAttributeNS(null, "ID", "_12345623562"); + Element sigElement = + (Element)clonedAssertion.getElementsByTagNameNS(WSConstants.SIG_NS, "Signature").item(0); + clonedAssertion.removeChild(sigElement); + + Element subjElement = + (Element)clonedAssertion.getElementsByTagNameNS(SAMLConstants.SAML20_NS, "Subject").item(0); + Element subjNameIdElement = + (Element)subjElement.getElementsByTagNameNS(SAMLConstants.SAML20_NS, "NameID").item(0); + subjNameIdElement.setTextContent("bob"); + + Element subjConfElement = + (Element)subjElement.getElementsByTagNameNS(SAMLConstants.SAML20_NS, "SubjectConfirmation").item(0); + subjConfElement.setAttributeNS(null, "Method", SAML2Constants.CONF_SENDER_VOUCHES); + + // Now insert the modified cloned Assertion into the Response before actual assertion + responseElement.insertBefore(clonedAssertion, assertionElement); + + // System.out.println(DOM2Writer.nodeToString(responseElement)); + + Response marshalledResponse = (Response)OpenSAMLUtil.fromDom(responseElement); + + Crypto issuerCrypto = new Merlin(); + KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + ClassLoader loader = Loader.getClassLoader(CombinedValidatorTest.class); + InputStream input = Merlin.loadInputStream(loader, "alice.jks"); + keyStore.load(input, "password".toCharArray()); + ((Merlin)issuerCrypto).setKeyStore(keyStore); + + // Validate the Response + SAMLProtocolResponseValidator validator = new SAMLProtocolResponseValidator(); + validator.validateSamlResponse( + marshalledResponse, issuerCrypto, new KeystorePasswordCallback() + ); + + // Test SSO validation + SAMLSSOResponseValidator ssoValidator = new SAMLSSOResponseValidator(); + ssoValidator.setIssuerIDP("http://cxf.apache.org/issuer"); + ssoValidator.setAssertionConsumerURL("http://recipient.apache.org"); + ssoValidator.setClientAddress("http://apache.org"); + ssoValidator.setRequestId("12345"); + ssoValidator.setSpIdentifier("http://service.apache.org"); + + // Parse the response + SSOValidatorResponse ssoResponse = + ssoValidator.validateSamlResponse(marshalledResponse, false); + SamlAssertionWrapper parsedAssertion = + new SamlAssertionWrapper(ssoResponse.getAssertionElement()); + + assertEquals("alice", parsedAssertion.getSubjectName()); + } + + private Element createResponse() throws Exception { + DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance(); + docBuilderFactory.setNamespaceAware(true); + DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder(); + Document doc = docBuilder.newDocument(); + + Status status = + SAML2PResponseComponentBuilder.createStatus( + SAMLProtocolResponseValidator.SAML2_STATUSCODE_SUCCESS, null + ); + Response response = + SAML2PResponseComponentBuilder.createSAMLResponse( + "http://cxf.apache.org/saml", "http://cxf.apache.org/issuer", status + ); + + // Create an AuthenticationAssertion + SAML2CallbackHandler callbackHandler = new SAML2CallbackHandler(); + callbackHandler.setStatement(SAML2CallbackHandler.Statement.AUTHN); + callbackHandler.setIssuer("http://cxf.apache.org/issuer"); + callbackHandler.setConfirmationMethod(SAML2Constants.CONF_BEARER); + callbackHandler.setSubjectName("alice"); + + SubjectConfirmationDataBean subjectConfirmationData = new SubjectConfirmationDataBean(); + subjectConfirmationData.setAddress("http://apache.org"); + subjectConfirmationData.setInResponseTo("12345"); + subjectConfirmationData.setNotAfter(new DateTime().plusMinutes(5)); + subjectConfirmationData.setRecipient("http://recipient.apache.org"); + callbackHandler.setSubjectConfirmationData(subjectConfirmationData); + + ConditionsBean conditions = new ConditionsBean(); + conditions.setNotBefore(new DateTime()); + conditions.setNotAfter(new DateTime().plusMinutes(5)); + + AudienceRestrictionBean audienceRestriction = new AudienceRestrictionBean(); + audienceRestriction.setAudienceURIs(Collections.singletonList("http://service.apache.org")); + conditions.setAudienceRestrictions(Collections.singletonList(audienceRestriction)); + callbackHandler.setConditions(conditions); + + SAMLCallback samlCallback = new SAMLCallback(); + SAMLUtil.doSAMLCallback(callbackHandler, samlCallback); + SamlAssertionWrapper assertion = new SamlAssertionWrapper(samlCallback); + + Crypto issuerCrypto = new Merlin(); + KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + ClassLoader loader = Loader.getClassLoader(CombinedValidatorTest.class); + InputStream input = Merlin.loadInputStream(loader, "alice.jks"); + keyStore.load(input, "password".toCharArray()); + ((Merlin)issuerCrypto).setKeyStore(keyStore); + + assertion.signAssertion("alice", "password", issuerCrypto, false); + + response.getAssertions().add(assertion.getSaml2()); + + Element policyElement = OpenSAMLUtil.toDom(response, doc); + doc.appendChild(policyElement); + assertNotNull(policyElement); + + return policyElement; + } +}
1c2a53080004Adding SAML SSO tests.
3 files changed · +226 −5
rt/rs/security/sso/saml/src/main/java/org/apache/cxf/rs/security/saml/sso/SAMLSSOResponseValidator.java+4 −5 modified@@ -92,7 +92,7 @@ public SSOValidatorResponse validateSamlResponse( } // Validate Assertions - boolean foundValidSubject = false; + org.opensaml.saml.saml2.core.Assertion validAssertion = null; Date sessionNotOnOrAfter = null; for (org.opensaml.saml2.core.Assertion assertion : samlResponse.getAssertions()) { // Check the Issuer @@ -114,7 +114,7 @@ public SSOValidatorResponse validateSamlResponse( org.opensaml.saml2.core.Subject subject = assertion.getSubject(); if (validateAuthenticationSubject(subject, assertion.getID(), postBinding)) { validateAudienceRestrictionCondition(assertion.getConditions()); - foundValidSubject = true; + validAssertion = assertion; // Store Session NotOnOrAfter for (AuthnStatement authnStatment : assertion.getAuthnStatements()) { if (authnStatment.getSessionNotOnOrAfter() != null) { @@ -123,10 +123,9 @@ public SSOValidatorResponse validateSamlResponse( } } } - } - if (!foundValidSubject) { + if (validAssertion == null) { LOG.fine("The Response did not contain any Authentication Statement that matched " + "the Subject Confirmation criteria"); throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity"); @@ -140,7 +139,7 @@ public SSOValidatorResponse validateSamlResponse( } // the assumption for now is that SAMLResponse will contain only a single assertion - Element assertionElement = samlResponse.getAssertions().get(0).getDOM(); + Element assertionElement = validAssertion.getDOM(); Element clonedAssertionElement = (Element)assertionElement.cloneNode(true); validatorResponse.setAssertionElement(clonedAssertionElement); validatorResponse.setAssertion(DOM2Writer.nodeToString(clonedAssertionElement));
rt/rs/security/sso/saml/src/test/java/org/apache/cxf/rs/security/saml/sso/AbstractSAMLCallbackHandler.java+4 −0 modified@@ -130,6 +130,10 @@ public void setSubjectLocality(String ipAddress, String dnsAddress) { this.subjectLocalityDnsAddress = dnsAddress; } + public void setSubjectName(String subjectName) { + this.subjectName = subjectName; + } + public void setResource(String resource) { this.resource = resource; }
rt/rs/security/sso/saml/src/test/java/org/apache/cxf/rs/security/saml/sso/CombinedValidatorTest.java+218 −0 added@@ -0,0 +1,218 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.cxf.rs.security.saml.sso; + +import java.io.InputStream; +import java.security.KeyStore; +import java.util.Collections; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import org.apache.wss4j.common.crypto.Crypto; +import org.apache.wss4j.common.crypto.Merlin; +import org.apache.wss4j.common.saml.OpenSAMLUtil; +import org.apache.wss4j.common.saml.SAMLCallback; +import org.apache.wss4j.common.saml.SAMLUtil; +import org.apache.wss4j.common.saml.SamlAssertionWrapper; +import org.apache.wss4j.common.saml.bean.AudienceRestrictionBean; +import org.apache.wss4j.common.saml.bean.ConditionsBean; +import org.apache.wss4j.common.saml.bean.SubjectConfirmationDataBean; +import org.apache.wss4j.common.saml.builder.SAML2Constants; +import org.apache.wss4j.common.util.Loader; +import org.apache.wss4j.dom.WSConstants; +import org.apache.wss4j.dom.WSSConfig; +import org.joda.time.DateTime; +import org.opensaml.saml.common.xml.SAMLConstants; +import org.opensaml.saml.saml2.core.Response; +import org.opensaml.saml.saml2.core.Status; + +/** + * Some unit tests for the SAMLProtocolResponseValidator and the SAMLSSOResponseValidator + */ +public class CombinedValidatorTest extends org.junit.Assert { + + static { + WSSConfig.init(); + OpenSAMLUtil.initSamlEngine(); + } + + @org.junit.Test + public void testSuccessfulValidation() throws Exception { + + Element responseElement = createResponse(); + Response marshalledResponse = (Response)OpenSAMLUtil.fromDom(responseElement); + + Crypto issuerCrypto = new Merlin(); + KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + ClassLoader loader = Loader.getClassLoader(CombinedValidatorTest.class); + InputStream input = Merlin.loadInputStream(loader, "alice.jks"); + keyStore.load(input, "password".toCharArray()); + ((Merlin)issuerCrypto).setKeyStore(keyStore); + + // Validate the Response + SAMLProtocolResponseValidator validator = new SAMLProtocolResponseValidator(); + validator.validateSamlResponse( + marshalledResponse, issuerCrypto, new KeystorePasswordCallback() + ); + + // Test SSO validation + SAMLSSOResponseValidator ssoValidator = new SAMLSSOResponseValidator(); + ssoValidator.setIssuerIDP("http://cxf.apache.org/issuer"); + ssoValidator.setAssertionConsumerURL("http://recipient.apache.org"); + ssoValidator.setClientAddress("http://apache.org"); + ssoValidator.setRequestId("12345"); + ssoValidator.setSpIdentifier("http://service.apache.org"); + + // Parse the response + SSOValidatorResponse ssoResponse = + ssoValidator.validateSamlResponse(marshalledResponse, false); + SamlAssertionWrapper parsedAssertion = + new SamlAssertionWrapper(ssoResponse.getAssertionElement()); + + assertEquals("alice", parsedAssertion.getSubjectName()); + } + + @org.junit.Test + public void testWrappingAttack3() throws Exception { + Element responseElement = createResponse(); + + // Get Assertion Element + Element assertionElement = + (Element)responseElement.getElementsByTagNameNS(SAMLConstants.SAML20_NS, "Assertion").item(0); + assertNotNull(assertionElement); + + // Clone it, strip the Signature, modify the Subject, change Subj Conf + Element clonedAssertion = (Element)assertionElement.cloneNode(true); + clonedAssertion.setAttributeNS(null, "ID", "_12345623562"); + Element sigElement = + (Element)clonedAssertion.getElementsByTagNameNS(WSConstants.SIG_NS, "Signature").item(0); + clonedAssertion.removeChild(sigElement); + + Element subjElement = + (Element)clonedAssertion.getElementsByTagNameNS(SAMLConstants.SAML20_NS, "Subject").item(0); + Element subjNameIdElement = + (Element)subjElement.getElementsByTagNameNS(SAMLConstants.SAML20_NS, "NameID").item(0); + subjNameIdElement.setTextContent("bob"); + + Element subjConfElement = + (Element)subjElement.getElementsByTagNameNS(SAMLConstants.SAML20_NS, "SubjectConfirmation").item(0); + subjConfElement.setAttributeNS(null, "Method", SAML2Constants.CONF_SENDER_VOUCHES); + + // Now insert the modified cloned Assertion into the Response before actual assertion + responseElement.insertBefore(clonedAssertion, assertionElement); + + // System.out.println(DOM2Writer.nodeToString(responseElement)); + + Response marshalledResponse = (Response)OpenSAMLUtil.fromDom(responseElement); + + Crypto issuerCrypto = new Merlin(); + KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + ClassLoader loader = Loader.getClassLoader(CombinedValidatorTest.class); + InputStream input = Merlin.loadInputStream(loader, "alice.jks"); + keyStore.load(input, "password".toCharArray()); + ((Merlin)issuerCrypto).setKeyStore(keyStore); + + // Validate the Response + SAMLProtocolResponseValidator validator = new SAMLProtocolResponseValidator(); + validator.validateSamlResponse( + marshalledResponse, issuerCrypto, new KeystorePasswordCallback() + ); + + // Test SSO validation + SAMLSSOResponseValidator ssoValidator = new SAMLSSOResponseValidator(); + ssoValidator.setIssuerIDP("http://cxf.apache.org/issuer"); + ssoValidator.setAssertionConsumerURL("http://recipient.apache.org"); + ssoValidator.setClientAddress("http://apache.org"); + ssoValidator.setRequestId("12345"); + ssoValidator.setSpIdentifier("http://service.apache.org"); + + // Parse the response + SSOValidatorResponse ssoResponse = + ssoValidator.validateSamlResponse(marshalledResponse, false); + SamlAssertionWrapper parsedAssertion = + new SamlAssertionWrapper(ssoResponse.getAssertionElement()); + + assertEquals("alice", parsedAssertion.getSubjectName()); + } + + private Element createResponse() throws Exception { + DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance(); + docBuilderFactory.setNamespaceAware(true); + DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder(); + Document doc = docBuilder.newDocument(); + + Status status = + SAML2PResponseComponentBuilder.createStatus( + SAMLProtocolResponseValidator.SAML2_STATUSCODE_SUCCESS, null + ); + Response response = + SAML2PResponseComponentBuilder.createSAMLResponse( + "http://cxf.apache.org/saml", "http://cxf.apache.org/issuer", status + ); + + // Create an AuthenticationAssertion + SAML2CallbackHandler callbackHandler = new SAML2CallbackHandler(); + callbackHandler.setStatement(SAML2CallbackHandler.Statement.AUTHN); + callbackHandler.setIssuer("http://cxf.apache.org/issuer"); + callbackHandler.setConfirmationMethod(SAML2Constants.CONF_BEARER); + callbackHandler.setSubjectName("alice"); + + SubjectConfirmationDataBean subjectConfirmationData = new SubjectConfirmationDataBean(); + subjectConfirmationData.setAddress("http://apache.org"); + subjectConfirmationData.setInResponseTo("12345"); + subjectConfirmationData.setNotAfter(new DateTime().plusMinutes(5)); + subjectConfirmationData.setRecipient("http://recipient.apache.org"); + callbackHandler.setSubjectConfirmationData(subjectConfirmationData); + + ConditionsBean conditions = new ConditionsBean(); + conditions.setNotBefore(new DateTime()); + conditions.setNotAfter(new DateTime().plusMinutes(5)); + + AudienceRestrictionBean audienceRestriction = new AudienceRestrictionBean(); + audienceRestriction.setAudienceURIs(Collections.singletonList("http://service.apache.org")); + conditions.setAudienceRestrictions(Collections.singletonList(audienceRestriction)); + callbackHandler.setConditions(conditions); + + SAMLCallback samlCallback = new SAMLCallback(); + SAMLUtil.doSAMLCallback(callbackHandler, samlCallback); + SamlAssertionWrapper assertion = new SamlAssertionWrapper(samlCallback); + + Crypto issuerCrypto = new Merlin(); + KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + ClassLoader loader = Loader.getClassLoader(CombinedValidatorTest.class); + InputStream input = Merlin.loadInputStream(loader, "alice.jks"); + keyStore.load(input, "password".toCharArray()); + ((Merlin)issuerCrypto).setKeyStore(keyStore); + + assertion.signAssertion("alice", "password", issuerCrypto, false); + + response.getAssertions().add(assertion.getSaml2()); + + Element policyElement = OpenSAMLUtil.toDom(response, doc); + doc.appendChild(policyElement); + assertNotNull(policyElement); + + return policyElement; + } +}
02245c656941Adding SAML SSO tests.
3 files changed · +233 −4
rt/rs/security/sso/saml/src/main/java/org/apache/cxf/rs/security/saml/sso/SAMLSSOResponseValidator.java+11 −4 modified@@ -92,7 +92,7 @@ public SSOValidatorResponse validateSamlResponse( } // Validate Assertions - boolean foundValidSubject = false; + org.opensaml.saml.saml2.core.Assertion validAssertion = null; Date sessionNotOnOrAfter = null; for (org.opensaml.saml2.core.Assertion assertion : samlResponse.getAssertions()) { // Check the Issuer @@ -114,7 +114,7 @@ public SSOValidatorResponse validateSamlResponse( org.opensaml.saml2.core.Subject subject = assertion.getSubject(); if (validateAuthenticationSubject(subject, assertion.getID(), postBinding)) { validateAudienceRestrictionCondition(assertion.getConditions()); - foundValidSubject = true; + validAssertion = assertion; // Store Session NotOnOrAfter for (AuthnStatement authnStatment : assertion.getAuthnStatements()) { if (authnStatment.getSessionNotOnOrAfter() != null) { @@ -123,10 +123,9 @@ public SSOValidatorResponse validateSamlResponse( } } } - } - if (!foundValidSubject) { + if (validAssertion == null) { LOG.fine("The Response did not contain any Authentication Statement that matched " + "the Subject Confirmation criteria"); throw new WSSecurityException(WSSecurityException.FAILURE, "invalidSAMLsecurity"); @@ -136,8 +135,16 @@ public SSOValidatorResponse validateSamlResponse( validatorResponse.setResponseId(samlResponse.getID()); validatorResponse.setSessionNotOnOrAfter(sessionNotOnOrAfter); // the assumption for now is that SAMLResponse will contain only a single assertion +<<<<<<< HEAD Element assertionElement = samlResponse.getAssertions().get(0).getDOM(); validatorResponse.setAssertion(DOM2Writer.nodeToString(assertionElement.cloneNode(true))); +======= + Element assertionElement = validAssertion.getDOM(); + Element clonedAssertionElement = (Element)assertionElement.cloneNode(true); + validatorResponse.setAssertionElement(clonedAssertionElement); + validatorResponse.setAssertion(DOM2Writer.nodeToString(clonedAssertionElement)); + +>>>>>>> 1c2a530... Adding SAML SSO tests. return validatorResponse; }
rt/rs/security/sso/saml/src/test/java/org/apache/cxf/rs/security/saml/sso/AbstractSAMLCallbackHandler.java+4 −0 modified@@ -132,6 +132,10 @@ public void setSubjectLocality(String ipAddress, String dnsAddress) { this.subjectLocalityDnsAddress = dnsAddress; } + public void setSubjectName(String subjectName) { + this.subjectName = subjectName; + } + public void setResource(String resource) { this.resource = resource; }
rt/rs/security/sso/saml/src/test/java/org/apache/cxf/rs/security/saml/sso/CombinedValidatorTest.java+218 −0 added@@ -0,0 +1,218 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.cxf.rs.security.saml.sso; + +import java.io.InputStream; +import java.security.KeyStore; +import java.util.Collections; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import org.apache.wss4j.common.crypto.Crypto; +import org.apache.wss4j.common.crypto.Merlin; +import org.apache.wss4j.common.saml.OpenSAMLUtil; +import org.apache.wss4j.common.saml.SAMLCallback; +import org.apache.wss4j.common.saml.SAMLUtil; +import org.apache.wss4j.common.saml.SamlAssertionWrapper; +import org.apache.wss4j.common.saml.bean.AudienceRestrictionBean; +import org.apache.wss4j.common.saml.bean.ConditionsBean; +import org.apache.wss4j.common.saml.bean.SubjectConfirmationDataBean; +import org.apache.wss4j.common.saml.builder.SAML2Constants; +import org.apache.wss4j.common.util.Loader; +import org.apache.wss4j.dom.WSConstants; +import org.apache.wss4j.dom.WSSConfig; +import org.joda.time.DateTime; +import org.opensaml.saml.common.xml.SAMLConstants; +import org.opensaml.saml.saml2.core.Response; +import org.opensaml.saml.saml2.core.Status; + +/** + * Some unit tests for the SAMLProtocolResponseValidator and the SAMLSSOResponseValidator + */ +public class CombinedValidatorTest extends org.junit.Assert { + + static { + WSSConfig.init(); + OpenSAMLUtil.initSamlEngine(); + } + + @org.junit.Test + public void testSuccessfulValidation() throws Exception { + + Element responseElement = createResponse(); + Response marshalledResponse = (Response)OpenSAMLUtil.fromDom(responseElement); + + Crypto issuerCrypto = new Merlin(); + KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + ClassLoader loader = Loader.getClassLoader(CombinedValidatorTest.class); + InputStream input = Merlin.loadInputStream(loader, "alice.jks"); + keyStore.load(input, "password".toCharArray()); + ((Merlin)issuerCrypto).setKeyStore(keyStore); + + // Validate the Response + SAMLProtocolResponseValidator validator = new SAMLProtocolResponseValidator(); + validator.validateSamlResponse( + marshalledResponse, issuerCrypto, new KeystorePasswordCallback() + ); + + // Test SSO validation + SAMLSSOResponseValidator ssoValidator = new SAMLSSOResponseValidator(); + ssoValidator.setIssuerIDP("http://cxf.apache.org/issuer"); + ssoValidator.setAssertionConsumerURL("http://recipient.apache.org"); + ssoValidator.setClientAddress("http://apache.org"); + ssoValidator.setRequestId("12345"); + ssoValidator.setSpIdentifier("http://service.apache.org"); + + // Parse the response + SSOValidatorResponse ssoResponse = + ssoValidator.validateSamlResponse(marshalledResponse, false); + SamlAssertionWrapper parsedAssertion = + new SamlAssertionWrapper(ssoResponse.getAssertionElement()); + + assertEquals("alice", parsedAssertion.getSubjectName()); + } + + @org.junit.Test + public void testWrappingAttack3() throws Exception { + Element responseElement = createResponse(); + + // Get Assertion Element + Element assertionElement = + (Element)responseElement.getElementsByTagNameNS(SAMLConstants.SAML20_NS, "Assertion").item(0); + assertNotNull(assertionElement); + + // Clone it, strip the Signature, modify the Subject, change Subj Conf + Element clonedAssertion = (Element)assertionElement.cloneNode(true); + clonedAssertion.setAttributeNS(null, "ID", "_12345623562"); + Element sigElement = + (Element)clonedAssertion.getElementsByTagNameNS(WSConstants.SIG_NS, "Signature").item(0); + clonedAssertion.removeChild(sigElement); + + Element subjElement = + (Element)clonedAssertion.getElementsByTagNameNS(SAMLConstants.SAML20_NS, "Subject").item(0); + Element subjNameIdElement = + (Element)subjElement.getElementsByTagNameNS(SAMLConstants.SAML20_NS, "NameID").item(0); + subjNameIdElement.setTextContent("bob"); + + Element subjConfElement = + (Element)subjElement.getElementsByTagNameNS(SAMLConstants.SAML20_NS, "SubjectConfirmation").item(0); + subjConfElement.setAttributeNS(null, "Method", SAML2Constants.CONF_SENDER_VOUCHES); + + // Now insert the modified cloned Assertion into the Response before actual assertion + responseElement.insertBefore(clonedAssertion, assertionElement); + + // System.out.println(DOM2Writer.nodeToString(responseElement)); + + Response marshalledResponse = (Response)OpenSAMLUtil.fromDom(responseElement); + + Crypto issuerCrypto = new Merlin(); + KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + ClassLoader loader = Loader.getClassLoader(CombinedValidatorTest.class); + InputStream input = Merlin.loadInputStream(loader, "alice.jks"); + keyStore.load(input, "password".toCharArray()); + ((Merlin)issuerCrypto).setKeyStore(keyStore); + + // Validate the Response + SAMLProtocolResponseValidator validator = new SAMLProtocolResponseValidator(); + validator.validateSamlResponse( + marshalledResponse, issuerCrypto, new KeystorePasswordCallback() + ); + + // Test SSO validation + SAMLSSOResponseValidator ssoValidator = new SAMLSSOResponseValidator(); + ssoValidator.setIssuerIDP("http://cxf.apache.org/issuer"); + ssoValidator.setAssertionConsumerURL("http://recipient.apache.org"); + ssoValidator.setClientAddress("http://apache.org"); + ssoValidator.setRequestId("12345"); + ssoValidator.setSpIdentifier("http://service.apache.org"); + + // Parse the response + SSOValidatorResponse ssoResponse = + ssoValidator.validateSamlResponse(marshalledResponse, false); + SamlAssertionWrapper parsedAssertion = + new SamlAssertionWrapper(ssoResponse.getAssertionElement()); + + assertEquals("alice", parsedAssertion.getSubjectName()); + } + + private Element createResponse() throws Exception { + DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance(); + docBuilderFactory.setNamespaceAware(true); + DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder(); + Document doc = docBuilder.newDocument(); + + Status status = + SAML2PResponseComponentBuilder.createStatus( + SAMLProtocolResponseValidator.SAML2_STATUSCODE_SUCCESS, null + ); + Response response = + SAML2PResponseComponentBuilder.createSAMLResponse( + "http://cxf.apache.org/saml", "http://cxf.apache.org/issuer", status + ); + + // Create an AuthenticationAssertion + SAML2CallbackHandler callbackHandler = new SAML2CallbackHandler(); + callbackHandler.setStatement(SAML2CallbackHandler.Statement.AUTHN); + callbackHandler.setIssuer("http://cxf.apache.org/issuer"); + callbackHandler.setConfirmationMethod(SAML2Constants.CONF_BEARER); + callbackHandler.setSubjectName("alice"); + + SubjectConfirmationDataBean subjectConfirmationData = new SubjectConfirmationDataBean(); + subjectConfirmationData.setAddress("http://apache.org"); + subjectConfirmationData.setInResponseTo("12345"); + subjectConfirmationData.setNotAfter(new DateTime().plusMinutes(5)); + subjectConfirmationData.setRecipient("http://recipient.apache.org"); + callbackHandler.setSubjectConfirmationData(subjectConfirmationData); + + ConditionsBean conditions = new ConditionsBean(); + conditions.setNotBefore(new DateTime()); + conditions.setNotAfter(new DateTime().plusMinutes(5)); + + AudienceRestrictionBean audienceRestriction = new AudienceRestrictionBean(); + audienceRestriction.setAudienceURIs(Collections.singletonList("http://service.apache.org")); + conditions.setAudienceRestrictions(Collections.singletonList(audienceRestriction)); + callbackHandler.setConditions(conditions); + + SAMLCallback samlCallback = new SAMLCallback(); + SAMLUtil.doSAMLCallback(callbackHandler, samlCallback); + SamlAssertionWrapper assertion = new SamlAssertionWrapper(samlCallback); + + Crypto issuerCrypto = new Merlin(); + KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + ClassLoader loader = Loader.getClassLoader(CombinedValidatorTest.class); + InputStream input = Merlin.loadInputStream(loader, "alice.jks"); + keyStore.load(input, "password".toCharArray()); + ((Merlin)issuerCrypto).setKeyStore(keyStore); + + assertion.signAssertion("alice", "password", issuerCrypto, false); + + response.getAssertions().add(assertion.getSaml2()); + + Element policyElement = OpenSAMLUtil.toDom(response, doc); + doc.appendChild(policyElement); + assertNotNull(policyElement); + + return policyElement; + } +}
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
23- cxf.apache.org/security-advisories.data/CVE-2015-5253.txt.ascnvdVendor AdvisoryWEB
- rhn.redhat.com/errata/RHSA-2016-0321.htmlnvdThird Party AdvisoryWEB
- www.openwall.com/lists/oss-security/2015/11/14/1nvdMailing ListThird Party AdvisoryWEB
- www.securitytracker.com/id/1034162nvdThird Party AdvisoryVDB Entry
- github.com/advisories/GHSA-3336-h95j-hvvfghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2015-5253ghsaADVISORY
- git-wip-us.apache.org/repos/asfghsaWEB
- github.com/apache/cxf/commit/02245c656941f28b6b2be5e461e6db04a70d2436ghsaWEB
- github.com/apache/cxf/commit/1c2a53080004d6ce275f2e70f46a0098d4140787ghsaWEB
- github.com/apache/cxf/commit/845eccb6484b43ba02875c71e824db23ae4f20c0ghsaWEB
- lists.apache.org/thread.html/r36e44ffc1a9b365327df62cdfaabe85b9a5637de102cea07d79b2dbf@%3Ccommits.cxf.apache.org%3EghsaWEB
- lists.apache.org/thread.html/rc774278135816e7afc943dc9fc78eb0764f2c84a2b96470a0187315c@%3Ccommits.cxf.apache.org%3EghsaWEB
- lists.apache.org/thread.html/rd49aabd984ed540c8ff7916d4d79405f3fa311d2fdbcf9ed307839a6@%3Ccommits.cxf.apache.org%3EghsaWEB
- lists.apache.org/thread.html/rec7160382badd3ef4ad017a22f64a266c7188b9ba71394f0d321e2d4@%3Ccommits.cxf.apache.org%3EghsaWEB
- lists.apache.org/thread.html/rfb87e0bf3995e7d560afeed750fac9329ff5f1ad49da365129b7f89e@%3Ccommits.cxf.apache.org%3EghsaWEB
- lists.apache.org/thread.html/rff42cfa5e7d75b7c1af0e37589140a8f1999e578a75738740b244bd4@%3Ccommits.cxf.apache.org%3EghsaWEB
- git-wip-us.apache.org/repos/asfnvd
- lists.apache.org/thread.html/r36e44ffc1a9b365327df62cdfaabe85b9a5637de102cea07d79b2dbf%40%3Ccommits.cxf.apache.org%3Envd
- lists.apache.org/thread.html/rc774278135816e7afc943dc9fc78eb0764f2c84a2b96470a0187315c%40%3Ccommits.cxf.apache.org%3Envd
- lists.apache.org/thread.html/rd49aabd984ed540c8ff7916d4d79405f3fa311d2fdbcf9ed307839a6%40%3Ccommits.cxf.apache.org%3Envd
- lists.apache.org/thread.html/rec7160382badd3ef4ad017a22f64a266c7188b9ba71394f0d321e2d4%40%3Ccommits.cxf.apache.org%3Envd
- lists.apache.org/thread.html/rfb87e0bf3995e7d560afeed750fac9329ff5f1ad49da365129b7f89e%40%3Ccommits.cxf.apache.org%3Envd
- lists.apache.org/thread.html/rff42cfa5e7d75b7c1af0e37589140a8f1999e578a75738740b244bd4%40%3Ccommits.cxf.apache.org%3Envd
News mentions
0No linked articles in our index yet.