Using a authorized_keys forced command with ssh and a wrapper-script like this:

#!/usr/bin/env bash

case "$SSH_ORIGINAL_COMMAND" in
  /var/lib/authorized-scripts/*)
    $SSH_ORIGINAL_COMMAND
    ;;
  *)
    exit 1
    ;;
esac

Can a malicious user somehow chain another command after /usr/bin/authorized-scripts/ and therefore overcome this security measure or is this secure?

This suggests that I could simply use ssh user@host '/var/lib/authorized-scripts/script.sh && cat /etc/passwd', but this did not work in my tests.

Can I somehow improve the security of this script while still allowing multiple commands with user-supplied arguments for a single ssh key?

I am aware that the allowed commands themselves should ofcourse not allow any kind of subshells (so, find for example is a nogo for example due to its exec functionality).

up vote 8 down vote accepted

Your forced command is unsafe. Consider:

ssh -l user your-server /var/lib/authorized-scripts/../../../bin/rm -rf /

The command name starts with /var/lib/authorized-scripts/ so it's safe, right? Nope!

You'll want something more along these lines:

#!/bin/sh

set -- $SSH_ORIGINAL_COMMAND
case "$1" in
  /var/lib/authorized-scripts/*)
    ;;
  *)
    exit 1
esac

command="${1#/var/lib/authorized-scripts/}"
shift
case "$command" in
  */*)
    # Simplest is to reject anything with a slash...
    exit 1
  .*)
    # ...and anything starting with dot.
    # If you need to whitelist subdirectories of /var/lib/authorized-scripts
    # then you need much more sophisticated pathname parsing and care.
    exit 1
  *)
    ;;
esac

exec "/var/lib/authorized-scripts/$command" "$@"

As for shell syntax like && and ; embedded in the command, that should be safe. As you've already discovered, such things are not interpreted by the shell in your solution but passed verbatim to the command.

However, wildcards in the arguments of the command will be interpreted. If you want to avoid that, put set -f before the line with set -- $SSH_ORIGINAL_COMMAND.

Make sure that the authorized scripts interpret their arguments in a safe way. The arguments could be options that allow shell injection (like find -exec does), file names, including symbolic links (which point to a file in a different directory), etc.

Finally: #!/usr/bin/env bash is not necessary. Your script uses no bashisms, so why not let be more portable by running under /bin/sh?

  • Thanks for discovering this! What about using shopt -s extglob and changing the case pattern to /var/lib/scripts/+([^/]))? There are 2 other commands that are allowed, so using this approach would shorten the script alot in comparison to your example. Are there any positive security effects in using set -- $SSH_ORIGINAL_COMMAND? You are using exec to run the script, I am pretty certain about what it does, but maybe you could add a short explanation to your answer. I'd be glad about some more explanation about why things like subshells or chaining commands will never work. Thanks again! – Zulakis Mar 28 '16 at 10:45
  • Your extglob pattern may be good enough (but not compatible with /bin/sh) although it still allows executing /var/lib/scripts/.. which might be okay because it will fail anyway (executing /var/lib as a directory is nonsense) but IMHO EIBTI. As for set -- $SSH_ORIGINAL_COMMAND that's just so you can reliably split the command into command name and separate arguments; not a security effect per se. Finally, I don't know what you mean by "subshells or chaining commands"? – Celada Mar 28 '16 at 10:53
  • What I mean is the question why embedding $(rm -rf /) in a command or appending && rm -rf / to a command will never work? Can you add a few bits about why you are using exec to your answer? You are right about explicit being better than explicit, I will just use /var/lib/scripts/[^./]*([^/]). – Zulakis Mar 28 '16 at 11:04
  • For embedding shell syntax in commands, try it and see what happens: $(rm -rf /) does not get interpreted by the shell but gets passed verbatim to the external command as 3 literal arguments $(rm, -rf, and /). Since we assume the command sanitizes its arguments, it's safe. As for exec, I am just using it because it makes sense to run the final command in the same process, replacing the wrapper script instead of spawning a new process. It's not strictly necessary. – Celada Mar 28 '16 at 11:11

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.