Dear Security Team,

Please review the following source debdiff:

diff -Nru opensaml-3.2.1/debian/changelog opensaml-3.2.1/debian/changelog
--- opensaml-3.2.1/debian/changelog     2023-01-18 19:21:05.000000000 +0100
+++ opensaml-3.2.1/debian/changelog     2025-03-14 21:47:50.000000000 +0100
@@ -1,3 +1,45 @@
+opensaml (3.2.1-3+deb12u1) bookworm-security; urgency=high
+
+  * [b3e86fd] New patch: CPPOST-126 - Simple signature verification fails to
+    detect parameter smuggling.
+    Security fix cherry-picked from v3.3.1 (upstream commit
+    22a610b322e2178abd03e97cdbc8fb50b45efaee).
+    Parameter manipulation allows the forging of signed SAML messages
+    =================================================================
+    A number of vulnerabilities in the OpenSAML library used by the
+    Shibboleth Service Provider allowed for creative manipulation of
+    parameters combined with reuse of the contents of older requests
+    to fool the library's signature verification of non-XML based
+    signed messages.
+    Most uses of that feature involve very low or low impact use cases
+    without critical security implications; however, there are two
+    scenarios that are much more critical, one affecting the SP and
+    one affecting some implementers who have implemented their own
+    code on top of our OpenSAML library and done so improperly.
+    The SP's support for the HTTP-POST-SimpleSign SAML binding for
+    Single Sign-On responses is its critical vulnerability, and
+    it is enabled by default (regardless of what one's published
+    SAML metadata may advertise).
+    The other critical case involves a mistake that does *not*
+    impact the Shibboleth SP, allowing SSO to occur over the
+    HTTP-Redirect binding contrary to the plain language of the
+    SAML Browser SSO profile. The SP does not support this, but
+    other implementers may have done so.
+    Contrary to the initial publication of this advisory, there is no
+    workaround within the SP configuration other than to remove the
+    "SimpleSigning" security policy rule from the security-policy.xml
+    file entirely.
+    That will also prevent support of legitimate signed requests or
+    responses via the HTTP-Redirect binding, which is generally used
+    only for logout messages within the SP itself. Removing support
+    for that binding in favor of HTTP-POST in any published metadata
+    is an option of course.
+    Full advisory:
+    https://shibboleth.net/community/advisories/secadv_20250313.txt
+    Thanks to Scott Cantor (Closes: #1100464)
+
+ -- Ferenc Wágner <wf...@debian.org>  Fri, 14 Mar 2025 21:47:50 +0100
+
 opensaml (3.2.1-3) unstable; urgency=medium
 
   * [02e846c] Update Standards-Version to 4.6.2 (no changes required)
diff -Nru opensaml-3.2.1/debian/gbp.conf opensaml-3.2.1/debian/gbp.conf
--- opensaml-3.2.1/debian/gbp.conf      2023-01-18 19:04:34.000000000 +0100
+++ opensaml-3.2.1/debian/gbp.conf      2025-03-14 21:47:14.000000000 +0100
@@ -1,5 +1,5 @@
 [DEFAULT]
-debian-branch = debian/master
+debian-branch = debian/bookworm
 upstream-branch = upstream/latest
 pristine-tar = True
 
diff -Nru 
opensaml-3.2.1/debian/patches/CPPOST-126-Simple-signature-verification-fails-to-detect-.patch
 
opensaml-3.2.1/debian/patches/CPPOST-126-Simple-signature-verification-fails-to-detect-.patch
--- 
opensaml-3.2.1/debian/patches/CPPOST-126-Simple-signature-verification-fails-to-detect-.patch
       1970-01-01 01:00:00.000000000 +0100
+++ 
opensaml-3.2.1/debian/patches/CPPOST-126-Simple-signature-verification-fails-to-detect-.patch
       2025-03-14 21:47:33.000000000 +0100
@@ -0,0 +1,339 @@
+From: Scott Cantor <canto...@osu.edu>
+Date: Thu, 13 Mar 2025 14:14:12 -0400
+Subject: CPPOST-126 - Simple signature verification fails to detect parameter
+ smuggling
+
+https://shibboleth.atlassian.net/browse/CPPOST-126
+---
+ saml/binding/impl/SimpleSigningRule.cpp          | 88 +++++++++++++++++-------
+ saml/saml2/binding/impl/SAML2ArtifactDecoder.cpp |  2 +
+ saml/saml2/binding/impl/SAML2ECPDecoder.cpp      |  3 +-
+ saml/saml2/binding/impl/SAML2POSTDecoder.cpp     | 31 ++++++---
+ saml/saml2/binding/impl/SAML2RedirectDecoder.cpp | 32 ++++++---
+ saml/saml2/binding/impl/SAML2SOAPDecoder.cpp     |  2 +-
+ 6 files changed, 110 insertions(+), 48 deletions(-)
+
+diff --git a/saml/binding/impl/SimpleSigningRule.cpp 
b/saml/binding/impl/SimpleSigningRule.cpp
+index 24e95a6..9c2122e 100644
+--- a/saml/binding/impl/SimpleSigningRule.cpp
++++ b/saml/binding/impl/SimpleSigningRule.cpp
+@@ -29,6 +29,7 @@
+ #include "binding/SecurityPolicy.h"
+ #include "binding/SecurityPolicyRule.h"
+ #include "saml2/core/Assertions.h"
++#include "saml2/core/Protocols.h"
+ #include "saml2/metadata/Metadata.h"
+ #include "saml2/metadata/MetadataCredentialCriteria.h"
+ #include "saml2/metadata/MetadataProvider.h"
+@@ -41,6 +42,7 @@
+ #include <xmltooling/signature/KeyInfo.h>
+ #include <xmltooling/signature/Signature.h>
+ #include <xmltooling/util/ParserPool.h>
++#include <xmltooling/util/URLEncoder.h>
+ 
+ using namespace opensaml::saml2md;
+ using namespace opensaml;
+@@ -66,7 +68,8 @@ namespace opensaml {
+ 
+     private:
+         // Appends a raw parameter=value pair to the string.
+-        static bool appendParameter(string& s, const char* data, const char* 
name);
++        static bool appendParameter(const GenericRequest& request, string& s, 
const char* data, const char* name);
++        static const char* getMessageParameterName(const XMLObject* message);
+ 
+         bool m_errorFatal;
+     };
+@@ -79,21 +82,48 @@ namespace opensaml {
+     static const XMLCh errorFatal[] = UNICODE_LITERAL_10(e,r,r,o,r,F,a,t,a,l);
+ };
+ 
+-bool SimpleSigningRule::appendParameter(string& s, const char* data, const 
char* name)
++bool SimpleSigningRule::appendParameter(const GenericRequest& request, 
string& s, const char* data, const char* name)
+ {
+-    const char* start = strstr(data,name);
++    // Make sure only a single instance of the parameter specified is found 
in the decoded query.
++    vector<const char*> valueHolder;
++    if (request.getParameters(name, valueHolder) > 1) {
++        throw SecurityPolicyException("Multiple $1 parameters present.", 
params(1, name));
++    }
++
++    string param_name(name);
++    param_name += '=';
++
++    const char* start = strstr(data, param_name.c_str());
+     if (!start)
+         return false;
++    if (start > data && *(start - 1) != '&')
++        throw SecurityPolicyException("Detected attempt to smuggle a prefixed 
$1 parameter.", params(1, name));
++
+     if (!s.empty())
+         s += '&';
+-    const char* end = strchr(start,'&');
++
++    const char* end = strchr(start, '&');
+     if (end)
+-        s.append(start, end-start);
++        s.append(start, end - start);
+     else
+         s.append(start);
++
+     return true;
+ }
+ 
++const char* SimpleSigningRule::getMessageParameterName(const XMLObject* 
message)
++{
++    if (dynamic_cast<const saml2p::StatusResponseType*>(message)) {
++        return "SAMLResponse";
++    }
++    else if (dynamic_cast<const saml2p::RequestAbstractType*>(message)) {
++        return "SAMLRequest";
++    }
++    else {
++        return nullptr;
++    }
++}
++
+ SimpleSigningRule::SimpleSigningRule(const DOMElement* e)
+     : SecurityPolicyRule(e), m_errorFatal(XMLHelper::getAttrBool(e, false, 
errorFatal))
+ {
+@@ -119,34 +149,50 @@ bool SimpleSigningRule::evaluate(const XMLObject& 
message, const GenericRequest*
+     }
+ 
+     const HTTPRequest* httpRequest = dynamic_cast<const 
HTTPRequest*>(request);
+-    if (!request || !httpRequest)
++    if (!request || !httpRequest) {
+         return false;
++    }
+ 
+-    const char* signature = request->getParameter("Signature");
+-    if (!signature)
++    // Make sure Signature only shows up once.
++    vector<const char*> valueHolder;
++    request->getParameters("Signature", valueHolder);
++    if (valueHolder.empty()) {
+         return false;
+-    
++    }
++    else if (valueHolder.size() > 1) {
++        throw SecurityPolicyException("Multiple Signature parameters 
present.");
++    }
++    const char* signature = valueHolder[0];
++
++    // The multiple parameter copy check for the GET case is applied down 
below in appendParameter.
+     const char* sigAlgorithm = request->getParameter("SigAlg");
+     if (!sigAlgorithm) {
+         log.warn("SigAlg parameter not found, no way to verify the 
signature");
+         return false;
+     }
+ 
++    const char* messageParameterName = getMessageParameterName(&message);
++    if (!messageParameterName) {
++        log.debug("ignoring unrecognized message type");
++        return false;
++    }
++
+     string input;
+     const char* pch;
+     if (!strcmp(httpRequest->getMethod(), "GET")) {
+         // We have to construct a string containing the signature input by 
accessing the
+         // request directly. We can't use the decoded parameters because we 
need the raw
+-        // data and URL-encoding isn't canonical.
++        // data and URL-encoding isn't canonical. We have to ensure only one 
copy a given
++        // parameter appears in the string in its decoded form, to ensure 
that other layers
++        // of the code only saw/see the same value we see here.
+ 
+         // NOTE: SimpleSign for GET means Redirect binding, which means we 
verify over the
+         // base64-encoded message directly.
+ 
+         pch = httpRequest->getQueryString();
+-        if (!appendParameter(input, pch, "SAMLRequest="))
+-            appendParameter(input, pch, "SAMLResponse=");
+-        appendParameter(input, pch, "RelayState=");
+-        appendParameter(input, pch, "SigAlg=");
++        appendParameter(*request, input, pch, messageParameterName);
++        appendParameter(*request, input, pch, "RelayState");
++        appendParameter(*request, input, pch, "SigAlg");
+     }
+     else {
+         // With POST, the input string is concatenated from the decoded form 
controls.
+@@ -158,24 +204,14 @@ bool SimpleSigningRule::evaluate(const XMLObject& 
message, const GenericRequest*
+         // why XMLSignature exists, and why this isn't really "simpler").
+ 
+         XMLSize_t x;
+-        pch = httpRequest->getParameter("SAMLRequest");
++        pch = httpRequest->getParameter(messageParameterName);
+         if (pch) {
+             XMLByte* decoded=Base64::decode(reinterpret_cast<const 
XMLByte*>(pch),&x);
+             if (!decoded) {
+                 log.warn("unable to decode base64 in POST binding message");
+                 return false;
+             }
+-            input = string("SAMLRequest=") + reinterpret_cast<const 
char*>(decoded);
+-            XMLString::release((char**)&decoded);
+-        }
+-        else {
+-            pch = httpRequest->getParameter("SAMLResponse");
+-            XMLByte* decoded=Base64::decode(reinterpret_cast<const 
XMLByte*>(pch),&x);
+-            if (!decoded) {
+-                log.warn("unable to decode base64 in POST binding message");
+-                return false;
+-            }
+-            input = string("SAMLResponse=") + reinterpret_cast<const 
char*>(decoded);
++            input = string(messageParameterName) + "=" + 
reinterpret_cast<const char*>(decoded);
+             XMLString::release((char**)&decoded);
+         }
+ 
+diff --git a/saml/saml2/binding/impl/SAML2ArtifactDecoder.cpp 
b/saml/saml2/binding/impl/SAML2ArtifactDecoder.cpp
+index 724c01e..9378942 100644
+--- a/saml/saml2/binding/impl/SAML2ArtifactDecoder.cpp
++++ b/saml/saml2/binding/impl/SAML2ArtifactDecoder.cpp
+@@ -95,6 +95,8 @@ XMLObject* SAML2ArtifactDecoder::decode(
+     const char* state = httpRequest->getParameter("RelayState");
+     if (state)
+         relayState = state;
++    if (httpRequest->getParameter("Signature"))
++        throw BindingException("Request contained unexpected Signature 
parameter.");
+ 
+     if (!m_artifactResolver || !policy.getMetadataProvider() || 
!policy.getRole())
+         throw BindingException("Artifact binding requires ArtifactResolver 
and MetadataProvider implementations be supplied.");
+diff --git a/saml/saml2/binding/impl/SAML2ECPDecoder.cpp 
b/saml/saml2/binding/impl/SAML2ECPDecoder.cpp
+index 610ac2f..3d1c180 100644
+--- a/saml/saml2/binding/impl/SAML2ECPDecoder.cpp
++++ b/saml/saml2/binding/impl/SAML2ECPDecoder.cpp
+@@ -86,7 +86,8 @@ XMLObject* SAML2ECPDecoder::decode(
+     const HTTPRequest* httpRequest = dynamic_cast<const 
HTTPRequest*>(&genericRequest);
+     if (httpRequest) {
+         string s = httpRequest->getContentType();
+-        if (s.find("application/vnd.paos+xml") == string::npos) {
++        if (s.find("application/vnd.paos+xml") == string::npos ||
++                s.find("application/x-www-form-urlencoded") != string::npos) {
+             log.warn("ignoring incorrect content type (%s)", s.c_str() ? 
s.c_str() : "none");
+             throw BindingException("Invalid content type for PAOS message.");
+         }
+diff --git a/saml/saml2/binding/impl/SAML2POSTDecoder.cpp 
b/saml/saml2/binding/impl/SAML2POSTDecoder.cpp
+index 534b8ff..e534944 100644
+--- a/saml/saml2/binding/impl/SAML2POSTDecoder.cpp
++++ b/saml/saml2/binding/impl/SAML2POSTDecoder.cpp
+@@ -92,11 +92,18 @@ XMLObject* SAML2POSTDecoder::decode(
+         throw BindingException("Unable to cast request object to HTTPRequest 
type.");
+     if (strcmp(httpRequest->getMethod(),"POST"))
+         throw BindingException("Invalid HTTP method ($1).", params(1, 
httpRequest->getMethod()));
+-    const char* msg = httpRequest->getParameter("SAMLResponse");
+-    if (!msg)
+-        msg = httpRequest->getParameter("SAMLRequest");
++    
++    bool isRequest = false;    
++    const char* msg = httpRequest->getParameter("SAMLRequest");
++    if (msg) {
++        isRequest = true;
++    } else {
++        msg = httpRequest->getParameter("SAMLResponse");
++    }
++
+     if (!msg)
+         throw BindingException("Request missing SAMLRequest or SAMLResponse 
form parameter.");
++
+     const char* state = httpRequest->getParameter("RelayState");
+     if (state)
+         relayState = state;
+@@ -121,16 +128,20 @@ XMLObject* SAML2POSTDecoder::decode(
+ 
+     saml2::RootObject* root = nullptr;
+     StatusResponseType* response = nullptr;
+-    RequestAbstractType* request = 
dynamic_cast<RequestAbstractType*>(xmlObject.get());
+-    if (!request) {
++    RequestAbstractType* request = nullptr;
++    if (isRequest) {
++        request = dynamic_cast<RequestAbstractType*>(xmlObject.get());
++        if (!request) {
++            throw BindingException("XML content for SAML 2.0 HTTP-POST 
Decoder was not a SAML 2.0 request message.");
++        }
++        root = static_cast<saml2::RootObject*>(request);
++    } else {
+         response = dynamic_cast<StatusResponseType*>(xmlObject.get());
+-        if (!response)
+-            throw BindingException("XML content for SAML 2.0 HTTP-POST 
Decoder must be a SAML 2.0 protocol message.");
++        if (!response) {
++            throw BindingException("XML content for SAML 2.0 HTTP-POST 
Decoder was not a SAML 2.0 response message.");
++        }
+         root = static_cast<saml2::RootObject*>(response);
+     }
+-    else {
+-        root = static_cast<saml2::RootObject*>(request);
+-    }
+     
+     SchemaValidators.validate(root);
+ 
+diff --git a/saml/saml2/binding/impl/SAML2RedirectDecoder.cpp 
b/saml/saml2/binding/impl/SAML2RedirectDecoder.cpp
+index ca46e57..a5956d0 100644
+--- a/saml/saml2/binding/impl/SAML2RedirectDecoder.cpp
++++ b/saml/saml2/binding/impl/SAML2RedirectDecoder.cpp
+@@ -90,16 +90,24 @@ XMLObject* SAML2RedirectDecoder::decode(
+     const HTTPRequest* httpRequest=dynamic_cast<const 
HTTPRequest*>(&genericRequest);
+     if (!httpRequest)
+         throw BindingException("Unable to cast request object to HTTPRequest 
type.");
+-    const char* msg = httpRequest->getParameter("SAMLResponse");
+-    if (!msg)
+-        msg = httpRequest->getParameter("SAMLRequest");
++
++    bool isRequest = false;    
++    const char* msg = httpRequest->getParameter("SAMLRequest");
++    if (msg) {
++        isRequest = true;
++    } else {
++        msg = httpRequest->getParameter("SAMLResponse");
++    }
++    
+     if (!msg)
+         throw BindingException("Request missing SAMLRequest or SAMLResponse 
query string parameter.");
++
+     const char* state = httpRequest->getParameter("RelayState");
+     if (state)
+         relayState = state;
+     else
+         relayState.erase();
++
+     state = httpRequest->getParameter("SAMLEncoding");
+     if (state && 
strcmp(state,samlconstants::SAML20_BINDING_URL_ENCODING_DEFLATE)) {
+         log.warn("SAMLEncoding (%s) was not recognized", state);
+@@ -132,16 +140,20 @@ XMLObject* SAML2RedirectDecoder::decode(
+ 
+     saml2::RootObject* root = nullptr;
+     StatusResponseType* response = nullptr;
+-    RequestAbstractType* request = 
dynamic_cast<RequestAbstractType*>(xmlObject.get());
+-    if (!request) {
++    RequestAbstractType* request = nullptr;
++    if (isRequest) {
++        request = dynamic_cast<RequestAbstractType*>(xmlObject.get());
++        if (!request) {
++            throw BindingException("XML content for SAML 2.0 HTTP-Redirect 
Decoder was not a SAML 2.0 request message.");
++        }
++        root = static_cast<saml2::RootObject*>(request);
++    } else {
+         response = dynamic_cast<StatusResponseType*>(xmlObject.get());
+-        if (!response)
+-            throw BindingException("XML content for SAML 2.0 HTTP-POST 
Decoder must be a SAML 2.0 protocol message.");
++        if (!response) {
++            throw BindingException("XML content for SAML 2.0 HTTP-Redirect 
Decoder was not a SAML 2.0 response message.");
++        }
+         root = static_cast<saml2::RootObject*>(response);
+     }
+-    else {
+-        root = static_cast<saml2::RootObject*>(request);
+-    }
+ 
+     SchemaValidators.validate(root);
+ 
+diff --git a/saml/saml2/binding/impl/SAML2SOAPDecoder.cpp 
b/saml/saml2/binding/impl/SAML2SOAPDecoder.cpp
+index 812342b..bbde3e5 100644
+--- a/saml/saml2/binding/impl/SAML2SOAPDecoder.cpp
++++ b/saml/saml2/binding/impl/SAML2SOAPDecoder.cpp
+@@ -86,7 +86,7 @@ XMLObject* SAML2SOAPDecoder::decode(
+ 
+     log.debug("validating input");
+     string s = genericRequest.getContentType();
+-    if (s.find("text/xml") == string::npos) {
++    if (s.find("text/xml") == string::npos || 
s.find("application/x-www-form-urlencoded") != string::npos) {
+         log.warn("ignoring incorrect content type (%s)", s.c_str() ? 
s.c_str() : "none");
+         throw BindingException("Invalid content type for SOAP message.");
+     }
diff -Nru opensaml-3.2.1/debian/patches/series 
opensaml-3.2.1/debian/patches/series
--- opensaml-3.2.1/debian/patches/series        1970-01-01 01:00:00.000000000 
+0100
+++ opensaml-3.2.1/debian/patches/series        2025-03-14 21:47:33.000000000 
+0100
@@ -0,0 +1 @@
+CPPOST-126-Simple-signature-verification-fails-to-detect-.patch

I'm ready to upload on your word.
-- 
Feri.

Reply via email to