CVE-2021-25263 ClickHouse任意文件读漏洞分析
最近的ByteCTF里用了ClickHouse这个数据库引擎,以为题目用了这个数据库的CVE,复现了但是没用到 ,还是记录下。
ClickHouse
ClickHouse是一个用于联机分析(OLAP)的列式数据库管理系统(DBMS),主要应用于数据统计分析领域。安全性方面算是比较完善的,近期披露出来的漏洞不多。
分析
官方文档里列出来最新的漏洞是CVE-2021-25263,漏洞介绍如下:
Fixed in ClickHouse 21.4.3.21, 2021-04-12
CVE-2021-25263
An attacker that has CREATE DICTIONARY privilege, can read arbitary file outside permitted directory.
Fix has been pushed to versions 20.8.18.32-lts, 21.1.9.41-stable, 21.2.9.41-stable, 21.3.6.55-lts, 21.4.3.21-stable and later.
在GitHub官方仓库里,找到了对应的PR FileDictionarySource fix absolute file path #22822
修复了src/Dictionaries/FileDictionarySource.cpp中的目录穿越问题。
可以发现,原来文件路径是通过判断文件开头是否为user_files_path
,使用../../../../
即可绕过。
参考官方文档,clickhouse的user_files_path
配置在/etc/clickhouse-server/config.xml
中,默认为/var/lib/clickhouse/user_files/
。
根据漏洞描述,可以猜测漏洞发生在CREATE DICTIONARY时,CREATE DICTIONARY支持通过文件创建,构造文件路径/var/lib/clickhouse/user_files/../../../../
即可任意文件读取。
复现
看了下dockerhub有clickhouse的镜像,直接pull一个下来,复现选择21.3.2.5版本。
1 |
|
进入数据库终端
1 |
|
漏洞点在CREATE DICTIONARY处,官方文档关于CREATE DICTIONARY的语法如下:
1 |
|
SOURCE
支持从文件中格式化加载字典,语法如下:
1 |
|
path
为路径穿越的目标文件,format
是文件格式化的方法。
那么,问题来了,怎么把一个文件的内容合法格式化加载到一个字典里?
clickhouse支持 Formats for Input and Output Data 中列出的所有格式,很多格式一眼看过去就知道用不了。
筛选一下,可能可用的部分format如下:
Format | 分割方式 |
---|---|
TabSeparated | TAB制表符分割 |
Template | 模板分割 |
CustomSeparated | 类似于Template |
LineAsString | 按行分割 |
Regexp | 正则分割 |
其中,LineAsString输出只有一个值,而字典要求key-value,需要返回至少两个值,没法用。
逐个筛查,最后发现Regexp不仅可以指定正则,还可以跳过报错行,非常适合拿来读文件使用。(前提:已知文件格式)
Regexp用法:
Regexp
Each line of imported data is parsed according to the regular expression.
When working with the
Regexp
format, you can use the following settings:
format_regexp
— String. Contains regular expression in the re2 format.
format_regexp_escaping_rule
—String. The following escaping rules are supported:
- CSV (similarly to CSV)
- JSON (similarly to JSONEachRow)
- Escaped (similarly to TSV)
- Quoted (similarly to Values)
- Raw (extracts subpatterns as a whole, no escaping rules)
format_regexp_skip_unmatched
— UInt8. Defines the need to throw an exeption in case theformat_regexp
expression does not match the imported data. Can be set to0
or1
.Usage
The regular expression from
format_regexp
setting is applied to every line of imported data. The number of subpatterns in the regular expression must be equal to the number of columns in imported dataset.Lines of the imported data must be separated by newline character
'\n'
or DOS-style newline"\r\n"
.The content of every matched subpattern is parsed with the method of corresponding data type, according to
format_regexp_escaping_rule
setting.If the regular expression does not match the line and
format_regexp_skip_unmatched
is set to 1, the line is silently skipped. Ifformat_regexp_skip_unmatched
is set to 0, exception is thrown.
format_regexp
设置正则,format_regexp_skip_unmatched
设置为1,跳过不匹配的行。
然后构造CREATE DICTIONARY语句,这里还有一个坑点。
CREATE DICTIONARY语句中有一个LAYOUT
设置,翻一下文档,不难发现,绝大多数的LAYOUT
只支持key的类型为UInt64
。如果设置的key为String
,就可能会导致创建的字典key重复出现,导致覆盖。
接着翻文档,文档中对于字典key的说明中,除了Numeric Key
以外,还有一种Composite Key
的选项。
Composite Key
The key can be a
tuple
from any types of fields. The layout in this case must becomplex_key_hashed
orcomplex_key_cache
.Tip
A composite key can consist of a single element. This makes it possible to use a string as the key, for instance.
这样,key的问题也解决了,构造SQL语句即可实现任意文件读取。
POC:
1 |
|
复现:
总结
这个漏洞的利用条件其实比较苛刻,需要数据库用户具有CREATE DICTIONARY权限。
其次,ClickHouse不支持堆叠查询,也就是说,这个漏洞无法在SQL注入中使用,必须SQL语句完全可控的情况下,才能利用。这种场景基本只有管理后台、ClickHouse HTTP接口/TCP接口未授权访问才存在,场景十分有限。但是,ClickHouse一般以root用户运行,一旦出现未授权访问,利用该漏洞可读取/etc/shadow
等文件。