Enthusiasm never stops


Secure chroot() remote file access via SFTP and SSH

If you want to replace the buggy and not-encrypted FTP protocol, and get rid of the FTP daemon on your system, the SFTP protocol comes to the rescue. Note that the SFTP protocol is something more than the SCP protocol (Secure copy), as it provides resuming interrupted transfers, directory listings, and remote file removal. This makes it more similar to the FTPS protocol (FTP over SSL) with the difference that it doesn’t require a separate FTP daemon, because the SSH daemon supports SFTP, which simplifies your network setup and lowers maintenance costs.

A standard security feature of the FTP servers is that logged in users are placed in a chroot jail directory, which restricts users from viewing and manipulating any other files but their own ones. Fortunately, the OpenSSH daemon supports chroot() too — see the sshd_config(5) man page.

Now that I’ve convinced you that SFTP is the right way to go for secure file transfers and remote file mounts, let’s see how we can configure it on a Debian or Ubuntu based server. All SFTP users will be kept in a root chroot() directory:

mkdir -m 751 /home/sftp-chroot /home/sftp-chroot/{dev,home}

The man page says that all components of the pathname must be root-owned directories that are not writable by any other user or group. Let’s verify this before going further:

ls -lad / /home /home/sftp-chroot /home/sftp-chroot/home
drwxr-xr-x 23 root root 4096 2011-01-31 17:15 /
drwxr-x--x  9 root root 4096 2011-01-31 18:05 /home
drwxr-x--x  4 root root 4096 2011-01-31 17:38 /home/sftp-chroot
drwxr-x--x  3 root root 4096 2011-02-01 08:46 /home/sftp-chroot/home

SFTP users’ home directories will be placed in “/home/sftp-chroot/home/$SFTPUSER” (after the chroot() the actual directory would be “/home/$SFTPUSER”). Note that this still gives full privacy, because users can’t list the root chroot() directory “/” nor the root home directory “/home”, as their permissions are 0751. Furthermore, even if one user guessed the username and home directory of another user, they cannot enter their home directory, because the users’ home directories give no permissions for the group “others”. The commands for managing users’ home directories are at the very end of this article.

In order for the chroot()’ed users and processes to be able to do logging, a listening UNIX socket of syslog must be created inside the root chroot() directory:

cat > /etc/rsyslog.d/sftp-chroot.conf <<'EOF' # the single quotes are important
# the following syslog socket will be used by all chrooted users
$AddUnixListenSocket /home/sftp-chroot/dev/log

# Log internal-sftp activity in a separate file
:programname, isequal, "internal-sftp" -/var/log/sftp.log
:programname, isequal, "internal-sftp" ~

/etc/init.d/rsyslog restart

We verify that the syslog UNIX socket was created:

ls -la /home/sftp-chroot/dev/log
srw-rw-rw- 1 root root 0 2011-01-31 16:42 /home/sftp-chroot/dev/log

We will also rotate the log files weekly:

cat > /etc/logrotate.d/sftp-chroot <<'EOF'
/var/log/sftp.log {
  rotate 52
    invoke-rc.d rsyslog reload > /dev/null

Finally, let’s set up the OpenSSH daemon accordingly:

# disable the standard "sftp" subsystem in the sshd config
perl -pi -e 's/^(\s*Subsystem\s+sftp\s+)/#$1/i' /etc/ssh/sshd_config

# enable the chroot "sftp" subsystem
cat >> /etc/ssh/sshd_config <<'EOF'

Subsystem sftp internal-sftp

Match group sftponly
  ChrootDirectory /home/sftp-chroot
  ForceCommand internal-sftp -l VERBOSE
  AllowTcpForwarding no
  PermitRootLogin no
  X11Forwarding no
  AllowAgentForwarding no
  # Change to yes to enable tunnelled clear text passwords, not recommended
  PasswordAuthentication no

groupadd sftponly
/etc/init.d/ssh restart

A few notes about the OpenSSH configuration:

  • This configuration requires OpenSSH 5.2 or later — discussion about this in the reference links below.
  • The SCP protocol is not supported, at least not very easily. If you really need SCP, you’d need to re-create a full binary environment, in order to provide standard SSH access in a chroot() way. This is not discussed here, nor tested by me. You are encouraged to use SFTP instead. [source article]
  • Some users report that the time-stamp of the messages in the syslog log file were wrong. This is indeed possible since there is no /etc/timezone file in the root chroot() directory. I haven’t encountered this bug, as my system is in GMT+0, or the bug no longer exists. [source article]

In the end, let’s take a look at Users management (sample Bash script below in the comments).

Adding an SFTP user:

export SFTPUSER='testsftp1'
useradd -d "/home/$SFTPUSER" --groups sftponly -s /bin/false -p '!' "$SFTPUSER"
passwd "$SFTPUSER" # if you enabled tunnelled clear text passwords, regardless of the security implications
mkdir -m 770 "/home/sftp-chroot/home/$SFTPUSER"
chown root:"$SFTPUSER" "/home/sftp-chroot/home/$SFTPUSER"

Enabling public/private key authentication, the more secure choice, for an SFTP user:

export SFTPUSER='testsftp1'
mkdir -m 750 "/home/$SFTPUSER" "/home/$SFTPUSER/.ssh"
chown root:"$SFTPUSER" "/home/$SFTPUSER" "/home/$SFTPUSER/.ssh"
vi "/home/$SFTPUSER/.ssh/authorized_keys" # add the public key

Deleting an SFTP user and its data:

export SFTPUSER='testsftp1'
userdel "$SFTPUSER"
rm -r "/home/sftp-chroot/home/$SFTPUSER"
rm -r "/home/$SFTPUSER" # if you authenticate using public keys

The end-result of all those efforts is that when an SFTP user logs in to your system, they see only their home directory and nothing else. This is because after the chroot, sshd(8) changes the working directory to the user’s home directory. Furthermore the user cannot list nor enter other home directories in the root chroot() directory, because of the directory setup we created.

Here is a sample log from the SFTP syslog file, click “show source” to view it:

Jan 31 17:55:10 m1 internal-sftp[13237]: session opened for local user testsftp1 from []
Jan 31 17:55:10 m1 internal-sftp[13237]: received client version 3
Jan 31 17:55:10 m1 internal-sftp[13237]: realpath "."
Jan 31 17:55:12 m1 internal-sftp[13237]: open "/home/testsftp1/fb.php" flags WRITE,CREATE,TRUNCATE mode 0644
Jan 31 17:55:13 m1 internal-sftp[13237]: close "/home/testsftp1/fb.php" bytes read 0 written 735
Jan 31 18:04:14 m1 internal-sftp[13237]: session closed for local user testsftp1 from []