How to create Chrooted Apache with mod_chroot

Here is a thorough howto on how I built a chrooted apache using mod_chroot. Along with chroot, I'm using mod_security for basic filtering and the suhosin PHP extension for adding additional security to the core of PHP on the server.

As some of you know, adding additional security, you often take away functionality and usability. These techniques won't be for everyone, and the learning curve is slight steep for debugging the initial problems. Once you learn what to look for and how to correct them, thing become much easier.

There are many ways to skin the chroot cat, and my way is just one of those ways. I've used mod_security's chroot in the past and how mod_chroot and mod_security apply the chroot are very similar, so you could either one. I want to still maintain mod_security due to it's great handling of automated script attacks and comment spam handling.

This howto is focused to Gentoo linux, but could easily be applied to other distributions, please view mod_chroot's site for their documentation and caveats.

Issues to contend with on chrooting apache. The first issue is the placement of Mysql's socket. It needs to be in the jail for Apache to use it. This causes additional problems. When you execute mysql from shell, you need to provide the new socket location. Simple solution is to add alias to your .bashrc. The other issue are relating to other programs needing mysql (ie: snort, etc)

Another common problem is PHP's mail function no longer works in chroot. You'll need to compile a small binary (ie: mini_sendmail or nbsmtp. I went with mini_sendmail since I was familiar with it. There are a couple of problems with this, mini_sendmail will need a shell to run, so you're going to need to bring in a shell in the jail. Not ideal, but not sure what to do for all those apps using PHP's mail. The second problem with mini_sendmail is that it needs user accounts, so I brought over a stripped down /etc/passwd into the jail

You'll also notice that I created a jail in /var/chroot/apache instead of the common /chroot. This is due to the way I originally set up partitions. /var is the largest available partition for me, and didn't want to create another bind mount to create this. You may want to locate your jail in another location.

I mentioned that I'm using mod_security and suhosin. These configurations will not be discussed here, just the configuration of mod_chroot. Stay tuned for the others.

emerge -v mod_chroot
vi /etc/apache2/modules.d/15_mod_chroot.conf
ChrootDir /var/chroot/apache
vi /etc/conf.d/apache
-D CHROOT
PIDFILE=/var/chroot/apache/var/run/apache2.pid
vi /etc/apache2/httpd.conf
PidFile "/var/run/apache2.pid"

# make jail environment

mkdir -p /var/chroot/apache/var/www
cd /var/chroot/apache
mkdir bin dev etc lib tmp usr var
mount -t auto -o bind /var/www /var/chroot/apache/var/www
mount -t auto -o bind /tmp /var/chroot/apache/tmp/
cd /var/chroot/apache/usr
mkdir bin lib sbin share
cd /var/chroot/apache/share
mkdir curl misc php
mount -t auto -o bind /usr/share/php /var/chroot/apache/usr/share/php
vi /etc/fstab and add mount points
/var/www /var/chroot/apache/var/www auto bind 0 0
/usr/share/php /var/chroot/apache/usr/share/php auto bind 0 0
/tmp /var/chroot/apache/tmp auto bind       0 0

# build var

cd /var/chroot/apache/var
mkdir run cache
mkdir /var/chroot/apache/var/cache/apache2
chown apache:root /var/chroot/apache/var/cache/apache2
cd /var/chroot/apache/var/run
mkdir mysqld
chown mysql:mysql /var/chroot/apache/var/run/mysqld

# build usr

cd /var/chroot/apache/usr/curl
cp /usr/share/curl/curl-ca-bundle.crt .
cd /var/chroot/apache/usr/misc
mkdir file
cd /var/chroot/apache/usr/file
cp /usr/share/misc/file/magic* .
cd /usr/local/src
wget http://www.acme.com/software/mini_sendmail/mini_sendmail-1.3.6.tar.gz
tar xjvf mini_sendmail-1.3.6.tar.gz
cd mini_sendmail-1.3.6
make
cd /var/chroot/apache/usr/sbin
cp /usr/local/src/mini_sendmail-1.3.6/mini_sendmail .
vi /etc/php/apache2-php4/php.ini
# add the following to php.ini
sendmail_path = /usr/sbin/mini_sendmail -t -i
cd /var/chroot/apache/usr/lib
cp /usr/lib/libmagic.so.1 .
cp /usr/lib/libnss_nis.so .
cd /var/chroot/apache/usr/bin
cp /usr/bin/file .

# build lib

cd /var/chroot/apache/lib
cp /lib/ld-linux.so.2 .
cp /lib/libc.so.6 .
cp /lib/libdl.so.2 .
cp /lib/libncurses.so.5 .
cp /lib/libnsl.so.1 .
cp /lib/libnss_compat.so.2 .
cp /lib/libnss_dns.so.2 .
cp /lib/libnss_files.so.2 .
cp /lib/libnss_nis.so.2 .
cp /lib/libz.so.1 .

# build etc

cd /var/chroot/apache/etc
cp /etc/hosts .
cp /etc/ld.so.cache .
cp /etc/localtime .
cp /etc/nsswitch.conf .
cp /etc/passwd .
vi passwd (leave bin, mail, postmaster, nobody and apache)
cp /etc/resolv.conf .
cp /etc/services .

# build dev

cd /var/chroot/apache/dev
mknod -m 444 urandom c 1 9
mknod -m 666 null c 1 3

# build bin

cd /var/chroot/apache/bin
cp /bin/sh .

# configure mysql to drop socket so apache can use it now

vi /etc/mysql/my.cnf
[client]
socket = /var/chroot/apache/var/run/mysqld/mysqld.sock

[server]
socket = /var/chroot/apache/var/run/mysqld/mysqld.sock
/etc/init.d/mysql restart
# make sure you're env knows to where it's at.. simple create alias
vi /root/.bashrc
alias mysql="mysql -S /var/chroot/apache/var/run/mysqld/mysqld.sock"

# now test

/etc/init.d/apache2 start
ps aux | grep apache (check to see if it's up)

tail /var/log/apache/error_log (look for chroot)
[Fri Apr 20 15:49:09 2007] [notice] mod_chroot: changed root to /var/chroot/apache.
[Fri Apr 20 15:49:09 2007] [notice] Apache configured -- resuming normal operations

Restart again to make sure all is good. Test your PHP mail script.

Two programs that will be absolutely invaluable are strace and ldd. When things go wrong, run apache with strace and view the output. Look for (No such file or directory) messages. These are libraries or files that are missing in the chroot. I ran into an interesting problem with fsockopen trying to connect to a host. It would fail due to getaddrname in PHP, after running strace, I discovered that it was missing DNS library (libnss_dns.so.2). Strace is to trace system calls and signals. Simple usage would be this:

strace -o mytrace -fF apache2 -D DEFAULT_VHOST -D PHP4 -D SSL -D SSL_DEFAULT_VHOST -D CHROOT

ldd shows shared libraries. When you bring in binaries into the jail and they don't work, look at their shared dependencies. For example, I wanted file to do approver look ups. I wanted to know what I need to bring in:

ldd /usr/bin/file
linux-gate.so.1 =>  (0xb7ee9000)
libmagic.so.1 => /usr/lib/libmagic.so.1 (0xb7ed1000)
libz.so.1 => /lib/libz.so.1 (0xb7ebd000)
libc.so.6 => /lib/libc.so.6 (0xb7d96000)
/lib/ld-linux.so.2 (0xb7eea000)

For `file` to work, I need these libraries in the jail. Using ldd is great!

Now while apache is running in the jail, it won't be able to 'reload'. Update your logrotate script to restart or it will die in the middle of the night!

/var/log/apache2/*log {
  missingok
  notifempty
  sharedscripts
  postrotate
  #/etc/init.d/apache2 reload > /dev/null 2>&1 || true
  /etc/init.d/apache2 restart > /dev/null 2>&1 || true
  endscript
}