0x0 背景介绍
Grafana是一个开源的度量分析和可视化套件。该漏洞源于Grafana的SQL 表达式(sqlExpressions)功能在处理SQL语法时存在安全漏洞,允许攻击者进行任意文件写入。攻击者可以通过链式利用该漏洞,覆盖Sqlyze驱动程序或写入恶意的AWS数据源配置文件。由于攻击者可以控制写入的文件内容,这将导致在Grafana主机上实现远程代码执行(RCE),甚至可能获取SSH连接权限。
0x1 环境搭建(Ubuntu24)
- 简单YML参考
bash
# docker-compose.yml
version: '3.8'
services:
grafana:
image: grafana/grafana:12.4.1
container_name: grafana-vuln
ports:
◦ "3000:3000"
environment:
◦ GF_SECURITY_ADMIN_USER=admin
◦ GF_SECURITY_ADMIN_PASSWORD=admin
# 关键配置:启用 sqlExpressions 功能开关,这是漏洞利用的前提
◦ GF_FEATURE_TOGGLES_ENABLE=sqlExpressions
volumes:
◦ grafana-storage:/var/lib/grafana
volumes:
grafana-storage:
0x2 漏洞复现
- 前置条件
| 条件 | 说明 |
|---|---|
| 身份与权限 | 能够执行数据源查询 |
| 功能开 | 关实例启用sqlExpressions特性 |
| 链式组件(RCE场景) | 环境中存在可被覆盖的Sqlyze相关文件,或AWS数据源配置落盘路径等(见 下文,也就是存在问题的插件) |
2.1-手动复现
首先必须说明,我环境没有Sqlyze和AWS数据源(因为搞不到),所以使用MYSQL辅助验证,严格意义上算复现了半个,剩下就是原理讨论,没错!!又是原理讨论
2.1.1 场景 A:通过统一查询 API 投递 SQL 表达式(攻击面验证)
目标:确认请求可到达服务端表达式管线,且 type: sql 与 expression 字段被解析。
bash
1.使用具备数据源查询权限的会话登录 Grafana,取得会话 Cookie(或 API Key / Service Account Token,视部署而定)。
2.准备一个基础查询 refId: A(任意已授权数据源,返回至少一帧表格或可转换时序数据)。
3.在同一 MetricRequest 中增加 refId: B,数据源为内置 __expr__,type 为 sql,expression 为受控 SQL 字符串。
4.向 POST /api/ds/query(或经特性开关改写后的 Query API 路径)发送 JSON 体。
-
首先创建一个测试的数据源,获取其ID

-
Payload 结构示意(字段名与 pkg/expr/service_test.go、service_sql_test.go 中构造一致)

bash
POST /api/ds/query HTTP/1.1
Host: grafana.example.com
Content-Type: application/json
Cookie: grafana_session=<会话 Cookie>
Content-Length: <自动>
{"from":"now-1h","to":"now","queries":[{"refId":"A","datasource":{"type":"grafana-testdata-datasource","uid":"<你的测试数据源 UID>"},"scenarioId":"random_walk","intervalMs":1000,"maxDataPoints":100},{"refId":"B","datasource":{"uid":"__expr__","type":"__expr__"},"type":"sql","expression":"SELECT * FROM A LIMIT 10" } ]}
-
创建一个MYSQL容器(是主动开启了不安全的secure_file_priv,用于验证)

-
进行写入直接写入容器(图不清晰呀...难道是大屏截图后转发压缩了?)

2.1.2 场景 B:与 Enterprise 生态链式 RCE(概念)
目标:说明从「SQL 表达式」到「远程代码执行」的业务级链条,而非单一HTTP 请求即完成RCE。
bash
1.攻击者在满足 场景 A 的前提下,利用 SQL 表达式实现对文件系统的非预期写入。
2.将恶意内容写入可被 Sqlyze 加载的驱动路径,或写入 AWS 数据源使用的配置文件路径。
3.在后续查询或插件加载过程中执行任意代码,进而可能获得对 Grafana 宿主机的 SSH 级控制(官方说明)依赖环境中是否安装、启用上述组件;
OSS源码树本身不自带Enterprise插件二进制,但SQL表达式能力在OSS 中由特性开关启用,与公告中攻击描述一致。
2.1.3 其他理论姿势展示认证方式差异与同一漏洞面的不同入口
1)验证接口
bash
POST /api/ds/query HTTP/1.1
Host: grafana.example.com
Content-Type: application/json
Cookie: grafana_session=<会话 Cookie>Content-Length: <自动>
{"from":"now-5m","to":"now","queries":[{"refId":"A","datasource":{"uid":"<ds-uid>","type":"testdata"},"maxDataPoints":100},{"refId":"B","datasource":{"uid":"__expr__","type":"__expr__"},"type":"sql","expression":"SELECT * FROM A"}]}
2)API Token(Header)
bash
POST /api/ds/query HTTP/1.1
Host: grafana.example.com
Content-Type: application/json
Cookie: grafana_session=<会话 Cookie>
{"from":"now-5m","to":"now","queries":[/* 同结构 */]}
3)经反向代理 / K8s Ingress 的改写路径若启用FlagQueryServiceRewrite,客户端可能访问被改写至Kubernetes API聚合层路径,例如:
bash
POST /apis/query.grafana.app/v0alpha1/namespaces/<org-namespace>/query HTTP/1.1
此时请求体 JSON结构仍为多查询 +__expr__SQL节点,与ds_query.go中重写逻辑一致。
- WAF/IDS 可关注特征
bash
URI 包含 /api/ds/query 或 query.grafana.app。
Body 同时出现 "__expr__" 与 "type":"sql"。
expression 字段中出现与文件、导出、路径相关的 SQL 关键字组合。
2.2-复现流量特征 (PCAP)
-
测试语句(明文流量,不要问为什么截图恶意流量...因为复现要了我半条命)

-
测试环境

0x3 漏洞原理分析
3.1 [入口定位] 从「面板查询」到「服务端表达式」:谁在处理 type: sql
- 先看在用户可控输入上:Grafana 的 Server-Side Expressions(SSE)允许在 MetricRequest 中声明 __expr__数据源,并在 JSON 里携带 type 与具体表达式。对于 SQL 表达式,结构体定义在 pkg/expr/query.go 中,SQLExpression 明确要求 expression 字符串:
go
// SQLQuery requires the sqlExpression feature flag
type SQLExpression struct {
Expression string `json:"expression" jsonschema:"minLength=1,example=SELECT * FROM A LIMIT 1"`
Format string `json:"format"`
}
- 接着看谁在控制这个能力:pkg/expr/nodes.go中,若命令类型为TypeSQL,则必须全局启用featuremgmt.FlagSqlExpressions,否则直接拒绝构建管线:
go
// pkg/expr/nodes.go
if commandType == TypeSQL {
//nolint:staticcheck // not yet migrated to OpenFeature
if !toggles.IsEnabledGlobally(featuremgmt.FlagSqlExpressions) {
return nil, fmt.Errorf("sql expressions are disabled")
}
}
- 预期边界是清晰的------SQL 表达式是可选特性,仅应在显式开启时可用
- 但「开启」本身只解决产品功能问题,并不自动等价于「对宿主文件系统零写入」
- 后续执行栈是否足够收敛,要看SQL引擎与允许语法集合
3.2 [执行路径] 从 SQLCommand.Execute 到 go-mysql-server:最后一道防线在哪里?
- SQLCommand在执行阶段将查询字符串交给sql.DB.QueryFrames
go
// :pkg/expr/sql_command.go
gr.logger.Debug("Executing query", "query", gr.query, "frames", len(allFrames))
db := sql.DB{}
frame, err := db.QueryFrames(ctx, tracer, gr.refID, gr.query, allFrames, sql.WithMaxOutputCells(gr.outputLimit), sql.WithTimeout(gr.timeout))
if err != nil {
rsp.Error = err
return rsp, nil
}
- 先经过AllowQuery(基于 Vitesssqlparser 的允许列表),再创建 go-mysql-server引擎,并设置 IsReadOnly: true
go
if allow, err := AllowQuery(name, query); err != nil || !allow {
if err != nil {
return nil, err
}
return nil, err
}
// ...
engine := sqle.New(a, &sqle.Config{
IsReadOnly: true,
})
- 这里存在两层预期安全设计:语法/函数白名单(parser_allow.go)------ 阻断明显危险函数(仓库中单测亦验证如load_file会被拦)。
- 引擎只读 ------ 试图从配置层面禁止写操作。
与官方披露的反差 Grafana Labs仍将 CVE-2026-27876 定性为可导致任意文件写入并进一步 RCE,说明在受影响版本中,上述组合未能在全部语义路径上形成等价于「纯内存只读分析」的安全边界。
3.3 [静态线索] 白名单里值得警惕的「INTO」节点
- 在AllowQuery的allowedNode中,*sqlparser.Into被放行:
go
// pkg/expr/sql/parser_allow.go
case *sqlparser.Into:
return
- 在MySQL语义族中,SELECT ... INTO类语句与「结果落盘/导出」强相关;
- 若底层引擎对某类INTO变体的处理与「只读引擎」假设不一致就会出现偏差。
同期我也对比了修复后的,修改了条件并增加更多白名单:
go
// 12.4.2版本 修改校验case *sqlparser.Into:// Plain SELECT statements may carry a typed-nil Into pointer.// Reject only when INTO is actually present. return v == nil
// 12.4.2版本 增加防绕过 case *sqlparser.SetOp:- return+ // SetOp.walkSubtree() does not traverse Into, so reject explicitly.+ return v.GetInto() == nil
增加了更多的白名单(也可能是其他漏洞的)
go
-[] 未修复版本(4个):
if, sum/avg/count/min/max, coalesce, str_to_date
-[] 修复版本(80+个):
条件:ifnull, nullif, least
聚合:stddev, variance, group_concat
窗口:row_number, rank, lead, lag 等
数学:abs, round, pow, sqrt, log, sin, cos 等
字符串:concat, length, substring, replace, regexp_* 等
日期:大量日期函数
JSON:完整 JSON 函数集
类型转换:cast, convert
3.4 [攻击链路] 从「文件写入」到「RCE」:
官方闭环与本地代码印证:
- 注入点:经认证用户可控的expression字符串(及与之配套的查询图依赖refId)。
- 爆发点(文件系统):官方SQL表达式语法允许以不当方式向文件系统写入内容。
- 二次爆发(代码执行):官方:覆盖Sqlyze 驱动相关文件,或写入 AWS 数据源配置,在后续加载或查询路径上达成RCE,并可能进一步获得SSH主机访问。
链路总结:
bash
HTTP POST /api/ds/query
-> MetricRequest.queries[] (含 __expr__ + type:sql + expression) [注入点]
-> expr.Service 构建 DAG / SQLCommand
-> sql.DB.QueryFrames: AllowQuery + go-mysql-server(IsReadOnly)
-> (受影响版本)文件写入 primitive [爆发点-1]
-> 覆盖 Sqlyze / AWS 数据源配置等 [爆发点-2]
-> 插件或进程加载恶意内容 -> RCE / SSH [极限危害]
0x4 修复建议
修复方案
- 升级最新版本:将组件升级安全版本:
bash
项目地址:
https://github.com/grafana/grafana
Grafana 12.4.2 版本,包含安全修复更新:
https://grafana.com/grafana/download/12.4.2?pg=grafana-security-release-critical-and-high-severity-security-fixes-for-cve-2026-27876-and-cve-2026-27880&plcmt=in-text
Grafana 12.3.6 安全修复版:
https://grafana.com/grafana/download/12.3.6?pg=grafana-security-release-critical-and-high-severity-security-fixes-for-cve-2026-27876-and-cve-2026-27880&plcmt=in-text
Grafana 12.2.8 安全修复版:
https://grafana.com/grafana/download/12.2.8?pg=grafana-security-release-critical-and-high-severity-security-fixes-for-cve-2026-27876-and-cve-2026-27880&plcmt=in-text
Grafana 12.1.10 版本,包含安全修复:
https://grafana.com/grafana/download/12.1.10?pg=grafana-security-release-critical-and-high-severity-security-fixes-for-cve-2026-27876-and-cve-2026-27880&plcmt=in-text
Grafana 11.6.14 安全修复版:
https://grafana.com/grafana/download/11.6.14?pg=grafana-security-release-critical-and-high-severity-security-fixes-for-cve-2026-27876-and-cve-2026-27880&plcmt=in-text
- 临时防护措施:
关闭特性: 在配置中禁用sqlExpressions功能(会影响业务使用)
防火墙 / WAF: 对包含__expr__ + sql的大体积或可疑expression请求做告警或限流(注意可别误杀合法请求)
限制访问: 仅允许管理网段或堡垒机访问Grafana UI/API;对/api/ds/query 实施更严格的身份与RBAC/FGAC
权限最小化: 以最小权限用户运行Grafana,宿主磁盘对进程可写目录做严格隔离,降低写入后的二次利用面。
官方建议: 如果你安装了Sqlyze:更新到至少v1.5.0或禁用它。禁用已安装的所有AWS数据源
- 官方链接:
1\] Grafana 安全公告: https://grafana.com/blog/grafana-security-release-critical-and-high-severity-security-fixes-for-cve-2026-27876-and-cve-2026-27880/