
Caso di studio e POC di CVE-2017-12635: Apache CouchDB 1.7.0 / 2.x < 2.1.1 - Escalation dei privilegi remota
Caso di studio e PoC di CVE-2017-12635 (Apache CouchDB 1.7.0 / 2.x < 2.1.1) - Escalation dei privilegi remota
Apache CouchDB è un database NoSQL orientato ai documenti, implementato in Erlang.
CouchDB utilizza molteplici formati e protocolli per archiviare, trasferire ed elaborare i propri dati: utilizza JSON per archiviare i dati, JavaScript come linguaggio di interrogazione tramite MapReduce e HTTP per un'API.
CouchDB può essere utilizzato sia come database a nodo singolo che come cluster.
A causa della discrepanza tra il parser JSON basato su Erlang e il parser JSON basato su JavaScript, esisteva una vulnerabilità in CouchDB precedente alla versione 1.7.0 e 2.x precedente alla 2.1.1 che permetteva a utenti non amministratori di aumentare i propri privilegi inviando documenti _users con chiavi roles duplicate utilizzate per il controllo degli accessi all'interno dei database, incluso il caso speciale del ruolo _admin, che denota utenti amministrativi.
Per ricapitolare, la vulnerabilità consente a utenti non amministratori di assegnarsi privilegi di amministratore.
Di default, CouchDB permette a chiunque di effettuare qualsiasi richiesta. Tutti hanno i privilegi per fare qualsiasi cosa.
CouchDB prevede il concetto di utente amministratore (ad esempio un amministratore, un super utente o root) che può fare qualsiasi cosa su un'installazione CouchDB. Per impostazione predefinita, tutti sono amministratori. Se non ti piace, puoi creare utenti amministratori specifici con nome utente e password come credenziali.
CouchDB definisce anche un insieme di richieste che solo gli utenti amministratori possono effettuare. Visita la documentazione ufficiale per maggiori informazioni.
CouchDB ha un database di autenticazione speciale che memorizza tutti gli utenti registrati come documenti JSON.
CouchDB utilizza un database speciale (chiamato _users per impostazione predefinita) per memorizzare le informazioni sugli utenti registrati. Questo è un database di sistema: ciò significa che, pur condividendo l'API comune del database, vengono applicati alcuni vincoli speciali di sicurezza e accordi sulla struttura dei documenti.
Solo gli amministratori possono GET, PUT o DELETE qualsiasi documento nel database _users.
Gli utenti possono solo accedere (GET /_users/org.couchdb.user:<username>) o modificare (PUT /_users/org.couchdb.user:<username>) i documenti di loro proprietà.
Ogni utente CouchDB è memorizzato in formato documento. Questi documenti contengono diversi campi obbligatori che CouchDB gestisce per il corretto processo di autenticazione. Siamo interessati al campo roles.
Il campo roles è un elenco di ruoli utente. CouchDB non fornisce ruoli predefiniti, quindi sei libero di definirne di propri in base alle tue esigenze. Tuttavia, non puoi impostare ruoli di sistema come _admin in quel campo. Inoltre, solo gli amministratori possono assegnare ruoli agli utenti - per impostazione predefinita tutti gli utenti non hanno ruoli.
CouchDB è scritto in Erlang, ma consente agli utenti di specificare script di validazione dei documenti in Javascript. Questi script vengono valutati automaticamente quando un documento viene creato o aggiornato. Vengono avviati in un nuovo processo e ricevono documenti serializzati in JSON dal lato Erlang.
CouchDB invia funzioni e documenti a un interprete JavaScript. Questo meccanismo è ciò che consente agli utenti di scrivere funzioni di validazione dei documenti in JavaScript. La funzione validate_doc_update viene eseguita per ogni documento creato o aggiornato. Se la funzione di validazione solleva un'eccezione, l'aggiornamento viene negato; altrimenti, gli aggiornamenti vengono accettati.
function(newDoc, oldDoc, userCtx, secObj) {...}
Argomenti:
newDoc – Nuova versione del documento che verrà archiviatooldDoc – Versione precedente del documento già archiviatouserCtx – Oggetto contesto utentesrcObj – Oggetto di sicurezzaAlla funzione vengono passati il nuovo documento dalla richiesta di aggiornamento, il documento corrente archiviato nel database, un Oggetto contesto utente contenente informazioni sull'utente che scrive il documento (se presente) e un Oggetto di sicurezza con elenchi di ruoli di sicurezza del database.
Il parser JSON utilizzato internamente da CouchDB è jiffy mentre quello utilizzato negli script di validazione da JavaScript è JSON.
Il problema è che c'è una discrepanza tra JSON e jiffy quando si gestiscono chiavi duplicate.
Per una data chiave, il parser Erlang memorizzerà entrambi i valori, mentre il parser Javascript memorizzerà solo l'ultimo.
Ad esempio, analizzando {"name":"John", "name":"Jane"} utilizzando entrambi i parser si otterrà:
jiffy: {[{<<"name">>,<<"John">>},{<<"name">>,<<"Jane">>}]}JSON: {name: "Jane"}La funzione getter per la rappresentazione interna dei dati di CouchDB restituirà solo il primo valore.
Possiamo bypassare tutta la validazione dell'input rilevante e creare un utente amministratore creando un utente con chiave roles duplicata.
Ecco come appare il documento del nuovo utente: {..., "roles": ["_admin"], "roles": [], ...} .
La funzione getter per la rappresentazione interna dei dati di CouchDB restituirà solo il primo valore. Di conseguenza, nel contesto Erlang, ci vedremo come aventi il ruolo _admin, mentre nel contesto Javascript appariremo senza permessi speciali.
Fortunatamente per l'attaccante, quasi tutta la logica importante riguardante autenticazione e autorizzazione, a parte lo script di validazione dell'input, avviene nella parte Erlang di CouchDB.
Per questa demo, utilizzeremo un'immagine Docker dal repository ufficiale couchdb. Abbiamo bisogno di un client HTTP per effettuare chiamate API e useremo cURL.
Qualsiasi sistema operativo può essere utilizzato per questa demo.
Prerequisiti:
Questi sono i comandi da eseguire per sfruttare la vulnerabilità:
Creare un container basato sull'immagine ufficiale couchdb
docker container run -d --name couchdb-sandbox -p 5984:5984 couchdb:1.6.1
Abbiamo scelto il tag 1.6.1 perché la versione 1.6.1 di CouchDB è vulnerabile.
Assicurarsi che l'istanza CouchDB sia avviata e funzionante
curl -X GET http://localhost:5984
Query: Tutti i database nell'istanza
curl -X GET http://localhost:5984/_all_dbs
Query: Creare un nuovo database chiamato records
curl -X PUT http://localhost:5984/records
Query: Assicurarsi che il database records sia stato creato
curl -X GET http://localhost:5984/_all_dbs
Possiamo ottenere, aggiungere e persino rimuovere tutti i record dall'istanza CouchDB perché un'installazione predefinita di CouchDB fornisce accesso a livello di amministratore a tutti gli utenti che si connettono. Questa configurazione è nota come Admin Party. Possiamo interrompere la festa semplicemente creando il primo account amministratore.
Query: Creare un account amministratore con credenziali admin:admin
curl -X PUT http://localhost:5984/_config/admins/admin -d '"admin"'
Query: Creare un nuovo database chiamato new_records
curl -X PUT http://localhost:5984/new_records
Oops! Non possiamo più creare un nuovo database perché l'Admin Party è terminato quando è stato creato il primo account amministratore.