Hack #18. Restrict Users to SCP and SFTP

Provide restricted file-transfer services to your users without resorting to FTP.

Sometimes, you’d like to provide file-transfer services to your users without setting up an FTP server. This leaves the option of letting them transfer files to and from your server using SCP or SFTP. However, because of the way OpenSSH’s sshd implements these subsystems, it’s usually impossible to do this without also giving the user shell access to the system. When an SCP or SFTP session is started, the daemon executes another executable to handle the request using the user’s shell, which means the user needs a valid shell.

One way to get around this problem is to use a custom shell that is capable of executing only the SCP and SFTP subsystems. One such program is rssh (http://www.pizzashack.org/rssh/), which has the added benefit of being able to chroot(), enabling you to limit access to the server’s filesystem as well.

To set up rssh, first download the compressed archive from program’s web site and unpack it. Then, run the standard ./configure and make:

$ tar xfz rssh-2.3.2.tar.gz 
$ cd rssh-2.3.2 
$ ./configure && make
            

Once rssh has finished compiling, become root and run make install. You can now create an account and set its shell to rssh. Try logging into it via SSH. You’ll notice that the connection is closed before you’re able to completely log in. You should also see this before the connection is closed:

This account is restricted by rssh.
This user is locked out.

If you believe this is in error, please contact your system administrator.

You should get similar results if you try to access the account with scp or sftp, because rssh’s default configuration locks out everything. To enable SFTP and SCP, add the following lines to your rssh.conf file (the file should be located in /usr/local/etc or somewhere similar):

allowsftp
allowscp

Now, try accessing the account with sftp:

$ sftp rssh_test@freebsd5-vm1
Connecting to freebsd5-vm1...
Password:
sftp> 

This has been easy so far. Now comes the hard part: configuring chroot(). Here you have two options: you can specify a common environment for all users that have been configured to use rssh, or you can create user-specific chroot() environments.

To create a global environment, you just need to specify the directory to chroot() by using the chrootpath configuration directive. For instance, to have rssh chroot() to /var/rssh_chroot, set up a proper environment there and add the following line to your rssh.conf file:

chrootpath=/var/rssh_chroot

Setting up rssh to use chroot() has one major caveat, though. Supporting chroot() requires the use of an SUID helper binary to perform the chroot() call for the user that has logged in. This is because only the root user can issue a chroot() call. This binary is extremely limited; all it does is perform the chroot() and take steps to ensure that it can only be executed by rssh. However, it’s something to keep in mind if you consider this a risk.

For a user-specific chroot() environment, you can add a line like this:

user=rssh_test:077:00011:/var/rssh_chroot

The first set of numbers after the username is the umask. The second set of digits is actually a bit-vector specifying the allowed means of access. From left to right, these are Rsync, Rdist, CVS, SFTP, and SCP. In the previous example, only SFTP and SCP are allowed.

Finally, the last portion of the line specifies which directory to chroot() to. One thing allowed by this configuration syntax that isn’t immediately obvious is the ability to specify per-user configurations without a directory to chroot() to: simply omit the directory. So, if you just want to allow one user to use only SCP but not SFTP (so they can’t browse the filesystem), you can add a line similar to this one:

user=rssh_test:077:00001

Now, all you need to do is set up the sandbox environment. Create a bin directory within the root directory of your sandbox and copy /bin/sh into it. Then, copy all of the requisite libraries for it to their proper places:

# cd /var/rssh_chroot
# mkdir bin && cp /bin/sh bin
# ldd bin/sh
bin/sh:
        libedit.so.4 => /lib/libedit.so.4 (0x2808e000)
        libncurses.so.5 => /lib/libncurses.so.5 (0x280a1000)
        libc.so.5 => /lib/libc.so.5 (0x280e0000)
        # mkdir lib
        # cp /lib/libedit.so.4 /lib/libncurses.so.5 /lib/libc.so.5 lib
            

Now, copy your scp and sftp binaries and all of their requisite libraries to their proper locations. Here is an example of doing so for scp (sftp should require the same libraries):

# ldd usr/bin/scp
usr/bin/scp:
        libssh.so.2 => /usr/lib/libssh.so.2 (0x2807a000)
        libcrypt.so.2 => /lib/libcrypt.so.2 (0x280a9000)
        libcrypto.so.3 => /lib/libcrypto.so.3 (0x280c1000)
        libz.so.2 => /lib/libz.so.2 (0x281b8000)
        libc.so.5 => /lib/libc.so.5 (0x281c8000)
        libgssapi.so.7 => /usr/lib/libgssapi.so.7 (0x282a2000)
        libkrb5.so.7 => /usr/lib/libkrb5.so.7 (0x282b0000)
        libasn1.so.7 => /usr/lib/libasn1.so.7 (0x282e8000)
        libcom_err.so.2 => /usr/lib/libcom_err.so.2 (0x28309000)
        libmd.so.2 => /lib/libmd.so.2 (0x2830b000)
        libroken.so.7 => /usr/lib/libroken.so.7 (0x28315000)
# cp /lib/libcrypt.so.2 /lib/libcrypto.so.3 /lib/libz.so.2 \
               /lib/libc.so.5 /lib/libmd.so.2 lib
# mkdir -p usr/lib
# cp /usr/lib/libssh.so.2 /usr/lib/libgssapi.so.7 /usr/lib/libkrb5.so.7 \ 
               /usr/lib/libasn1.so.7 /usr/lib/libcom_err.so.2 \ 
               /usr/lib/libroken.so.7 usr/lib/
            

Next, copy rssh_chroot_helper to the proper place and copy your dynamic linker (the program that is responsible for issuing the chroot() call):

# mkdir -p usr/local/libexec
# cp /usr/local/libexec/rssh_chroot_helper usr/local/libexec
# mkdir libexec && cp /libexec/ld-elf.so.1 libexec/
            

Tip

This example is for FreeBSD. For Linux, you’ll likely want to use /lib/ld-linux.so.2.

Then, recreate /dev/null in your chroot() environment:

# ls -la /dev/null
crw-rw-rw-  1 root  wheel    2,   2 Apr 10 16:22 /dev/null
# mkdir dev && mknod dev/null c 2 2 && chmod a+w dev/null
            

Now create a dummy password file:

# mkdir etc && cp /etc/passwd etc
            

Edit the password file to remove all the entries for other accounts, leaving only the accounts that will be used in the jail.

Now, try connecting with sftp:

$ sftp rssh_test@freebsd5-vm1
Connecting to freebsd5-vm1...
Password:
sftp> ls /etc
/etc/.
/etc/..
/etc/passwd

All that’s left to do is to create a /dev/log and change your syslogd startup options to listen for log messages on the /dev/log in your chroot() environment. Using the -a option and specifying additional log sockets will usually take care of this:

# /sbin/syslogd -a /home/rssh_test/dev/log
            

rssh is an incredibly useful tool that can remove the need for insecure legacy services. In addition to supporting SCP and SFTP, it supports CVS, Rdist, and Rsync. Check out the rssh(1) and rssh.conf(5) manual pages for more information on setting those up.