GHSA-95cv-r8x4-vh75 OpenList: Authenticated users can rename files outside their base path via batch rename `src_name` traversal
SummaryThe `/api/fs/batch_rename` handler validates and authorizes only the requested source directory. It rejects path separators in `new_name`, but it does not validate `src_name`. The handler concatenates `src_dir` and attacker-controlled `src_name`, then passes the result to the filesystem rename layer, where the path is normalized.An authenticated user with rename permission can set `src_name` to traversal segments such as `../../ab/secret.txt`. When the user's base path is `/team/a` and `src_dir` is `/writable`, the authorized directory becomes `/team/a/writable`, but the final source path normalizes to `/team/ab/secret.txt`. The file outside the user's base path is then renamed.DetailsThe HTTP API registers filesystem management routes under the authenticated group:`server/router.go:104` registers `_fs(auth.Group("/fs"))`.`server/router.go:198` through `server/router.go:205` expose `/api/fs/batch_rename`.The vulnerable code is in `server/handles/fsbatch.go`:`src_dir` is constrained through `user.JoinPath(req.SrcDir)` (`server/handles/fsbatch.go:170` through `server/handles/fsbatch.go:174`).Write permission is checked only for that constrained directory (`server/handles/fsbatch.go:176` through `server/handles/fsbatch.go:185`).The loop checks `renameObject.NewName` with `checkRelativePath`, but does not check `renameObject.SrcName` (`server/handles/fsbatch.go:186` through `server/handles/fsbatch.go:194`).The handler builds `filePath := fmt.Sprintf("%s/%s", reqPath, renameObject.SrcName)` and passes it to `fs.Rename` (`server/handles/fsbatch.go:195` through `server/handles/fsbatch.go:196`).The single-file rename path shows the intended pattern: `checkRelativePath(req.Name)` rejects separators, empty strings, `.`, and `..` before renaming (`server/handles/fsmanage.go:284` through `server/handles/fsmanage.go:333`). Batch rename applies this protection to the destination name only, not to the source name.Lower layers normalize the source path before operating on it:`utils.FixAndCleanPath` replaces backslashes with slashes, forces an absolute slash prefix, and calls `path.Clean` (`pkg/utils/path.go:18` through `pkg/utils/path.go:24`).`JoinBasePath` rejects traversal in the original `src_dir`, not in the later concatenated `src_name` (`pkg/utils/path.go:80` through `pkg/utils/path.go:87`).False-positive checks performed:The user in the PoC had only normal authenticated user role plus rename permission, not admin role.The handler successfully authorized `/team/a/writable`, then renamed `/team/ab/secret.txt`, proving that the later source path escaped the authorized directory.`new_name` validation remained in effect; the exploit uses traversal only in `src_name`.The test checked the original sibling file disappeared and the renamed sibling file contained the same contents.PoCSafe local reproduction used a temporary in-memory sqlite database and temporary Local storage root. No external services were contacted by the PoC route; Go dependency/toolchain downloads may occur if the environment lacks cached modules.Add this temporary test under `server/handles/security_poc_test.go` in a clean checkout of the tested commit. If also testing the share finding, the helper functions can be shared between the two tests.Run:Observed vulnerable output in this environment:Combined final confirmation command used during the audit:Observed combined output:Negative/control cases checked:`src_dir` traversal is rejected by `user.JoinPath` because `JoinBasePath` detects relative traversal in the original request path.`new_name` traversal is rejected by `checkRelativePath` because it contains `/`, `\\`, `.`, or `..` patterns.The exploit succeeds because `src_name` is not passed through the same relative filename check before concatenation.Cleanup:ImpactA restricted authenticated user can rename files outside the authorized source directory and outside their configured base path. In a multi-user deployment, a user confined to `/team/a` can rename a guessed sibling file such as `/team/ab/secret.txt` to `/team/ab/renamed.txt` by submitting traversal segments in `src_name`.This is an integrity violation against other users' files. It can also cause limited availability impact by moving files away from expected names, and it may reveal whether guessed out-of-base files exist based on success or error responses.Suggested remediationValidate `renameObject.SrcName` with the same relative filename constraints already applied to `renameObject.NewName`, or derive source objects only from a trusted directory listing of `reqPath`.A minimal fix is to call `checkRelativePath(renameObject.SrcName)` before constructing `filePath`. Add regression tests covering:`src_name: "file.txt"` succeeds;`src_name: "../secret.txt"` is denied;`src_name: "../../ab/secret.txt"` is denied when base path is `/team/a` and `src_dir` is `/writable`;`new_name` traversal remains denied.CreditsThai Son Dinh from VinSOC Labs (R&D)
CVSS v3.1 score: 7.6