Vi serve il cloud computing? Iniziate subito

Una panoramica sull'MS-RPC e sui suoi meccanismi di sicurezza

Akamai Wave Blue

scritto da

Ben Barnea

December 08, 2022

Akamai Wave Blue

scritto da

Ben Barnea

Ben Barnea è Security Researcher in Akamai con interesse e competenze nella conduzione di ricerche sulle vulnerabilità e sulla sicurezza di basso livello in varie architetture, tra cui quelle in Windows e Linux, nonché nell'IoT (Internet of Things) e nei dispositivi mobili. Adora scoprire come funzionano i meccanismi complessi e, soprattutto, come non funzionano.

A causa della sua natura di vettore remoto, l'RPC attira molta attenzione dal punto di vista della sicurezza.
A causa della sua natura di vettore remoto, l'RPC attira molta attenzione dal punto di vista della sicurezza.

Che cos'è l'RPC?

Una chiamataRPC(Remote Procedure Call) è una forma di comunicazione interprocesso (IPC). Consente al client di richiamare una procedura esposta da un server RPC. Il client chiama la funzione, come se fosse una normale chiamata di procedura, senza (quasi) dover codificare i dettagli per l'interazione remota. Il server può essere ospitato in un processo diverso sullo stesso computer o su un computer remoto.

In questo articolo, esamineremo l'implementazione RPC di Microsoft (MS-RPC). L'MS-RPC deriva dall'implementazione di riferimento (V1.1) del protocollo RPC al cuore del Distributed Computing Environment.

L'RPC viene ampiamente utilizzato da Windows per molti servizi diversi, ad esempio la pianificazione delle attività, la creazione dei servizi, le impostazioni delle stampanti e della condivisione, e la gestione dei dati crittografati memorizzati da remoto. A causa della sua natura di vettore remoto, l'RPC attira molta attenzione dal punto di vista della sicurezza. Questo blog intende descrivere le nozioni base del funzionamento dell'MS-RPC, concentrandosi sui suoi meccanismi di sicurezza incorporati.

Come funziona l'MS-RPC?

Le procedure e i loro parametri sono definiti in un linguaggio descrittivo denominato IDL (Interface Definition Language). Ad ogni interfaccia viene assegnato un identificatore univoco, denominato UUID, il quale viene usato sia dal server che dal client per rivolgersi all'esatta interfaccia esposta dal server.

Il file IDL viene, quindi, compilato usando il compilatore IDL di Microsoft IDL nei file di codifica dell'intestazione e dell'origine che contengono la funzionalità di runtime. Tecnicamente parlando, si tratta di stub usati sia dal server che dal client che passano il controllo al runtime RPC, implementato in rpcrt4.dll. Il runtime RPC sul lato client mette in ordine i dati e li passa al runtime RPC sull'altro lato (Figura 1).

Runtime RPC

Figura 1: Runtime RPC

Potreste chiedervi come fanno il server e il client a comunicare tra loro dal punto di vista di una rete (o locale). Il server ascolta le connessioni RPC in entrata registrando la combinazione di una sequenza di protocollo e un endpoint. Una sequenza di protocollo può essere, ad esempio, ncacn_ip_tcp (TCP), ncacn_np (named pipe) oppure ncalrpc (LPC). L'endpoint può essere una porta, come TCP 5555, o \\pipe\\example, se viene usata una named pipe. Le named pipe vengono trasportate su SMB tramite la porta TCP 445 l'azione IPC$ nascosta. L'elenco completo delle sequenze di protocolli è disponibile sul sito Web Microsoft.

Quindi, il server ascolta le connessioni su alcuni endpoint. Come fa il client a sapere dove deve connettersi? La risposta dipende dal tipo di endpoint: dinamico o conosciuto (noto anche come statico).

Un endpoint dinamico è un endpoint registrato sul lato server usando il mapper degli endoint. Il mapper degli endoint (noto come epmapper) è un servizio RPC che mappa un servizio sull'effettivo endpoint. L'epmapper utilizza le porte TCP 135 e 593 per l'RPC su HTTP. Pertanto, un client può enumerare (usando API designate) tutti i server RPC registrati dinamicamente su un computer remoto usando l'epmapper.

Un endpoint conosciuto è un endpoint che non si è registrato tramite l'epmapper. Il client dovrebbe conoscere l'endpoint registrato dal server in anticipo. Ciò può accadere se l'endpoint è codificato nel codice del client e del server, oppure se l'endpoint è presente nel file IDL. Ecco un esempio dall'IDL del servizio Print Spooler.

[
uuid(12345678-1234-ABCD-EF00-0123456789AB),
version(1.0),
ms_union,
endpoint("ncacn_np:[\\pipe\\spoolss]"),
pointer_default(unique)
]

L'RPC rappresenta una connessione logica tra un client e un server tramite un handle di associazione. Sia il server che il client usano delle funzioni per manipolare i dati di associazione, ad esempio, per la configurazione delle informazioni di autenticazione. Quando il client imposta l'autenticazione sull'associazione, quest'ultima viene considerata autenticata. Quando il programma client chiama la funzione RPC, avviene l'associazione della rete. Dal punto di vista del traffico di rete, il client RPC avvia l'interazione RPC inviando una richiesta di associazione con le informazioni sull'autenticazione nel pacchetto. Il server può rispondere con bind_ack (conferma) o bind_nak (si è verificato un errore).

Frammento di Wireshark che mostra una risoluzione dinamica degli endpoint

Figura 2: Frammento di Wireshark che mostra una risoluzione dinamica degli endpoint

Nel frammento di Wireshark della Figura 2, possiamo vedere la risoluzione dell'endpoint dinamico per l'interfaccia dell'utilità di pianificazione. Quando il client dispone delle informazioni sull'endpoint per l'utilità di pianificazione, crea una connessione. A questo punto, possiamo vedere una nuova connessione con un altro processo di associazione, incluso un pacchetto AUTH3 come parte dell'autenticazione dell'associazione.

Come per gli endpoint, esistono vari tipi di associazione: automatica, implicita ed esplicita (Figura 3), che differiscono per il livello di controllo dell'applicazione sul processo di associazione. 

  • Associazione automatica (obsoleta): il client e le applicazioni del server non gestiscono il processo di associazione, lasciandone il pieno controllo al runtime RPC.
  • Associazione implicita: il client ha la possibilità di configurare l'handle di associazione prima che questa avvenga. Dopo che il client ha stabilito un'associazione, la libreria di runtime RPC gestisce il resto.
  • Associazione esplicita:  il client deve configurare l'handle di associazione. Dopodiché, il runtime RPC lo comunica al server.
Confronto tra diversi tipi di associazione

Sicurezza dell'RPC per richieste remote

Ora che conosciamo le basi del funzionamento dell'RPC, vogliamo comprendere quali azioni, policy e meccanismi possono impedire a un client di accedere a una funzione. È interessante sia da una prospettiva offensiva che difensiva.

Autenticazione del trasporto

Alcuni sistemi di trasporto, come ad esempio le named pipe o HTTP, hanno l'autenticazione come parte del loro protocollo. Ad esempio, le named pipe vengono trasportate su SMB, che dispone di autenticazione. Ciò significa sostanzialmente che, quando un server registra un endpoint named pipe, solo i client che dispongono delle credenziali di un utente valido possono connettersi a quell'endpoint. In un ambiente di dominio, disporre di un utente di dominio nello stesso dominio è sufficiente per superare la verifica di autenticazione. Se i computer non fanno parte di un dominio, allora il client deve disporre le credenziali di un utente locale sul server remoto (Figura 4). Tratteremo delle esclusioni più avanti nel post.

Errore di sistema che nega l'accesso

Fig. 4: Esempio di un errore di sistema che nega l'accesso

Autenticazione dell'associazione

Il server può avere un meccanismo di autenticazione usando l'autenticazione dell'associazione. Affinché ciò avvenga, il server deve registrare le informazioni sull'autenticazione chiamando RpcServerRegisterAuthInfo. Il client deve usare il nome principale del servizio corretto e il metodo usato dal server, altrimenti riceverà "RPC_S_UNKNOWN_AUTHN_SERVICE" dal server.

Un client può autenticarsi su un server impostando i dati di autenticazione e autorizzazione sull'associazione usando le API, come RpcBindingSetAuthInfo e RpcBindingSetAuthInfoEx. Il client specifica il metodo di autenticazione (ossia, NTLM, Kerberos, Negotiate, SCHANNEL, ecc.) e il nome principale del servizio.

Due cose importanti da sapere:

  1. Se un client imposta l'autenticazione sull'associazione, e il server non ha registrato alcuna informazione sull'autenticazione, Il server restituirà "RPC_S_UNKNOWN_AUTHN_SERVICE".

  2. Il semplice fatto che il server abbia registrato un'associazione di autenticazione non significa che il client deve usare un'associazione di autenticazione. Inoltre, il runtime RPC non invierà l'associazione di autenticazione con credenziali non valide. Più avanti in questo post esamineremo come il server può impedire l'accesso alle associazioni non autenticate.

Vale la pena sottolineare che il runtime RPC salva le informazioni sull'autenticazione globalmente. Ciò significa che, se due server RPC condividono lo stesso processo e uno di essi registra le informazioni di autenticazione, anche l'altro server disporrà delle informazioni di autenticazione. Un client può ora autenticare l'associazione quando accede a ciascuno dei server. Chiamiamo questo comportamento multiplexing SSPI.

Sicurezza degli endpoint

Un server può impostare un descrittore di sicurezza sull'endpoint. Un descrittore di sicurezza è un meccanismo di sicurezza generico tramite verifica degli accessi di Windows. Consente la creazione di "regole" che consentono o negano l'accesso a un oggetto a terminati soggetti. Quando si tenta di accedere all'oggetto, il sistema operativo confronta il token di accesso del chiamante con il descrittore di sicurezza per vedere se l'accesso è consentito. In questo caso, l'oggetto è l'endpoint e il token di accesso è quello del client, derivato dall'autenticazione del protocollo di trasporto. Questa verifica è applicabile solo ai trasporti autenticati, come named pipe, ALPC, e HTTP (quando viene usata l'autenticazione). Il TCP è un protocollo di trasporto non autenticato e, pertanto, non avrà questo tipo di verifica degli accessi.

Allo stesso modo del multiplexing SSPI, anche gli endpoint sono multiplex, ossia l'interfaccia e l'endpoint non sono collegati. Un server registra le interfacce e gli endpoint separatamente. Se un processo ha diversi endpoint registrati, ogni interfaccia registrata in questo processo è accessibile tramite ciascuno di questi endpoint.

Immaginate, ad esempio, di registrare la vostra interfaccia e creare un endpoint che ascolti su \\pipe\mypipe. Un altro server RPC ospitato nello stesso processo ha registrato il suo proprio endpoint su TCP 7777. La vostra interfaccia sarà accessibile anche tramite TCP. Ciò supererà le restrizioni di sicurezza imposte sull'endpoint, ad esempio un descrittore di sicurezza (Figura 5). Pertanto, si consiglia di non fare affidamento sulla sicurezza dell'endpoint o di verificare che il client arrivi dal protocollo di trasporto previsto.

Esempio di bypass del descrittore di sicurezza degli endpoint a causa del multiplexing degli endpoint

Figura 5: Esempio di bypass del descrittore di sicurezza degli endpoint a causa del multiplexing degli endpoint

Sicurezza dell'interfaccia

Pensando alla sicurezza dell'interfaccia, esistono tre modi per proteggerla: impostare un callback di sicurezza, impostare un descrittore di sicurezza e usare i flag dell'interfaccia.

Impostazione di un callback di sicurezza

Il primo meccanismo di sicurezza dell'interfaccia è il callback di sicurezza. Un callback di sicurezza è un callback personalizzato implementato dallo sviluppatore del server. La logica insita nel callback di sicurezza dipende dallo sviluppatore e consente al server di limitare l'accesso all'interfaccia. Se il callback restituisce RPC_S_OK, al client è consentito l'accesso.

Se un callback di sicurezza è registrato, ai client non autenticati viene automaticamente negato l'accesso tramite il runtime RPC, a meno che non si imposti il flag RPC_IF_ALLOW_CALLBACKS_WITH_NO_AUTH .

Impostare un callback di sicurezza e il flag RPC_IF_ALLOW_SECURE_ONLY non vuol dire che il client disponga di privilegi elevati. Pertanto, il callback di sicurezza è il luogo in cui il server può informarsi riguardo al livello di privilegi del client. Ciò avviene chiamando RpcBindingInqAuthClient. Un'altra comune verifica è quella del metodo di trasporto usato dal client per applicare la connessione tramite un trasporto autenticato sicuro, ad esempio le named pipe. Ciò avviene chiamando RpcBindingServerFromClient → RpcBindingToStringBinding → RpcStringBindingParse e confrontando il parametro Protseq (sequenza di protocollo). Ciò impedisce anche l'abuso del multiplexing degli endpoint.

Memorizzazione nella cache del callback di sicurezza

Se un callback di sicurezza è registrato, verrà chiamato durante le verifiche di sicurezza. Se il callback di sicurezza supera le verifiche (ossia restituisce RPC_S_OK), allora il risultato verrà memorizzato nella cache, se viene usata la memorizzazione nella cache. La prossima volta che lo stesso client chiamerà una funzione sull'interfaccia, dal momento che il risultato del callback di sicurezza è memorizzato nella cache, il callback di sicurezza non verrà richiamato nuovamente, ma verrà usata la voce memorizzata nella cache.

L'implementazione della memorizzazione nella cache è semplice, ma dipende da alcuni fattori.

  • La memorizzazione nella cache è legata al contesto di sicurezza del client, che dipende dall'associazione. Pertanto, se il server (o qualsiasi altro server nel processo) non ha registrato alcuna associazione di autenticazione, o se il client non ha impostato l'associazione di autenticazione, la memorizzazione nella cache sarà disabilitata per questa chiamata.
  • Se il server registra l'interfaccia con il flag RPC_IF_SEC_NO_CACHE, il runtime RPC forza la chiamata del callback di sicurezza per ciascuna chiamata, disabilitando, quindi, il meccanismo di memorizzazione nella cache.
  • Anche il flag RPC_IF_SEC_CACHE_PER_PROC dell'interfaccia non documentato influisce sul meccanismo di memorizzazione nella cache. Se il server ha specificato questo flag, allora la memorizzazione nella cache avverrà sulla chiamata e non sull'interfaccia . Ciò significa che, se la cache detiene un valore di successo per la funzione X sull'interfaccia TEST, allora la chiamata della funzione Y sull'interfaccia TEST attiverà di nuovo il callback di sicurezza. 

Il meccanismo di memorizzazione nella cache provoca delle vulnerabilità logiche per qualsiasi callback di sicurezza dipendente dalla funzione chiamata dal client. Se siete sviluppatori o revisori RPC, dovreste conoscere il meccanismo di memorizzazione nella cache. Abbiamo pubblicato una ricerca approfondita sull'implementazione della memorizzazione nella cache, incluse le vulnerabilità che abbiamo riscontrato a causa di questo meccanismo.

Impostazione di un descrittore di sicurezza

Il secondo meccanismo di protezione di un'interfaccia è l'impostazione di un descrittore di sicurezza su questa interfaccia. Solo la funzione RpcServerRegisterIf3 dispone dell'opzione per registrare un descrittore di sicurezza sull'interfaccia. Se il descrittore di sicurezza non è impostato, il descrittore di sicurezza predefinito è:

  • NT AUTHORITY\ANONYMOUS LOGON
  • Tutti
  • NT AUTHORITY\RESTRICTED
  • BUILTIN\Administrators
  • SELF

Uso dei flag dell'interfaccia

Il terzo modo per proteggere un'interfaccia consiste nell'usare i flag dell'interfaccia, che vengono impostati quando si crea l'interfaccia tramite le funzioni RpcServerRegisterIf*. Flag interessanti dal punto di vista della sicurezza sono:

RPC_IF_ALLOW_CALLBACKS_WITH_NO_AUTH : il callback di sicurezza verrà richiamato per ciascuna chiamata, indipendentemente dal metodo di trasporto usato o dal livello di autenticazione. 

RPC_IF_ALLOW_LOCAL_ONLY : sono consentite solo richieste locali.

RPC_IF_ALLOW_SECURE_ONLY : le connessioni sono limitate ai client che dispongono di un livello di autorizzazione superiore a RPC_C_AUTHN_LEVEL_NONE. La specificazione di questo flag nega l'accesso ai client che arrivano da una sessione NULL. Questo flag non garantisce il livello di privilegio di un utente chiamante, garantisce solo che il client dispone di credenziali valide.

RPC_IF_SEC_NO_CACHE : questo flag disabilita completamente la memorizzazione nella cache per il callback di sicurezza dell'interfaccia.

RPC_IF_SEC_CACHE_PER_PROC : anziché disabilitare l'intero meccanismo di memorizzazione nella cache, questo flag cambia il comportamento predefinito, ossia tramite chiamata invece che tramite interfaccia.

Policy a livello di sistema

Esistono molteplici policy a livello di sistema che vengono impostate a seconda del tipo di computer: client, server o controller di dominio (DC).

Un'interessante policy di sistema correlata alla sicurezza degli endpoint è la policy "Limita client RPC non autenticati". È possibile impostare tre valori.

  1. "Autenticato": il runtime RPC bloccherà l'accesso ai client TCP che non si sono autenticati in precedenza se il flag RPC_IF_ALLOW_CALLBACKS_WITH_NO_AUTH non è impostato sull'interfaccia.
  2. "Autenticato senza eccezioni": tutte le connessioni non autenticate sono bloccate.
  3. "Nessuna": a tutti i client RPC è consentito connettersi ai server RPC in esecuzione sul computer.

Pertanto, un'interessante superficie di attacco per i client non autenticati sarebbero le interfacce che sono accessibili tramite TCP e che registrano il flag RPC_IF_ALLOW_CALLBACKS_WITH_NO_AUTH. Ovviamente, il client deve superare la verifica del callback di sicurezza per poter richiamare le funzioni delle interfacce.

Come menzionato in precedenza, le connessioni tramite named pipe, che sono trasportate su SMB, hanno la propria autenticazione come parte del protocollo SMB. Un client può usare un "Accesso anonimo" (noto anche come sessione NULL) quando si connette tramite SMB. Due policy di sistema correlate sono "Limita l'accesso anonimo a named pipe e azioni" e "Accesso alle rete: le named pipe sono accessibili anonimamente". Se la prima policy è abilitata, solo le named pipe definite nella seconda policy possono connettersi anonimamente. Per le workstation, la seconda policy è vuota, il che significa sostanzialmente che non è possibile usare una sessione NULL rispetto alle workstation nel dominio. Per un computer DC, l'elenco delle named pipe nella policy include "\pipe\netlogon", "\pipe\samr" e "\pipe\lsarpc". Ciò è interessante dal punto di vista degli autori di attacchi in quanto si tratta di endpoint ai quali un computer può connettersi dall'esterno del dominio.

Infine, riguardo alla verifica del descrittore di sicurezza degli endpoint, esiste una policy di sistema disabilitata per impostazione predefinita: "Accesso alla rete: applica le autorizzazioni Tutti agli utenti anonimi". Se questa policy è abilitata, l'identificatore di sicurezza Tutti viene aggiunto al token creato per le connessioni anonime.

Verifica della sicurezza delle procedure

Il programmatore del server può implementare le verifiche di sicurezza come parte delle funzioni che sono esposte sull'interfaccia e, pertanto, ha la possibilità di modificare o personalizzare la logica della verifica per la funzione richiamata. Una verifica di sicurezza comune è una verifica degli accessi, come quella mostrata nella Figura 6 (presa da srvsvc):

Un esempio di comune verifica di sicurezza, ossia una verifica degli accessi

Figura 6: Un esempio di comune verifica di sicurezza, ossia una verifica degli accessi

In questo esempio, le chiamate LocalrSessionGetInfo SsAccessCheck. SsAccessCheck si spaccia in pratica per il client chiamando RpcImpersonateClient, seguita da una chiamata alla funzione NtAccessCheckAndAuditAlarm, che segue la verifica degli accessi. Questa è seguita da una chiamata RpcRevertToSelf per ripristinare il token del server. È importante ricordarsi di verificare il valore restituito da RpcImpersonateClient in quanto, se non riesce, il server continua ad essere eseguito nel token di sicurezza del processo del server e non del processo del client.

Riepilogo

Questo blog ha presentato molte informazioni sull'MS-RPC e sui suoi meccanismi di sicurezza. Incoraggiamo altri ricercatori a osservare diversi server MS-RPC in quanto presentano un'ampia superficie di attacco, con il potenziale di nuove vulnerabilità. Speriamo che il nostro post aiuterà altri ricercatori a muovere i primi passi verso la ricerca dell'MS-RPC. 

Continueremo a creare e raccogliere altre risorse sull'RPC per il nostro archivio GitHub. Grazie a tutti coloro che hanno contribuito a questo argomento con la loro conoscenza ed esperienza, in particolar modo James Forshaw e Carsten Sandkerpiù estesi e sofisticati.



Akamai Wave Blue

scritto da

Ben Barnea

December 08, 2022

Akamai Wave Blue

scritto da

Ben Barnea

Ben Barnea è Security Researcher in Akamai con interesse e competenze nella conduzione di ricerche sulle vulnerabilità e sulla sicurezza di basso livello in varie architetture, tra cui quelle in Windows e Linux, nonché nell'IoT (Internet of Things) e nei dispositivi mobili. Adora scoprire come funzionano i meccanismi complessi e, soprattutto, come non funzionano.