Skip to content

SAML Response Signature Verification Bypass

Critical
danielegiallonardo published GHSA-vq63-8f72-f486 Feb 17, 2025

Package

nuget CIE.AspNetCore.Authentication (NuGet)

Affected versions

<= 2.0.4

Patched versions

2.1.0

Description

Descrizione

L'autenticazione mediante Spid e CIE è basata sullo standard SAML2 il quale prevede due entità:

  • Identity Provider (IdP): il sistema che autentica gli utenti e fornisce informazioni di identità (assertion SAML) al Service Provider, in sostanza, è responsabile della gestione delle credenziali e dell'identità degli utenti;
  • Service Provider (SP): il sistema che fornisce un servizio all'utente e si affida all'Identity Provider per autenticare l'utente, riceve le assertion SAML dall'IdP per concedere l'accesso alle risorse.

La libreria cie-aspnetcore è riferita alla seconda entità, ovvero il SP, e implement le logiche di validazione delle assertion SAML presenti all'interno delle SAML response. Segue uno schema riassuntivo di un flusso di autenticazione via SAML:

Come mostrato nello schema, l'IdP, dopo aver verificato le credenziali dell'utente, genera una risposta SAML firmata, questa viene propagata al SP dal browser dell'utente e il SP, dopo aver verificato la firma può estrarre i dati che servono per costruire la sessione dell'utente.

La logica di validazione della firma è centrale in quanto garantisce che non si possa creare una risposta SAML con assertion arbitrarie e quindi impersonare altri utenti.

Segue il codice della validazione implementata in cie-aspnetcore.

internal static bool VerifySignature(XmlDocument signedDocument, IdentityProvider? identityProvider = null){
    //...SNIP...
    SignedXml signedXml = new SignedXml(signedDocument);
    if (identityProvider is not null)
    {
        bool validated = false;
        foreach (var certificate in identityProvider.X509SigningCertificates){
            var publicMetadataCert = new X509Certificate2(Convert.FromBase64String(certificate));
            XmlNodeList nodeList = (signedDocument.GetElementsByTagName("ds:Signature")?.Count > 1) ?
                                    signedDocument.GetElementsByTagName("ds:Signature") :
                                   (signedDocument.GetElementsByTagName("ns2:Signature")?.Count > 1) ?
                                    signedDocument.GetElementsByTagName("ns2:Signature") :
                                    signedDocument.GetElementsByTagName("Signature");
            signedXml.LoadXml((XmlElement)nodeList[0]);
            validated |= signedXml.CheckSignature(publicMetadataCert, true);
        }
        return validated;
    }
    else{
        XmlNodeList nodeList = (signedDocument.GetElementsByTagName("ds:Signature")?.Count > 0) ?
                               signedDocument.GetElementsByTagName("ds:Signature") :
                               signedDocument.GetElementsByTagName("Signature");
        signedXml.LoadXml((XmlElement)nodeList[0]);
        return signedXml.CheckSignature();
    }
    //...SNIP...
}

Il parametro signedDocument contiene la risposta SAML in formato XML, mentre il parametro identityProvider può contenere le info dell'IdP. Nel caso il parametro identityProvider sia stato specificato vengono estratti i certificati pubblici di tale IdP vengono estratti, così da forzare l'uso degli stessi durante la verifica della firma, altrimenti vengono usati i certificati configurati all'interno dell'applicazione.

Successivamente, viene generata una nodeList all'interno della quale vengono salvati tutti gli elementi XML contenenti una firma XML di parte o di tutto l'envelope della risposta SAML.

Infine, viene estratto il primo elemento di questa lista, ovvero la prima firma trovata, e viene verificata.

In un normale flusso di autenticazione, la risposta SAML simile alla seguente (si noti che sono stati omessi alcuni campi e attributi per semplificare la lettura):

<samlp:Response ID="response_id" IssueInstant="2025-01-07T13:37:00Z" Version="2.0" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol">
    <saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">
        https://demo.spid.gov.it/validator
    </saml:Issuer>
    <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
        <ds:SignedInfo>
            <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
            <ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
            <ds:Reference URI="#response_id">
                <ds:Transforms>
                    <ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
                </ds:Transforms>
                <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
                <ds:DigestValue>
                    <!-- DIGEST -->
                </ds:DigestValue>
            </ds:Reference>
        </ds:SignedInfo>
        <ds:SignatureValue>
           <!-- SIGNATURE -->
        </ds:SignatureValue>
        <ds:KeyInfo>
            <ds:X509Data>
                <ds:X509Certificate>
                    <!-- CERTIFICATE -->
                </ds:X509Certificate>
            </ds:X509Data>
        </ds:KeyInfo>
    </ds:Signature>
    <samlp:Status>
        <samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
    </samlp:Status>
    <saml:Assertion ID="assertion_id" IssueInstant="2025-01-07T13:37:00Z" Version="2.0" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
        <saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">
            https://demo.spid.gov.it/validator
        </saml:Issuer>
        <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
            <ds:SignedInfo>
                <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
                <ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
                <ds:Reference URI="#assertion_id">
                    <ds:Transforms>
                        <ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
                    </ds:Transforms>
                    <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
                    <ds:DigestValue>
                        <!-- DIGEST -->
                    </ds:DigestValue>
                </ds:Reference>
            </ds:SignedInfo>
            <ds:SignatureValue>
                <!-- SIGNATURE -->
            </ds:SignatureValue>
            <ds:KeyInfo>
                <ds:X509Data>
                    <ds:X509Certificate>
                        <!-- CERTIFICATE -->
                    </ds:X509Certificate>
                </ds:X509Data>
            </ds:KeyInfo>
        </ds:Signature>
        <saml:AttributeStatement>
            <saml:Attribute Name="spidCode" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
                <saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">
                    AGID-001
                </saml:AttributeValue>
            </saml:Attribute>
            <!-- ... SNIP ... -->
        </saml:AttributeStatement>
    </saml:Assertion>
</samlp:Response>

Il codice dell'SDK otterrebbe come primo elemento della nodeList, ovvero nodeList[0], la firma riferita all'intera risposta SAML, infatti il riferimento della prima firma <ds:Reference URI="#response_id"> punta al root object <samlp:Response ID="response_id" ...>. Pertanto, verificando questa firma si avrà la certezza che l'intero contenuto della risposta SAML sia integro e autentico.

Tuttavia, non esiste una garanzia che la prima firma faccia riferimento al root object, ne consegue che se un attaccante inietta un elemento firmato come primo elemento, tutte le altre firme non saranno verificate. L'unico requisito è possedere un elemento XML firmato in modo legittimo dall'IdP, condizione facilmente soddisfabile utilizzando i metadata pubblici dell'IdP.

La risposta SAML sarebbe così strutturata:

Impatto

Un attaccante potrebbe creare una risposta SAML arbitraria che verrebbe accettata dai SP che utilizzano gli SDK vulnerabili, consentendogli di impersonare qualsiasi utilizzatore di Spid e/o CIE.

Complessità dell'attacco

L'attaccante necessità di un blocco XML contente una firma valida da parte di uno degli IdP accettati dal SP. Come descritto in precedenza, questo requisito è soddisfacibile leggendo i metadati pubblici dell'IdP che sono rappresentati da un blocco XML firmato dell'IdP.

Problematiche correlate

N/A

PoC

Nota bene: il PoC si riferisce a spid-aspnetcore che condivide le medesime logiche di verifica delle risposte SAML.

  1. Clonare il repository https://github.com/italia/spid-aspnetcore.git
  2. Dalla root del progetto, entrare nella cartella relativa alla webapp di esempio: samples/1_SimpleSPWebApp/SPID.AspNetCore.WebApp/
  3. Modificare all'interno del file appsettings.json il valore della chiave AssertionConsumerServiceURL inserendo un dominio personalizzato: https://$CUSTOM_DOMAIN:$CUSTOM_PORT/signin-spid
  4. Compilare ed eseguire la webapp di esempio tramite il seguente comando, avendo cura di sostituire i placeholder con gli stessi valori utilizzati al punto 3: dotnet build "SPID.AspNetCore.WebApp.csproj" -o ./app/build && dotnet publish "SPID.AspNetCore.WebApp.csproj" -o ./app/publish && dotnet ./app/publish/SPID.AspNetCore.WebApp.dll -urls=https://$CUSTOM_DOMAIN:$CUSTOM_PORT
  5. Visitare l'URL: https://$CUSTOM_DOMAIN:$CUSTOM_PORT/
  6. Cliccare "Entra con SPID" > "DemoSpid" (secondo IdP della lista)
  7. Visitare la sezione "Response" > "Check Response"
  8. Inserire all'interno del campo "Audience" (colonna di destra) la seguente stringa: https://spid.aspnetcore.it/
  9. Cliccare "Invia response al Service Provider", notare il redirect verso /home/loggedin e di conseguenza la corretta esecuzione del login sul portale d'esempio

  1. Replicare i punti dal 5 all'8 compresi
  2. Intercettare la richiesta HTTP generata al punto 8 tramite un HTTP Proxy, ad esempio BurpSuite di PortSwigger
  3. Eseguire URL-decoding e Base64-decoding del parametro POST SAMLResponse
  4. Inserire alla seconda riga dell'XML il contenuto presente al seguente URL: https://demo.spid.gov.it/metadata.xml
  5. Modificare il contenuto del tag <saml:Assertion>, ad esempio modificare l'attributo email inserendo un valore arbitrario: [email protected]
  6. Eseguire Base64-encoding e successivamente URL-encoding del parametro SAMLResponse
  7. Inviare la richiesta e notare il redirect verso /home/loggedin il quale dimostra la corretta identificazione e quindi anche la verifica della firma arbitraria inserita nella SAMLResponse nonostante la modifica dell'assertion

Soluzione Consigliata

Verificare tutte le firme presenti all'interno della risposta SAML e non accettare elementi XML non firmati.

Riferimenti

Crediti

Severity

Critical

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v3 base metrics

Attack vector
Network
Attack complexity
Low
Privileges required
None
User interaction
None
Scope
Unchanged
Confidentiality
High
Integrity
High
Availability
None

CVSS v3 base metrics

Attack vector: More severe the more the remote (logically and physically) an attacker can be in order to exploit the vulnerability.
Attack complexity: More severe for the least complex attacks.
Privileges required: More severe if no privileges are required.
User interaction: More severe when no user interaction is required.
Scope: More severe when a scope change occurs, e.g. one vulnerable component impacts resources in components beyond its security scope.
Confidentiality: More severe when loss of data confidentiality is highest, measuring the level of data access available to an unauthorized user.
Integrity: More severe when loss of data integrity is the highest, measuring the consequence of data modification possible by an unauthorized user.
Availability: More severe when the loss of impacted component availability is highest.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N

CVE ID

CVE-2025-24895

Weaknesses

No CWEs

Credits