Skip to content

Commit

Permalink
fix: check if the req path is relative path (close #2531)
Browse files Browse the repository at this point in the history
  • Loading branch information
xhofe committed Nov 30, 2022
1 parent f9788ea commit b5bf5f4
Show file tree
Hide file tree
Showing 8 changed files with 172 additions and 68 deletions.
5 changes: 5 additions & 0 deletions internal/model/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package model

import (
"github.com/alist-org/alist/v3/internal/errs"
"github.com/alist-org/alist/v3/pkg/utils"
"github.com/pkg/errors"
)

Expand Down Expand Up @@ -89,3 +90,7 @@ func (u User) CanWebdavRead() bool {
func (u User) CanWebdavManage() bool {
return u.IsAdmin() || (u.Permission>>9)&1 == 1
}

func (u User) JoinPath(reqPath string) (string, error) {
return utils.JoinBasePath(u.BasePath, reqPath)
}
9 changes: 9 additions & 0 deletions pkg/utils/path.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import (
"path/filepath"
"runtime"
"strings"

"github.com/alist-org/alist/v3/internal/errs"
)

// StandardizePath convert path like '/' '/root' '/a/b'
Expand Down Expand Up @@ -60,3 +62,10 @@ func EncodePath(path string, all ...bool) string {
}
return strings.Join(seg, "/")
}

func JoinBasePath(basePath, reqPath string) (string, error) {
if strings.HasSuffix(reqPath, "..") || strings.Contains(reqPath, "../") {
return "", errs.RelativePath
}
return stdpath.Join(basePath, reqPath), nil
}
10 changes: 6 additions & 4 deletions server/handles/aria2.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package handles

import (
stdpath "path"

"github.com/alist-org/alist/v3/internal/aria2"
"github.com/alist-org/alist/v3/internal/conf"
"github.com/alist-org/alist/v3/internal/db"
Expand Down Expand Up @@ -58,9 +56,13 @@ func AddAria2(c *gin.Context) {
common.ErrorResp(c, err, 400)
return
}
req.Path = stdpath.Join(user.BasePath, req.Path)
reqPath, err := user.JoinPath(req.Path)
if err != nil {
common.ErrorResp(c, err, 403)
return
}
for _, url := range req.Urls {
err := aria2.AddURI(c, url, req.Path)
err := aria2.AddURI(c, url, reqPath)
if err != nil {
common.ErrorResp(c, err, 500)
return
Expand Down
72 changes: 51 additions & 21 deletions server/handles/fsmanage.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,25 +26,29 @@ func FsMkdir(c *gin.Context) {
return
}
user := c.MustGet("user").(*model.User)
req.Path = stdpath.Join(user.BasePath, req.Path)
reqPath, err := user.JoinPath(req.Path)
if err != nil {
common.ErrorResp(c, err, 403)
return
}
if !user.CanWrite() {
meta, err := db.GetNearestMeta(stdpath.Dir(req.Path))
meta, err := db.GetNearestMeta(stdpath.Dir(reqPath))
if err != nil {
if !errors.Is(errors.Cause(err), errs.MetaNotFound) {
common.ErrorResp(c, err, 500, true)
return
}
}
if !common.CanWrite(meta, req.Path) {
if !common.CanWrite(meta, reqPath) {
common.ErrorResp(c, errs.PermissionDenied, 403)
return
}
}
if err := fs.MakeDir(c, req.Path); err != nil {
if err := fs.MakeDir(c, reqPath); err != nil {
common.ErrorResp(c, err, 500)
return
}
fs.ClearCache(stdpath.Dir(req.Path))
fs.ClearCache(stdpath.Dir(reqPath))
common.SuccessResp(c)
}

Expand All @@ -69,17 +73,25 @@ func FsMove(c *gin.Context) {
common.ErrorResp(c, errs.PermissionDenied, 403)
return
}
req.SrcDir = stdpath.Join(user.BasePath, req.SrcDir)
req.DstDir = stdpath.Join(user.BasePath, req.DstDir)
srcDir, err := user.JoinPath(req.SrcDir)
if err != nil {
common.ErrorResp(c, err, 403)
return
}
dstDir, err := user.JoinPath(req.DstDir)
if err != nil {
common.ErrorResp(c, err, 403)
return
}
for _, name := range req.Names {
err := fs.Move(c, stdpath.Join(req.SrcDir, name), req.DstDir)
err := fs.Move(c, stdpath.Join(srcDir, name), dstDir)
if err != nil {
common.ErrorResp(c, err, 500)
return
}
}
fs.ClearCache(req.SrcDir)
fs.ClearCache(req.DstDir)
fs.ClearCache(srcDir)
fs.ClearCache(dstDir)
common.SuccessResp(c)
}

Expand All @@ -98,11 +110,19 @@ func FsCopy(c *gin.Context) {
common.ErrorResp(c, errs.PermissionDenied, 403)
return
}
req.SrcDir = stdpath.Join(user.BasePath, req.SrcDir)
req.DstDir = stdpath.Join(user.BasePath, req.DstDir)
srcDir, err := user.JoinPath(req.SrcDir)
if err != nil {
common.ErrorResp(c, err, 403)
return
}
dstDir, err := user.JoinPath(req.DstDir)
if err != nil {
common.ErrorResp(c, err, 403)
return
}
var addedTask []string
for _, name := range req.Names {
ok, err := fs.Copy(c, stdpath.Join(req.SrcDir, name), req.DstDir)
ok, err := fs.Copy(c, stdpath.Join(srcDir, name), dstDir)
if ok {
addedTask = append(addedTask, name)
}
Expand All @@ -112,7 +132,7 @@ func FsCopy(c *gin.Context) {
}
}
if len(req.Names) != len(addedTask) {
fs.ClearCache(req.DstDir)
fs.ClearCache(dstDir)
}
if len(addedTask) > 0 {
common.SuccessResp(c, fmt.Sprintf("Added %d tasks", len(addedTask)))
Expand All @@ -137,12 +157,16 @@ func FsRename(c *gin.Context) {
common.ErrorResp(c, errs.PermissionDenied, 403)
return
}
req.Path = stdpath.Join(user.BasePath, req.Path)
if err := fs.Rename(c, req.Path, req.Name); err != nil {
reqPath, err := user.JoinPath(req.Path)
if err != nil {
common.ErrorResp(c, err, 403)
return
}
if err := fs.Rename(c, reqPath, req.Name); err != nil {
common.ErrorResp(c, err, 500)
return
}
fs.ClearCache(stdpath.Dir(req.Path))
fs.ClearCache(stdpath.Dir(reqPath))
common.SuccessResp(c)
}

Expand All @@ -166,9 +190,13 @@ func FsRemove(c *gin.Context) {
common.ErrorResp(c, errs.PermissionDenied, 403)
return
}
req.Dir = stdpath.Join(user.BasePath, req.Dir)
reqDir, err := user.JoinPath(req.Dir)
if err != nil {
common.ErrorResp(c, err, 403)
return
}
for _, name := range req.Names {
err := fs.Remove(c, stdpath.Join(req.Dir, name))
err := fs.Remove(c, stdpath.Join(reqDir, name))
if err != nil {
common.ErrorResp(c, err, 500)
return
Expand All @@ -185,8 +213,10 @@ func Link(c *gin.Context) {
common.ErrorResp(c, err, 400)
return
}
user := c.MustGet("user").(*model.User)
rawPath := stdpath.Join(user.BasePath, req.Path)
//user := c.MustGet("user").(*model.User)
//rawPath := stdpath.Join(user.BasePath, req.Path)
// why need not join base_path? because it's always the full path
rawPath := req.Path
storage, err := fs.GetStorage(rawPath)
if err != nil {
common.ErrorResp(c, err, 500)
Expand Down
73 changes: 46 additions & 27 deletions server/handles/fsread.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,39 +56,43 @@ func FsList(c *gin.Context) {
}
req.Validate()
user := c.MustGet("user").(*model.User)
req.Path = stdpath.Join(user.BasePath, req.Path)
meta, err := db.GetNearestMeta(req.Path)
reqPath, err := user.JoinPath(req.Path)
if err != nil {
common.ErrorResp(c, err, 403)
return
}
meta, err := db.GetNearestMeta(reqPath)
if err != nil {
if !errors.Is(errors.Cause(err), errs.MetaNotFound) {
common.ErrorResp(c, err, 500, true)
return
}
}
c.Set("meta", meta)
if !common.CanAccess(user, meta, req.Path, req.Password) {
if !common.CanAccess(user, meta, reqPath, req.Password) {
common.ErrorStrResp(c, "password is incorrect", 403)
return
}
if !user.CanWrite() && !common.CanWrite(meta, req.Path) && req.Refresh {
if !user.CanWrite() && !common.CanWrite(meta, reqPath) && req.Refresh {
common.ErrorStrResp(c, "Refresh without permission", 403)
return
}
objs, err := fs.List(c, req.Path, req.Refresh)
objs, err := fs.List(c, reqPath, req.Refresh)
if err != nil {
common.ErrorResp(c, err, 500)
return
}
total, objs := pagination(objs, &req.PageReq)
provider := "unknown"
storage, err := fs.GetStorage(req.Path)
storage, err := fs.GetStorage(reqPath)
if err == nil {
provider = storage.GetStorage().Driver
}
common.SuccessResp(c, FsListResp{
Content: toObjsResp(objs, req.Path, isEncrypt(meta, req.Path)),
Content: toObjsResp(objs, reqPath, isEncrypt(meta, reqPath)),
Total: int64(total),
Readme: getReadme(meta, req.Path),
Write: user.CanWrite() || common.CanWrite(meta, req.Path),
Readme: getReadme(meta, reqPath),
Write: user.CanWrite() || common.CanWrite(meta, reqPath),
Provider: provider,
})
}
Expand All @@ -100,27 +104,33 @@ func FsDirs(c *gin.Context) {
return
}
user := c.MustGet("user").(*model.User)
var reqPath string
if req.ForceRoot {
if !user.IsAdmin() {
common.ErrorStrResp(c, "Permission denied", 403)
return
}
} else {
req.Path = stdpath.Join(user.BasePath, req.Path)
tmp, err := user.JoinPath(req.Path)
if err != nil {
common.ErrorResp(c, err, 403)
return
}
reqPath = tmp
}
meta, err := db.GetNearestMeta(req.Path)
meta, err := db.GetNearestMeta(reqPath)
if err != nil {
if !errors.Is(errors.Cause(err), errs.MetaNotFound) {
common.ErrorResp(c, err, 500, true)
return
}
}
c.Set("meta", meta)
if !common.CanAccess(user, meta, req.Path, req.Password) {
if !common.CanAccess(user, meta, reqPath, req.Password) {
common.ErrorStrResp(c, "password is incorrect", 403)
return
}
objs, err := fs.List(c, req.Path)
objs, err := fs.List(c, reqPath)
if err != nil {
common.ErrorResp(c, err, 500)
return
Expand Down Expand Up @@ -218,27 +228,31 @@ func FsGet(c *gin.Context) {
return
}
user := c.MustGet("user").(*model.User)
req.Path = stdpath.Join(user.BasePath, req.Path)
meta, err := db.GetNearestMeta(req.Path)
reqPath, err := user.JoinPath(req.Path)
if err != nil {
common.ErrorResp(c, err, 403)
return
}
meta, err := db.GetNearestMeta(reqPath)
if err != nil {
if !errors.Is(errors.Cause(err), errs.MetaNotFound) {
common.ErrorResp(c, err, 500)
return
}
}
c.Set("meta", meta)
if !common.CanAccess(user, meta, req.Path, req.Password) {
if !common.CanAccess(user, meta, reqPath, req.Password) {
common.ErrorStrResp(c, "password is incorrect", 403)
return
}
obj, err := fs.Get(c, req.Path)
obj, err := fs.Get(c, reqPath)
if err != nil {
common.ErrorResp(c, err, 500)
return
}
var rawURL string

storage, err := fs.GetStorage(req.Path)
storage, err := fs.GetStorage(reqPath)
provider := "unknown"
if err == nil {
provider = storage.Config().Name
Expand All @@ -252,21 +266,21 @@ func FsGet(c *gin.Context) {
if storage.GetStorage().DownProxyUrl != "" {
rawURL = fmt.Sprintf("%s%s?sign=%s",
strings.Split(storage.GetStorage().DownProxyUrl, "\n")[0],
utils.EncodePath(req.Path, true),
sign.Sign(req.Path))
utils.EncodePath(reqPath, true),
sign.Sign(reqPath))
} else {
rawURL = fmt.Sprintf("%s/p%s?sign=%s",
common.GetApiUrl(c.Request),
utils.EncodePath(req.Path, true),
sign.Sign(req.Path))
utils.EncodePath(reqPath, true),
sign.Sign(reqPath))
}
} else {
// file have raw url
if u, ok := obj.(model.URL); ok {
rawURL = u.URL()
} else {
// if storage is not proxy, use raw url by fs.Link
link, _, err := fs.Link(c, req.Path, model.LinkArgs{IP: c.ClientIP(), Header: c.Request.Header})
link, _, err := fs.Link(c, reqPath, model.LinkArgs{IP: c.ClientIP(), Header: c.Request.Header})
if err != nil {
common.ErrorResp(c, err, 500)
return
Expand All @@ -276,7 +290,7 @@ func FsGet(c *gin.Context) {
}
}
var related []model.Obj
parentPath := stdpath.Dir(req.Path)
parentPath := stdpath.Dir(reqPath)
sameLevelFiles, err := fs.List(c, parentPath)
if err == nil {
related = filterRelated(sameLevelFiles, obj)
Expand All @@ -288,11 +302,11 @@ func FsGet(c *gin.Context) {
Size: obj.GetSize(),
IsDir: obj.IsDir(),
Modified: obj.ModTime(),
Sign: common.Sign(obj, parentPath, isEncrypt(meta, req.Path)),
Sign: common.Sign(obj, parentPath, isEncrypt(meta, reqPath)),
Type: utils.GetFileType(obj.GetName()),
},
RawURL: rawURL,
Readme: getReadme(meta, req.Path),
Readme: getReadme(meta, reqPath),
Provider: provider,
Related: toObjsResp(related, parentPath, isEncrypt(parentMeta, parentPath)),
})
Expand Down Expand Up @@ -324,7 +338,12 @@ func FsOther(c *gin.Context) {
return
}
user := c.MustGet("user").(*model.User)
req.Path = stdpath.Join(user.BasePath, req.Path)
var err error
req.Path, err = user.JoinPath(req.Path)
if err != nil {
common.ErrorResp(c, err, 403)
return
}
meta, err := db.GetNearestMeta(req.Path)
if err != nil {
if !errors.Is(errors.Cause(err), errs.MetaNotFound) {
Expand Down
Loading

0 comments on commit b5bf5f4

Please sign in to comment.