AppArmor protection for your Apache (including mod_php, mod_python and others)

The biggest weak of Apache httpd web server is lack of security when using it in multiuser enviroment.
All httpd processes run under the same UID and GID which means that user JOE can create simple
php script which when run via httpd will be able to open and read other users web files (which means
that other users database passwords hidden somewhere in web configuration files are not protected).

There are some ways to protect your files:

  • FastCGI (mainly for PHP; allows to run php scripts under different privileges)
  • CGI (running PHP and other scripts through suexec)
  • nonstandard apache MPMs like peruser, metuxmpm (allow to run parts of httpd with different UID/GID; unfortunately very alpha quality)

The primary problem with all above is that performance drops dramaticly.

That’s where AppArmor comes to a rescue.
AppArmor is kind of SuSE response to SELinux. SELinux is preety good
when it comes to creating fine grained policy rules but tends to be quite complicated when it comes to writting policies for programs.
The AppArmor on the other hand is much simpler but also quite limited. AppArmor can be used to restrict file and capabilities(7)
access only. That’s enough for us – we want exactly that – limit access to parts of filesystem. 
The primary reason why we choose AppArmor over SELinux here is the ,,change hat” functionality.
It allows to define subpolicies for a program and the program is able to switch between subpolicies.
apache-mod_apparmor allows to switch subpolicy on per virtual host, directory and location basis!

How to do that?

First you need to patch you kernel with apparmor patches (these are very small and non-intrusive);
most of AppArmor lives in separate directory in kernel tree: security/apparmor so it’s not conflicting
even with grsecurity patches. You will also need to download and build apparmor-parser, apparmor-profiles
and apparmor-utils packages. All these available on AppArmor Home Page.

AppArmor keeps policy files in /etc/apparmor.d/. These are simple text files, for example /etc/apparmor.d/usr.sbin.httpd.prefork policy:

# vim:syntax=apparmor
# Last Modified: Tue Dec 12 02:37:27 2006
#include
/usr/sbin/httpd.prefork flags=(complain) {
#include
#include
/usr/sbin/httpd.prefork mr,
capability setuid,
capability setgid,
capability kill,
capability dac_override,
capability dac_read_search,

/etc/httpd/apache.conf r,
/etc/httpd/conf.d r,
/etc/httpd/conf.d/* r,
/etc/httpd/ssl/* r,
/usr/lib{,64}/apache/*.so mr,
/etc/httpd/webapps.d r,
/etc/gai.conf r,
/etc/httpd/magic r,
/etc/mime.types r,
/usr/share/file/magic* r,
/etc/openssl/** r,

/var/log/httpd/** w,
/var/log/archive/httpd/* w,

/etc/php4/** r,
/usr/lib{,64}/php4/*.so mr,
/etc/php/** r,
/usr/lib{,64}/php/*.so mr,

/var/run/httpd.pid rw,
/var/run/httpd/** rw,

/proc/[0-9]*/attr/current rw,
/etc/snmp/** r,
/usr/share/snmp/** r,

/usr/share/perl5/** r,
/usr/lib{,64}/perl5/** r,
/usr/lib{,64}/perl5/**.so* mr,

/usr/X11R6/lib{,64}/lib*.so* mr,

^HANDLING_UNTRUSTED_INPUT flags=(complain) {
/home/services/httpd/** r,
/var/log/httpd/** w,
/var/log/archive/httpd/* w,
/home/users/**/.htaccess r,
}

^HAT_no_access flags=(complain) {
/home/services/httpd/** r,
/var/log/httpd/** w,
/var/log/archive/httpd/* w,
}

#include
}

r – read, w – write, ix – inherited policy on execution, * – simple globbing, ** – glob that also matches slash character (there is more of these of course – see man apparmor.d(5) for details).

HANDLING_UNTRUSTED_INPUT and HAT_no_access are HATs configuration (HAT is previously mentioned subpolicy). By default mod_apparmor runs in HANDLING_UNTRUSTED_INPUT hat. That hat can be changed from configuration file for example:

AADefaultHatName HAT_no_access


AADefaultHatName HAT_domain_org
[…]


AADefaultHatName HAT_other_domain_com
[…]

will cause that mod_apparmor sets appropriate hat on per virtual host basis (as mentioned earlier we can use AADefaultHatName in Location and Directory directives, too).

Now we need to sets policies for these new vhost hats, but first we will put common rules into single abstraction/httpd-hat file that later will be used in HAT policies:

#include

capability setuid,
capability setgid,

/proc/[0-9]*/mounts r,
/proc/filesystems r,

/home/services/httpd/** r,
/var/log/httpd/** w,
/var/log/archive/httpd/* w,

/usr/lib{,64}/perl5/** r,
/usr/lib{,64}/perl5/**.so* mr,

/etc/mysql/mysql-client.conf r,
/etc/services r,
/etc/protocols r,
/etc/nsswitch.conf r,
/etc/hosts r,
/etc/host.conf r,
/etc/resolv.conf r,
/etc/mtab r,
/etc/fstab r,
/etc/xml/* r,
/etc/fonts/** r,

/usr/share/** r,

/var/cache/fontconfig/* r,
/var/run/php r,
/var/run/php/** rw,
/var/run/nscd/socket rw,
/tmp r,
/tmp/** rwl,

/bin/* ixr,
/usr/bin/* ixr,

/usr/lib{,64}/lib*.so* mr,
/usr/X11R6/lib{,64}/lib*.so* mr,

/usr/lib{,64}/ImageMagick-** r,
/usr/lib{,64}/ImageMagick-**.so* mr,

and finally HAT policies in abstractions/httpd-users:

^HAT_domain_org {
#include
/home/users/web-pages/domain_org rw,
/home/users/web-pages/domain_org/** rw,
/home/users/web-pages/domain_org/cgi-bin/** ixrw,
}

^HAT_other_domain_com {
#include
/home/users/web-pages/other_domain_com rw,
/home/users/web-pages/other_domain_com/** rw,
/home/users/web-pages/other_domain_com/cgi-bin/** ixrw,
}

That’s all. We load policy using rcapparmor init script (/etc/rc.d/init.d/apparmor in PLD/Linux). We can put profile into complain mode (everything is logged but no restriction is in effect) or in enforce mode (apparmor will enforce profile and log rejects). Example /var/log/audit/audit.log:

type=UNKNOWN[1500] msg=audit(1166014976.862:130983): REJECTING r access to /var/cache/fontconfig/2ee5dd3f6641dbe23533346fa3fce51a-x86-64.cache-2 (convert(13663) pro
file /usr/sbin/httpd.prefork active HAT_domain_org)
type=UNKNOWN[1500] msg=audit(1166015781.907:130984): REJECTING r access to /etc/fonts/conf.avail/20-fix-globaladvance.conf (convert(17219) profile /usr/sbin/httpd.p
refork active HAT_domain_org)
type=UNKNOWN[1500] msg=audit(1166015781.907:130985): REJECTING r access to /etc/fonts/conf.avail/20-lohit-gujarati.conf (convert(17219) profile /usr/sbin/httpd.pref
ork active HAT_domain_org)
[…]
type=UNKNOWN[1500] msg=audit(1166016116.536:131037): REJECTING r access to /var/cache/fontconfig/2ee5dd3f6641dbe23533346fa3fce51a-x86-64.cache-2 (convert(17596) pro
file /usr/sbin/httpd.prefork active HAT_other_domain_com)
type=UNKNOWN[1500] msg=audit(1166016139.393:131038): REJECTING r access to /var/tmp (httpd.prefork(7442) profile /usr/sbin/httpd.prefork active HAT_other_domain_com)
type=UNKNOWN[1500] msg=audit(1166016139.405:131039): REJECTING r access to /var/tmp (httpd.prefork(7442) profile /usr/sbin/httpd.prefork active HAT_other_domain_com)
type=UNKNOWN[1500] msg=audit(1166016139.429:131040): REJECTING r access to /var/tmp (httpd.prefork(7442) profile /usr/sbin/httpd.prefork active HAT_other_domain_com)

This log file is very usefull when creating policy (of course apparmor provides some tools that will create policy for you by parsing log file but I was doing everything manually with vim in one hand and tail in second).

Note that .htaccess checking is done at HANDLING_UNTRUSTED_INPUT level, before vhost HAT is applied.

ps. you will probably need to pass capability.disable=1 selinux=off when booting kernel. Otherwise apparmor won’t even load.