Securing LAMP
Jesus Oquendo
echo @infiltrated|sed 's/^/sil/g;s/$/.net/g'
http://pgp.mit.edu:11371/pks/lookup?op=get&search=0x1383A743

Hopefully this document will provide a proven method to configure, test and run a Secure LAMP environment. LAMP refers to a set of programs commonly used together to run dynamic interactive web sites and servers. Usually it is based on Linux, Apache, MySQL and PHP, Perl or Python. (for those who don't already know this...) It will also explain how to use mod_security as an IPS. This document is not particulalry intended for inexperienced admins, not that it has anything uber technical in it, it is not written and as a cartoony how to filled with sEcUrE mY bAwX kind of content. It is written to explain something specific and offer some examples... If you're looking for a more intricate "How To For Dummies", I suggest you Google it.

Most LAMP configurations and write-ups I've come across have solely instructed people on the configuration of the AMP portion of "LAMP". Seems as if some have forgotten what it means to secure their webserver which is humorous to me when I see email on security lists about sites being owned, XSS garbage, etc. XSS? Non existent to me. I don't care what someone wants to try.

Many times administrators often forget to do security checks from the ground up. They often will rely on simple methods of testing a machine. An NMAP scan here, a Metasploit scan there... Let's build a secure LAMP machine from scratch shall we. Here is what I've down to harden my "LAMP" servers. I started with Scientific Linux for this write-up:

[root@armada etc]# uname -a
Linux armada 2.6.9-34.EL #1 Mon Mar 13 11:31:17 CST 2006 i686 athlon i386 GNU/Linux
The server solely needs to run Apache, MySQL, and PHP. There should be nothing else starting up on this machine. It does not connect to a printer, it is not networked to another machine, its sole purpose, push interactive webpages.

The machine will always start in runlevel3 so let.s take a look at what is currently starting and minimize it

[root@armada ~]# cd /etc/
[root@armada etc]# ls rc3.d/
K02NetworkManager  K24irda          K50snmpd      K84ospfd      S10network     	    S25netfs       S56rawdevices  S97rhnsd
K05saslauthd       K25squid         K50snmptrapd  K84ripd       K99readahead_early  S12syslog      S26apmd        S56xinetd
K10dc_server       K30spamassassin  K50tux        K84ripngd     S00microcode_ctl    S13irqbalance  S26gfs         S80sendmail    
K10psacct          K35smb           K54dovecot    K85mdmpd      S05kudzu            S13portmap     S26lm_sensors  S85gpm         
K12dc_client       K35vncserver     K73ypbind     K85zebra      S06cpuspeed         S14nfslock     S28autofs      S90crond
K15httpd           K35winbind       K74nscd       K87auditd     S08arptables_jf     S15mdmonitor   S40smartd      S90xfs
K15vocald          K36lisa          K74ntpd       K89netplugd   S08iptables         S18rpcidmapd   S44acpid       S95anacron
K20afs             K36mysqld        K84bgpd       K90bluetooth  S09isdn             S19rpcgssd     S55cups        S95atd
K20nfs             K50netdump       K84ospf6d     K94diskdump   S09pcmcia           S20ccsd        S55sshd        S97messagebus

Quite a few programs don.t you think. After I'm done modifying this directory the output will be:

[root@armada etc]# ls rc3.d/
K25squid	S12syslog	S90crond
K15httpd	S08iptables	K36mysqld
K94diskdump	S09pcmcia	S55sshd
S97messagebus

K02NetworkManger - Not needed. My IP Addressing information is statically assigned
K24irda - There are no infrared devices on this server
K50snmpd - I'm not taking snmp traps of this machine
K84opsfd - Nope. Not running Zebra

And the rest you ask? Hopefully you will get the picture. I don't want these other programs starting so I placed them in a safe place outside of the rc*.d directories. This included going into other rc directories and moving them elsewhere so they won't start. Yes I could have done a chkconfig PROGRAM_NAME but I want to make sure they're way away from the normal directory. One just never knows who's going to attack what, how, or where.

So many services so little time.

Same rule as above applies. What services am I running? Do I truly need X service? No. No need for it to be there. /etc/services gets butchered as wel.

hosts.allow and hosts.deny?

Have these files have been forgotten nowadays. I still use them in scripts. For example, I have a simple sshd brute force blocking script written in nine lines:

#!/bin/sh

if [ -e /tmp/hosts.deny ]
then
rm /tmp/hosts.deny
fi

awk '/error retrieving/{getline;print $13}' /var/log/secure|sort -ru >> /tmp/hosts.deny
diff /etc/hosts.deny /tmp/hosts.deny|grep ">"|awk '{print $2}' >> /etc/hosts.deny

What is does is looks at /var/log/secure searches out those trying to get in and automatically blocks them into /etc/hosts.deny. I could.ve add them to an ipf or IPTables rule but I didn.t want to type much. This is just as effective for me. I can modify this for modsecurity as well.

Do not pass go ... Go directly to jail

It would take me yet another chapter in a book to write about Jail/Chroot so instead I offer readers: "Chroot jail HOWTO for Linux"... Apache, MySQL, they're all jailed...

http://www.oreillynet.com/cs/user/view/cs_msg/23694

I could have included my own entire write-up by why re-invent the wheel.

Configuring SLAMP

In order to minimize this document from becoming way too long and an eyesore here are my command line arguments for installing the necessary programs. Hopefully those reading this are somewhat established in using at minimum a *nix variant with administrative experience.

[root@armada ~]# mkdir slamp
[root@armada ~]# cd slamp/

Getting all of the programs I will need for this.

[root@armada slamp]# wget http://apache.mirrors.redwire.net/httpd/httpd-2.0.59.tar.gz
[root@armada slamp]# wget http://www.openssl.org/source/openssl-0.9.8d.tar.gz
[root@armada slamp]# wget http://www.modsecurity.org/download/modsecurity-apache_2.0.3.tar.gz
[root@armada slamp]# wget http://us3.php.net/get/php-5.1.6.tar.gz/from/us2.php.net/mirror
[root@armada slamp]# wget 
http://dev.mysql.com/get/Downloads/MySQL-5.0/mysql-standard-5.0.24a-linux-i686.tar.gz/from/http://mirror.trouble-free.net/mysql_mirror/
[root@armada slamp]# for x in `ls` ; do tar -zxvf $i ; done
[root@armada slamp]# cd openssl-0.9.8d
[root@armada openssl-0.9.8d]# ./config --prefix=/usr/openssl
[root@armada openssl-0.9.8d]# make ; make install ; make clean
[root@armada openssl-0.9.8d]# cd ../httpd-2.0.59
[root@armada httpd-2.0.59]# ./configure --prefix=/usr/local/apache --enable-module=rewrite \
    --enable-unique-id \
    --enable-ssl \
    --enable-rewrite \
    --enable-so \
    --with-ssl=/usr/openssl ; make ; make install ; make clean
[root@armada httpd-2.0.59]# cd ../php-5.1.6 
[root@armada php-5.1.6]# ./configure --with-apxs2=/usr/local/apache/bin/apxs --with-mysql ; make ; make install
[root@armada php-5.1.6]# cd ../mysql-standard-5.0.24a-linux-i686/
[root@armada mysql-standard-5.0.24a-linux-i686]# ./configure
[root@armada mysql-standard-5.0.24a-linux-i686]# Starting mysqld daemon with databases from /root/slamp/mysql-standard-5.0.24a-linux-i686/data
STOPPING server from pid file /root/slamp/mysql-standard-5.0.24a-linux-i686/data/armada.pid
061027 16:12:20  mysqld ended
[root@armada mysql-standard-5.0.24a-linux-i686]# cd /usr/local/apache/conf
[root@armada conf]# mkdir ssl.crt ; cd ssl.crt
[root@armada ssl.crt]# openssl genrsa 4096 > sci.key
Generating RSA private key, 4096 bit long modulus
...
e is 65537 (0x10001)
[root@armada ssl.crt]# openssl genrsa 4096 > sci.key
[root@armada ssl.crt]# openssl req -new -x509 -nodes -sha1 -days 365 -key sci.key > sci.crt
[root@armada ssl.crt]# openssl x509 -noout -fingerprint -text < sci.crt > sci.info
[root@armada apache2]# /usr/local/apache/bin/apachectl startssl
At this point most of the work is done. Some editing here and there to install my certificates and I now test https
[root@armada ssl.crt]# lsof|grep https
httpd     21420    root    4u     IPv6     674316                 TCP *:https (LISTEN)
httpd     21421  nobody    4u     IPv6     674316                 TCP *:https (LISTEN)
httpd     21422  nobody    4u     IPv6     674316                 TCP *:https (LISTEN)
httpd     21423  nobody    4u     IPv6     674316                 TCP *:https (LISTEN)
httpd     21424  nobody    4u     IPv6     674316                 TCP *:https (LISTEN)
httpd     21425  nobody    4u     IPv6     674316                 TCP *:https (LISTEN)
[root@armada ssl.crt]# /usr/local/apache/bin/apachectl stop
[root@armada ssl.crt]# cd ~slamp/modsecurity-apache_2.0.3/apache2
EDITED MAKEFILE
[root@armada apache2]# make ; make install
[root@armada apache2]# nano /usr/local/apache/conf/httpd.conf
ADDED:
LoadModule security2_module modules/mod_security2.so
LoadFile /usr/lib/libxml2.so
[root@armada apache2]# /usr/local/apache/bin/apachectl startssl
[root@armada apache2]# lsof|grep https
httpd     25045    root    4u     IPv6     677603                 TCP *:https (LISTEN)
httpd     25046  nobody    4u     IPv6     677603                 TCP *:https (LISTEN)
httpd     25047  nobody    4u     IPv6     677603                 TCP *:https (LISTEN)
httpd     25048  nobody    4u     IPv6     677603                 TCP *:https (LISTEN)
httpd     25049  nobody    4u     IPv6     677603                 TCP *:https (LISTEN)
httpd     25050  nobody    4u     IPv6     677603                 TCP *:https (LISTEN)
[root@armada apache2]# cd
[root@armada root]# nmap -sS -sV -sR -O -v -v 192.168.1.111

Starting nmap 3.70 ( http://www.insecure.org/nmap/ ) at 2006-10-28 13:06 EDT
ommitted unnecessary NMAP output

PORT     STATE SERVICE              VERSION
22/tcp   open  ssh                  OpenSSH 3.9p1-hpn (protocol 1.99)
80/tcp   open  http                 Apache httpd 2.0.59 ((Unix) mod_ssl/2.0.59 OpenSSL/0.9.8d PHP/5.1.6)
443/tcp  open  http                 Apache httpd 2.0.59 ((Unix) mod_ssl/2.0.59 OpenSSL/0.9.8d PHP/5.1.6)
Device type: general purpose
Running: Linux 2.4.X|2.5.X|2.6.X
OS details: Linux 2.4.0 - 2.5.20, Gentoo 1.2 linux (Kernel 2.4.19-gentoo-rc5), Linux 2.4.20, Linux 2.4.20 - 2.4.22 w/grsecurity.org patch, Linux 
2.5.25 - 2.6.3 or Gentoo 1.2 Linux 2.4.19 rc1-rc7)
OS Fingerprint:
T1(Resp=Y%DF=Y%W=7FFF%ACK=S++%Flags=AS%Ops=MNNTNW)
T2(Resp=N)
T3(Resp=Y%DF=Y%W=7FFF%ACK=S++%Flags=AS%Ops=MNNTNW)
T4(Resp=Y%DF=Y%W=0%ACK=O%Flags=R%Ops=)
T5(Resp=Y%DF=Y%W=0%ACK=S++%Flags=AR%Ops=)
T6(Resp=Y%DF=Y%W=0%ACK=O%Flags=R%Ops=)
T7(Resp=Y%DF=Y%W=0%ACK=S++%Flags=AR%Ops=)
PU(Resp=Y%DF=N%TOS=C0%IPLEN=164%RIPTL=148%RID=E%RIPCK=E%UCK=E%ULEN=134%DAT=E)


Nmap run completed -- 1 IP address (1 host up) scanned in 19.088 seconds

Everything is going according to planned. At this point I need to configure my modsecurity rules into httpd.conf, my rules are extreme so I won't include them in this document but I will provide an excellent link with rules at the end of this document.

MODSECURITY as in IPS

One thing I have done with modsecurity is, I've used its exec function in a method that I have not seen written about. Modsecurity as an HTTP IPS! I can say I've turned modsecurity into an HTTP based IPS of sorts.

Here is an example rule I have in my configuration:

SecFilterDefaultAction "deny,log,status:403,exec:/usr/local/apache/bin/modsecips"

When I assign the exec:/usr/local/apache/bin/modsecips to my rules it executes a script I wrote to block attackers on the fly. Pretty neat if you ask me. When the modsecips script is called it checks the audit logs and blocks the offender. Here's how it works exactly... Inside of my modsecurity statement, I added a line with the argument # attacker. The purpose of this line is for the script to insert a new rule in the correct place...

#!/bin/sh
# MODSECIPS
# simple script to redirect HTTP offenders
# J. Oquendo (c) 2006 Infiltrated.net

tail -n 20 /usr/local/apache2/logs/modsec_audit.log \
awk '/403/ && /Request/{print "perl -pi -e '\''s/# attacker/# attacker\nSecFilterSelective REMOTE_ADDR "$3" nolog,redirect:http:\\/\\/sans.org/g'\'' 
/usr/local/apache2/conf/httpd.conf"}'|sh
/usr/local/apache2/bin/apachectl restart

Here is a step by step break-down of the script. On execution it is going to print the last twenty lines mod security sees as 403 errors. It prints only the offender's IP address and passes that off to a perl inline replacement command that adds a specific modsecurity rule to httpd.conf and restarts Apache.

Here it is in action step by step:

$ tail -n 20 /usr/local/apache2/logs/modsec_audit.log|awk '/403/ && /Request/{print $3}'
217.67.229.133
$ tail -n 20 /usr/local/apache2/logs/modsec_audit.log|\
awk '/403/ && /Request/\
{print "perl -pi -e '\''s/# attacker/# attacker\\nSecFilterSelective REMOTE_ADDR "$3" nolog,redirect:http:\\/\\/sans.org/g'\'' httpd.conf"}'
perl -pi -e 's/# attacker/# attacker\nSecFilterSelective REMOTE_ADDR 217.67.229.133 nolog,redirect:http:\/\/sans.org/g' /usr/local/apache2/conf/httpd.conf
Piping the perl output above to be executed, places the following entry into my httpd.conf file right under the line with the # attacker statement:
SecFilterSelective REMOTE_ADDR 217.67.229.133 nolog,redirect:http://sans.org
And how do I know this for sure...
$ awk '/attacker/{getline;print}' httpd.conf
SecFilterSelective REMOTE_ADDR 217.67.229.133 nolog,redirect:http://sans.org
I can see it in my httpd.conf file. So how about remodifying the sshbrute force? Sure why not. The modsecips script above simply redirects, but it can easily by modified to be applied into your firewall in this case hosts.deny.
#!/bin/sh
# J. Oquendo
# modsecips.denies

tail -n 20 /usr/local/apache2/logs/modsec_audit.log \
awk '/403/ && /Request/{print $4}' >> /tmp/hosts.deny
diff /etc/hosts.deny /tmp/hosts.deny|grep ">"|awk '{print $2}' >> /etc/hosts.deny
It's that simple... Now some may think that Apache will go bonkers restarting because of someone continously attacking (scanning) but this isn't the case. Once modsecurity sees the rule with offenders IP address, they're not going anywhere else... Now I take the time to patch up the machine a bit more, (chkrootkit, Titan (modified brutally), Nagios, etc) and its a wrap||rap.


http://www.modsecurity.org
http://leavesrustle.com/tools/modsecurity/
http://www.gotroot.com/downloads/ftp/mod_security/


perl -e 'print $i=pack(c5,(40*2),sqrt(7600),(unpack(c,Q)-3+1+3+3-7),oct(104),10,oct(101));'