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.
- Clonare il repository https://github.com/italia/spid-aspnetcore.git
- Dalla root del progetto, entrare nella cartella relativa alla webapp di esempio:
samples/1_SimpleSPWebApp/SPID.AspNetCore.WebApp/
- Modificare all'interno del file
appsettings.json
il valore della chiave AssertionConsumerServiceURL
inserendo un dominio personalizzato: https://$CUSTOM_DOMAIN:$CUSTOM_PORT/signin-spid
- 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
- Visitare l'URL:
https://$CUSTOM_DOMAIN:$CUSTOM_PORT/
- Cliccare "Entra con SPID" > "DemoSpid" (secondo IdP della lista)
- Visitare la sezione "Response" > "Check Response"
- Inserire all'interno del campo "Audience" (colonna di destra) la seguente stringa:
https://spid.aspnetcore.it/
- Cliccare "Invia response al Service Provider", notare il redirect verso
/home/loggedin
e di conseguenza la corretta esecuzione del login sul portale d'esempio

- Replicare i punti dal 5 all'8 compresi
- Intercettare la richiesta HTTP generata al punto 8 tramite un HTTP Proxy, ad esempio BurpSuite di PortSwigger
- Eseguire URL-decoding e Base64-decoding del parametro POST
SAMLResponse
- Inserire alla seconda riga dell'XML il contenuto presente al seguente URL: https://demo.spid.gov.it/metadata.xml
- Modificare il contenuto del tag
<saml:Assertion>
, ad esempio modificare l'attributo email
inserendo un valore arbitrario: [email protected]
- Eseguire Base64-encoding e successivamente URL-encoding del parametro
SAMLResponse
- 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
Descrizione
L'autenticazione mediante Spid e CIE è basata sullo standard SAML2 il quale prevede due entità:
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
.Il parametro
signedDocument
contiene la risposta SAML in formato XML, mentre il parametroidentityProvider
può contenere le info dell'IdP. Nel caso il parametroidentityProvider
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):
Il codice dell'SDK otterrebbe come primo elemento della
nodeList
, ovveronodeList[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.samples/1_SimpleSPWebApp/SPID.AspNetCore.WebApp/
appsettings.json
il valore della chiaveAssertionConsumerServiceURL
inserendo un dominio personalizzato:https://$CUSTOM_DOMAIN:$CUSTOM_PORT/signin-spid
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
https://$CUSTOM_DOMAIN:$CUSTOM_PORT/
https://spid.aspnetcore.it/
/home/loggedin
e di conseguenza la corretta esecuzione del login sul portale d'esempioSAMLResponse
<saml:Assertion>
, ad esempio modificare l'attributoemail
inserendo un valore arbitrario:[email protected]
SAMLResponse
/home/loggedin
il quale dimostra la corretta identificazione e quindi anche la verifica della firma arbitraria inserita nellaSAMLResponse
nonostante la modifica dell'assertionSoluzione Consigliata
Verificare tutte le firme presenti all'interno della risposta SAML e non accettare elementi XML non firmati.
Riferimenti
Crediti
smaury
Oisfi di Shielderpaupu
Cavaglià di Shielderfromveeko
Davico di Shielder