From e197589707e8121888004ef9d3ed4d376ea53d67 Mon Sep 17 00:00:00 2001 From: j2rong4cn Date: Sun, 3 May 2026 14:34:37 +0800 Subject: [PATCH 1/2] fix(fsmanage): improve path validation in FsMove, FsCopy, and FsRemove functions --- server/handles/fsmanage.go | 62 +++++++++++++++++++++----------------- 1 file changed, 35 insertions(+), 27 deletions(-) diff --git a/server/handles/fsmanage.go b/server/handles/fsmanage.go index 9ead8d60a..3c258c03a 100644 --- a/server/handles/fsmanage.go +++ b/server/handles/fsmanage.go @@ -17,7 +17,6 @@ import ( "github.com/OpenListTeam/OpenList/v4/server/common" "github.com/gin-gonic/gin" "github.com/pkg/errors" - log "github.com/sirupsen/logrus" ) type MkdirOrLinkReq struct { @@ -110,15 +109,15 @@ func FsMove(c *gin.Context) { common.ErrorResp(c, errs.PermissionDenied, 403) return } - - validPaths := make([]string, 0, len(req.Names)) - for _, name := range req.Names { + if !strings.HasSuffix(srcDir, "/") { + srcDir += "/" + } + for i, name := range req.Names { // ensure req.Names is not a relative path - srcPath := stdpath.Join(req.SrcDir, name) - srcPath, err = user.JoinPath(srcPath) - if err != nil { - common.ErrorResp(c, err, 403) - return + srcPath := stdpath.Join(srcDir, name) + if !strings.HasPrefix(srcPath+"/", srcDir) { + req.Names[i] = "" + continue } if !req.Overwrite { base := stdpath.Base(srcPath) @@ -135,14 +134,17 @@ func FsMove(c *gin.Context) { } } } - validPaths = append(validPaths, srcPath) + req.Names[i] = srcPath } // Create all tasks immediately without any synchronous validation // All validation will be done asynchronously in the background var addedTasks []task.TaskExtensionInfo - for i, p := range validPaths { - t, err := fs.Move(c.Request.Context(), p, dstDir, len(validPaths) > i+1) + for i, p := range req.Names { + if p == "" { + continue + } + t, err := fs.Move(c.Request.Context(), p, dstDir, len(req.Names) > i+1) if t != nil { addedTasks = append(addedTasks, t) } @@ -210,14 +212,15 @@ func FsCopy(c *gin.Context) { return } - validPaths := make([]string, 0, len(req.Names)) - for _, name := range req.Names { + if !strings.HasSuffix(srcDir, "/") { + srcDir += "/" + } + for i, name := range req.Names { // ensure req.Names is not a relative path - srcPath := stdpath.Join(req.SrcDir, name) - srcPath, err = user.JoinPath(srcPath) - if err != nil { - common.ErrorResp(c, err, 403) - return + srcPath := stdpath.Join(srcDir, name) + if !strings.HasPrefix(srcPath+"/", srcDir) { + req.Names[i] = "" + continue } if !req.Overwrite { base := stdpath.Base(srcPath) @@ -226,26 +229,29 @@ func FsCopy(c *gin.Context) { return } if res, _ := fs.Get(c.Request.Context(), stdpath.Join(dstDir, base), &fs.GetArgs{NoLog: true}); res != nil { - if !req.SkipExisting && !req.Merge { + if !req.SkipExisting { common.ErrorStrResp(c, fmt.Sprintf("file [%s] exists", name), 403) return - } else if !req.Merge || !res.IsDir() { + } else { continue } } } - validPaths = append(validPaths, srcPath) + req.Names[i] = srcPath } // Create all tasks immediately without any synchronous validation // All validation will be done asynchronously in the background var addedTasks []task.TaskExtensionInfo - for i, p := range validPaths { + for i, p := range req.Names { + if p == "" { + continue + } var t task.TaskExtensionInfo if req.Merge { - t, err = fs.Merge(c.Request.Context(), p, dstDir, len(validPaths) > i+1) + t, err = fs.Merge(c.Request.Context(), p, dstDir, len(req.Names) > i+1) } else { - t, err = fs.Copy(c.Request.Context(), p, dstDir, len(validPaths) > i+1) + t, err = fs.Copy(c.Request.Context(), p, dstDir, len(req.Names) > i+1) } if t != nil { addedTasks = append(addedTasks, t) @@ -362,10 +368,12 @@ func FsRemove(c *gin.Context) { common.ErrorResp(c, errs.PermissionDenied, 403) return } + if !strings.HasSuffix(reqPath, "/") { + reqPath += "/" + } for i, name := range req.Names { fullPath := stdpath.Join(reqPath, name) - if !strings.HasPrefix(fullPath+"/", reqPath+"/") { - log.Warnf("FsRemove: path traversal attempt skipped: %s (dir: %s)\n", name, req.Dir) + if !strings.HasPrefix(fullPath+"/", reqPath) { req.Names[i] = "" continue } From 4731e90f747cb5be044c3b59c91b4358b17e5434 Mon Sep 17 00:00:00 2001 From: j2rong4cn Date: Sun, 3 May 2026 14:49:00 +0800 Subject: [PATCH 2/2] . --- server/handles/fsmanage.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/handles/fsmanage.go b/server/handles/fsmanage.go index 3c258c03a..94e7c3fec 100644 --- a/server/handles/fsmanage.go +++ b/server/handles/fsmanage.go @@ -229,10 +229,10 @@ func FsCopy(c *gin.Context) { return } if res, _ := fs.Get(c.Request.Context(), stdpath.Join(dstDir, base), &fs.GetArgs{NoLog: true}); res != nil { - if !req.SkipExisting { + if !req.SkipExisting && !req.Merge { common.ErrorStrResp(c, fmt.Sprintf("file [%s] exists", name), 403) return - } else { + } else if !req.Merge || !res.IsDir() { continue } }