fix(fsmanage): improve path validation (#2437)

* fix(fsmanage): improve path validation in FsMove, FsCopy, and FsRemove functions
This commit is contained in:
j2rong4cn
2026-05-06 11:58:00 +08:00
committed by GitHub
parent d3a6b06566
commit c7c0cfaeb7

View File

@@ -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)
@@ -234,18 +237,21 @@ func FsCopy(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 {
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
}