The subtitlesHandler endpoint (GET /api/media/subtitles) accepts two user-controlled query parameters: path and name, both of which are used in filesystem operations without sanitization, creating two independent path traversal vectors.
The primary vector is the path parameter: it is passed directly to idx.GetRealPath() without calling SanitizeUserPath(), allowing an attacker to escape the storage root and set parentDir to any directory on the host. No existing anchor file is required.
The secondary vector is the name parameter: it is joined with parentDir via filepath.Join(parentDir, name) without stripping directory components, allowing traversal relative to any resolved parentDir.
Any authenticated user (regardless of role or permissions) can exploit either vector to read any text file readable by the server process, including /etc/passwd, SSH keys, database credentials, and JWT signing keys.
1. path parameter lacks SanitizeUserPath() — primary vector (http/media.go:54)
userscope, err := d.user.GetScopeForSourceName(source)
// ...
realPath, _, err := idx.GetRealPath(userscope, path) // path is raw user input, no sanitization
// ...
parentDir := filepath.Dir(realPath) // line 59: attacker controls this directory
SanitizeUserPath() explicitly rejects .. segments:
func SanitizeUserPath(userPath string) (string, error) {
// ...
for _, segment := range segments {
if segment == ".." {
return "", fmt.Errorf("invalid path: path traversal detected")
}
}
// ...
}
Every other handler in the codebase calls SanitizeUserPath() before GetRealPath(). This handler skips it, so path=../../etc/passwd resolves parentDir to /etc, with no anchor file required.
2. name parameter used directly in filepath.Join — secondary vector (http/media.go:63)
name := r.URL.Query().Get("name") // line 37 — raw user input
// ......
0.0.0-20260608182036-f3f4bbe80cb5Exploitability
AV:NAC:LPR:LUI:NScope
S:CImpact
C:HI:NA:N7.7/CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:NInput Validation