长亭百川云 - 文章详情

漏洞分析:《CVE-2021-43798 Grafana 未授权任意文件读取》

深潜之眼

77

2024-07-13

0x00 前言

这个漏洞这几天大家提得也比较多,我就有了对此做一下漏洞分析的念头。

由于这几天没怎么上Twitter和关注群聊,所以从得知消息到昨天7号四点多复现成功后,我算是比较末尾那一批,因为五点多的时候各大src 也已经开始通报漏洞了。

0x01 漏洞分析与复现

漏洞复现:

我7号那天是直接去官网下安装包,无脑下一步;完成后默认是 3000 端口,默认凭证 admin/admin

我这里版本是8.3.0

发送 payloads,可以看到读取内容成功:

漏洞分析:

我一开始是直接用 github 的在线 vscode 审计 Grafana 的源码,看了一会后觉得很纳闷,最后发现16小时前已经被修复了...

16小时前已修复:

可以看到 8.3.1 版本目前共有 1个更改的文件 、3 个添加 和2 个删除。改动位置为 pkg/api/plugins.go ,分别在 284、288、289 行添加了代码,将原 287、288行的代码删除。

那么我们就把 8.3.0 的源码下载到本地,并找到 pkg/api/plugins.go 开始审计

找到改动处,可以看到内容属于 getPluginAssets 这个结构体。

大家看看这个结构体第三行的路径注释,是不是与我文中的 payloads ( https://xxxxxx.xx/public/plugins/grafana-clock-panel/../../../../../../etc/passwd ) 很相似 ? 没错,这里就是我们请求该路径后,会触发的地方之一。

我们来看看这个结构体做了什么:

往下

结构体具体代码:

`// getPluginAssets returns public plugin assets (images, JS, etc.)``//``// /public/plugins/:pluginId/*``func (hs *HTTPServer) getPluginAssets(c *models.ReqContext) {`  `pluginID := web.Params(c.Req)[":pluginId"]`  `plugin, exists := hs.pluginStore.Plugin(c.Req.Context(), pluginID)`  `if !exists {`    `c.JsonApiErr(404, "Plugin not found", nil)`    `return`  `}``   `  `requestedFile := filepath.Clean(web.Params(c.Req)["*"])`  `pluginFilePath := filepath.Join(plugin.PluginDir, requestedFile)``   `  `if !plugin.IncludedInSignature(requestedFile) {`    `hs.log.Warn("Access to requested plugin file will be forbidden in upcoming Grafana versions as the file "+`      `"is not included in the plugin signature", "file", requestedFile)`  `}``   `  `// It's safe to ignore gosec warning G304 since we already clean the requested file path and subsequently`  `// use this with a prefix of the plugin's directory, which is set during plugin loading`  `// nolint:gosec`  `f, err := os.Open(pluginFilePath)`  `if err != nil {`    `if os.IsNotExist(err) {`      `c.JsonApiErr(404, "Plugin file not found", err)`      `return`    `}`    `c.JsonApiErr(500, "Could not open plugin file", err)`    `return`  `}`  `defer func() {`    `if err := f.Close(); err != nil {`      `hs.log.Error("Failed to close file", "err", err)`    `}`  `}()``   `  `fi, err := f.Stat()`  `if err != nil {`    `c.JsonApiErr(500, "Plugin file exists but could not open", err)`    `return`  `}``   `  `if hs.Cfg.Env == setting.Dev {`    `c.Resp.Header().Set("Cache-Control", "max-age=0, must-revalidate, no-cache")`  `} else {`    `c.Resp.Header().Set("Cache-Control", "public, max-age=3600")`  `}``   `  `http.ServeContent(c.Resp, c.Req, pluginFilePath, fi.ModTime(), f)``}`

看到这里,我们再回看一下这个结构体的第三行注释 /public/plugins/:pluginId/*

和我们的payloads (https://xxxxxx.xx/public/plugins/grafana-clock-panel/../../../../../../etc/passwd )

代码中的pluginld,就代表我们 payloads 里的插件名grafana-clock-panel。 那么再构造 payload 就很显然了,我们找到一个默认存在或者大概率存在的 plugin 就可以触发此结构体,从而完成未授权读取任意文件。

Grafana默认存在的插件:

`alertmanager``cloud-monitoring``cloudwatch``dashboard``elasticsearch``grafana``grafana-azure-monitor-datasource``graphite``influxdb``jaeger``loki``mixed``mssql``mysql``opentsdb``postgres``prometheus``tempo``testdata``zipkin`

0x03 修复建议

更新至最新版本

END

目前普遍的利用方式是将插件名作为字典来对目标进行爆破,比较有价值的是读取.db文件。

相关推荐
关注或联系我们
添加百川云公众号,移动管理云安全产品
咨询热线:
4000-327-707
百川公众号
百川公众号
百川云客服
百川云客服

Copyright ©2024 北京长亭科技有限公司
icon
京ICP备 2024055124号-2