ClickHouse不支持参数化查询,易致SQL注入;clickhouse-client和HTTP接口均直执行字符串,无语法解析;防护须在应用层实现白名单校验、类型强转与代理层隔离。

ClickHouse 本身不支持参数化查询(如预编译语句或绑定变量),直接拼接用户输入极易引发 SQL 注入,尤其在 Web 接口或 BI 工具直连场景下风险极高。
为什么 clickhouse-client 和 HTTP 接口都默认不防注入
ClickHouse 的 clickhouse-client 是纯命令行工具,所有输入经 shell 解析后原样传给服务端;HTTP 接口(如 /?query=...)则直接将 URL 参数作为 SQL 执行,无语法解析或参数隔离机制。这意味着:user_id=123 若未校验就拼进 SELECT * FROM users WHERE id = {user_id},攻击者传 user_id=123 OR 1=1 就能绕过条件。
- 官方未提供类似 PostgreSQL 的
PREPARE/EXECUTE或 MySQL 的mysql_stmt_bind_param接口 -
clickhouse-client --query和 HTTPquery参数均是字符串直执行,不经过任何参数化中间层 - 即使使用
FORMAT JSONEachRow或INSERT SELECT,只要 SQL 字符串含未过滤的用户输入,风险依旧存在
实际可用的三类防护手段
必须在应用层(而非 ClickHouse 侧)完成输入控制,核心思路是:**禁止拼接、强制白名单、剥离执行权**。
-
字段/值白名单校验:对用户可选的列名、时间范围、枚举值(如
status IN ('active','pending'))全部走预定义列表比对,拒绝任何不在名单中的输入 -
类型强转 + 范围截断:对数字类参数(如
limit、offset)用parseInt()后限定最大值(如Math.min(limit, 10000));日期字符串用正则^\d{4}-\d{2}-\d{2}$严格匹配 -
改用只读代理层:在 ClickHouse 前加一层轻量代理(如 Nginx + Lua 或自研 Go 服务),把用户请求转为固定模板 + 安全参数,再调用
clickhouse-client -q或 HTTP 接口,禁止透传原始 query
HTTP 接口调用时最容易忽略的坑
很多人以为加了 user/password 就安全,其实 HTTP 接口的 query 参数仍是明文暴露在日志、监控、代理缓存中。
- 错误示例:
curl "http://ck:8123/?query=SELECT%20*%20FROM%20logs%20WHERE%20url%3D'$user_input'"——$user_input未转义即拼入 URL,URL 编码可被绕过 - 正确做法:改用 POST +
application/x-www-form-urlencoded,且 body 中只传结构化参数(如{"table":"logs","filter":{"url":"example.com"}}),由后端映射到预设 SQL 模板 - 务必关闭 ClickHouse 的
enable_http_compression=0和send_progress_in_http_headers=0,避免敏感信息泄露到响应头
分布式表场景下的额外风险点
当查询涉及 Distributed 表时,SQL 会分发到多个本地节点执行。若攻击者构造恶意子查询(如 SELECT * FROM remote('127.0.0.1', 'system', 'processes')),可能穿透集群边界读取元数据甚至系统表。
- 必须在用户账号级别限制
GRANT权限,禁用SYSTEM、CLUSTER、remote()等高危函数 - 配置
users.xml中的段,禁止生产账号从非可信 IP 访问system.*表 - 对
Distributed表的WHERE条件,优先使用IN子句替代JOIN或子查询,减少远程执行面
真正难的不是写一个“能跑”的查询,而是确保任意输入组合都不会触发非预期的表扫描或跨库访问——这要求权限收得比 MySQL 更严,输入校验比前端更硬,代理层逻辑比业务代码更早介入。别指望数据库自己拦住注入,它连“参数”这个概念都没有。











