Sunday 13th January 2019
This article outlines a number of techniques for restricting and locking down SSH users on Linux systems, and how you can use multiple different protections at once to create the most secure setup.
Skip to Section:
Restricting and Locking Down SSH Users ┣━━ SSH Security Strategy ┣━━ Restricted Shells ┣━━ Chrooting ┣━━ authorized_keys File Options ┣━━ ForceCommand Configuration ┣━━ IP/Host Whitelisting ┗━━ Creating a Secure SFTP Configuration
My personal strategy when it comes to locking down and restricting SSH users is to ensure that there are always multiple protections in place, so that if one were to fail, it fails securely, rather than failing open.
There are many ways that an SSH security configuration could fail:
The key message is that relying on more than one security control is great defence-in-depth and hardening, and really has the potential to save you should something go wrong.
In this article, I have explained each control individually, however in most cases they can be easily combined without any problems. At the end of this page, I have documented a secure SFTP configuration, which combines multiple security controls in order to result in a secure, restricted directory that can be accessed via SFTP.
User accounts can be restricted by setting their shell to be either restricted, or completely disabled.
rssh is a restricted shell which allows you to impose restrictions on user accounts accessing an SSH server. You can view the manual page here: https://linux.die.net/man/1/rssh
For Debian-based systems, you can install rssh using apt install rssh
. In order for rssh to effective, you'll have to set the shell of the user you want to restrict to be rssh. The path of the rssh binary is probably /usr/bin/rssh
, however you can double-check with which rssh
.
In order to change the shell of an existing user to rssh, you can use usermod --shell /usr/bin/rssh username
, and in order to create a new user with rssh as the default shell, you can use adduser --shell /usr/bin/rssh username
.
Now when you try to log in as the restricted user, you'll see the following message:
This account is restricted by rssh. This user is locked out. If you believe this is in error, please contact your system administrator.
In order to remove specific restrictions to give access to the system features you need, edit the file /etc/rssh.conf
:
# This is the default rssh config file # set the log facility. "LOG_USER" and "user" are equivalent. logfacility = LOG_USER # Leave these all commented out to make the default action for rssh to lock # users out completely... #allowscp #allowsftp #allowcvs #allowrdist #allowrsync #allowsvnserve ...
To remove the restrictions for a particular service (e.g. scp), simply remove the hash (#) from the start of the line, then save the file. Log out and back in, and the user will have the newly adjusted restrictions.
An alternative restricted shell is nologin, which is a shell that completed disables the ability for the user to log in. On Debian-based systems the path is usually /usr/sbin/nologin
, however you can double-check using which nologin
.
In order to change the shell of an existing user to nologin, you can use usermod --shell /usr/sbin/nologin username
, and in order to create a new user with nologin as the default shell, you can use adduser --shell /usr/sbin/nologin username
.
If you attempt to log in to a user which uses the nologin shell, you'll see the following message:
This account is currently not available.
The nologin shell is best used along with other protections in this article in order to provide failover should one of your configuration files be overwritten or a user account changed accidentally.
Chrooting simply refers to changing the perceived root directory of a system, but the term 'chroot jail' is often used to describe the use of chroot to provide security. Chrooting can provide security by limiting the resources and files that a particular user or application can access, helping to prevent a further system compromise or privilege escalation should the chrooted user or application turn rogue.
There are several different ways to chroot on Linux, but a particularly useful method for chrooting SSH users is the ChrootDirectory
option in sshd_config
. It's most useful when used inside a Match
block, as shown below. Match
blocks should always be used right at the bottom of your sshd_config
file, as all configuration after them (until the end of the file or another Match
block) will only apply to situations that match the criteria:
Match User jamie ChrootDirectory /home/jamie/
This configuration will restrict the jamie
user to /home/jamie/
. Running ls /
as this chrooted user will show the contents of /home/jamie
, but this will be transparent to the user in the chroot jail.
Setting this configuration and connecting via SSH will probably result in an error like below:
packet_write_wait: Connection to 192.168.1.8 port 22: Broken pipe
This is because the chroot directory must be fully owned by root in order for chrooting to be possible. You can change this using chown root:root /path/to/chroot/dir
.
Then, upon connecting, you'll most likely see a further error:
/bin/bash: No such file or directory
This is because the user is trying to start their shell, which in this case is /bin/bash
. However, /bin/bash
doesn't exist at the path /home/jamie/bin/bash
, so the connection fails.
In order to resolve this, all of the required files and directories for whatever you want to be able to do within the chroot jail need to be available. To run a basic bash shell, the required files/directories are usually just the following:
However, in some cases you'll also need /usr
.
You can copy all of these into your chroot jail by executing the following commands whilst in your desired chroot directory:
$ mkdir bin $ cp /bin/bash bin $ cp /lib /lib64 .
The total size of these files depends on the exact system type, but for a Xubuntu 16.04 machine they are around 1 GB. It would be very inefficient to store multiple copies of these files if you had more than 1 chroot jail, however you can use bind mounts to share the files between your host and the chroot directory. This can have serious security implications if not done properly, as it would potentially allow the chrooted user or application to modify files outside of their jail, which could lead to a full chroot escape and system compromise.
In addition to the basic files required to get a bash shell working, you'll also need to copy in anything else that is needed to do whatever you want your chrooted SSH user to be able to do. If you need to copy in more executable files, the ldd
(list dynamic dependencies) command will help you to identify which libraries a particular program requires, so that you can copy those in too. For example:
ldd /bin/cat linux-vdso.so.1 => (0x00007ffcc838e000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fd58773e000) /lib64/ld-linux-x86-64.so.2 (0x00007fd587b08000)
When you log in to the chrooted user via SSH, you'll be fully restricted to the chroot directory, and will only be able to use the commands/programs that are available to you. For example, with a basic chroot containing only only bash and the required libraries, the resulting chroot environment will just have a raw bash prompt, and the only commands available will be bash builtins such as pwd
, cd
and printf
.
authorized_keys
File OptionsIt's possible to impose restrictions on users on a per-key basis using options in the authorized_keys
file.
There are two main options that are particularly useful for security: restrict
and command
. You can view a full list of options on the manual page here: https://linux.die.net/man/8/sshd
restrict
'The restrict
option will enable all of the following restrictions for users authenticating against the key:
no-agent-forwarding
- Disable SSH agent forwarding.no-port-forwarding
- Deny all port forwarding requests.no-pty
- Deny requests to allocate a tty.no-user-rc
- Disable execution of ~/.ssh/rc
.no-X11-forwarding
- Deny requests to forward an X11 sessionYou can then re-enable specific functionality using the corresponding options without the no-
prefix.
If you wish to have an insecure-by-default setup and just lock down individual options, you can just use the restrictions with the no-
prefix on their own (i.e. without setting restrict
), however it is definitely more secure to use restrict
and then override specific restrictions where needed.
command
'The command
option allows you to force execution of a specific command using the user's shell upon initial connection. The output of the command is sent back to the connecting client, and then it is disconnected (unless another feature such as TCP forwarding is in-use).
The command
is best used along with the restrictions describe above in order to ensure a high level of security.
Please note that the command
option will be overridden by ForceCommand
if it is set in sshd_config
.
authorized_keys
The options for a specific key should be set in the authorized_keys
file in a comma-separated format directly before the key. A space should be used between the options and the key. In the examples below I have used ssh-rsa AAAB...
as a placeholder for the key, but in reality this is where your SSH public key goes.
Example configuration:
restrict,X11-forwarding,command="ls -la" ssh-rsa AAAB...
This particular setup will restrict any users that connect using this key, but still allow X11 forwarding to take place. Upon connection, the command ls -la
will be executed using the user's shell.
If you just want to restrict the user fully, then you'll need to use:
restrict ssh-rsa AAAB...
...and if you want to enforce no specific restrictions, but enforce the execution of a specific command, use:
command="echo \"Hello\"" ssh-rsa AAAB...
Notice that double quotes (") must be escaped in the command configuration value.
ForceCommand
ConfigurationThe ForceCommand
option is very similar to the command
option described above, however it is set in the server configuration in sshd_config
rather than on a per-key basis in authorized_keys
.
ForceCommand
is most useful in Match
blocks, as shown below:
Match User jamie ForceCommand echo "Hello"
This configuration will force the user jamie
to execute the command echo "Hello"
upon connection, and then disconnect (unless something else such as X11 forwarding keeps the connection open).
You can also use ForceCommand internal-sftp
to force the creation of an in-process SFTP server which doesn't require any support files when used in a chroot jail. This option is best combined with the ChrootDirectory
option.
SSH users can be restricted to connecting from specific hosts using the AllowUsers
option in sshd_config
.
The syntax for AllowUsers
is localuser[@remotehost]
. Ensure that you don't accidentally use remoteuser[@remotehost]
, as this is incorrect.
For example, in order to restrict the jamie
user to connecting from 192.168.1.2:
AllowUsers jamie@192.168.1.2
You can specify multiple users in the same line:
AllowUsers jamie@192.168.1.2 fred@192.168.1.3
You can also choose multiple different hosts for the same user, for example an IPv4 and IPv6 address, or the addresses of completely separate machines entirely:
AllowUsers jamie@192.168.1.2 jamie@2a01:db8::1
Wildcards are supported too, both on user names and IPv4 addresses:
AllowUsers jamie@192.168.* fred@10.*.5.* *@172.16.0.1
My Raspberry Pi cluster uploads statistics from each of the nodes to the main JamieWeb server every 10 minutes using a secure SFTP link. This is locked down thoroughly in order to ensure that the impact would be as low as possible were the Raspberry Pi's to be compromised and taken over by an attacker.
The result is that the Raspberry Pi's only have SFTP access to a specific directory, and they can only read and write the exact files that they need to be able to, and nothing more.
The configuration is as follows:
/usr/sbin/nologin
/etc/ssh/sshd_config
has the following configuration:
Match User service_pi ForceCommand internal-sftp ChrootDirectory /home/service_pi/sftp/
/home/service_pi/.ssh/authorized_keys
has the following options:
restrict,command="false" ssh-rsa AAAB...
/home/service_pi/sftp/
) is fully owned by root, and only the exact files that service_pi
needs to be able to write to have write permissions.This configuration provides a high level of defence-in-depth and hardening, and means that even if multiple of these controls were to fail, it is unlikely that an attacker would be able to get to a shell on my server to cause more serious damage.