Centos7 - Apache Chroot Jail


One method of hardening apache in centos7 is by running apache in a chroot jail.

We are going to use modsecurity to enforce the chroot jail, using the SecChrootDir command. Then, where possible, we will use read-only directory bindmounts to expose the system files apache needs to run to the jail.
#first we're going to install and configure apache 2.4 in centos7
yum install httpd mod_security -y;
 
#disable some apache modules
sed -i "s/^/### X ###/" /etc/httpd/conf.d/autoindex.conf
sed -i "s/^/### X ###/" /etc/httpd/conf.d/userdir.conf
sed -i "s/^/### X ###/" /etc/httpd/conf.d/welcome.conf
sed -i "s/^/### X ###/" /etc/httpd/conf.d/mod_security.conf
cat << EOT >> /etc/httpd/conf.d/mod_security.conf
<IfModule mod_security2.c>
    SecChrootDir "/var/chroot"
</IfModule>
EOT
 
#disable references to /var/www
sed -i '/^<Directory ".var.www.html">/,/^<\/Directory>/ s/^/### X ###/'    /etc/httpd/conf/httpd.conf
sed -i '/^<IfModule alias_module>/,/^<\/IfModule>/ s/^/### X ###/'         /etc/httpd/conf/httpd.conf
sed -i '/^<Directory ".var.www.cgi-bin">/,/^<\/Directory>/ s/^/### X ###/' /etc/httpd/conf/httpd.conf
sed -i '/^<Directory ".var.www">/,/^<\/Directory>/ s/^/### X ###/'         /etc/httpd/conf/httpd.conf
sed -i 's|^DocumentRoot.*|### X ###DocumentRoot "/var/www/html/|'          /etc/httpd/conf/httpd.conf
 
cat << EOT >> /etc/httpd/conf/httpd.conf
ServerName localhost
<Directory "/var/chroot">
    AllowOverride None
    Require all granted
</Directory>
TraceEnable off
ServerSignature Off
Include /etc/httpd/sites.d/*.conf
EOT
 
#setup an example virtualhost
mkdir -p /etc/httpd/sites.d/
cat << 'EOT' >> /etc/httpd/sites.d/www.example.com.conf
<VirtualHost *:80>
    ServerName www.example.com
    ServerAlias example.com
    DocumentRoot /var/chroot/www.example.com
    ErrorLog  /var/log/httpd/error.log
    CustomLog /var/log/httpd/access.log combined
</VirtualHost>
EOT
mkdir -p /var/chroot/www.example.com
 
#for selinux management, if needed
yum install policycoreutils-python -y;
semanage fcontext -a -s system_u -t httpd_config_t      '/etc/httpd/sites.d(/.*)?'
restorecon -R  /etc/httpd/sites.d
semanage fcontext -a -s system_u -t httpd_sys_content_t '/var/chroot/www.example.com(/.*)?'
restorecon -R  /var/chroot/www.example.com
 
##many websites need these selinux options
#setsebool -P httpd_can_network_connect_db 1
#setsebool -P httpd_can_network_connect 1
#setsebool -P httpd_can_sendmail 1
 
##you may also want to install mod_ssl
#yum install mod_ssl -y;


#And now... lets build the chroot
mkdir -p /var/chroot/etc/
cat /etc/passwd |egrep 'root:x|apache|nobody' > /var/chroot/etc/passwd
cat /etc/group  |egrep 'root:x|apache|nobody' |sed 's|bin/bash|sbin/nologin|' > /var/chroot/etc/group
cat /etc/shadow |egrep 'root:x|apache|nobody' > /var/chroot/etc/shadow
chmod 000 /var/chroot/etc/shadow
 
mkdir -p /var/chroot/etc/httpd/
mkdir -p /var/chroot/etc/pki/
mkdir -p /var/chroot/var/log/httpd
mkdir -p /var/chroot/run/httpd
ln -s ../../run/httpd /var/chroot/etc/httpd/run
ln -s ../../var/log/httpd /var/chroot/etc/httpd/logs
 
mkdir -p /var/chroot/{dev,etc,tmp,usr,log,var}
mkdir -p /var/chroot/usr/{bin,lib,lib64,libexec,local,sbin,src,share}
mkdir -p /var/chroot/usr/share/misc/
mkdir -p /var/chroot/usr/lib/locale
mkdir -p /var/chroot/run/{httpd,systemd}
ln -s usr/lib /var/chroot/lib
ln -s usr/lib64 /var/chroot/lib64
ln -s usr/sbin /var/chroot/sbin
ln -s usr/bin /var/chroot/bin
ln -s .. /var/chroot/var/chroot
ln -s ../tmp /var/chroot/var/tmp
chmod 1777 /var/chroot/tmp
 
mkdir -p /var/chroot/dev
mknod -m 666 /var/chroot/dev/null c 1 3
mknod -m 666 /var/chroot/dev/urandom c 1 9
mknod -m 666 /var/chroot/dev/zero c 1 5
#add more /dev paths here if your application requires them
 
cat << EOF >> /etc/fstab
##systemd doesn't like the readonly bindmount here, so just do a regular bindmount for now
/run/systemd              /var/chroot/run/systemd      none  bind            0 0
/etc/pki                  /var/chroot/etc/pki          none  bind            0 0
/usr/lib/locale           /var/chroot/usr/lib/locale   none  bind            0 0
/usr/lib64                /var/chroot/usr/lib64        none  bind            0 0
/usr/share                /var/chroot/usr/share        none  bind            0 0
##if you add an entry here, also add in /root/bin/chroot-init.sh
EOF
 
#will run on boot
cat << EOF > /etc/systemd/system/multi-user.target.wants/chroot.service
[Unit]
Description=chroot init service
[Service]
Type=Idle
ExecStart=/root/bin/chroot-init.sh
[Install]
WantedBy=multi-user.target
EOF
 
#script to remount bindmounts as read-only
mkdir -p /root/bin/
cat << 'EOF' > /root/bin/chroot-init.sh
#!/bin/bash
# used by /etc/systemd/system/chroot.service
mount -a
mount -o remount,ro /var/chroot/run/systemd
mount -o remount,ro /var/chroot/etc/pki
mount -o remount,ro /var/chroot/usr/lib/locale 
mount -o remount,ro /var/chroot/usr/lib64
mount -o remount,ro /var/chroot/usr/share
#cp files instead of readonly bind mount
for i in {hosts,resolv.conf,nsswitch.conf,localtime}; do 
    cp -f /etc/$i /var/chroot/etc/$i
done
#if you need to add files to you chroot jail, add more cp commands here
EOF
chmod 755 /root/bin/chroot-init.sh
/root/bin/chroot-init.sh
 
systemctl stop httpd
systemctl start httpd
systemctl enable httpd
systemctl restart httpd
 
#if you're running selinux you may want to run these...
semanage fcontext -a -s system_u -t etc_t               '/var/chroot/etc(/.*)?'
semanage fcontext -a -s system_u -t cert_t              '/var/chroot/etc/pki(/.*)?'
semanage fcontext -a -s system_u -t httpd_config_t -f f '/var/chroot/etc/httpd'
semanage fcontext -a -s system_u -t shadow_t       -f f '/var/chroot/etc/shadow'
semanage fcontext -a -s system_u -t passwd_file_t  -f f '/var/chroot/etc/passwd'
semanage fcontext -a -s system_u -t passwd_file_t  -f f '/var/chroot/etc/group'
semanage fcontext -a -s system_u -t net_conf_t     -f f '/var/chroot/etc/hosts'
semanage fcontext -a -s system_u -t net_conf_t     -f f '/var/chroot/etc/resolv.conf'
restorecon -R  /var/chroot/etc
 
semanage fcontext -a -s system_u -t device_t              '/var/chroot/dev(/.*)?'
semanage fcontext -a -s system_u -t urandom_device_t -f c '/var/chroot/dev/urandom'
semanage fcontext -a -s system_u -t null_device_t -f c    '/var/chroot/dev/null'
semanage fcontext -a -s system_u -t zero_device_t -f c    '/var/chroot/dev/zero'
restorecon -R  /var/chroot/dev
 
semanage fcontext -a -s system_u -t httpd_var_run_t     '/var/chroot/run/httpd(/.*)?'
semanage fcontext -a -s system_u -t bin_t               '/var/chroot/usr/bin(/.*)?'
semanage fcontext -a -s system_u -t var_log_t           '/var/chroot/log(/.*)?'
semanage fcontext -a -s system_u -t tmp_t               '/var/chroot/tmp(/.*)?'
semanage fcontext -a -s system_u -t httpd_var_run_t     '/var/chroot/run/httpd(/.*)?'
restorecon -R /var/chroot/run/httpd
restorecon -R /var/chroot/log
restorecon -R /var/chroot/tmp
restorecon -R /var/chroot/usr/bin
semanage fcontext -l
getsebool -a
getenforce


#now, lets prove the jail is working....
yum install php curl -y
systemctl restart httpd
cat << EOF > /var/chroot/www.example.com/chroot.php
<?php 
print_r(glob("/*"));
?>
EOF
 
curl 127.0.0.1/chroot.php
#should output something like...
Array
(
    [0] => /bin
    [1] => /dev
    [2] => /etc
    [3] => /lib
    [4] => /lib64
    [5] => /log
    [6] => /run
    [7] => /sbin
    [8] => /tmp
    [9] => /usr
    [10] => /var
    [11] => /www.example.com
)

By using read-only bindmounts it allows the chroot jail libraries to stay in sync with the system libraries whenever the host OS is patched or updated. Depending on what apache modules or php extensions you use, you may find you need to add additional system files to your chroot jail. For DIRs the simplest way is to add the dirs to /etc/fstab and /root/bin/chroot-init.sh. For files, add cp commands to /root/bin/chroot-init.sh, and then. For instance, php exec() uses /bin/sh internally, so if your application requires that... you'd need to cp /bin/sh to /var/chroot/bin/sh.


code snippets are licensed under Creative Commons CC-By-SA 3.0 (unless otherwise specified)