fix: correctly check if command is allowed when using shell

This commit is contained in:
Henrique Dias 2025-06-26 21:09:16 +02:00
parent f84a6db680
commit 4d830f707f
No known key found for this signature in database
4 changed files with 25 additions and 34 deletions

View File

@ -6,6 +6,7 @@ import (
"log" "log"
"net/http" "net/http"
"os/exec" "os/exec"
"slices"
"strings" "strings"
"time" "time"
@ -60,7 +61,16 @@ var commandsHandler = withUser(func(w http.ResponseWriter, r *http.Request, d *d
} }
} }
command, err := runner.ParseCommand(d.settings, raw) // Fail fast
if !d.server.EnableExec || !d.user.Perm.Execute {
if err := conn.WriteMessage(websocket.TextMessage, cmdNotAllowed); err != nil { //nolint:govet
wsErr(conn, r, http.StatusInternalServerError, err)
}
return 0, nil
}
command, name, err := runner.ParseCommand(d.settings, raw)
if err != nil { if err != nil {
if err := conn.WriteMessage(websocket.TextMessage, []byte(err.Error())); err != nil { //nolint:govet if err := conn.WriteMessage(websocket.TextMessage, []byte(err.Error())); err != nil { //nolint:govet
wsErr(conn, r, http.StatusInternalServerError, err) wsErr(conn, r, http.StatusInternalServerError, err)
@ -68,7 +78,7 @@ var commandsHandler = withUser(func(w http.ResponseWriter, r *http.Request, d *d
return 0, nil return 0, nil
} }
if !d.server.EnableExec || !d.user.CanExecute(command[0]) { if !slices.Contains(d.user.Commands, name) {
if err := conn.WriteMessage(websocket.TextMessage, cmdNotAllowed); err != nil { //nolint:govet if err := conn.WriteMessage(websocket.TextMessage, cmdNotAllowed); err != nil { //nolint:govet
wsErr(conn, r, http.StatusInternalServerError, err) wsErr(conn, r, http.StatusInternalServerError, err)
} }

View File

@ -1,33 +1,24 @@
package runner package runner
import ( import (
"os/exec"
"github.com/filebrowser/filebrowser/v2/settings" "github.com/filebrowser/filebrowser/v2/settings"
) )
// ParseCommand parses the command taking in account if the current // ParseCommand parses the command taking in account if the current
// instance uses a shell to run the commands or just calls the binary // instance uses a shell to run the commands or just calls the binary
// directly. // directly.
func ParseCommand(s *settings.Settings, raw string) ([]string, error) { func ParseCommand(s *settings.Settings, raw string) (command []string, name string, err error) {
var command []string name, args, err := SplitCommandAndArgs(raw)
if err != nil {
if len(s.Shell) == 0 || s.Shell[0] == "" { return
cmd, args, err := SplitCommandAndArgs(raw)
if err != nil {
return nil, err
}
_, err = exec.LookPath(cmd)
if err != nil {
return nil, err
}
command = append(command, cmd)
command = append(command, args...)
} else {
command = append(s.Shell, raw) //nolint:gocritic
} }
return command, nil if len(s.Shell) == 0 || s.Shell[0] == "" {
command = append(command, name)
command = append(command, args...)
} else {
command = append(s.Shell, raw)
}
return command, name, nil
} }

View File

@ -60,7 +60,7 @@ func (r *Runner) exec(raw, evt, path, dst string, user *users.User) error {
raw = strings.TrimSpace(strings.TrimSuffix(raw, "&")) raw = strings.TrimSpace(strings.TrimSuffix(raw, "&"))
} }
command, err := ParseCommand(r.Settings, raw) command, _, err := ParseCommand(r.Settings, raw)
if err != nil { if err != nil {
return err return err
} }

View File

@ -2,7 +2,6 @@ package users
import ( import (
"path/filepath" "path/filepath"
"slices"
"github.com/spf13/afero" "github.com/spf13/afero"
@ -104,12 +103,3 @@ func (u *User) Clean(baseScope string, fields ...string) error {
func (u *User) FullPath(path string) string { func (u *User) FullPath(path string) string {
return afero.FullBaseFsPath(u.Fs.(*afero.BasePathFs), path) return afero.FullBaseFsPath(u.Fs.(*afero.BasePathFs), path)
} }
// CanExecute checks if an user can execute a specific command.
func (u *User) CanExecute(command string) bool {
if !u.Perm.Execute {
return false
}
return slices.Contains(u.Commands, command)
}