2025 年初,安全研究员 Mehmet Ince 在对开源产品分析平台 PostHog 进行安全评估时,发现了一个复杂的安全漏洞链。这个攻击链从看似无害的 SSRF 漏洞开始,通过 ClickHouse 的 SQL 注入漏洞,最终利用默认 PostgreSQL 凭据实现了远程代码执行(RCE)。本文将从技术角度深入分析这一漏洞链,并设计一套可落地的多层防御架构。
漏洞链全景:三层攻击路径
第一层:SSRF 漏洞的发现与利用
PostHog 作为一款支持数千个外部集成的产品分析平台,其核心功能之一是通过 webhook 与外部系统通信。安全研究员在代码审计中发现,虽然test_slack_webhook端点进行了 SSRF 验证,但保存 webhook 配置的端点却没有相同的安全检查。
技术细节:
- CVE-2024-9710: Rust Webhook Handler SSRF 信息泄露漏洞
- CVE-2025-1522: database_schema SSRF 信息泄露漏洞
- CVE-2025-1521: slack_incoming_webhook SSRF 信息泄露漏洞
攻击者可以通过直接向项目 API 发送PATCH请求,绕过前端验证,将 webhook URL 设置为localhost或其他内部地址。Rust webhook worker 在处理作业时,不会重新验证这些 URL,直接发起服务器端请求,形成了 SSRF 原语。
第二层:ClickHouse SQL 注入漏洞
PostHog 的架构明确将 ClickHouse 作为主要分析后端。默认情况下,ClickHouse 在 TCP 端口 8123 上暴露 HTTP API,且自托管部署中通常不需要认证。
关键发现:ClickHouse 的postgresql()表函数存在 SQL 注入漏洞。该函数允许 ClickHouse 从远程 PostgreSQL 数据库读取或写入数据,但在处理用户输入时使用了错误的转义机制。
-- 攻击者控制的输入
SELECT * FROM postgresql('db:5432','posthog','posthog_table\'','posthog','posthog')
ClickHouse 尝试使用反斜杠转义单引号,但在 PostgreSQL 中,正确的转义方式是使用两个单引号。这个转义错误导致了 SQL 注入漏洞。
第三层:RCE 链的形成
攻击者通过 SSRF 访问 ClickHouse HTTP API,利用 SQL 注入漏洞执行任意 PostgreSQL 查询。通过COPY FROM PROGRAM功能,可以在 PostgreSQL 服务器上执行操作系统命令,实现 RCE。
完整攻击链:
- 绕过 webhook URL 验证,保存指向内部 ClickHouse 的 URL
- 触发 webhook,Rust worker 发起 SSRF 请求到 ClickHouse HTTP API
- 利用 ClickHouse 的 postgresql 表函数 SQL 注入漏洞
- 通过
COPY FROM PROGRAM执行系统命令 - 利用默认 PostgreSQL 凭据获取 shell 访问权限
技术深度分析:漏洞成因与绕过机制
SSRF 验证的 TOCTOU 问题
PostHog 的 SSRF 验证存在时间差攻击(TOCTOU)风险。raise_if_user_provided_url_unsafe函数在验证时检查 URL 是否安全,但在实际请求时可能已经发生变化。攻击者可以通过快速修改 DNS 记录或使用 URL 重定向绕过验证。
ClickHouse 的转义逻辑缺陷
ClickHouse 在处理 PostgreSQL 表函数参数时,错误地使用了反斜杠转义。在 PostgreSQL 中,字符串常量中的单引号应该通过两个连续的单引号转义,而不是反斜杠。
-- ClickHouse的错误转义
'posthog_table\''
-- PostgreSQL期望的转义
'posthog_table'''
这个转义错误使得攻击者可以闭合原始查询,注入自己的 SQL 语句。
默认凭据的风险放大
PostHog 在自托管部署中使用默认的 PostgreSQL 凭据(用户名和密码均为 "posthog")。虽然这简化了安装过程,但在安全漏洞存在时显著放大了风险。攻击者一旦获得 SQL 注入能力,就可以直接使用这些默认凭据访问数据库。
多层防御架构设计
第一层:输入验证与边界控制
实施要点:
- 统一验证层:所有接受 URL 输入的端点必须使用相同的验证逻辑,避免测试端点与保存端点验证不一致
- SSRF 防护清单:
- 禁止访问 RFC 1918 私有地址空间(10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16)
- 禁止访问回环地址(127.0.0.0/8, ::1)
- 禁止访问链路本地地址(169.254.0.0/16)
- 实施 DNS 重绑定防护
- TOCTOU 防护:在请求发起时重新验证 URL,避免时间差攻击
代码实现示例:
def validate_url_safety(url):
"""统一的URL安全验证函数"""
parsed = urlparse(url)
# 检查IP地址黑名单
if parsed.hostname in INTERNAL_IP_BLACKLIST:
raise ValidationError("Internal addresses are not allowed")
# DNS解析检查
try:
resolved_ips = socket.gethostbyname_ex(parsed.hostname)[2]
for ip in resolved_ips:
if is_internal_ip(ip):
raise ValidationError("DNS resolves to internal address")
except socket.gaierror:
raise ValidationError("Invalid hostname")
return True
第二层:ClickHouse 安全配置
安全参数配置:
-
HTTP API 访问控制:
# clickhouse-config.xml <http_port>8123</http_port> <http_server_default_response></http_server_default_response> <!-- 启用认证 --> <users> <default> <password>strong_password_here</password> <access_management>1</access_management> <networks> <ip>::/0</ip> </networks> </default> </users> <!-- 限制表函数使用 --> <allow_experimental_table_functions>0</allow_experimental_table_functions> -
网络隔离策略:
- ClickHouse HTTP API 仅允许来自应用服务器的访问
- 使用网络策略或防火墙规则限制 8123 端口的访问
- 考虑使用 Unix 域套接字替代 TCP 端口
-
查询审计与监控:
-- 启用查询日志 SET log_queries = 1; -- 监控异常查询模式 CREATE MATERIALIZED VIEW suspicious_queries ENGINE = MergeTree() ORDER BY (event_time) AS SELECT query, user, client_hostname, event_time FROM system.query_log WHERE query LIKE '%postgresql(%' OR query LIKE '%FROM PROGRAM%' OR query LIKE '%COPY %'
第三层:PostgreSQL 安全加固
安全配置清单:
-
修改默认凭据:安装后立即更改默认用户名和密码
-
最小权限原则:
-- 创建专用用户,仅授予必要权限 CREATE USER posthog_app WITH PASSWORD 'strong_password'; GRANT CONNECT ON DATABASE posthog TO posthog_app; GRANT SELECT, INSERT, UPDATE ON ALL TABLES IN SCHEMA public TO posthog_app; REVOKE EXECUTE ON FUNCTION pg_catalog.pg_ls_dir FROM posthog_app; REVOKE EXECUTE ON FUNCTION pg_catalog.pg_read_file FROM posthog_app; -
禁用危险功能:
-- 限制COPY FROM PROGRAM使用 ALTER ROLE posthog_app NOSUPERUSER; -- 或通过扩展限制 CREATE EXTENSION pgaudit; CREATE POLICY block_program_copy ON command USING (command_tag != 'COPY'); -
连接限制:
-- pg_hba.conf配置 # 仅允许应用服务器访问 host posthog posthog_app 10.0.1.0/24 md5 # 拒绝所有其他连接 host all all 0.0.0.0/0 reject
第四层:运行时监控与检测
自动化检测机制:
-
SSRF 攻击检测:
class SSRFDetector: def __init__(self): self.suspicious_patterns = [ r'localhost', r'127\.0\.0\.1', r'192\.168\.', r'10\.', r'172\.(1[6-9]|2[0-9]|3[0-1])\.', r'metadata\.google\.internal', r'169\.254\.' ] def detect(self, url, response): """检测SSRF攻击尝试""" for pattern in self.suspicious_patterns: if re.search(pattern, url) or re.search(pattern, response.text): return True return False -
SQL 注入检测:
- 监控包含异常转义字符的查询
- 检测
COPY FROM PROGRAM等危险操作 - 实施查询频率限制
-
RCE 尝试检测:
# 系统进程监控 # 监控异常进程创建 auditctl -a always,exit -F arch=b64 -S execve -k posthog_monitor # 网络连接监控 # 检测到未知IP的出站连接
可落地的安全参数配置
Webhook 安全配置
# posthog安全配置
security:
webhook:
# URL验证配置
validation:
enabled: true
block_internal_ips: true
dns_rebinding_protection: true
timeout: 5 # 秒
# 请求限制
rate_limit:
enabled: true
requests_per_minute: 60
burst_size: 10
# 重定向控制
redirects:
max_redirects: 3
allow_internal_redirects: false
ClickHouse 安全参数
clickhouse:
security:
# API访问控制
http_api:
enabled: true
port: 8123
require_authentication: true
ip_whitelist:
- "10.0.1.0/24" # 应用服务器网段
# 查询限制
query_restrictions:
max_query_size: 1048576 # 1MB
max_execution_time: 30 # 秒
read_only_mode: true # 生产环境启用
# 表函数控制
table_functions:
postgresql_enabled: false
url_enabled: false
file_enabled: false
PostgreSQL 安全基线
# postgresql.conf安全配置
# 连接安全
listen_addresses = '10.0.1.100' # 仅监听内网IP
port = 5432
ssl = on
# 认证配置
password_encryption = scram-sha-256
auth_delay.milliseconds = 1000 # 认证延迟,防止暴力破解
# 查询监控
log_statement = 'ddl' # 记录DDL语句
log_min_duration_statement = 1000 # 记录执行超过1秒的查询
# 危险功能限制
shared_preload_libraries = 'pgaudit'
pgaudit.log = 'all, -misc'
监控与告警清单
实时监控指标
-
SSRF 尝试监控:
- 包含内部 IP 的 webhook URL 保存尝试
- 到内部服务的异常 HTTP 请求
- DNS 解析到内部地址的尝试
-
SQL 注入检测:
- 包含异常转义字符的 ClickHouse 查询
postgresql()表函数的异常使用- 查询执行时间异常
-
RCE 迹象监控:
- PostgreSQL 中
COPY FROM PROGRAM的执行 - 异常进程创建
- 到未知 IP 的出站网络连接
- PostgreSQL 中
告警阈值配置
alerts:
ssrf_attempt:
threshold: 3 # 每小时3次尝试
severity: high
sql_injection:
threshold: 1 # 立即告警
severity: critical
rce_attempt:
threshold: 1 # 立即告警
severity: critical
default_credentials:
detection: true # 检测默认凭据使用
severity: medium
应急响应与修复流程
漏洞确认后的立即行动
-
隔离受影响系统:
- 临时禁用 webhook 功能
- 限制 ClickHouse HTTP API 访问
- 加强 PostgreSQL 访问控制
-
补丁应用:
# 更新PostHog到安全版本 docker-compose pull docker-compose up -d # 验证修复 curl -X POST https://your-posthog/api/user/test_slack_webhook/ \ -H "Content-Type: application/json" \ -d '{"webhook":"http://localhost/"}' # 应该返回错误 -
凭据轮换:
-- 轮换PostgreSQL密码 ALTER USER posthog WITH PASSWORD 'new_strong_password_$(date +%s)'; -- 轮换ClickHouse密码 ALTER USER default IDENTIFIED WITH sha256_password BY 'new_password';
长期安全改进
-
架构优化:
- 考虑使用消息队列替代直接 HTTP 调用
- 实现服务间 mTLS 认证
- 引入 API 网关进行统一访问控制
-
安全测试集成:
- 将 SSRF 测试纳入 CI/CD 流水线
- 定期进行渗透测试
- 实施自动化安全扫描
-
监控体系完善:
- 建立安全事件关联分析
- 实施用户行为分析(UEBA)
- 构建威胁情报集成
总结与最佳实践
PostHog 安全事件揭示了现代微服务架构中的典型安全挑战:单个组件的漏洞可能通过服务间依赖被串联放大。防御此类攻击链需要多层安全控制:
- 纵深防御:在每一层(应用、数据库、网络)都实施安全控制
- 最小权限:每个组件仅拥有完成其功能所需的最小权限
- 输入验证:对所有用户输入进行严格验证,包括内部 API 调用
- 默认安全:安装后立即修改默认配置,特别是凭据
- 持续监控:建立全面的安全监控和告警体系
通过实施本文提出的多层防御架构和安全参数配置,组织可以显著降低类似攻击链的风险,同时为未来的安全威胁做好准备。安全不是一次性的任务,而是需要持续投入和改进的过程。
资料来源:
- Mehmet Ince, "Inside PostHog: How SSRF, a ClickHouse SQL Escaping 0day, and Default PostgreSQL Credentials Formed an RCE Chain", mdisec.com, 2025-12-15
- NVD, "CVE-2025-1520 - PostHog ClickHouse Table Functions SQL Injection Remote Code Execution Vulnerability", nvd.nist.gov