https://bugs.internet2.edu/jira/browse/SSPCPP-351
authorscantor <scantor@cb58f699-b61c-0410-a6fe-9272a202ed29>
Tue, 15 Feb 2011 16:24:02 +0000 (16:24 +0000)
committerscantor <scantor@cb58f699-b61c-0410-a6fe-9272a202ed29>
Tue, 15 Feb 2011 16:24:02 +0000 (16:24 +0000)
git-svn-id: https://svn.shibboleth.net/cpp-sp/branches/REL_2@3403 cb58f699-b61c-0410-a6fe-9272a202ed29

schemas/shibboleth-2.0-native-sp-config.xsd
shibsp/handler/impl/AbstractHandler.cpp
shibsp/handler/impl/AssertionConsumerService.cpp
shibsp/handler/impl/LocalLogoutInitiator.cpp
shibsp/handler/impl/SAML2Logout.cpp
shibsp/handler/impl/SAML2LogoutInitiator.cpp
shibsp/handler/impl/SAML2SessionInitiator.cpp
shibsp/handler/impl/Shib1SessionInitiator.cpp
shibsp/handler/impl/WAYFSessionInitiator.cpp
shibsp/internal.h

index a36efe3..6d775b1 100644 (file)
@@ -9,7 +9,7 @@
        elementFormDefault="qualified"
        attributeFormDefault="unqualified"
        blockDefault="substitution"
-       version="2.4">
+       version="2.4.2">
 
   <import namespace="http://www.w3.org/2000/09/xmldsig#" schemaLocation="xmldsig-core-schema.xsd" />
   <import namespace="urn:oasis:names:tc:SAML:2.0:assertion" schemaLocation="saml-schema-assertion-2.0.xsd"/>
     </restriction>
   </simpleType>
 
+  <simpleType name="relayStateLimitType">
+    <restriction base="string">
+      <enumeration value="none"/>
+      <enumeration value="exact"/>
+      <enumeration value="host"/>
+      <enumeration value="whitelist"/>
+    </restriction>
+  </simpleType>
+
   <complexType name="PluggableType">
     <sequence>
       <any namespace="##any" processContents="lax" minOccurs="0" maxOccurs="unbounded"/>
     <attribute name="postTemplate" type="conf:string"/>
     <attribute name="postExpire" type="boolean"/>
     <attribute name="relayState" type="conf:string"/>
+    <attribute name="relayStateLimit" type="conf:relayStateLimitType"/>
+    <attribute name="relayStateWhitelist" type="conf:listOfURIs"/>
     <anyAttribute namespace="##other" processContents="lax"/>
   </complexType>
 
index 33d722a..967351b 100644 (file)
@@ -1,5 +1,5 @@
 /*
- *  Copyright 2001-2010 Internet2
+ *  Copyright 2001-2011 Internet2
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -112,7 +112,65 @@ namespace shibsp {
         }
     }
 
+    void SHIBSP_DLLLOCAL limitRelayState(
+        Category& log, const Application& application, const HTTPRequest& httpRequest, const char* relayState
+        ) {
+        const PropertySet* sessionProps = application.getPropertySet("Sessions");
+        if (sessionProps) {
+            pair<bool,const char*> relayStateLimit = sessionProps->getString("relayStateLimit");
+            if (relayStateLimit.first) {
+                vector<string> whitelist;
+                if (!strcmp(relayStateLimit.second, "exact")) {
+                    // Scheme and hostname have to match.
+                    if (!strcmp(httpRequest.getScheme(), "https") && httpRequest.getPort() == 443) {
+                        whitelist.push_back(string("https://") + httpRequest.getHostname() + '/');
+                    }
+                    else if (!strcmp(httpRequest.getScheme(), "http") && httpRequest.getPort() == 80) {
+                        whitelist.push_back(string("http://") + httpRequest.getHostname() + '/');
+                    }
+                    ostringstream portstr;
+                    portstr << httpRequest.getPort();
+                    whitelist.push_back(string(httpRequest.getScheme()) + "://" + httpRequest.getHostname() + ':' + portstr.str() + '/');
+                }
+                else if (!strcmp(relayStateLimit.second, "host")) {
+                    // Allow any scheme or port.
+                    whitelist.push_back(string("https://") + httpRequest.getHostname() + '/');
+                    whitelist.push_back(string("http://") + httpRequest.getHostname() + '/');
+                    whitelist.push_back(string("https://") + httpRequest.getHostname() + ':');
+                    whitelist.push_back(string("http://") + httpRequest.getHostname() + ':');
+                }
+                else if (!strcmp(relayStateLimit.second, "whitelist")) {
+                    // Literal set of comparisons to use.
+                    pair<bool,const char*> whitelistval = sessionProps->getString("relayStateWhitelist");
+                    if (whitelistval.first) {
+#ifdef HAVE_STRTOK_R
+                        char* pos=nullptr;
+                        const char* token = strtok_r(const_cast<char*>(whitelistval.second), " ", &pos);
+#else
+                        const char* token = strtok(const_cast<char*>(whitelistval.second), " ");
+#endif
+                        while (token) {
+                            whitelist.push_back(token);
+#ifdef HAVE_STRTOK_R
+                            token = strtok_r(nullptr, " ", &pos);
+#else
+                            token = strtok(nullptr, " ");
+#endif
+                        }
+                    }
+                }
 
+                for (vector<string>::const_iterator w = whitelist.begin(); w != whitelist.end(); ++w) {
+                    if (XMLString::startsWithI(relayState, w->c_str())) {
+                        return;
+                    }
+                }
+
+                log.warn("relayStateLimit policy (%s), blocked redirect to (%s)", relayStateLimit.second, relayState);
+                throw opensaml::SecurityPolicyException("Blocked unacceptable redirect location.");
+            }
+        }
+    }
 };
 
 void SHIBSP_API shibsp::registerHandlers()
index e9176c8..4b14643 100644 (file)
@@ -1,5 +1,5 @@
 /*
- *  Copyright 2001-2010 Internet2
+ *  Copyright 2001-2011 Internet2
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -153,6 +153,7 @@ pair<bool,long> AssertionConsumerService::processMessage(
         );
 
     string relayState;
+    bool relayStateOK = true;
     try {
         // Decode the message and process it in a protocol-specific way.
         auto_ptr<XMLObject> msg(m_decoder->decode(relayState, httpRequest, *(policy.get())));
@@ -161,6 +162,7 @@ pair<bool,long> AssertionConsumerService::processMessage(
         DDF postData = recoverPostData(application, httpRequest, httpResponse, relayState.c_str());
         DDFJanitor postjan(postData);
         recoverRelayState(application, httpRequest, httpResponse, relayState);
+        limitRelayState(m_log, application, httpRequest, relayState.c_str());
         implementProtocol(application, httpRequest, httpResponse, *(policy.get()), NULL, *msg.get());
 
         auto_ptr_char issuer(policy->getIssuer() ? policy->getIssuer()->getName() : nullptr);
@@ -181,13 +183,15 @@ pair<bool,long> AssertionConsumerService::processMessage(
         }
     }
     catch (XMLToolingException& ex) {
-        // Check for isPassive error condition.
-        const char* sc2 = ex.getProperty("statusCode2");
-        if (sc2 && !strcmp(sc2, "urn:oasis:names:tc:SAML:2.0:status:NoPassive")) {
-            pair<bool,bool> ignore = getBool("ignoreNoPassive", m_configNS.get());  // namespace-qualified if inside handler element
-            if (ignore.first && ignore.second && !relayState.empty()) {
-                m_log.debug("ignoring SAML status of NoPassive and redirecting to resource...");
-                return make_pair(true, httpResponse.sendRedirect(relayState.c_str()));
+        if (relayStateOK) {
+            // Check for isPassive error condition.
+            const char* sc2 = ex.getProperty("statusCode2");
+            if (sc2 && !strcmp(sc2, "urn:oasis:names:tc:SAML:2.0:status:NoPassive")) {
+                pair<bool,bool> ignore = getBool("ignoreNoPassive", m_configNS.get());  // namespace-qualified if inside handler element
+                if (ignore.first && ignore.second && !relayState.empty()) {
+                    m_log.debug("ignoring SAML status of NoPassive and redirecting to resource...");
+                    return make_pair(true, httpResponse.sendRedirect(relayState.c_str()));
+                }
             }
         }
         if (!relayState.empty())
index 5468df9..8a74dc3 100644 (file)
@@ -1,5 +1,5 @@
 /*
- *  Copyright 2001-2009 Internet2
+ *  Copyright 2001-2011 Internet2
  * 
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -105,7 +105,9 @@ pair<bool,long> LocalLogoutInitiator::run(SPRequest& request, bool isHandler) co
 
     // Route back to return location specified, or use the local template.
     const char* dest = request.getParameter("return");
-    if (dest)
+    if (dest) {
+        limitRelayState(m_log, app, request, dest);
         return make_pair(true, request.sendRedirect(dest));
+    }
     return sendLogoutPage(app, request, request, "local");
 }
index acfdd26..229496d 100644 (file)
@@ -1,5 +1,5 @@
 /*
- *  Copyright 2001-2010 Internet2
+ *  Copyright 2001-2011 Internet2
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -548,8 +548,10 @@ pair<bool,long> SAML2Logout::doRequest(const Application& application, const HTT
         if (sc && XMLString::equals(sc->getValue(), StatusCode::PARTIAL_LOGOUT))
             return sendLogoutPage(application, request, response, "partial");
 
-        if (!relayState.empty())
+        if (!relayState.empty()) {
+            limitRelayState(m_log, application, request, relayState.c_str());
             return make_pair(true, response.sendRedirect(relayState.c_str()));
+        }
 
         // Return template for completion of logout.
         return sendLogoutPage(application, request, response, "global");
index cba23f6..5d0f14a 100644 (file)
@@ -1,5 +1,5 @@
 /*
- *  Copyright 2001-2010 Internet2
+ *  Copyright 2001-2011 Internet2
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -375,6 +375,7 @@ pair<bool,long> SAML2LogoutInitiator::doRequest(
                 else {
                     const char* returnloc = httpRequest.getParameter("return");
                     if (returnloc) {
+                        limitRelayState(m_log, application, httpRequest, returnloc);
                         ret.second = httpResponse.sendRedirect(returnloc);
                         ret.first = true;
                     }
@@ -395,6 +396,7 @@ pair<bool,long> SAML2LogoutInitiator::doRequest(
         string relayState;
         const char* returnloc = httpRequest.getParameter("return");
         if (returnloc) {
+            limitRelayState(m_log, application, httpRequest, returnloc);
             relayState = returnloc;
             preserveRelayState(application, httpResponse, relayState);
         }
index f44dca2..96e3fca 100644 (file)
@@ -1,5 +1,5 @@
 /*
- *  Copyright 2001-2010 Internet2
+ *  Copyright 2001-2011 Internet2
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -266,6 +266,7 @@ pair<bool,long> SAML2SessionInitiator::run(SPRequest& request, string& entityID,
 
         // Always need to recover target URL to compute handler below.
         recoverRelayState(app, request, request, target, false);
+        limitRelayState(m_log, app, request, target.c_str());
 
         pair<bool,bool> flag = getBool("isPassive", request);
         isPassive = (flag.first && flag.second);
index d3000ca..5ed0c11 100644 (file)
@@ -1,5 +1,5 @@
 /*
- *  Copyright 2001-2010 Internet2
+ *  Copyright 2001-2011 Internet2
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -139,6 +139,7 @@ pair<bool,long> Shib1SessionInitiator::run(SPRequest& request, string& entityID,
         // Since we're passing the ACS by value, we need to compute the return URL,
         // so we'll need the target resource for real.
         recoverRelayState(app, request, request, target, false);
+        limitRelayState(m_log, app, request, target.c_str());
     }
     else {
         // Check for a hardwired target value in the map or handler.
index 8d6bd90..36379cb 100644 (file)
@@ -1,5 +1,5 @@
 /*
- *  Copyright 2001-2010 Internet2
+ *  Copyright 2001-2011 Internet2
  * 
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -110,6 +110,7 @@ pair<bool,long> WAYFSessionInitiator::run(SPRequest& request, string& entityID,
         // Since we're passing the ACS by value, we need to compute the return URL,
         // so we'll need the target resource for real.
         recoverRelayState(request.getApplication(), request, request, target, false);
+        limitRelayState(m_log, request.getApplication(), request, target.c_str());
 
         prop.second = request.getParameter("discoveryURL");
         if (prop.second && *prop.second)
index 6ccfbc3..bd49761 100644 (file)
@@ -1,5 +1,5 @@
 /*
- *  Copyright 2001-2007 Internet2
+ *  Copyright 2001-2011 Internet2
  * 
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 
 #include <memory>
 #include <xmltooling/logging.h>
+#include <xmltooling/io/HTTPRequest.h>
+#include <shibsp/Application.h>
 
 using namespace xmltooling::logging;
 using namespace xercesc;
 
+namespace shibsp {
+    void SHIBSP_DLLLOCAL limitRelayState(
+        xmltooling::logging::Category& log,
+        const Application& application,
+        const xmltooling::HTTPRequest& httpRequest,
+        const char* relayState
+        );
+};
+
 #endif /* __shibsp_internal_h__ */