Linux Systems Articles for better insights

Building Chroot Jails for SSH accounts

Creating a Linux Chroot Jail for SSH Access

I wanted to setup a way to allow SSH access for an ssh jump bastion, but limit the local accounts heavily. I used a chroot jail with minimal applications to solve the problem. I'm using Ubuntu 16 with OpenSSH 5.1p1. Should be very similar on any Linux host.

Setup your test user

The way I'm setting this up, is that all my chrooted users will be added to the sshusers group. So we must setup the group, then add the user.

$ groupadd sshusers
$ adduser -g sshusers user

Setup the jail directories

The next step is to setup all the directories needed. This needs to emulate the / directory to a bare minimum. That is we need a dev, etc, lib, usr, and bin directory as well as usr/bin/. The base directory has to be owned by root.

$ mkdir -p /var/jail/{dev,etc,lib,usr,bin}
$ mkdir -p /var/jail/usr/bin
$ chown root.root /var/jail You also need the /dev/null file:
$ mknod -m 666 /var/jail/dev/null c 1 3 You need to fill up the etc directory with a few minimum files:
$ cd /var/jail/etc
$ cp /etc/ld.so.cache .
$ cp /etc/ld.so.conf .
$ cp /etc/nsswitch.conf .
$ cp /etc/hosts . Once this is done you need to figure out what commands you want accessible by your limited users. In this example I only want the users to be able to get into bash and use the ls command. So you must copy the binaries to the jail.
$ cd /var/jail/usr/bin
$ cp /usr/bin/ls .
$ cp /usr/bin/bash . Now that you've got all the binaries in place, you need to add the proper shared libraries. To find out what libraries are need you can run ldd /path/to/bin. The output looks similar to this:
$ ldd /bin/ls
         linux-gate.so.1 =>    (0xb7f2b000)
         librt.so.1 => /lib/librt.so.1 (0xb7f1d000)
         libacl.so.1 => /lib/libacl.so.1 (0xb7f16000)
         libc.so.6 => /lib/libc.so.6 (0xb7dcf000)
         libpthread.so.0 => /lib/libpthread.so.0 (0xb7db7000)
         /lib/ld-linux.so.2 (0xb7f2c000)
         libattr.so.1 => /lib/libattr.so.1 (0xb7db2000) Then you have to manually copy each file to the lib directory in your jail. That is a pain. Especially if there is a lot of shared libraries for a binary you want. I came across a useful script called l2chroot which automatically finds the libraries and copies them to your chroot jail.
cd /sbin
wget -O l2chroot http://www.cyberciti.biz/files/lighttpd/l2chroot.txt
chmod +x l2chroot Edit the l2chroot file and change BASE=”/webroot” to BASE=”/var/jail”. This tells l2chroot where your jail is located so it copies everything to the right place. Now go ahead and run the command on the binaries you want.
l2chroot ls
l2chroot bash

Other fun things to copy:

  • ping
  • ssh
  • ssh-keygen
  • nmap
  • curl
  • chmod, chown, rm, touch, rmdir, cat, less, more

Ensure bash is moved under /var/jail/bin/bash

Configure SSHd to Chroot your users

All that is left is to set a few things in your sshd configuration file. You need to make sure you have at least OpenSSH 4.8p1, because before that they didn't have this nice ChrootDirectory() function. Previously there was a few extra steps you had to take to get it working, but really you should have a newer version anyway. To configure ChrootDirectory add the following to /etc/ssh/sshd_config:

Match group sshusers
          ChrootDirectory /var/jail/
          X11Forwarding no
          AllowTcpForwarding no Note that this also disables X11Forwarding and does not allow port forwarding. If you want to setup a box to allow secure tunneling for your friends, you may want to change this.

Optional Steps

When you login to your test user, you'll notice a prompt as such:


That is not a very useful bash prompt. So if you want something a little better I recommend simply copying the contents of /etc/skel to /var/jail/home/user. This gives you a .bashrc file which sets the PS1 variable to a much nicer looking prompt. Here's what mine looks like:

phrygian:~> echo $PS1

Allow getting username etc within programs, required for ssh command to work

This fixed the 'No user exists for uid 1000' error.

cp /usr/lib64/libnsl.so.1 /var/jail/lib64/
cp /usr/lib64/libnss_* /var/jail/lib64/

This fixed the 'debug1: read_passphrase: can't open /dev/tty: No such file or directory'

mknod -m 666 /var/jail/dev/tty c 5 0

Create device nodes for ssh and other utilities

mknod -m 666 /var/jail/dev/null c 1 3
mknod /var/jail/dev/random c 1 8
mknod /var/jail/dev/urandom c 1 9

OPTIONAL: Allow dns lookups for programs that need it

cp /etc/resolv.conf /var/jail/etc/
cp /etc/services /var/jail/etc/
cp /etc/host.conf /var/jail/etc/
cp /etc/nsswitch.conf /var/jail/etc/
cp /lib64/libnss_dns* /var/jail/lib64/
cp /etc/localtime /var/jail/etc/

Allow ping fix 'operation not permitted'

Things need to be run as suid root.

chown root:root /var/jail//usr/local/bin/ping; chmod u+srwx,go=rx /var/jail//usr/local/bin/ping

Things like ssh require /dev/tty you may need ot make devices nodes from scratch


mknod -m 622 /dev/console c 5 1
mknod -m 666 /dev/null c 1 3
mknod -m 666 /dev/zero c 1 5
mknod -m 666 /dev/ptmx c 5 2
mknod -m 666 /dev/tty c 5 0
mknod -m 444 /dev/random c 1 8
mknod -m 444 /dev/urandom c 1 9
chown -v root:tty /dev/{console,ptmx,tty}

Be aware if you make these devices breakouts from chroot are possible (into the host)


python -c 'import pty; pty.spawn("/bin/sh")'
echo os.system('/bin/bash')


/bin/sh -i


perl e 'exec "/bin/sh";'
perl: exec "/bin/sh";


ruby: exec "/bin/sh"


lua: os.execute('/bin/sh')

(From within IRB)

exec "/bin/sh"

(From within vi)


(From within vi)

:set shell=/bin/bash:shell

(From within nmap)


Getting things like 'ping' to work when "operation not permitted"

The fix you will be looking for is set_uid_root sticky bit on file mode. WARN: These will be run as root on the host, yet another potential jail escape to root.

chown root:root /var/jail/bin/ping
chmod u+srwx,go=rx /var/jail/bin/ping