漏洞分析:《CVE-2021-43798 Grafana 未授权任意文件读取》
2021-12-8 19:33:7 Author: mp.weixin.qq.com(查看原文) 阅读量:5 收藏

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默认存在的插件:

alertmanagercloud-monitoringcloudwatchdashboardelasticsearchgrafanagrafana-azure-monitor-datasourcegraphiteinfluxdbjaegerlokimixedmssqlmysqlopentsdbpostgresprometheustempotestdatazipkin

0x03 修复建议

更新至最新版本

END

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


文章来源: https://mp.weixin.qq.com/s?__biz=Mzg5MzU4NTgwNQ==&mid=2247484616&idx=1&sn=41a45e00398263dc40c8eecea9d2dc43&chksm=c02dd42af75a5d3c84694735b16bf1fde6eedc8813d2c57898d6e0729a05e1f86db75dc3f7ab&scene=58&subscene=0#rd
如有侵权请联系:admin#unsafe.sh