mirror of
https://github.com/photoprism/photoprism.git
synced 2026-07-17 16:49:04 +00:00
Security: Extend denylist coverage for names, paths and extensions #5459
Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
parent
31515f9524
commit
038d38d552
3 changed files with 57 additions and 4 deletions
|
|
@ -159,13 +159,24 @@ func TestStaticRoutesWebOverlay(t *testing.T) {
|
|||
conf := config.NewMinimalTestConfig(t.TempDir())
|
||||
webDir := conf.WebStoragePath()
|
||||
require.NoError(t, os.MkdirAll(filepath.Join(webDir, "node", "secrets"), fs.ModeDir))
|
||||
require.NoError(t, os.MkdirAll(filepath.Join(webDir, "config", "portal"), fs.ModeDir))
|
||||
require.NoError(t, os.MkdirAll(filepath.Join(webDir, "config", "certificates"), fs.ModeDir))
|
||||
require.NoError(t, os.MkdirAll(filepath.Join(webDir, "tls"), fs.ModeDir))
|
||||
require.NoError(t, os.MkdirAll(filepath.Join(webDir, "db"), fs.ModeDir))
|
||||
require.NoError(t, os.MkdirAll(filepath.Join(webDir, "docs"), fs.ModeDir))
|
||||
require.NoError(t, os.WriteFile(filepath.Join(webDir, "options.yml"), []byte("blocked"), fs.ModeFile))
|
||||
require.NoError(t, os.WriteFile(filepath.Join(webDir, "Options.YML"), []byte("blocked"), fs.ModeFile))
|
||||
require.NoError(t, os.WriteFile(filepath.Join(webDir, "config.yaml"), []byte("blocked"), fs.ModeFile))
|
||||
require.NoError(t, os.WriteFile(filepath.Join(webDir, "id_rsa"), []byte("blocked"), fs.ModeFile))
|
||||
require.NoError(t, os.WriteFile(filepath.Join(webDir, "auth.json"), []byte("blocked"), fs.ModeFile))
|
||||
require.NoError(t, os.WriteFile(filepath.Join(webDir, "join_token"), []byte("blocked"), fs.ModeFile))
|
||||
require.NoError(t, os.WriteFile(filepath.Join(webDir, "tls", "server.pem"), []byte("blocked"), fs.ModeFile))
|
||||
require.NoError(t, os.WriteFile(filepath.Join(webDir, "db", "dump.sql"), []byte("blocked"), fs.ModeFile))
|
||||
require.NoError(t, os.WriteFile(filepath.Join(webDir, "docs", "public.toml"), []byte("blocked"), fs.ModeFile))
|
||||
require.NoError(t, os.WriteFile(filepath.Join(webDir, "docs", "client_secret"), []byte("blocked"), fs.ModeFile))
|
||||
require.NoError(t, os.WriteFile(filepath.Join(webDir, "node", "secrets", "token.txt"), []byte("blocked"), fs.ModeFile))
|
||||
require.NoError(t, os.WriteFile(filepath.Join(webDir, "config", "portal", "options.yml"), []byte("blocked"), fs.ModeFile))
|
||||
require.NoError(t, os.WriteFile(filepath.Join(webDir, "config", "certificates", "fullchain.pem"), []byte("blocked"), fs.ModeFile))
|
||||
require.NoError(t, os.WriteFile(filepath.Join(webDir, "docs", "public.txt"), []byte("ok"), fs.ModeFile))
|
||||
|
||||
r := gin.New()
|
||||
|
|
@ -177,8 +188,15 @@ func TestStaticRoutesWebOverlay(t *testing.T) {
|
|||
"/Options.YML",
|
||||
"/config.yaml",
|
||||
"/id_rsa",
|
||||
"/auth.json",
|
||||
"/join_token",
|
||||
"/tls/server.pem",
|
||||
"/db/dump.sql",
|
||||
"/docs/public.toml",
|
||||
"/docs/client_secret",
|
||||
"/node/secrets/token.txt",
|
||||
"/config/portal/options.yml",
|
||||
"/config/certificates/fullchain.pem",
|
||||
}
|
||||
|
||||
for _, filePath := range blocked {
|
||||
|
|
|
|||
|
|
@ -11,21 +11,41 @@ import (
|
|||
// overlayBlockedFileNames lists sensitive file base names that must never be
|
||||
// served by the web overlay, even when present under /storage/web.
|
||||
var overlayBlockedFileNames = map[string]struct{}{
|
||||
"client_secret": {},
|
||||
"config.yaml": {},
|
||||
"config.yml": {},
|
||||
"id_ed25519": {},
|
||||
"id_ed25519.pub": {},
|
||||
"id_rsa": {},
|
||||
"id_rsa.pub": {},
|
||||
"config.toml": {},
|
||||
"config.yaml": {},
|
||||
"config.yml": {},
|
||||
"defaults.yaml": {},
|
||||
"defaults.yml": {},
|
||||
"options.yaml": {},
|
||||
"options.yml": {},
|
||||
"hub.yml": {},
|
||||
"hub.yaml": {},
|
||||
"auth.json": {},
|
||||
"join_token": {},
|
||||
"client_secret": {},
|
||||
}
|
||||
|
||||
// overlayBlockedFileExt lists sensitive file extensions that must never be
|
||||
// served by the web overlay, even when present under /storage/web.
|
||||
var overlayBlockedFileExt = map[string]struct{}{
|
||||
".key": {},
|
||||
".rsa": {},
|
||||
".jwk": {},
|
||||
".pem": {},
|
||||
".sql": {},
|
||||
".toml": {},
|
||||
}
|
||||
|
||||
// overlayBlockedPathPrefixes lists sensitive slash paths rooted under the
|
||||
// overlay tree that must always be denied (path-prefix match).
|
||||
var overlayBlockedPathPrefixes = []string{
|
||||
"node/secrets",
|
||||
"config/portal",
|
||||
"config/certificates",
|
||||
}
|
||||
|
||||
// OverlayHasAmbiguousPath returns true when a request path is unsafe due to
|
||||
|
|
@ -109,11 +129,19 @@ func OverlayPathBlocked(webPath string) bool {
|
|||
}
|
||||
|
||||
lowerPath := strings.ToLower(relPath)
|
||||
lowerBase := path.Base(lowerPath)
|
||||
|
||||
if _, blocked := overlayBlockedFileNames[path.Base(lowerPath)]; blocked {
|
||||
// Block requests for sensitive file names like "auth.json".
|
||||
if _, blocked := overlayBlockedFileNames[lowerBase]; blocked {
|
||||
return true
|
||||
}
|
||||
|
||||
// Block file extensions commonly used for private keys or backups.
|
||||
if _, blocked := overlayBlockedFileExt[path.Ext(lowerBase)]; blocked {
|
||||
return true
|
||||
}
|
||||
|
||||
// Additionally check for sensitive path prefixes that should not be served.
|
||||
for _, prefix := range overlayBlockedPathPrefixes {
|
||||
if lowerPath == prefix || strings.HasPrefix(lowerPath, prefix+"/") {
|
||||
return true
|
||||
|
|
|
|||
|
|
@ -75,7 +75,14 @@ func TestOverlayPathBlocked(t *testing.T) {
|
|||
{name: "SpecialDirBlocked", webPath: "__MACOSX/test.txt", blocked: true},
|
||||
{name: "BlockedByName", webPath: "options.yml", blocked: true},
|
||||
{name: "BlockedByNameCaseInsensitive", webPath: "Options.YML", blocked: true},
|
||||
{name: "BlockedByNameAuthJson", webPath: "auth.json", blocked: true},
|
||||
{name: "BlockedByNameJoinToken", webPath: "join_token", blocked: true},
|
||||
{name: "BlockedByExtensionPem", webPath: "tls/server.pem", blocked: true},
|
||||
{name: "BlockedByExtensionToml", webPath: "docs/public.toml", blocked: true},
|
||||
{name: "BlockedByExtensionSQL", webPath: "backup/database.sql", blocked: true},
|
||||
{name: "BlockedByPrefix", webPath: "node/secrets/token.txt", blocked: true},
|
||||
{name: "BlockedByPrefixConfigPortal", webPath: "config/portal/options.yml", blocked: true},
|
||||
{name: "BlockedByPrefixCertificates", webPath: "config/certificates/fullchain.pem", blocked: true},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue