mirror of
https://github.com/drakkan/sftpgo.git
synced 2025-12-07 06:40:54 +03:00
rsync: enforce a supported format and limit the allowed options
Many rsync options are unsafe to use in restricted environments and may pose security risks. Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
This commit is contained in:
@@ -427,6 +427,10 @@ func (c *sshCommand) getSystemCommand() (systemCommand, error) {
|
||||
return command, errUnsupportedConfig
|
||||
}
|
||||
if c.command == "rsync" {
|
||||
if !canAcceptRsyncArgs(args) {
|
||||
c.connection.Log(logger.LevelWarn, "invalid rsync command, args: %+v", args)
|
||||
return command, errors.New("invalid or unsupported rsync command")
|
||||
}
|
||||
// we cannot avoid that rsync creates symlinks so if the user has the permission
|
||||
// to create symlinks we add the option --safe-links to the received rsync command if
|
||||
// it is not already set. This should prevent to create symlinks that point outside
|
||||
@@ -435,11 +439,11 @@ func (c *sshCommand) getSystemCommand() (systemCommand, error) {
|
||||
// already set. This should make symlinks unusable (but manually recoverable)
|
||||
if c.connection.User.HasPerm(dataprovider.PermCreateSymlinks, c.getDestPath()) {
|
||||
if !slices.Contains(args, "--safe-links") {
|
||||
args = append([]string{"--safe-links"}, args...)
|
||||
args = slices.Insert(args, len(args)-2, "--safe-links")
|
||||
}
|
||||
} else {
|
||||
if !slices.Contains(args, "--munge-links") {
|
||||
args = append([]string{"--munge-links"}, args...)
|
||||
args = slices.Insert(args, len(args)-2, "--munge-links")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -456,6 +460,85 @@ func (c *sshCommand) getSystemCommand() (systemCommand, error) {
|
||||
return command, nil
|
||||
}
|
||||
|
||||
var (
|
||||
acceptedRsyncOptions = []string{
|
||||
"--existing",
|
||||
"--ignore-existing",
|
||||
"--remove-source-files",
|
||||
"--delete",
|
||||
"--delete-before",
|
||||
"--delete-during",
|
||||
"--delete-delay",
|
||||
"--delete-after",
|
||||
"--delete-excluded",
|
||||
"--ignore-errors",
|
||||
"--force",
|
||||
"--partial",
|
||||
"--delay-updates",
|
||||
"--size-only",
|
||||
"--blocking-io",
|
||||
"--stats",
|
||||
"--progress",
|
||||
"--list-only",
|
||||
"--dry-run",
|
||||
}
|
||||
)
|
||||
|
||||
func canAcceptRsyncArgs(args []string) bool {
|
||||
// We support the following formats:
|
||||
//
|
||||
// rsync --server -vlogDtpre.iLsfxCIvu --supported-options . ARG # push
|
||||
// rsync --server --sender -vlogDtpre.iLsfxCIvu --supported-options . ARG # pull
|
||||
//
|
||||
// Then some options with a single dash and containing "e."" followed by
|
||||
// supported options, listed in acceptedRsyncOptions, with double dash then
|
||||
// dot and a finally single argument specifying the path to operate on.
|
||||
idx := 0
|
||||
if len(args) < 4 {
|
||||
return false
|
||||
}
|
||||
// The first argument must be --server.
|
||||
if args[idx] != "--server" {
|
||||
return false
|
||||
}
|
||||
idx++
|
||||
// The second argument must be --sender or an argument starting with a
|
||||
// single dash and containing "e."
|
||||
if args[idx] == "--sender" {
|
||||
idx++
|
||||
}
|
||||
// Check that this argument starts with a dash and contains e. but does start or end with e.
|
||||
if !strings.HasPrefix(args[idx], "-") ||
|
||||
strings.HasPrefix(args[idx], "--") || strings.HasPrefix(args[idx], "-e") ||
|
||||
!strings.Contains(args[idx], "e.") || strings.HasSuffix(args[idx], "e.") {
|
||||
return false
|
||||
}
|
||||
idx++
|
||||
// We now expect optional supported options like --delete or a dot followed
|
||||
// by the path to operate on. We don't support multiple paths in sender
|
||||
// mode.
|
||||
if len(args) < idx+2 {
|
||||
return false
|
||||
}
|
||||
// A dot is required we'll check the expected position later.
|
||||
if !slices.Contains(args, ".") {
|
||||
return false
|
||||
}
|
||||
for _, arg := range args[idx:] {
|
||||
if slices.Contains(acceptedRsyncOptions, arg) {
|
||||
idx++
|
||||
} else {
|
||||
if arg == "." {
|
||||
idx++
|
||||
break
|
||||
}
|
||||
// Unsupported argument.
|
||||
return false
|
||||
}
|
||||
}
|
||||
return len(args) == idx+1
|
||||
}
|
||||
|
||||
// for the supported commands, the destination path, if any, is the last argument
|
||||
func (c *sshCommand) getDestPath() string {
|
||||
if len(c.args) == 0 {
|
||||
|
||||
Reference in New Issue
Block a user