How to restrict SSH users to a predefined set of commands after login?
Asked Answered
E

9

106

This is a idea for a security. Our employees shall have access to some commands on a linux server but not all. They shall e.g. have the possibility to access a log file (less logfile) or start different commands (shutdown.sh / run.sh).

Background information:

All employees access the server with the same user name: Our product runs with "normal" user permissions, no "installation" is needed. Just unzip it in your user dir and run it. We manage several servers where our application is "installed". On every machine there is a user johndoe. Our employees sometimes need access to the application on command line to access and check log files or to restart the application by hand. Only some people shall have full command line access.

We are using ppk authentication on the server.

It would be great if employee1 can only access the logfile and employee2 can also do X etc...

Solution: As a solution I'll use the command option as stated in the accepted answer. I'll make my own little shell script that will be the only file that can be executed for some employees. The script will offer several commands that can be executed, but no others. I'll use the following parameters in authorized_keys from as stated here:

command="/bin/myscript.sh",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty
ssh-dss AAAAB3....o9M9qz4xqGCqGXoJw= user@host

This is enough security for us. Thanks, community!

Edmea answered 31/12, 2008 at 9:41 Comment(7)
Is standard Linux ACL permission-based security not sufficient? What extra features do you need?Morie
@jamesbrady: No. All employees access the server with the same user name (also added to the question). So all users have the same rights.Edmea
That is an awful idea, Marcel.Usurp
@Vinko, @PEZ: I've added some background informations. Instead of saying "stupid idea" you could provide comments with value. What is in your opinion a better idea?Edmea
I still don't see any excuse on having multiple users share the same username.Infusible
"My own little shell script"? It sounds quite dangerous. Unless you're an expert, there are probably many ways to escape from it to the full shell. I would rather trust a well-written, debugged and maintained program (several have been mentioned).Mel
I'm happy that you solved but please post your solution as an answer, not in the question. Thank you so much. So you can also flag yourself as favorite answer. I cannot do it since I cannot edit the question since the edit queue is full, as usual. I know that this is an old question but my comment is still relevant to avoid potential confusion to community readers.Foretooth
B
76

You can also restrict keys to permissible commands (in the authorized_keys file).

I.e. the user would not log in via ssh and then have a restricted set of commands but rather would only be allowed to execute those commands via ssh (e.g. "ssh somehost bin/showlogfile")

Brewington answered 31/12, 2008 at 10:6 Comment(8)
That looks interesting. Is it possible to define multiple commands?Edmea
This article gives you a few options for multiple commands using the authorized_keys file: linuxjournal.com/article/8257Clachan
@rd834...: Thanks a lot. I think this gave me a "good" solution... (added to question). I'll accept this answer as "correct".Edmea
The O'Reilly SSH book has an excellent explanation of how to do this, including allowing multiple commands by setting up a script that looks at SSH_ORIGINAL_COMMAND environment variable. oreilly.com/catalog/sshtdg/chapter/ch08.htmlDipsomaniac
this serverfault answer has a nice tip for how to allow multiple commands by using the exported SSH_ORIGINAL_COMMAND variable.Keystroke
An example of this would be niceCompressive
The O'Reilly SSH book mentioned by Alex Dupuy is no longer available on the original site. However, it can be retrieved from web.archive.org/web/20140920215444/http://oreilly.com/catalog/…Prune
I would like to mention the link from @Clachan in the description but the edit queue is full now, as usualForetooth
O
37

ssh follows the rsh tradition by using the user's shell program from the password file to execute commands.

This means that we can solve this without involving ssh configuration in any way.

If you don't want the user to be able to have shell access, then simply replace that user's shell with a script. If you look in /etc/passwd you will see that there is a field which assigns a shell command interpreter to each user. The script is used as the shell both for their interactive login ssh user@host as well as for commands ssh user@host command arg ....

Here is an example. I created a user foo whose shell is a script. The script prints the message my arguments are: followed by its arguments (each on a separate line and in angle brackets) and terminates. In the log in case, there are no arguments. Here is what happens:

webserver:~# ssh foo@localhost
foo@localhost's password:
Linux webserver [ snip ]
[ snip ]
my arguments are:
Connection to localhost closed.

If the user tries to run a command, it looks like this:

webserver:~# ssh foo@localhost cat /etc/passwd
foo@localhost's password:
my arguments are:
<-c>
<cat /etc/passwd>

Our "shell" receives a -c style invocation, with the entire command as one argument, just the same way that /bin/sh would receive it.

So as you can see, what we can do now is develop the script further so that it recognizes the case when it has been invoked with a -c argument, and then parses the string (say by pattern matching). Those strings which are allowed can be passed to the real shell by recursively invoking /bin/bash -c <string>. The reject case can print an error message and terminate (including the case when -c is missing).

You have to be careful how you write this. I recommend writing only positive matches which allow only very specific things, and disallow everything else.

Note: if you are root, you can still log into this account by overriding the shell in the su command, like this su -s /bin/bash foo. (Substitute shell of choice.) Non-root cannot do this.

Here is an example script: restrict the user into only using ssh for git access to repositories under /git.

#!/bin/sh

if [ $# -ne 2 ] || [ "$1" != "-c" ] ; then
  printf "interactive login not permitted\n"
  exit 1
fi

set -- $2

if [ $# != 2 ] ; then
  printf "wrong number of arguments\n"
  exit 1
fi

case "$1" in
  ( git-upload-pack | git-receive-pack )
    ;; # continue execution
  ( * )
    printf "command not allowed\n"
    exit 1
    ;;
esac

# Canonicalize the path name: we don't want escape out of
# git via ../ path components.

gitpath=$(readlink -f "$2")  # GNU Coreutils specific

case "$gitpath" in
  ( /git/* )
     ;; # continue execution
  ( * )
    printf "access denied outside of /git\n"
    exit 1
    ;;
esac

if ! [ -e "$gitpath" ] ; then
   printf "that git repo doesn't exist\n"
   exit 1
fi

"$1" "$gitpath"

Of course, we are trusting that these Git programs git-upload-pack and git-receive-pack don't have holes or escape hatches that will give users access to the system.

That is inherent in this kind of restriction scheme. The user is authenticated to execute code in a certain security domain, and we are kludging in a restriction to limit that domain to a subdomain. For instance if you allow a user to run the vim command on a specific file to edit it, the user can just get a shell with :!sh[Enter].

Optometry answered 20/10, 2012 at 23:57 Comment(6)
Seriously, this is VERY DANGEROUS. What will stop me from executing git-receive-pack '/git/';dd if=/dev/urandom of=/dev/sda?Encamp
@Encamp Do we have a command injection hole here? That needs to be addressed. Also, the abuse of file patterns that I perpetrated in the case doesn't look correct to me.Optometry
Yes, /bin/bash -c "$2" is insecure (similar to how SQL injection works). You could filter the string with "magic quotes" like PHP. But the easy way to absolutely ensure security is to call a command manually and then pass the parameters within double quotes. Because the security of the whole thing then depends on that command being the weakest link (harder to verify). Most interesting to see how your answer has 22 upvotes, but nobody noticed this ;) Will you update your own answer?Encamp
@Encamp Yes; I just did. I replaced the script with one which breaks the -c argument with set and then takes only the first two words from it. The first must be an allowed git- command, and the second must be a path which is canonicalized, checked against the allowed prefix and also checked for existence. Can you think of any way to break this?Optometry
Unless someone creates an alias (or overrides a command) for any of the given commands, then no, Bash double quotes should be safe (and if not, then this is a bug in Bash).Encamp
Using authorized_keys' command + restrict seems a much cleaner approach than using /etc/passwd. One issue I see here is that you can forward sockets (e.g. the systemd user daemon Unix socket and ask to start a process for the user) via forwarded socket. Another issue could arise when sshd is configured with Subsysem sftp internal-sftp. In this case, you'll be able to access all files via sftp. Future ssh versions might add addition options which could be dangerous.Lobelia
U
17

What you are looking for is called Restricted Shell. Bash provides such a mode in which users can only execute commands present in their home directories (and they cannot move to other directories), which might be good enough for you.

I've found this thread to be very illustrative, if a bit dated.

Usurp answered 31/12, 2008 at 10:2 Comment(3)
What if the user does "!/bin/sh" or some such from the less prompt?Juju
A major problem with restricted shell is that to secure it, you must use .profile or .bashrc to restrict the PATH, disable builtins, etc., but those files are only invoked for interactive shells. If a user uses ssh to run a remote command they are not invoked. This allows a user with ssh access to an account with SHELL=/bin/rbash to just do something like "ssh remotehost bash" to get a non-interactive but unrestricted shell. You need SSH forced commands as HD suggested, but this can protect against shell escapes as PEZ asked (once PATH is locked down - it includes /bin:/usr/bin by default).Dipsomaniac
I take that back, bash will invoke .bashrc (not .profile) when running non-interactively under sshd; however, you have to make sure that you set PATH explicitly and disable builtins and aliases in .bashrc - changing to a subdirectory not in/above PATH or .ssh/ or .bashrc is also a good idea. Having any command that can write to arbitrary files will create a problem - these are not always obvious, e.g. sed 'w' command could be used to overwrite .bashrc and break out. A chroot jail will always be safer if you can use it, and ssh forced commands will be more restricted.Dipsomaniac
E
7

Why don't you write your own login-shell? It would be quite simple to use Bash for this, but you can use any language.

Example in Bash

Use your favorite editor to create the file /root/rbash.sh (this can be any name or path, but should be chown root:root and chmod 700):

#!/bin/bash

commands=("man" "pwd" "ls" "whoami")
timestamp(){ date +'%Y-%m-%s %H:%M:%S'; }
log(){ echo -e "$(timestamp)\t$1\t$(whoami)\t$2" > /var/log/rbash.log; }
trycmd()
{
    # Provide an option to exit the shell
    if [[ "$ln" == "exit" ]] || [[ "$ln" == "q" ]]
    then
        exit
        
    # You can do exact string matching for some alias:
    elif [[ "$ln" == "help" ]]
    then
        echo "Type exit or q to quit."
        echo "Commands you can use:"
        echo "  help"
        echo "  echo"
        echo "${commands[@]}" | tr ' ' '\n' | awk '{print "  " $0}'
    
    # You can use custom regular expression matching:
    elif [[ "$ln" =~ ^echo\ .*$ ]]
    then
        ln="${ln:5}"
        echo "$ln" # Beware, these double quotes are important to prevent malicious injection
        
        # For example, optionally you can log this command
        log COMMAND "echo $ln"
    
    # Or you could even check an array of commands:
    else
        ok=false
        for cmd in "${commands[@]}"
        do
            if [[ "$cmd" == "$ln" ]]
            then
                ok=true
            fi
        done
        if $ok
        then
            $ln
        else
            log DENIED "$cmd"
        fi
    fi
}

# Optionally show a friendly welcome-message with instructions since it is a custom shell
echo "$(timestamp) Welcome, $(whoami). Type 'help' for information."

# Optionally log the login
log LOGIN "$@"

# Optionally log the logout
trap "trap=\"\";log LOGOUT;exit" EXIT

# Optionally check for '-c custom_command' arguments passed directly to shell
# Then you can also use ssh user@host custom_command, which will execute /root/rbash.sh
if [[ "$1" == "-c" ]]
then
    shift
    trycmd "$@"
else
    while echo -n "> " && read ln
    do
        trycmd "$ln"
    done
fi

All you have to do is set this executable as your login shell. For example, edit your /etc/passwd file, and replace your current login shell of that user /bin/bash with /root/rbash.sh.

This is just a simple example, but you can make it as advanced as you want, the idea is there. Be careful to not lock yourself out by changing login shell of your own and only user. And always test weird symbols and commands to see if it is actually secure.

You can test it with: su -s /root/rbash.sh.

Beware, make sure to match the whole command, and be careful with wildcards! Better exclude Bash-symbols such as ;, &, &&, ||, $, and backticks to be sure.

Depending on the freedom you give the user, it won't get much safer than this. I've found that often I only needed to make a user that has access to only a few relevant commands, and in that case this is really the better solution. However, do you wish to give more freedom, a jail and permissions might be more appropriate. Mistakes are easily made, and only noticed when it's already too late.

Encamp answered 4/5, 2017 at 22:31 Comment(6)
I really liked this approach. Do you have any idea how can we handle multi-words commands like "service httpd restart"?Popeyed
@Popeyed You can execute commands in a variable, e.g. ln="service httpd restart", with: ${ln[@]}. Still be sure to think about security issues, a whitelist for only alphanumeric characters and whitespaces would be useful in such a case. But you can also just do: if [[ "$ln" == "restart-httpd"];then service httpd restart;fi, and work with simple commands like that.Encamp
/root/rbash.sh: Permission denied I tried chmod 777 as wellJerroldjerroll
@Christian, did you add /root/rbash.sh to /etc/shells? It depends on the distribution. You can also try temporarily disabling selinux, and check log messages for clues (look at the timestamp) in files in /var/log.Encamp
@yeti no, was explained to add to /root :) Will try now.Jerroldjerroll
@Jerroldjerroll I had the same problem. After moving the script to /home directory, the error is gone. Remember to give full execute permissions.Popeyed
C
6

GNU Rush may be the most flexible and secure way to accomplish this:

GNU Rush is a Restricted User Shell, designed for sites that provide limited remote access to their resources, such as svn or git repositories, scp, or the like. Using a sophisticated configuration file, GNU Rush gives you complete control over the command lines that users execute, as well as over the usage of system resources, such as virtual memory, CPU time, etc.

Chante answered 3/10, 2019 at 7:18 Comment(2)
This is a good suggestion, but seems a bit involved. Could you provide an example configuration?Larocca
I don't even remember if I ended up using Rush myself, but it doesn't get more authoritative than the manual: gnu.org.ua/software/rush/manual/rush.html#Quick-StartChante
B
4

You should acquire `rssh', the restricted shell

You can follow the restriction guides mentioned above, they're all rather self-explanatory, and simple to follow. Understand the terms `chroot jail', and how to effectively implement sshd/terminal configurations, and so on.

Being as most of your users access your terminals via sshd, you should also probably look into sshd_conifg, the SSH daemon configuration file, to apply certain restrictions via SSH. Be careful, however. Understand properly what you try to implement, for the ramifications of incorrect configurations are probably rather dire.

Buffington answered 31/12, 2008 at 21:57 Comment(3)
Note that pizzashack.org/rssh is an excellent (possibly the best) solution for the special case where you want to allow scp/sftp/rdist/rsync/cvs (and don't need access to anything outside a chroot jail) - however, it does not solve the general question of the original poster, who wanted users to be able to view log files and run certain run/shutdown scripts.Dipsomaniac
rssh was removed from Debian Buster. I think the new hotness is GNU rush.Chante
@Chante I believe rssh is now unmaintained, and has a known security issue: sourceforge.net/p/rssh/mailman/message/36519118Haema
V
1

You might want to look at setting up a jail.

Villasenor answered 31/12, 2008 at 21:2 Comment(3)
The question is tagged 'linux'. Aren't jails specific to FreeBSD?Haema
Yes, jails are FreeBSD specific. Linux has Chroot jails, which are a level less secure.Careycarfare
I would like to mention explicitly in the answer that this is indeed related to FreeBSD but the edit queue is full now, as usualForetooth
J
1

[Disclosure: I wrote sshdo which is described below]

If you want the login to be interactive then setting up a restricted shell is probably the right answer. But if there is an actual set of commands that you want to allow (and nothing else) and it's ok for these commands to be executed individually via ssh (e.g. ssh user@host cmd arg blah blah), then a generic command whitelisting control for ssh might be what you need. This is useful when the commands are scripted somehow at the client end and doesn't require the user to actually type in the ssh command.

There's a program called sshdo for doing this. It controls which commands may be executed via incoming ssh connections. It's available for download at:

http://raf.org/sshdo/ (read manual pages here) https://github.com/raforg/sshdo/

It has a training mode to allow all commands that are attempted, and a --learn option to produce the configuration needed to allow learned commands permanently. Then training mode can be turned off and any other commands will not be executed.

It also has an --unlearn option to stop allowing commands that are no longer in use so as to maintain strict least privilege as requirements change over time.

It is very fussy about what it allows. It won't allow a command with any arguments. Only complete shell commands can be allowed.

But it does support simple patterns to represent similar commands that vary only in the digits that appear on the command line (e.g. sequence numbers or date/time stamps).

It's like a firewall or whitelisting control for ssh commands.

And it supports different commands being allowed for different users.

Jacobi answered 24/4, 2019 at 3:42 Comment(0)
S
0

Another way of looking at this is using POSIX ACLs, it needs to be supported by your file system, however you can have fine-grained tuning of all commands in linux the same way you have the same control on Windows (just without the nicer UI). link

Another thing to look into is PolicyKit.

You'll have to do quite a bit of googling to get everything working as this is definitely not a strength of Linux at the moment.

Sempach answered 31/12, 2008 at 22:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.