diff --git a/kernel/api/file.go b/kernel/api/file.go index 61da124e2..307ae38cb 100644 --- a/kernel/api/file.go +++ b/kernel/api/file.go @@ -24,7 +24,9 @@ import ( "net/http" "os" "path/filepath" + "runtime" "strconv" + "strings" "time" "github.com/88250/gulu" @@ -225,8 +227,12 @@ func getFile(c *gin.Context) { } func refuseToAccess(c *gin.Context, fileAbsPath string, ret *gulu.Result) bool { + // 规范化并解析符号链接,防止通过大小写或符号链接绕过 + fileNorm := normalizeAndResolve(fileAbsPath) + // 禁止访问配置文件 conf/conf.json - if filepath.Join(util.ConfDir, "conf.json") == fileAbsPath { + confPath := normalizeAndResolve(filepath.Join(util.ConfDir, "conf.json")) + if fileNorm == confPath { ret.Code = http.StatusForbidden ret.Msg = http.StatusText(http.StatusForbidden) c.JSON(http.StatusAccepted, ret) @@ -234,7 +240,8 @@ func refuseToAccess(c *gin.Context, fileAbsPath string, ret *gulu.Result) bool { } // 禁止访问 data/snippets/conf.json - if filepath.Join(util.DataDir, "snippets", "conf.json") == fileAbsPath { + snippetPath := normalizeAndResolve(filepath.Join(util.DataDir, "snippets", "conf.json")) + if fileNorm == snippetPath { ret.Code = http.StatusForbidden ret.Msg = http.StatusText(http.StatusForbidden) c.JSON(http.StatusAccepted, ret) @@ -242,7 +249,8 @@ func refuseToAccess(c *gin.Context, fileAbsPath string, ret *gulu.Result) bool { } // 禁止访问 data/templates 目录 - if util.IsSubPath(filepath.Join(util.DataDir, "templates"), fileAbsPath) { + templatesBase := normalizeAndResolve(filepath.Join(util.DataDir, "templates")) + if util.IsSubPath(templatesBase, fileNorm) { ret.Code = http.StatusForbidden ret.Msg = http.StatusText(http.StatusForbidden) c.JSON(http.StatusAccepted, ret) @@ -251,6 +259,22 @@ func refuseToAccess(c *gin.Context, fileAbsPath string, ret *gulu.Result) bool { return false } +// normalizeAndResolve 将路径转为绝对、解析符号链接并清理;在需要时转为小写以实现不区分大小写比较 +func normalizeAndResolve(p string) string { + if abs, err := filepath.Abs(p); err == nil { + p = abs + } + if eval, err := filepath.EvalSymlinks(p); err == nil { + p = eval + } + p = filepath.Clean(p) + // 在 Windows 和 macOS 上文件系统通常为不区分大小写,使用小写统一比较 + if runtime.GOOS == "windows" || runtime.GOOS == "darwin" { + p = strings.ToLower(p) + } + return p +} + func readDir(c *gin.Context) { ret := gulu.Ret.NewResult() defer c.JSON(http.StatusOK, ret)