ClickHouse SQL注入风险极高,需通过参数化查询、禁用高危函数、配置SQL防御规则及最小权限控制四层防护。正确使用--param_*和类型化占位符,禁用url()/file()/executable(),限制system表访问,并在CI中扫描硬编码拼接。

ClickHouse的SQL注入风险比多数人想的更直接、更危险——它不是“可能被利用”,而是只要接口拼接字符串,url()、file()、executable() 这类函数就能立刻变成攻击者的执行通道。
用 clickhouse-client 的 --param_* 参数代替字符串拼接
这是最轻量、最可控的第一道防线。ClickHouse原生支持运行时参数替换,所有传入值都会被当作纯数据处理,不会进入SQL解析阶段。
- 错误写法:
clickhouse-client --query="SELECT * FROM logs WHERE user = '$INPUT'"—— shell变量展开后直接拼进SQL,$INPUT里塞' OR 1=1--就完事 - 正确写法:
clickhouse-client --param_user="'alice'" --query="SELECT * FROM logs WHERE user = {user: String}"——{user: String}是类型化占位符,值由客户端内部安全注入 - 注意:
{user}不带类型声明(如:String)在某些旧版本中会触发隐式类型推导,存在绕过风险;务必显式标注类型 - 多个参数可叠加:
--param_from=2024-01-01 --param_to=2024-12-31 --query="SELECT * FROM events WHERE dt BETWEEN {from: Date} AND {to: Date}"
禁用高危表函数,从源头掐断执行能力
绝大多数ClickHouse注入攻击链,最终都依赖 url()、file()、executable() 这几个函数发起外连或读取本地文件。它们不是“可选功能”,而是默认开启的“攻击面”。
- 在
config.xml的段中,为业务用户配置禁用列表:<profile_name><br><allow_experimental_funnel_functions>0</allow_experimental_funnel_functions><br><allow_url_fopen>0</allow_url_fopen><br><allow_file_read>0</allow_file_read><br><allow_executable>0</allow_executable><br></profile_name>
- 这些开关是硬限制:一旦设为
0,任何用户(包括default)调用对应函数都会报错DB::Exception: Function url is disabled - 不要依赖“只给低权限用户”——攻击者一旦拿到任意可登录账号,就能通过
system表枚举结构,再结合开放函数完成横向渗透
启用SQL防御规则(MRS/Manager界面或手动配置)
参数化+禁用函数能防住90%的手动注入,但对自动化扫描、批量试探、低质量大查询等场景,需要运行时策略拦截。
- MRS平台用户:登录 Manager → “集群 > SQL防御” → 添加规则,例如匹配
FROM url\(.*\)或SELECT \* FROM system\.的正则,动作设为“拦截” - 命令行用户:可在
users.xml中为 profile 配置和1048576 ,防止超长恶意AST构造100 - 关键点:规则生效有延迟(默认1分钟),且需重新登录客户端才加载新配置;上线前务必在测试环境验证是否误杀正常报表SQL
- 特别注意
UNION ALL类型注入:ClickHouse不支持UNION SELECT简写,但攻击者会直接写全称,防御规则必须覆盖UNION\s+ALL\s+SELECT而非只盯UNION SELECT
永远不要信任 system 和 information_schema 的访问控制
很多人以为“没开外网端口就安全”,却忘了ClickHouse的元数据表本身就是攻击情报源。只要能执行任意查询,system.tables 就等于把数据库地图双手奉上。
- 最小权限原则在这里必须落地:为应用创建专用用户,仅授予
SELECT权限,且限定到具体库和表,例如:GRANT SELECT ON mydb.events TO app_reader; - 禁止该用户访问
system库:REVOKE SHOW DATABASES, SHOW TABLES ON *.* FROM app_reader;(注意:ClickHouse 22.8+ 支持细粒度system权限控制) - 检查现有用户权限:
SELECT * FROM system.grants WHERE user = 'default';—— 如果看到ALL PRIVILEGES,立刻收紧 - 别忽略
information_schema:它只是system表的兼容层映射,禁用system访问后,information_schema查询也会失败
真正难防的不是单点注入,而是当参数化、函数禁用、权限隔离、SQL防御四层都配了,却在某个临时脚本里又写了 eval("SELECT ... " + user_input) —— 这种“绕过自己防线”的操作,在运维和BI工具集成中高频出现,得靠代码审查和CI阶段SQL语法扫描来兜底。











