mandag 3. januar 2011

Restrict ssh access to one command, but allow parameters

Sometimes one needs to allow a script to login to a server using a SSH key to do a job. That can be achieved by adding the scripts SSH public key to the remote user's authorized_keys file. The private keys are often stored without password to allow the script to use the key and not stopping execution by an interactive prompt for the private key password.

This puts the remote server at risk because if the originating server is compromised an attacker would easily gain access to the remote server as well. To reduce the damage of a compromised private key, one often restricts the access of the key to the minimum required to get the script's job done. This can be accomplished by using SSH-key options in the authorized_keys file.

from="",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty,command="/home/user/bin/" ssh-rsa  AAAAB3NzA..Dxq=

This is quite effective, and restricts the attacker to executing the specified forced command, and only from a specified host, with no terminal or X11 access.

Other times one needs to do a sync job using rsync. Let's see how that works out with the above scheme.

Example command:
/usr/bin/rsync /some/dir/

Example entry in authorized_keys:
from="",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty,command="/usr/bin/rsync" ssh-rsa  AAAAB3NzA..Dxq=

Trying that, you'll soon noticed that all your command line arguments are ignored, and you'll get the help text from rsync as output. The forced command option does not allow arbitrary command line arguments to the command.

There is a workaround by using the $SSH_ORIGINAL_COMMAND environment variable that the ssh program creates. The problem with that is that you'll need a wrapper script to deal with the arguments because it will have the executable twice in the argument list:
from="",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty,command="/usr/bin/rsync $SSH_ORIGINAL_COMMAND" ssh-rsa  AAAAB3NzA..Dxq=

This will produce this command:
/usr/bin/rsync /usr/bin/rsync /some/dir/

A solution to this is the wrapper script that interprets the environment variable and executes the correct command:
    "/usr/bin/rsync "*)
        echo "Permission denied."
        exit 1

This adds complexity and requires a script to be installed on the remote server. It is also possible to achieve a similar solution without the wrapper script:

from="",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty,command="/usr/bin/rsync ${SSH_ORIGINAL_COMMAND#* }"

Notice the '#* ' right after SSH_ORIGINAL_COMMAND. The # modifier in bash is used to remove the smallest prefix pattern, and so '#* ' will remove everything up until and including the first space.

There are some security concerns however. For instance this would be possible from the machine "":

rsync authorized_keys user@server:/home/user/.ssh/authorized_keys

That would allow an attacker to swap the file with anything, and thus the security is easily breached. To prevent that,  make the file owned by root and remove write permissions on the file, or make it immutable with chattr.

chown root:root /home/user/.ssh/authorized_keys
chmod a-w       /home/user/.ssh/authorized_keys
chattr +i       /home/user/.ssh/authorized_keys

Allowing the unrestricted use of the rsync program will allow an attacker to replace any files the user has write access to. Beware that there probably are other holes in this that I didn't find.