← Field Guides
Active DirectoryHardeningKerberosNTLMPowerShellSecuritySPN

SPN in Active Directory: Cos'è, Come Verificarlo e Registrarlo

Pubblicato: Aggiornato:

Contesto

Leggendo gli articoli su RC4 e sul fallback Kerberos-NTLM, avrai notato che entrambi menzionano gli SPN come uno dei motivi principali per cui Kerberos non riescono e il sistema ricade su NTLM. Ma cosa è veramente un SPN? E perché è così critico?

Questo articolo parte da una visione completamente astratta e scende progressivamente nei dettagli, fino ai problemi diagnosticabili che vedi tutti i giorni in ambienti Active Directory reali.

Se vuoi collegarlo ai due articoli precedenti, questo è il pezzo che chiude il cerchio operativo:

Indice

Livello 0: Astratto — Il concetto di base

Immagina questo scenario.

Tu stai al PC, apri un browser e accedi a https://mywebsite.contoso.com. Il browser si connette al server via HTTPS e il sistema operativo desidera autenticazione Kerberos. Perfetto.

Ma un istante dopo: come fa il Key Distribution Center (KDC), cioe il componente Kerberos esposto dai Domain Controller, a sapere che il token che sta per emettere e per quel servizio SQL e non per un altro?

Risposta: il KDC non guarda l'indirizzo IP, non guarda la porta TCP, non guarda il certificato HTTPS. Guarda un nome di servizio registrato, unico nel dominio. Quel nome si chiama SPN.

In breve: KDC significa Key Distribution Center. È il servizio di Active Directory che valida le richieste Kerberos e rilascia ticket (TGT/TGS) ai client.

L'SPN è la chiave che collega il servizio fisico alla sua identità Kerberos nel dominio. Senza quella chiave, il KDC non sa cosa stai chiedendo, e il client non può costruire un ticket valido.

Conseguenza immediata: il client, vedendo fallire Kerberos, chiede al servizio "ok, allora usiamo NTLM?". E il servizio, se NTLM è ancora abilitato, risponde "certo".

Questo è il motivo per cui gli SPN mancanti creano silenziosamente fallback NTLM in ambienti che credono di stare usando Kerberos.

Mappa concettuale funzionamento SPN

Contenuto: diagramma Client -> KDC -> Servizio con lookup SPN e rilascio ticket Kerberos.


Livello 1: Concettuale — Anatomia e registro

La sintassi di uno SPN

Un SPN ha il seguente formato:

<ServiceType>/<Host>:<Port>@<REALM>

Vediamo ogni componente:

ComponenteSignificatoEsempio
ServiceTypeTipo di servizio Kerberos registratoHTTP, MSSQLSvc, ldap, host
HostNome del computer (FQDN o nome breve a volte)sqlserver.contoso.com o sqlserver
PortPorta (opzionale, ma talvolta necessaria)1433 per SQL Server, omesso per HTTP
REALMDominio / realm Kerberos (opzionale in molti casi)CONTOSO.COM

Esempi reali:

  • HTTP/webapp.contoso.com — servizio web su webapp
  • HTTP/webapp.contoso.com:8080 — servizio web sulla porta 8080
  • MSSQLSvc/sqlserver.contoso.com:1433 — SQL Server
  • ldap/dc01.contoso.com — LDAP su un Domain Controller
  • host/fileserver.contoso.com — servizio generico host
  • cifs/fileserver.contoso.com — CIFS (SMB) su un file server

Chi registra un SPN e dove?

Quando registri un SPN nel dominio, il SPN è collegato a un account di Active Directory. Questo account è solitamente:

  • Computer account (es. CONTOSO\SQLSERVER$) — quando il servizio gira localmente sul computer
  • User account (es. CONTOSO\sqlservice_account) — quando il servizio gira con un account di servizio specifico

L'SPN viene memorizzato nell'attributo servicePrincipalName dell'account.

Regola fondamentale: un SPN può essere registrato su un solo account nel dominio. Se lo registri su due account, crei un conflitto che rompe la negoziazione Kerberos per entrambi.

Chi fa cosa: automatico vs manuale

Questa è la domanda pratica più importante per un tecnico: "devo registrarlo io o ci pensa AD?"

In automatico (nella maggior parte dei casi standard):

  • servizi di sistema su computer domain-joined: l'account computer registra e aggiorna molti SPN built-in (es. HOST/, CIFS/, LDAP/ su DC)
  • SQL Server con account e permessi corretti puo registrare automaticamente MSSQLSvc/... all'avvio
  • alcuni servizi Microsoft, se eseguiti nel contesto previsto, fanno self-registration all'avvio

Manuale (quando devi intervenire tu):

  • il servizio gira con un account utente/gMSA senza diritti adeguati di write su servicePrincipalName
  • usi alias DNS, CNAME, listener, VIP o nomi applicativi che non coincidono con il nome host standard
  • hai ambienti migrati/legacy con SPN mancanti, duplicati o obsoleti
  • il servizio non registra da solo o la registrazione automatica fallisce in silenzio

Regola operativa semplice:

  • prima prova il percorso automatico (più pulito e sostenibile)
  • poi verifica sempre se lo SPN esiste davvero ed e unico
  • se non c'e o e errato, registrazione manuale + validazione immediata

Perché registrarlo e soprattutto quando

Se l'obiettivo è autenticazione Kerberos affidabile, lo SPN non è un "nice to have": è il puntatore che permette al KDC di emettere il ticket per il servizio giusto.

Casi in cui e obbligatorio (o fortemente raccomandato):

  • servizio esposto a utenti o applicazioni domain-joined che devono autenticarsi in Kerberos
  • servizio che gira con account dedicato (user account o gMSA), non con il solo computer account standard
  • scenari con delega Kerberos (double-hop, backend SQL, servizi web con impersonation)
  • nomi applicativi/alias/listener (es. app.contoso.com, listener SQL, VIP LB) diversi dal nome macchina
  • onboarding di nuovi servizi e migrazioni (prima del go-live)

Quando devi verificarlo o aggiornarlo:

  • creazione di un nuovo servizio
  • cambio service account
  • cambio hostname/FQDN/alias/CNAME/listener/porta
  • migrazione server, failover, replatforming, consolidamento
  • troubleshooting di fallback NTLM o errori Kerberos intermittenti

Casi in cui puo non servire un intervento manuale:

  • servizi built-in che si registrano da soli e risultano già corretti/univoci
  • workload che non usano Kerberos per design e non richiedono SSO/delega
  • ambienti dove il servizio e solo locale e non espone autenticazione integrata AD

Casi in cui va evitato/omesso:

  • registrare SPN "a tentativi" su account sbagliati (rischio ticket decryption failure)
  • registrare lo stesso SPN su più account (duplicati)
  • usare SPN per mascherare problemi DNS/networking (es. accesso via IP): prima correggi naming/FQDN
  • lasciare SPN legacy dopo rename/decommission: diventano stale e creano ambiguita

Regola pratica: se un client AD deve arrivare a quel servizio con Kerberos, lo SPN deve essere presente, univoco e sull'account giusto; se non c'e questo requisito, non aggiungerlo "per sicurezza".

Decision tree SPN: auto-registration o intervento manuale

Perché il KDC ha bisogno dell'SPN

Quando un client avvia una richiesta Kerberos per un servizio, il flusso è:

  1. Client richiede il ticket: "Voglio un ticket per HTTP/webapp.contoso.com"
  2. KDC ricerca l'SPN: scansiona Active Directory cercando un account con l'attributo servicePrincipalName che contiene esattamente HTTP/webapp.contoso.com
  3. KDC emette il ticket: se trova l'account, emette un ticket criptato con la password di quell'account
  4. Client invia il ticket al servizio: il servizio decripta il ticket usando la propria password (che deve corrispondere)
  5. Autenticazione riuscita: se la decriptazione riesce, il servizio sa che il client è autenticato

Se il passo 2 fallisce (SPN non trovato), il KDC:

  • Ritorna un errore al client (KRB_ERR_S_PRINCIPAL_UNKNOWN o simile)
  • Il client, vedendo l'errore, chiede al servizio: "provo con NTLM?"
  • Il servizio risponde: "ok"
  • L'autenticazione cade a NTLM

Questa è la sequenza che vedi ripetersi: SPN mancante → Kerberos fallisce → NTLM viene usato.

Fallback path Kerberos -> NTLM in 3 step

Livello 2: Pratico — Come trovare gli SPN

Metodo 1: PowerShell - Visualizzare tutti gli SPN in un dominio

# Mostra TUTTI gli SPN registrati nel dominio
Get-ADObject -LDAPFilter "(servicePrincipalName=*)" -Properties servicePrincipalName | 
  Select-Object Name, ObjectClass, servicePrincipalName | 
  Format-Table -AutoSize -Wrap

Questo comando ritorna ogni account (computer, user, service account) che ha un SPN registrato.

Metodo 2: PowerShell - Cercare uno SPN specifico

# Cerca un SPN specifico (ad esempio, un'applicazione web)
$spn = "HTTP/webapp.contoso.com"
Get-ADObject -LDAPFilter "(servicePrincipalName=$spn)" -Properties servicePrincipalName

Utile quando conosci il servizio e vuoi verificare se è già registrato.

Metodo 3: Setspn (strumento legacy ma efficace)

# Mostra tutti gli SPN nel dominio
setspn -Q */*

# Cerca un SPN specifico
setspn -Q HTTP/webapp.contoso.com

# Cerca tutti gli SPN di un account specifico
setspn -L sqlserver

# Cerca un SPN con un servizio specifico
setspn -Q HTTP/*

setspn è il comando nativo per gestire gli SPN ed è ancora ampiamente usato.

Ricerca SPN da prompt (carosello da 2 screenshot)

Contenuto: Output leggibile di query globale e query specifica.

Metodo 4: Event ID di Active Directory

L'Event ID 4661 (Detailed Tracking) registra tentativi di accesso agli attributi AD, incluso servicePrincipalName. E utile per capire chi ha letto/modificato oggetti sensibili e per correlare cambi SPN con incidenti di autenticazione.

Per renderlo realmente utile devi attivare l'auditing lato Domain Controller:

  1. Apri gpmc.msc e modifica la GPO applicata ai Domain Controller (tipicamente Default Domain Controllers Policy).
  2. Vai in: Computer Configuration > Policies > Windows Settings > Security Settings > Advanced Audit Policy Configuration > DS Access.
  3. Abilita almeno:
    • Audit Directory Service Access (Success, opzionale Failure)
    • Audit Directory Service Changes (Success)
  4. Esegui gpupdate /force sui DC e verifica la policy con auditpol /get /category:*.

Dove trovi gli eventi:

  • Event Viewer > Windows Logs > Security sui Domain Controller.
  • In SIEM, filtra EventID=4661 e cerca servicePrincipalName nel messaggio/XML.

Perché serve operativamente:

  • attribuire cambi SPN a un utente/processo preciso
  • costruire timeline in caso di fallback NTLM improvviso
  • individuare modifiche non autorizzate durante hardening o migrazioni

Script PowerShell rapido di triage (ultime 24 ore su tutti i DC):

Import-Module ActiveDirectory

$start = (Get-Date).AddHours(-24)
$dcs = Get-ADDomainController -Filter * | Select-Object -ExpandProperty HostName

$report = foreach ($dc in $dcs) {
  Get-WinEvent -ComputerName $dc -FilterHashtable @{ LogName = 'Security'; Id = 4661; StartTime = $start } -ErrorAction SilentlyContinue |
    Where-Object { $_.Message -match 'servicePrincipalName' } |
    Select-Object @{N='DomainController';E={$dc}}, TimeCreated, Id, RecordId, MachineName, Message
}

$report |
  Sort-Object TimeCreated -Descending |
  Tee-Object -Variable spn4661 |
  Select-Object -First 30 TimeCreated, DomainController, Id, RecordId

$spn4661 | Export-Csv .\spn-event4661-last24h.csv -NoTypeInformation -Encoding UTF8
Write-Host "Eventi 4661 con servicePrincipalName trovati: $($spn4661.Count)"

Livello 3: Diagnostico — I problemi veri

Problema 1: SPN mancante

Sintomo: un servizio "funziona" ma il client cade su NTLM invece di usare Kerberos.

Causa: nessun SPN registrato per il servizio.

Esempio reale:

Hai appena configurato SQL Server su sqlserver.contoso.com. L'account di servizio è CONTOSO\sqlservice_account. Ma dimentichi di registrare lo SPN MSSQLSvc/sqlserver.contoso.com:1433 sull'account.

Un client prova a connettersi con Kerberos:

  • Il KDC ricerca MSSQLSvc/sqlserver.contoso.com:1433
  • Non lo trova
  • Ritorna un errore
  • Il client cade su NTLM
  • La connessione "funziona" ugualmente, perché NTLM è ancora abilitato

Come riconoscerlo:

Leggi il log Kerberos nel Visualizzatore eventi del client o del server. Vedrai un evento simile a:

Event ID 3: Kerberos authentication ticket request failed. 
Status: 0xC0000225 (SPN_NOT_REGISTERED or PRINCIPAL_NOT_FOUND)

Se vedi questo, è una quasi certezza che lo SPN manca.

Problema 2: SPN duplicato

Sintomo: autenticazione Kerberos incoerente, alcuni ticket funzionano, altri no. Accesso intermittente.

Causa: lo stesso SPN è registrato su più account.

Esempio reale:

Un collega registra HTTP/webapp.contoso.com sull'account CONTOSO\apppool1_account per il servizio web. Mesi dopo, durante una migrazione, registra lo stesso SPN anche su CONTOSO\apppool2_account per il nuovo pool.

Ora il KDC, ricevendo una richiesta per HTTP/webapp.contoso.com, non sa quale account usare. A volte ritorna il ticket per apppool1, a volte per apppool2, a volte non sa cosa fare e fallisce.

Come riconoscerlo:

# Cerca SPN duplicati
$allSpns = Get-ADObject -LDAPFilter "(servicePrincipalName=*)" -Properties servicePrincipalName
$spnGroups = $allSpns | ForEach-Object { 
  $_.servicePrincipalName | ForEach-Object { $_ }
} | Group-Object
$duplicates = $spnGroups | Where-Object { $_.Count -gt 1 }
$duplicates | ForEach-Object {
  Write-Host "Duplicato trovato: $($_.Name) registrato $($_.Count) volte"
}
Duplicati SPN (single screenshot)

Problema 3: SPN registrato sull'account sbagliato

Sintomo: il servizio gira con un account X, ma lo SPN è registrato su un account Y completamente diverso.

Causa: configurazione umana errata o legacy non allineata.

Esempio reale:

Una shared mailbox in Exchange gira con l'account CONTOSO\SharedMailbox$, ma l'SPN ldap/sharedmailbox.contoso.com è registrato su un vecchio account CONTOSO\OldMailboxAccount$ che non è nemmeno più usato.

Quando un client tenta di autenticarsi:

  • Il KDC trova ldap/sharedmailbox su OldMailboxAccount$
  • Emette un ticket criptato con la password di OldMailboxAccount$
  • Il servizio (che gira come SharedMailbox$) non riesce a decriptare il ticket
  • Autenticazione fallisce

Come riconoscerlo:

Verifica che l'account con cui gira il servizio sia lo stesso su cui è registrato l'SPN:

# Esempio per SQL Server
$sqlService = Get-Service MSSQLSERVER
$serviceAccount = $sqlService.ServiceAccount  # Ritorna il nome dell'account

# Cerca l'SPN per quel servizio
Get-ADObject -LDAPFilter "(servicePrincipalName=MSSQLSvc/*)" -Properties servicePrincipalName, Name | 
  Where-Object { $_.Name -eq $serviceAccount }

# Se non ritorna nulla, l'SPN non è sul giusto account

Problema 4: SPN obsoleto o in conflitto

Sintomo: dopo una migrazione o rebrand, autenticazione vecchia che dovrebbe essere morta rimane attiva.

Causa: SPN vecchio non rimosso dal dominio.

Esempio reale:

Il tuo dominio aveva sqlserver.old.contoso.com. Lo rinomini a sqlserver.new.contoso.com e registri il nuovo SPN. Ma dimentichi di rimuovere il vecchio SPN MSSQLSvc/sqlserver.old.contoso.com:1433.

Alcuni client, per abitudine o per configurazione vecchia, continuano a fare richieste al vecchio nome. Improvvisamente questi client vedono conflitti o comportamenti strani.

Come riconoscerlo:

Fai una lista di tutti gli SPN e identifica quelli che non corrispondono a servizi attivi:

Get-ADObject -LDAPFilter "(servicePrincipalName=*)" -Properties servicePrincipalName, Name | 
  Sort-Object Name

Ispeziona manualmente e identifica SPN che non corrispondono a servizi attuali.

Problema 5: Kerberos Constrained Delegation (KCD) che fallisce solo nel secondo hop

Sintomo: l'utente accede correttamente al frontend (IIS/app/API), ma il backend SQL o CIFS fallisce con 401, KDC_ERR_BADOPTION o KRB_AP_ERR_MODIFIED.

Causa tipica: la delega e configurata, ma gli SPN backend in msDS-AllowedToDelegateTo non corrispondono ai nomi realmente usati dall'applicazione (FQDN, porta, alias, listener).

Caso reale:

  • Frontend: HTTP/app.contoso.com su account CONTOSO\\svc-web
  • Backend atteso: MSSQLSvc/sql01.contoso.com:1433
  • Backend usato davvero dall'app: MSSQLSvc/sql-listener.contoso.com:1433

Il primo hop riesce, il secondo no. Il problema non e "Kerberos generico": e mismatch tra SPN target e delega consentita.

# Verifica configurazione KCD classica sull'account frontend
Get-ADUser -Identity "svc-web" -Properties msDS-AllowedToDelegateTo, TrustedToAuthForDelegation |
  Select-Object SamAccountName, TrustedToAuthForDelegation, msDS-AllowedToDelegateTo

# Cerca lo SPN backend realmente usato
setspn -Q MSSQLSvc/sql-listener.contoso.com:1433

Checklist rapida KCD:

  • SPN frontend HTTP/... presente e unico sull'account giusto
  • msDS-AllowedToDelegateTo contiene esattamente lo SPN backend usato a runtime
  • l'app non usa IP o alias diversi da quelli previsti in delega

Problema 6: Resource-Based Constrained Delegation (RBCD) configurata ma non effettiva

Sintomo: in scenari moderni (microservizi, tier multipli, identity bridge) la delega "dovrebbe" funzionare ma i ticket S4U2Proxy non vengono emessi.

Causa tipica: RBCD e impostata sul computer account backend, ma il principal frontend autorizzato e sbagliato oppure manca il suo SPN valido.

# Verifica chi puo delegare verso il backend (RBCD)
Get-ADComputer -Identity "SQL01" -Properties PrincipalsAllowedToDelegateToAccount |
  Select-Object -ExpandProperty PrincipalsAllowedToDelegateToAccount

# Verifica SPN del frontend che richiede delega
setspn -L WEB01

Pattern operativo utile:

  • In KCD classica controlli soprattutto msDS-AllowedToDelegateTo sul frontend.
  • In RBCD controlli soprattutto PrincipalsAllowedToDelegateToAccount sul backend.
  • In entrambi i casi, senza SPN frontend/backend coerenti il flusso si rompe.

Problema 7: Utenti in Protected Users, Kerberos obbligatorio e fallback NTLM negato

Sintomo: l'applicazione "sembra funzionare" per utenti standard (che possono cadere su NTLM), ma fallisce per utenti nel gruppo Protected Users con errori di accesso o 401.

Punto chiave: un utente Protected Users non puo usare fallback NTLM. Se Kerberos non va a buon fine, la sessione fallisce invece di degradare.

Domanda tipica: "I Protected Users hanno SPN particolari registrati?"

Risposta: no. Gli SPN non sono una proprieta speciale degli utenti Protected Users. Gli SPN restano registrati su account di servizio/computer (target del servizio), non sul fatto che l'utente sia Protected o meno.

Quello che cambia è il comportamento di autenticazione:

  • utente standard: in alcuni casi puo finire su NTLM e "sembra andare"
  • utente Protected Users: NTLM bloccato, quindi servono SPN corretti e percorso Kerberos pulito end-to-end

Implicazione operativa: i Protected Users sono un ottimo "canary" per scoprire SPN mancanti, naming incoerente, alias non registrati o deleghe non allineate.

Checklist diagnostica rapida:

  • conferma membership nel gruppo Protected Users
  • verifica SPN del servizio target (setspn -Q ...) e univocita
  • controlla che il client usi FQDN previsto (non IP, non alias non registrato)
  • correla eventi Kerberos (4768, 4769, 4771) con eventuali tracce NTLM (4776)

Script PowerShell utile: trova utenti Protected Users e segnala eventuali eventi NTLM recenti (anomali da investigare)

Import-Module ActiveDirectory

$start = (Get-Date).AddHours(-24)
$protectedUsers = Get-ADGroupMember -Identity "Protected Users" -Recursive |
  Where-Object { $_.objectClass -eq 'user' } |
  ForEach-Object { Get-ADUser $_.DistinguishedName -Properties SamAccountName } |
  Select-Object -ExpandProperty SamAccountName

$dcs = Get-ADDomainController -Filter * | Select-Object -ExpandProperty HostName

$ntlmFindings = foreach ($dc in $dcs) {
  Get-WinEvent -ComputerName $dc -FilterHashtable @{ LogName = 'Security'; Id = 4776; StartTime = $start } -ErrorAction SilentlyContinue |
    ForEach-Object {
      $msg = $_.Message
      $matchedUser = $protectedUsers | Where-Object { $msg -match [Regex]::Escape($_) } | Select-Object -First 1
      if ($matchedUser) {
        [PSCustomObject]@{
          TimeCreated = $_.TimeCreated
          DomainController = $dc
          EventId = $_.Id
          ProtectedUser = $matchedUser
          RecordId = $_.RecordId
          Message = $msg
        }
      }
    }
}

$ntlmFindings |
  Sort-Object TimeCreated -Descending |
  Tee-Object -Variable protectedNtlmEvents |
  Select-Object -First 30 TimeCreated, DomainController, ProtectedUser, EventId, RecordId

$protectedNtlmEvents | Export-Csv .\protected-users-ntlm-events-last24h.csv -NoTypeInformation -Encoding UTF8
Write-Host "Eventi NTLM (4776) legati a Protected Users nelle ultime 24h: $($protectedNtlmEvents.Count)"

Se il report non e vuoto, hai quasi sempre un percorso applicativo che tenta ancora NTLM o una configurazione Kerberos/SPN incompleta.

Quick-win: script per individuare SPN stale candidati alla rimozione

Questo script non cancella nulla. Produce una lista prioritaria di candidati stale da validare prima della rimozione.

Import-Module ActiveDirectory

$staleComputerDays = 90
$staleUserDays = 180
$now = Get-Date

$objects = Get-ADObject -LDAPFilter "(servicePrincipalName=*)" -Properties \
  servicePrincipalName,
  objectClass,
  samAccountName,
  distinguishedName,
  whenChanged,
  lastLogonTimestamp,
  userAccountControl

$report = foreach ($obj in $objects) {
  $lastLogon = if ($obj.lastLogonTimestamp) {
    [DateTime]::FromFileTime([Int64]$obj.lastLogonTimestamp)
  } else {
    $null
  }

  $daysSinceLogon = if ($lastLogon) { ($now - $lastLogon).Days } else { $null }
  $isDisabled = (($obj.userAccountControl -band 2) -ne 0)
  $threshold = if ($obj.objectClass -eq "computer") { $staleComputerDays } else { $staleUserDays }

  foreach ($spn in $obj.servicePrincipalName) {
    $looksStaleByName = $spn -match "old|legacy|decom|deprecated|backup"
    $inactiveTooLong = $daysSinceLogon -ne $null -and $daysSinceLogon -ge $threshold

    if ($isDisabled -or $inactiveTooLong -or $looksStaleByName) {
      [PSCustomObject]@{
        CandidateReason = @(
          if ($isDisabled) { "DisabledAccount" }
          if ($inactiveTooLong) { "Inactive_${daysSinceLogon}d" }
          if ($looksStaleByName) { "NamePattern" }
        ) -join ","
        ObjectClass = $obj.objectClass
        SamAccountName = $obj.samAccountName
        LastLogon = $lastLogon
        SPN = $spn
        DistinguishedName = $obj.distinguishedName
      }
    }
  }
}

$report |
  Sort-Object CandidateReason, ObjectClass, SamAccountName |
  Tee-Object -Variable staleCandidates |
  Export-Csv -Path .\spn-stale-candidates.csv -NoTypeInformation -Encoding UTF8

Write-Host "Candidati stale trovati: $($staleCandidates.Count)"

Checklist sicurezza prima della rimozione reale:

  • verifica ownership applicativa del servizio
  • verifica assenza di ticket recenti correlati
  • rimozione in change window
  • rollback pronto (setspn -S ...) in caso di regressione

Livello 4: Remediazione — Come correggere gli SPN

Registrare un SPN correttamente

Su un computer account (COMPUTER$)

# Aggiungi un SPN a un computer account
$computer = Get-ADComputer "sqlserver"
Set-ADServicePrincipalName -Identity $computer -Add "MSSQLSvc/sqlserver.contoso.com:1433"

# Verifica
Get-ADServicePrincipalName -Identity $computer

O con setspn:

setspn -A MSSQLSvc/sqlserver.contoso.com:1433 sqlserver

Su un service account (USER)

# Aggiungi un SPN a un account utente
$account = Get-ADUser "sqlservice_account"
Set-ADServicePrincipalName -Identity $account -Add "MSSQLSvc/sqlserver.contoso.com:1433"

# Verifica
Get-ADServicePrincipalName -Identity $account

O con setspn:

setspn -A MSSQLSvc/sqlserver.contoso.com:1433 sqlservice_account

Rimuovere un SPN

# Rimuovi un SPN
$account = Get-ADObject "sqlserver"
Set-ADServicePrincipalName -Identity $account -Remove "MSSQLSvc/sqlserver.contoso.com:1433"

O con setspn:

setspn -D MSSQLSvc/sqlserver.contoso.com:1433 sqlserver

Rimuovere un SPN duplicato

Se hai un SPN duplicato, devi prima decidere quale account deve mantenerlo, poi rimovere l'altro:

# Trova i duplicati
$spn = "HTTP/webapp.contoso.com"
$accounts = Get-ADObject -LDAPFilter "(servicePrincipalName=$spn)" -Properties Name, servicePrincipalName

# Visualizza quale account deve mantenere il SPN (di solito quello che gira il servizio)
$accounts | ForEach-Object { Write-Host "Account: $($_.Name)" }

# Rimuovi il SPN dall'account sbagliato
$wrongAccount = Get-ADObject "apppool1_account"
Set-ADServicePrincipalName -Identity $wrongAccount -Remove $spn

Validare un SPN con ktpass su Linux

Dopo aver registrato un SPN, è buona pratica generare una keytab (su sistemi Linux) o validare che la configurazione sia coerente:

# Genera una keytab per un service account (uso Linux o Cygwin)
ktpass -princ MSSQLSvc/sqlserver.contoso.com:1433@CONTOSO.COM ^
       -mapuser CONTOSO\sqlservice_account ^
       -pass MyServicePassword ^
       -ptype KRB5_NT_PRINCIPAL ^
       -out sqlserver.keytab

Questo comando assicura che la password dell'account, l'SPN e la keytab siano tutte sincronizzate.


Livello 5: Integrazione — SPN e i problemi che hai già letto

Collegamento a RC4 e Kerberos Enforcement

Quando Microsoft ha abilitato l'enforcement di RC4 (vedi articolo precedente), uno dei problemi era che alcuni account non avevano AES abilitato. Ma come scopri quali account hanno un problema se non sai quali servizi hanno un SPN registrato?

Risposta: devi prima fare una lista completa di tutti gli SPN nel dominio, poi verificare che gli account su cui sono registrati supportino tutti AES.

Collegamento al fallback NTLM

Se hai letto l'articolo sul fallback Kerberos-NTLM, una delle cause più comuni elencate era SPN mancante o duplicato. Ora capisci il perché: senza SPN valido, il KDC non può emettere un ticket, e il client cade automaticamente su NTLM.

Se vuoi davvero eliminare NTLM dal tuo dominio, devi prima essere sicuro che ogni servizio che usa Kerberos abbia un SPN valido e unico registrato.

SPN corretti permettono al KDC di emettere ticket di servizio; la chiave dell'account KRBTGT protegge invece i ticket TGT del dominio. Per la pianificazione e la validazione della rotazione di questa chiave, consulta il runbook di rotazione KRBTGT in Active Directory.

Checklist pre-hardening

Prima di abilitare Protected Users o disabilitare NTLM:

  • Identifica tutti i servizi nel dominio che richiedono autenticazione
  • Verifica che ognuno di essi abbia un SPN registrato
  • Verifica che nessun SPN sia duplicato
  • Verifica che ogni SPN sia registrato sull'account che gira il servizio
  • Verifica che tutti gli account con SPN supportino AES
  • Testa che ogni servizio possa autenticarsi con Kerberos (non NTLM)
  • Rimuovi gli SPN obsoleti

Event ID e segnali da monitorare (runbook SOC)

Quando vuoi verificare la qualità reale degli SPN, non basta una foto statica di Active Directory: devi unire inventario SPN e telemetria autenticazione.

Eventi da seguire con priorita:

  • 4769 (TGS request): per vedere servizi richiesti e cifratura ticket
  • 4771 (Kerberos pre-auth failed): utile per errori lato KDC in fase iniziale
  • 4625 / 4624 su server target: per correlare fallback e auth package
  • 4776: validazione NTLM, ottimo indicatore di fallback non voluto

Codici ricorrenti collegati agli SPN:

  • KDC_ERR_S_PRINCIPAL_UNKNOWN -> SPN mancante/non risolvibile
  • KRB_AP_ERR_MODIFIED -> ticket emesso per account diverso da quello che decripta (SPN sull'account sbagliato o duplicato)
  • KDC_ERR_BADOPTION -> delega richiesta non consentita (frequente in KCD/RBCD)

Mini-query di triage (da adattare al SIEM):

# Ultimi 7 giorni: richieste TGS verso HTTP/MSSQLSvc + possibili errori correlati
$start = (Get-Date).AddDays(-7)
Get-WinEvent -FilterHashtable @{LogName='Security'; Id=4769; StartTime=$start} |
  Where-Object {
    $_.Message -match 'Service Name:\s+(HTTP|MSSQLSvc)/' -or
    $_.Message -match 'Status:\s+0x7|0xD|0x1F'
  } |
  Select-Object TimeCreated, Id, Message

Piano operativo in 30 giorni (SPN-first)

Se vuoi ridurre in modo misurabile il fallback NTLM senza outage, questa sequenza è la più robusta:

  1. Settimana 1 - Inventory: esporta tutti gli SPN, trova duplicati, mappa owner applicativi.
  2. Settimana 2 - Fix ad alto impatto: SQL, IIS, file services, servizi con alto volume 4769.
  3. Settimana 3 - Casi avanzati: KCD/RBCD, alias DNS, listener, keytab Linux.
  4. Settimana 4 - Validation: test con account in Protected Users e auditing NTLM attivo.

KPI utili per misurare il miglioramento:

  • numero SPN duplicati (target: 0)
  • percentuale sessioni SQL in KERBEROS
  • trend Event ID 4776 su DC (target: in discesa)
  • incidenti auth post-change (target: nessun incremento)

Senza questa checklist, il tuo progetto di hardening fallirà in modi difficili da debuggare.


Livello 6: Hardening SPN — Controlli e accesso

Principio: Least Privilege su modifica SPN

Un SPN è un attributo scritto su un account di Active Directory. Chi puo scriverlo, puo potenzialmente:

  • registrare un SPN su un account diverso da quello previsto
  • creare conflitti intercettando ticket diretti a servizi legittimi
  • facilitare abuso di delega (KCD/RBCD)

Controllo minimo da implementare subito:

Non chiunque in Domain Admins dovrebbe casualmente modificare un SPN. Invece:

  1. Delega il diritto di write solo ai proprietari del servizio tramite security group AGILE.
  2. Abilita auditing (Event ID 5136 - Directory Service Changes) per registrare ogni modifica SPN.
  3. Centralizza la registrazione tramite processo change management, non permessi diretti su AD.

Script: Audit chi ha diritti di write su SPN

Import-Module ActiveDirectory

# Cerca tutti gli oggetti con diritti di modifica su servicePrincipalName
$rootDSE = Get-ADRootDSE
$results = @()

$filter = "(|(objectClass=user)(objectClass=computer))"
Get-ADObject -LDAPFilter $filter -Properties * -SearchBase $rootDSE.defaultNamingContext |
  ForEach-Object {
    $obj = $_
    $acl = Get-Acl -Path "AD:\$($obj.DistinguishedName)" -ErrorAction SilentlyContinue
    
    if ($acl) {
      $acl.Access |
        Where-Object { $_.ObjectType -eq 'f3a64788-5305-11d1-a9c5-0000f80367c1' } |  # servicePrincipalName GUID
        ForEach-Object {
          $results += [PSCustomObject]@{
            Target = $obj.Name
            Principal = $_.IdentityReference
            AccessType = $_.AccessControlType
            Rights = $_.ActiveDirectoryRights
          }
        }
    }
  }

$results | Export-Csv -Path .\spn-write-permissions.csv -NoTypeInformation
Write-Host "Account con diritti di write su SPN: $($results.Count)"

Policy: Disabilita auto-registration su SPN ad alto rischio

Alcuni account di sistema non devono registrare SPN da soli. Policy GPO da considerare:

Computer Configuration > Policies > Windows Settings > Security Settings > 
  Local Policies > Security Options
  
"Network security: Restrict NTLM: Outgoing NTLM traffic to remote servers"
  -> Set to "Deny all"

Questo forza Kerberos e impedisce fallback silenzioso. Ma prima assicurati che gli SPN siano corretti.

Per ulteriore protezione, su Windows Server:

# Disabilita la registrazione automatica di SPN per un account
Set-ADUser -Identity "svc-account" -ServicePrincipalNames @()

# Se il servizio ha davvero bisogno di SPN, registralo manualmente SOLO una volta
Set-ADServicePrincipalName -Identity "svc-account" -Add "HTTP/myapp.contoso.com"

Validazione periodica automatizzata

Uno script che gira ogni settimana e verifica la coerenza:

Import-Module ActiveDirectory

$report = @()

# 1. Cerca SPN duplicati
$allSpns = Get-ADObject -LDAPFilter "(servicePrincipalName=*)" -Properties servicePrincipalName
$spnGroups = $allSpns | ForEach-Object { $_.servicePrincipalName } | Group-Object
$duplicates = $spnGroups | Where-Object { $_.Count -gt 1 }

$duplicates | ForEach-Object {
  $report += [PSCustomObject]@{
    CheckType = "Duplicate"
    Severity = "HIGH"
    Finding = "SPN registered $($_.Count) times"
    SPN = $_.Name
    RemediationPriority = 1
  }
}

# 2. Cerca SPN su account disabilitati
$disabledWithSpn = Get-ADObject -LDAPFilter "(&(servicePrincipalName=*)(userAccountControl:1.2.840.113556.1.4.803:=2)))" -Properties servicePrincipalName, Name
$disabledWithSpn | ForEach-Object {
  $report += [PSCustomObject]@{
    CheckType = "DisabledAccountWithSPN"
    Severity = "MEDIUM"
    Finding = "Disabled account still has SPN"
    SPN = $_servicePrincipalName -join "; "
    AccountName = $_.Name
    RemediationPriority = 2
  }
}

# 3. Cerca account senza AES mentre hanno SPN (durante hardening RC4)
$noAES = Get-ADUser -Filter { (servicePrincipalName -like "*") -and (msDS-SupportedEncryptionTypes -ne 24) } -Properties servicePrincipalName, msDS-SupportedEncryptionTypes
$noAES | ForEach-Object {
  $report += [PSCustomObject]@{
    CheckType = "NoAES"
    Severity = "HIGH"
    Finding = "SPN account missing AES encryption support"
    SPN = $_.servicePrincipalName -join "; "
    AccountName = $_.SamAccountName
    RemediationPriority = 1
  }
}

# Export e alert
$report | Export-Csv -Path ".\spn-validation-$(Get-Date -Format 'yyyyMMdd-HHmmss').csv" -NoTypeInformation
if ($report) {
  Write-Host "⚠️ SPN findings: $($report.Count) issues found"
  # Oppure invia email al team di sicurezza
}

Livello 7: Abuse patterns — Attacchi su SPN e delega

Vettore 1: S4U2Proxy abuse (Kerberos delegation)

Scenario: Un attaccante compromette un account con SPN registrato che ha delega abilitata. Puo usare S4U2Proxy per ottenere ticket verso backend senza il consenso dell'utente finale.

Esempio:

  • Account compromesso: CONTOSO\WEB-APP$ con SPN HTTP/webapp.contoso.com
  • Delega consentita: CONTOSO\WEB-APP$ → MSSQLSvc/database.contoso.com:1433
  • Attacco: L'attaccante usa il ticket TGT di WEB-APP$ per richiedere un ticket al database senza passare attraverso l'utente.

Mitigazione:

  1. Abilita Constrained Delegation solo se strettamente necessario.

    # Verifica quanti account hanno delega abilitata
    Get-ADUser -Filter { TrustedToAuthForDelegation -eq $true } | Select-Object SamAccountName
    
  2. Usa Resource-Based Constrained Delegation (RBCD) al posto di KCD classica quando possibile (è più granulare).

  3. Monitora S4U2Self e S4U2Proxy richieste (Event ID 4781 e correlazioni con 4769).

  4. Disabilita "Unconstrained Delegation" (TrustedForDelegation) se non indispensabile.

Vettore 2: Creazione di SPN fittizio per intercettazione

Scenario: Un utente con permessi insufficienti registra un SPN falso su un account compromesso per intercettare ticket diretti a un servizio legittimo.

Esempio:

  • Servizio legittimo: HTTP/database-api.contoso.com su account CONTOSO\dbapi_svc
  • Attacco: l'attaccante registra HTTP/database-api.contoso.com su un altro account che controlla
  • Risultato: il KDC, non sapendo quale account è "corretto", potrebbe emettere ticket all'account sbagliato

Mitigazione:

  1. Verifica regolarmente che ogni SPN sia univoco (lo script di validazione automatizzata sopra lo fa).
  2. Limita chi puo scrivere l'attributo servicePrincipalName tramite delegazione AGILE e ACL su OU.
  3. Abilita "Enforce unique SPN" nelle tue procedure di onboarding: non permettere mai due SPN identici.

Vettore 3: Compromise di account delegati (KCD/RBCD)

Scenario: Un account autorizzato a delegare (es. frontend app server) viene compromesso. L'attaccante puo richiedere ticket verso backend senza autenticazione aggiuntiva.

Mitigazione specifica per KCD:

# 1. Limita il numero di backend a cui puo delegare
Get-ADUser -Identity "frontend-app" -Properties msDS-AllowedToDelegateTo |
  Select-Object msDS-AllowedToDelegateTo

# 2. Verifica che siano esattamente gli SPN necessari, NULLA DI PIÙ
# 3. Se possibile, passa a RBCD (è più granulare)

Mitigazione specifica per RBCD:

# 1. Verifica chi è autorizzato a delegare verso un backend
Get-ADComputer -Identity "backend-sql" -Properties PrincipalsAllowedToDelegateToAccount |
  Select-Object -ExpandProperty PrincipalsAllowedToDelegateToAccount

# 2. Minimizza la lista: solo account necessari
# 3. Monitora modifiche a questa lista

Controllo di accesso: Configura permessi su PrincipalsAllowedToDelegateToAccount in modo che solo le applicazioni legittime possano essere aggiunte.

Vettore 4: Stale SPN su account legacy usati da attaccanti

Scenario: Un account vecchio non disabilitato e ancora con SPN registrato viene compromesso. L'attaccante lo usa per farsi passare per un servizio.

Mitigazione:

  1. Rimuovi SPN da account disabilitati (la validazione automatica sopra lo trova).
  2. Crea un inventory mapping di quale SPN appartiene a quale servizio e team.
  3. Durante decommissioning, disabilita PRIMA l'account, poi DOPO il TTL di sicurezza, rimuovi l'SPN.
# Rimuovi SPN da account legacy
$legacy = Get-ADUser "svc-app-old"
$legacy.servicePrincipalName | ForEach-Object {
  Set-ADServicePrincipalName -Identity $legacy -Remove $_
}

Livello 8: Monitoraggio continuo e alerting

Struttura di alert nel SIEM

Configura alert su questi pattern:

EventoDescrizioneQuery SIEMSeverity
SPN creato su disabled accountNuovo SPN su account disabledEvent 5136 + userAccountControl:disabledMEDIUM
SPN duplicato rilevatoStesso SPN su 2+ accountCustom correlation: 4769 stesso SPN da account diversiHIGH
Modifica SPN non autorizzataChange da account al di fuori della lista di autorizzati5136 modificato da account non in security group SPN-adminsHIGH
S4U2Proxy anomaloRichiesta S4U2Proxy verso backend non in allowlist4781 con SPN non in msDS-AllowedToDelegateToHIGH
Protected User fallback NTLMUtente Protected Users tenta NTLM4776 correlato con SamAccountName in gruppo Protected UsersCRITICAL

Dashboard KPI

Su Splunk / Elastic / Azure Sentinel:

1. Trend SPN duplicati (target: 0, alert se > 0)
2. Trend modifiche SPN non previste (target: baseline + accensione solo durante change window)
3. Event 4781 frequency (S4U2Proxy richieste - target: stabile, alert se spike)
4. Event 4769 TGS requests verso servizi ad alto rischio (SQL, file shares) suddivisi per auth package (target: 100% KERBEROS, alert se > 5% NTLM)
5. Event 4776 NTLM per Protected Users (target: 0, alert se > 0)

Script di correzione automatica (cautela: test prima)

Per duplicati SPN in ambienti piccoli/controllati, puoi automatizzare la rimozione dell'istanza più vecchia:

# ⚠️ UNSAFE: esegui SOLO in lab prima di produzione
$duplicates = # ... ricerca duplicati (vedi sopra)

foreach ($dup in $duplicates) {
  $accounts = Get-ADObject -LDAPFilter "(servicePrincipalName=$($dup.Name))" -Properties servicePrincipalName, whenCreated
  
  if ($accounts.Count -gt 1) {
    $oldest = $accounts | Sort-Object whenCreated | Select-Object -First 1
    Write-Host "Removing from oldest: $($oldest.Name)"
    
    # Verifica che non sia il servizio attivo
    # Verifica che non abbia logon recenti
    # SOLO ALLORA:
    Set-ADServicePrincipalName -Identity $oldest -Remove $dup.Name
  }
}

Continuous Compliance Report

Settimanale da mandar al team di sicurezza:

# Compila il report di compliance SPN
$today = Get-Date
$reportPath = ".\spn-compliance-$($today.ToString('yyyyMMdd')).txt"

$report = @"
=== SPN Compliance Report - $today ===

DUPLICATI TROVATI: $((Get-ADObject -LDAPFilter "(servicePrincipalName=*)" -Properties servicePrincipalName | Measure-Object).Count)
"@

# ... aggiungi metriche

$report | Out-File -Path $reportPath
# Oppure email

Conclusione

Un SPN è il ponte invisibile tra il servizio fisico e la sua identità Kerberos. Quando è assente, duplicato, mal configurato o abusato, Kerberos fallisce e il sistema ricade su NTLM.

Se stai leggendo questo dopo aver letto gli articoli su RC4 e fallback NTLM, la connessione dovrebbe ora essere chiara: non puoi hardening Active Directory senza prima avere il controllo completo e verificato degli SPN nel tuo dominio.

Con i controlli di accesso, abuse pattern awareness e monitoraggio continuo della Sezione 6-8, trasformi gli SPN da punto cieco della sicurezza a asset gestito e sorvegliato come si deve.

La prossima volta che un progetto di sicurezza "si arena" su autenticazione inaspettata, controlla gli SPN. Spesso è lì che scopri il vero problema.


Supporta questo progetto

Se questa guida ti e stata utile nel lavoro quotidiano, puoi supportare la creazione di nuovi contenuti tecnici con una donazione libera, che mi permetterà di acquistare nuovo hardware per mantenere il mio LAB.

I contenuti di questa guida sono forniti a titolo informativo, senza garanzie. L'applicazione di qualsiasi procedura è sotto la responsabilità dell'utente. Disclaimer.

Apprezzamento

Se questa guida ti è utile, lascia un like.

Guide correlate

LinkedIn