
Dans ClickHouse, il existe une fonctionnalité qui permet à un utilisateur disposant des permissions appropriées de créer des dictionnaires afin d'interagir avec différentes bases de données et d'exécuter des requêtes spécifiques sur celles-ci. Pour PostgreSQL, les requêtes SELECT sont encapsulées dans une instruction COPY( {QUERY} ) TO STDOUT avant d'être exécutées sur la base de données. En ajoutant une parenthèse à un dictionnaire créé, il est possible de s'échapper de l'instruction COPY( {QUERY} ) TO STDOUT et d'exécuter des instructions SQL arbitraires, ce qui peut à son tour conduire à l'exécution de commandes arbitraires sur le serveur de base de données.
Les dictionnaires PosgreSQL sont créés avec la structure suivante :
SOURCE(POSTGRESQL(
port 5432
host 'postgresql-hostname'
user 'postgres_user'
password 'postgres_password'
db 'db_name'
table 'table_name'
replica(host 'example01-1' port 5432 priority 1)
replica(host 'example01-2' port 5432 priority 2)
where 'id=10'
invalidate_query 'SQL_QUERY'
query 'SELECT id, value_1, value_2 FROM db_name.table_name'
))
La documentation des moteurs PostgreSQL utilisés dans les dictionnaires mentionne ce qui suit :
Les requêtes SELECT côté PostgreSQL s'exécutent sous la forme COPY (SELECT ...) TO STDOUT dans une transaction PostgreSQL en lecture seule, avec un commit après chaque requête SELECT.
Étant donné qu'aucune validation supplémentaire n'est effectuée sur les requêtes définies dans le dictionnaire avant leur envoi à l'instance Postgres, il est possible de sortir de l'instruction "COPY(...) TO STDOUT" en commençant la requête par une ")", créant ainsi une transaction qui n'est pas en lecture seule. À titre d'exemple, la requête suivante peut être utilisée dans ClickHouse pour créer un dictionnaire Postgres "malveillant" :
CREATE DICTIONARY exec_dict(id UInt64, value UInt64 DEFAULT 0) PRIMARY KEY id SOURCE(POSTGRESQL(port 5432 host '172.17.0.3' user 'postgres' password 'password' db 'postgres' query 'SELECT 1) TO PROGRAM \'id>/tmp/test\';-- ')) LAYOUT(DIRECT())
Une fois le dictionnaire créé, il peut ensuite être chargé dans ClickHouse en le référençant. ClickHouse renverra une erreur indiquant que la fonction COPY attendue par ClickHouse a échoué. Cependant, le reste de la requête s'exécutera toujours sur la base de données PostgreSQL sous-jacente. En abusant de la fonctionnalité PROGRAM de PostgreSQL, des commandes arbitraires peuvent être exécutées.
Bien que les tests initiaux aient été effectués sur la version 25.8.10.7, lorsque le rapport a été soumis le 30 janvier 2025, la version la plus récente à l'époque était la 26.3.9.8 et la vulnérabilité était toujours présente. Après examen, le problème a été marqué comme "non applicable" le 10 avril 2026, avec la déclaration suivante :
non - ce n'est pas dû à une raison de sécurité mais surtout à une question d'efficacité. Un utilisateur disposant d'identifiants postgres + de la fonction de table distante peut déjà faire beaucoup, ou se connecter directement à la base de données postgresql et exécuter ces requêtes.
En tant que tel, ce n'est pas un risque, l'attaquant a ici besoin d'identifiants valides pour la base de données postgresql, d'un utilisateur valide sur CLickHouse et de la permission d'utiliser la fonction de table postgresql.
En ce qui concerne la protection de la base de données Postgresql, les utilisateurs devraient utiliser des permissions judicieuses pour tout identifiant postgresql utilisé par l'utilisateur CLickHouse - ce qui signifie des permissions limitées, une portée restreinte, et non pas directement les identifiants "postgres" par défaut.
En tant que tel, cela reste applicable sur les versions les plus récentes de ClickHouse au moment de la rédaction de ce rapport :
Il semble peu probable qu'un correctif soit publié, ce qui signifie que toutes les versions actuelles et potentiellement futures de ClickHouse seront affectées.