Jonathan Elkabas e Tomer Nahum

Nota dell'editore

Questo scenario fa parte di una serie di esempi che dimostrano l'uso di EntraGoat, il nostro ambiente di simulazione Entra ID. Una panoramica di EntraGoat e del suo valore è disponibile qui.


Grafemi la corona (e i ruoli)

Lo Scenario 2 di EntraGoat dimostra come l'autenticazione basata su certificati, legata a un service principal esistente e a permessi applicativi troppo privilegiati, possa portare alla compromissione dell'amministratore globale.

L'attaccante inizia con l'accesso a un certificato trapelato che è stato esposto attraverso gli artefatti della pipeline CI/CD. Il certificato è valido per un mandante di servizio che ha il valore AppRoleAssignment.ReadWrite.All permesso di applicazione.

Autenticandosi in un contesto di sola app, l'attaccante abusa di questa autorizzazione per assegnarne un'altra, RoleManagement.ReadWrite.Directoryallo stesso preside di servizio. Ciò consente al service principal di autoassegnare qualsiasi ruolo di directory (incluso l'amministratore globale) a qualsiasi security principal che desideri. Infine, l'attaccante reimposta la password dell'amministratore e recupera il flag di scenario.

Questo scenario mette in evidenza i rischi della dispersione dei certificati, degli ambiti di Graph troppo privilegiati e della natura dei token per le sole applicazioni. Inoltre, mette in evidenza la distinzione tra l'applicazione dei permessi tramite le richieste di token e la valutazione della directory in tempo reale, che spiegheremo in questa guida.


Panoramica del percorso di attacco

  1. Punto d'appoggio iniziale: L'aggressore ottiene un certificato codificato in base 64 e la relativa password, secondo quanto riferito da una ricognizione della pipeline CI/CD.
  2. Identificazione: Il certificato corrisponde a un preside di servizio esistente chiamato Corporate Finance Analytics.
  3. Abuso di permessi: L'attaccante si autentica utilizzando il certificato e scopre che il principale del servizio ha AppRoleAssignment.ReadWrite.All permesso di applicazione.
  4. Escalation dei privilegi: L'attaccante utilizza questa autorizzazione per assegnare RoleManagement.ReadWrite.Directory allo stesso committente del servizio.
  5. Acquisizione del ruolo: Con l'accesso alla gestione dei ruoli della directory, l'attaccante aggiunge il mandante del servizio come membro del ruolo Global Administrator della directory.
  6. Compromissione dell'account: l'attaccante reimposta la password dell'utente Global Admin e si autentica per recuperare il flag.

Flusso di attacco

La Figura 1 mostra il flusso di questo attacco.

Figura 1. Flusso del Grafico Me lo scenario di attacco alla Corona (e ai Ruoli)

Come fa questo attacco ad aggirare i normali controlli di autenticazione?

Questo scenario viola il modello di applicazione di Microsoft Entra ID, in cui le applicazioni sono costituite da una registrazione globale e da un mandante di servizio locale dell'inquilino. Le autorizzazioni per le sole applicazioni vengono richieste a a livello di registrazione dell'applicazione e concesse a livello di service principal. Inoltre, i service principal possono autenticarsi indipendentemente dagli utenti utilizzando il flusso di credenziali client OAuth 2.0.

Quando viene registrato un certificato valido per un mandante di servizio (al suo keyCredentials.customKeyIdentifier ), può essere usato per autenticarsi senza input interattivi o autenticazione multifattoriale (MFA). Questo percorso di autenticazione solo per le app opera al di fuori delle protezioni centrate sull'utente, consentendo a un certificato compromesso di accedere direttamente alle API Graph con le autorizzazioni o i ruoli assegnati al mandante del servizio.

Questo percorso di attacco combina:

  • Scarsa igiene dei certificati (meno rilevante per Entra ID)
  • Permessi delle app eccessivi e non controllati
  • La progettazione intenzionale di AppRoleAssignment.ReadWrite.All per bypassare* l'esperienza del consenso dell'amministratore in contesti di sola app

Concatenando queste condizioni, gli aggressori possono autenticarsi come mandante di un servizio, eseguire l'escalation a scopi grafici sensibili come RoleManagement.ReadWrite.Directory di concedersi un ruolo privilegiato (fino a quello di Amministratore globale) senza mai toccare una sessione utente dopo aver ottenuto le credenziali del responsabile del servizio.

NOTA: Ulteriori percorsi di attacco possono essere concatenati a questo, poiché qualsiasi identità con Application Administrator o Cloud Application Administrator o qualsiasi servizio principale con l'opzione Application.ReadWrite.All ruolo di app o di proprietà sul servizio principale di destinazione, come dimostrato in Scenario 1-Può anche sfruttare questo percorso di attacco aggiungendo credenziali per l'accesso backdoor al servizio principale.

Infine, è importante distinguere tra i diversi usi dell'autenticazione basata su certificati (CBA). Quando la configurazione CBA è disabilitata a livello di tenant, questa restrizione si applica solo all'autenticazione interattiva degli utenti, non al flusso di asserzioni client OAuth per le applicazioni. I mandanti del servizio possono comunque autenticarsi utilizzando i certificati se una credenziale valida è registrata nella loro KeyCredentials proprietà. Entra ID lo tratta come un meccanismo di autenticazione separato. Questo disaccoppiamento architettonico può portare a false ipotesi sull'applicazione dei criteri di certificazione a livello di inquilino.

*In realtà, questa autorizzazione non "aggira" il consenso dell'amministratore, ma è il consenso dell'amministratore. L'autorizzazione stessa richiede inizialmente il consenso dell'amministratore, quindi consente al titolare (e agli avversari, ovviamente) di concedere programmaticamente il consenso ad altre applicazioni nell'ambito dell'interazione macchina-macchina.


Come rilevare e difendere dall'uso improprio dell'autenticazione solo app

La postura difensiva di Entra ID deve concentrarsi sulla limitazione dell'uso di ampie autorizzazioni per le app e sull'attento monitoraggio dei principali servizi che ricoprono ruoli sensibili quali AppRoleAssignment.ReadWrite.All e RoleManagement.ReadWrite.Directory.


I team di sicurezza dovrebbero:

  • Verificate regolarmente i principi del servizio per i ruoli delle app ad alto privilegio.
  • Tenere traccia della creazione di nuove credenziali sui presidi di servizio, soprattutto se effettuata al di fuori dell'automazione approvata.
  • Rilevare le modifiche alle assegnazioni di ruolo delle app che potrebbero consentire l'escalation dei privilegi.

Le soluzioni Semperis forniscono livelli di difesa, a partire dagli indicatori di esposizione (IOE) e dagli indicatori di compromissione (IOC) che identificano le configurazioni a rischio e segnalano le impostazioni predefinite pericolose, come i principali servizi con autorizzazioni sensibili, i criteri di autorizzazione troppo permissivi per la configurazione delle applicazioni e i controlli mancanti sulla sicurezza dei principali servizi.


Approfondimento dello scenario: Soluzione descritta passo dopo passo

Vediamo i passaggi per simulare l'aggiramento dei controlli di autenticazione e capire come questo permetta di compromettere il Global Admin.


Fase 1: Punto d'appoggio iniziale con un certificato compromesso

Iniziamo lo Scenario 2 con un certificato compromesso(Figura 2), presumibilmente scaricato durante la ricognizione della pipeline CI/CD. È codificato in base64, protetto da password e abbandonato come un carico che cade da un camion DevOps.

Figura 2. Un certificato compromesso, scaricato sul nostro grembo

Per identificare il proprietario del certificato, lo decodifichiamo in un oggetto X509Certificate2 utilizzabile e ne ispezioniamo i metadati:

      $certBase64 = "[BASE64_CERTIFICATE_BLOG]"
$certPassword = "GoatAccess!123"

$certBytes = [System.Convert]::FromBase64String($certBase64)
$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($certBytes, $certPassword, [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable)

$cert | Seleziona l'oggetto Soggetto, Emittente, Impronta, Non prima, Non dopo | Elenco di formati

Possiamo vedere che il certificato è autofirmato e rilasciato per un'applicazione chiamata Corporate Finance Analytics (Figura 3).

Figura 3. Identificazione dell'applicazione per cui è stato emesso il certificato

In alternativa, è possibile scorrere tutte le registrazioni di applicazioni nel tenant e cercare un thumbprint di certificato corrispondente. Ogni oggetto applicazione in Entra ID ha un KeyCredentials che contiene i metadati relativi ai certificati o alle chiavi pubbliche associate all'applicazione, utilizzati per l'autenticazione in contesti di sola applicazione. Ogni voce include un elemento CustomKeyIdentifierche memorizza l'impronta del certificato (Figura 4) in forma binaria e consente la ricerca o la correlazione con certificati noti.

Figura 4. Impronta del certificato

La funzione seguente può eseguire la ricerca:

      function Find-AppRegistrationByThumbprint {
    param([string]$Thumbprint)
    
    # Get all application registrations and check for matching certificate thumbprint
    $allApps = Get-MgApplication -All
    
    foreach ($app in $allApps) {
        if ($app.KeyCredentials) {
            foreach ($keyCred in $app.KeyCredentials) {
                # Compare thumbprints (certificate matching)
                if ($keyCred.CustomKeyIdentifier) {
                    $credThumbprint = [System.Convert]::ToHexString($keyCred.CustomKeyIdentifier)
                    if ($credThumbprint -eq $Thumbprint) {
                        Write-Host "Certificate match found for: $($app.DisplayName)" -ForegroundColor Cyan
                        return $app
                    }
                }
            }
        }
    }
    return $null
}

Microsoft Graph non memorizza né restituisce mai il contenuto effettivo del certificato o le chiavi private. L'interrogazione del KeyCredential L'attributo che utilizza Graph API rivela solo i metadati del certificato registrato, mai il certificato stesso, indipendentemente dai privilegi dell'identità richiedente. Ciò rafforza la necessità di gestire e conservare in modo sicuro le chiavi private in tutta l'organizzazione.

Per identificare il mandante del servizio associato, ci si autentica come utente a basso livello di privilegio usando Connect-MgGraph. Una volta estratto l'AppId dietro Corporate Finance Analytics (Figura 5), siamo in grado di autenticarci direttamente come principale del servizio con il certificato.

Figura 5. Estrazione dell'AppId

NOTA: Anche se CBA è disattivato nel tenant(Figura 6), l'autenticazione al service principal utilizzando il certificato avrà comunque successo. Questo perché la CBA basata sull'utente si riferisce a un'esperienza di accesso interattiva, basata sul browser, mentre i service principal si basano su un flusso di credenziali client OAuth 2.0 non interattivo che utilizza le asserzioni del certificato. La disabilitazione di CBA riguarda solo l'autenticazione interattiva dell'utente con i certificati e non ha alcun impatto sull'autenticazione programmatica del service principal. Si tratta di percorsi di autenticazione separati nella piattaforma Entra ID.

Figura 6. Il CBA è disabilitato in questo inquilino Entra ID.

Passo 2: scoprire i permessi delle app e costruire il nostro percorso di attacco

Dopo essersi autenticati come principale del servizio utilizzando il certificato, si controlla il suo roles nel JWT concesso tramite il file Get-MgContext e scoprire che contiene AppRoleAssignment.ReadWrite.All (Figura 7). Questa autorizzazione consente al service principal di assegnare QUALSIASI ruolo applicativo a tutti i service principal, compreso se stesso.

Figura 7. Verifica della richiesta di ruoli del committente del servizio

Per l'escalation, si enumera il servizio principale di Microsoft Graph (GraphAggregatorService), che contiene tutti i ruoli OAuth assegnabili ed è presente in ogni tenant Entra come applicazione di prima parte. È istanziata da una registrazione globale dell'applicazione ospitata nel tenant di Microsoft e può essere identificata dall'AppId statico di 00000003-0000-0000-c000-000000000000. Come tutte le applicazioni multi-tenant, viene visualizzata in ciascun tenant del cliente come un direttore del servizio locale-l'identità effettiva utilizzata per applicare il controllo degli accessi. (Abbiamo parlato del modello di applicazione Entra ID in Scenario 1.)

L'AppId in Figura 8 punta alla definizione globale di Microsoft Graph, che contiene tutti i ruoli OAuth assegnabili che definiscono ambiti di autorizzazione solo per le applicazioni, come ad esempio Directory.Read.All o RoleManagement.ReadWrite.Directory. Ogni tenant detiene un'istanza di service principal locale dell'applicazione globale, e Le autorizzazioni OAuth sono assegnate a livello di service principal. via AppRoleAssignment. Ciò consente alle applicazioni di operare indipendentemente dagli utenti, con gli ambiti definiti nel manifest globale dell'applicazione ma applicati dal service principal locale dell'inquilino.

Curiosità: attualmente esistono 576 autorizzazioni Graph uniche!

Figura 8. Enumerazione dei permessi OAuth assegnabili al grafico

Ora che abbiamo individuato il principal del servizio Microsoft Graph e abbiamo i suoi permessi OAuth assegnabili, il passo successivo è scegliere con quale permesso effettuare l'escalation. Il più efficace per il nostro scopo di attacco è RoleManagement.ReadWrite.Directory, secondo una nota del sito ufficiale documentazione (Figura 9):

Figura 9. Avviso di Microsoft sui permessi che consentono di concedere l'autorizzazione

Questo livello di accesso è pericoloso per il design. Offre un controllo programmatico completo sulle API di gestione dei ruoli Entra ID e supporta l'escalation diretta dei privilegi senza l'interazione dell'utente.


Passo 3: Assegnazione di autorizzazioni pericolose

Possiamo assegnare questa autorizzazione al nostro service principal compromesso usando i seguenti comandi:

      $roleManagementRole = $graphSP.AppRoles | Where-Object { $_.Value -eq "RoleManagement.ReadWrite.Directory" }

$appRoleAssignmentParams = @{
    PrincipalId = $SP.Id
    ResourceId = $graphSP.Id
    AppRoleId = $roleManagementRole.Id
}

New-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $SP.Id -BodyParameter $appRoleAssignmentParams

Dopo l'esecuzione di questo comando, anche se è stato concesso con successo al mandante del servizio l'opzione RoleManagement.ReadWrite.Directory permesso, come ispezionato da Get-MgContext-Non osserviamo alcun cambiamento nell'attuale contesto di sicurezza (Figura 10). Si tratta di un comportamento atteso.

Figura 10. Osservazione dell'assenza di modifiche nel contesto di sicurezza del mandante del servizio

I token di accesso emessi da Entra ID sono come istantanee statiche delle richieste concesse al momento dell'emissione del token. Quando Connect-MgGraph viene eseguito, agisce come client OAuth2.0 e avvia un flusso di autenticazione contro l'endpoint del token (https://login.microsoftonline.com/{tenant-id}/oauth2/v2.0/token). Questo endpoint convalida le credenziali presentate ed emette un token di accesso JWT firmato con le richieste del chiamante (come ruoli dell'app, ambiti e contesto del tenant) in base allo stato di autorizzazione corrente al momento della richiesta.

Poiché i token di accesso non vengono aggiornati dinamicamente quando le autorizzazioni cambiano, i ruoli dell'applicazione appena concessi (come quello appena aggiunto) non verranno visualizzati finché non si ottiene esplicitamente un nuovo token. Per ottenere i privilegi aggiornati, dobbiamo terminare la sessione esistente (Disconnect-MgGraph) e riaccedere ad esso (Connect-MgGraph) per attivare l'emissione di un nuovo token di accesso che includa le nuove richieste (Figura 11).

Figura 10. Osservazione dell'assenza di modifiche nel contesto di sicurezza del mandante del servizio

Con il RoleManagement.ReadWrite.Directory il service principal può ora modificare l'assegnazione dei ruoli della directory per qualsiasi identità del tenant, compresa l'aggiunta al ruolo di Global Administrator:

      $globalAdminRoleId = "62e90394-69f5-4237-9190-012177145e10" # GA role GUID
$globalAdminRole = Get-MgDirectoryRole -Filter "roleTemplateId eq '$globalAdminRoleId'" -ErrorAction SilentlyContinue

$roleMemberParams = @{
    "@odata.id" = "https://graph.microsoft.com/v1.0/servicePrincipals/$($SP.Id)"
}

New-MgDirectoryRoleMemberByRef -DirectoryRoleId $globalAdminRole.Id -BodyParameter $roleMemberParams

Ora, se controlliamo i ruoli assegnati al service principal compromesso, possiamo vedere il GUID dell'amministratore globale(Figura 12).

Figura 12. Visualizzazione del GUID dell'amministratore globale

Se avete seguito da vicino, una differenza comportamentale fondamentale può emergere:

Perché è necessario ottenere un nuovo JWT per l'identità dopo aver concesso un'autorizzazione all'app ma non dopo aver assegnato un ruolo di directory?

La risposta sta nel modo in cui Entra ID applica questi due modelli di autorizzazione:

  • Autorizzazioni per l'applicazione (come RoleManagement.ReadWrite.Directory) sono emessi come richieste statiche all'interno del token di accesso JWT al momento dell'autenticazione. Questi permessi sono rappresentati nell'elemento roles rivendicazione dell'array (o scp nei flussi delegati). Il token è in realtà un'asserzione firmata crittograficamente che riflette i ruoli e gli ambiti dell'applicazione del chiamante al momento dell'emissione e di solito qualsiasi modifica a queste autorizzazioni richiede la riemissione del token.
  • Ruoli di directory (come Global Administrator) sono anch'essi emessi come rivendicazioni statiche all'interno del token di accesso, ma seguono un modello di applicazione diverso. Mentre i token possono includere assegnazioni di ruoli di directory tramite l'opzione wids o la richiesta di risarcimento o il group (in caso di appartenenza a un gruppo), la maggior parte delle API Microsoft di solito valutare questi ruoli dinamicamente in fase di esecuzione. Quando viene effettuata una richiesta, il backend interroga le assegnazioni di ruolo correnti in Entra ID per l'ID dell'oggetto del chiamante. Questa ricerca in tempo reale consente ai nuovi ruoli assegnati di entrare in vigore immediatamente senza richiedere il rinnovo del token.

Detto questo, nella documentazione di riferimento per le richieste di token di accesso, Microsoft osserva esplicitamente che:

Il roles, groups, scp, e wids Le richieste non sono un elenco esaustivo di come una risorsa potrebbe autorizzare un utente o un'applicazione, né un elenco esaustivo dei permessi concessi al chiamante. La risorsa di destinazione può utilizzare un altro metodo per autorizzare l'accesso alle proprie risorse protette.

Passo 4: passaggio a una sessione di amministrazione

Con il ruolo GA assegnato al nostro service principal, ora disponiamo di privilegi completi a livello di directory. Questo ci permette di reimpostare la password dell'utente admin di destinazione e di assumerne l'identità(Figura 13).

Figura 13. Assunzione dell'identità del nostro utente amministratore target

Per ricapitolare l'ultima fase, non utilizzeremo TAP o il portale Azure come nello Scenario 1.

Invece, sfrutteremo il sistema di BARK1 Get-MSGraphTokenWithUsernamePassword per autenticarsi a Microsoft Graph con le nuove credenziali di amministrazione e recuperare il flag dal file /me endpoint (Figura 14)- rimanendo fedeli al titolo di questo scenario: Grafatemi la corona (e i ruoli).

Figura 14. La bandiera viene catturata

Una volta completato lo scenario, si esegue lo script di pulizia(Figura 15) per ripristinare il tenant allo stato originale.

Figura 15. La pulizia di EntraGoat ci prepara al prossimo scenario

Individuare i punti ciechi

Questo scenario illustra come le autorizzazioni grafiche per le sole app, combinate con l'autenticazione basata su certificati, possano creare punti ciechi nella governance tradizionale delle identità.

Non è necessario alcun compromesso con l'utente. Non è richiesto alcun flusso interattivo. Concatenando due autorizzazioni graficheAppRoleAssignment.ReadWrite.All e RoleManagement.ReadWrite.Directory-L'attaccante esegue l'escalation silenziosa di un service principal in un Global Administrator.

I team di sicurezza devono trattare le autorizzazioni delle applicazioni e le credenziali del service principal come risorse critiche, non come identità secondarie. L'applicazione di una governance rigorosa sull'uso dei certificati e sull'assegnazione delle autorizzazioni grafiche è essenziale per prevenire l'escalation di privilegi senza testa, come quella descritta nello Scenario 2.


Affronta la tua prossima sfida EntraGoat

Nota finale

  1. https://github.com/BloodHoundAD/BARK

Esclusione di responsabilità

Questo contenuto è fornito solo a scopo educativo e informativo. Il suo scopo è quello di promuovere la consapevolezza e la correzione responsabile delle vulnerabilità di sicurezza che possono esistere sui sistemi di cui si è proprietari o che si è autorizzati a testare. È severamente vietato l'uso non autorizzato di queste informazioni per scopi malevoli, sfruttamento o accesso illegale. Semperis non approva né condona alcuna attività illegale e declina ogni responsabilità derivante dall'uso improprio del materiale. Inoltre, Semperis non garantisce l'accuratezza o la completezza dei contenuti e non si assume alcuna responsabilità per eventuali danni derivanti dal loro utilizzo.