In this introductory article, I will present several methods for helping to secure DNS and Web Servers. I am using BIND 9.9.4 and Apache HTTPD 2.4.6 on CentOS 6.4. BIND and HTTPD are by far the most common DNS and Web Server platforms. I will show how to build the software from source, as well as perform an initial secure configuration. I will presume a minimal installation of CentOS 6.4. If you’re using another Linux or UNIX variant, you will need to adjust the procedures accordingly. This article presumes that the reader is already familiar with DNS and Web Servers, particularly BIND and HTTPD.
Fully hardening these services is a very complex subject and we only touch on the major aspects of configuration below.
Planning your installation
It is best to perform the software installation on dedicated filesystems in order to contain any damage should a software component become compromised. Logical volume management makes this easy. For example, on the system I’m installing to, /usr/local and /var/log are separate filesystems, as will be /var/chroot. Ensure that the operating system is fully patched prior to commencing the installation. You should also ensure that appropriate security hardening has been performed on your host operating system. There are hardening procedures for a wide range of UNIX and Linux operating systems available at (http://cisecurity.org) that may be fit for your purposes.
Whilst the following examples are performed on a single test machine, in Production you should only use one service per server. This is not the best approach from a security perspective as it leaves multiple avenues open on a server for compromise – exploit HTTPD and you’ve potentially got access to exploit BIND, for instance. It also leaves the fallout from a Denial-of-Service attack on one service to bring down any others on the server.
Using many physical machines for relatively low-resource hungry services is not cost-effective. A virtualisation mechanism such as KVM (Kernel Virtual Machine) on Linux and Zones or Logical Domains on Solaris could be used to partition a physical host to serve multiple services in a more secure fashion.
You should also plan for high-availability. The exploitation of a single webserver, for example, shouldn’t bring down your entire web site or application. Clustering and/or load-balancing are two mechanisms that can be used to implement such high-availability.
Multiple levels of firewalling should also be used. Network-based firewalls as well as host-based (e.g. iptables) can be used in combination with the steps outlined below to provide a defence-in-depth type approach to security.
Compile from source
To get the latest security updates, it’s worth considering compiling your software from the latest source code. Doing this, there is a danger of introducing stability issues (due to bleeding-edge functionality), so you should also bear that in mind. You can always use your system’s packaging system to “roll-your-own” packages to distribute your compiled software in an efficient and manageable way. In this article, I’ll be covering compilation from source, but will not cover packaging as it is dependant on the UNIX/Linux variant being used. Compiling from source will also leave you with leaner binaries, and you can compile-in the bare support required, which again closes off security issues with certain modules. For example, if you don’t need proxy support for your Apache instance, don’t bother compiling it in.
First, we need to configure the build environment by installing the appropriate packages. On CentOS, as with other RHEL-derivatives, we will use yum:
|
1 |
# yum install -y gcc gcc-c++ make openssl-devel perl pcre-devel |
This will install the gcc and g++ compilers (for C and C++ respectively), as well as make, and all the required dependencies. We also install the OpenSSL development libraries, as these are required for some aspects of BIND functionality that we want (for example, DNSSEC). Perl is also required for BINDs internal symtables. pcre-devel is required for HTTPD.
Next, download the latest BIND, HTTPD, APR and APR-util source tarballs. BIND can be obtained from http://www.isc.org, APR and APR-util from http://apr.apache.org and HTTPD from http://httpd.apache.org. I will presume that both have been downloaded to /usr/local/src on our DNS/Web Server, and that your unprivileged user has write permissions to this directory.
|
1 2 3 4 5 6 |
$ ls -l /usr/local/src total 13620 -rw-------. 1 toki toki 772927 Nov 5 19:08 apr-1.4.8.tar.bz2 -rw-------. 1 toki toki 693258 Nov 5 19:08 apr-util-1.5.2.tar.bz2 -rw-------. 1 toki toki 7513017 Nov 5 16:28 bind-9.9.4.tar.gz -rw-------. 1 toki toki 4949897 Nov 5 16:28 httpd-2.4.6.tar.bz2 |
We will start with BIND, so extract the tarball:
|
1 2 |
$ cd /usr/local/src $ tar xzf bind-9.9.4.tar.gz |
Next, we need to configure BIND as with any other standard software compilation routine – we use configure to prepare the build, specify which options we want (and which we don’t), prior to performing the make and actually compiling the software.
The configure and build should be performed as a non-privileged user. You can run ./configure --help to obtain a list of available options. Let’s change to the appropriate directory, and run configure:
|
1 2 |
$ cd /usr/local/src/bind-9.9.4 $ ./configure --prefix=/usr/local/bind-9.9.4 --enable-threads |
We enable threads, and specify an installation prefix of /usr/local/bind-9.9.4. This will be symlinked to /usr/local/named, giving us an easy method of upgrade. We can compile and install new versions of the software side-by-side to the existing installation, and then just update the symlink. A rollback then just involves changing the symlink back to its original state. At the end of the configure, a report is displayed showing what we enabled, and what we didn’t enable:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
======================================================================== Configuration summary: ------------------------------------------------------------------------ Optional features enabled: Multiprocessing support (--enable-threads) GSS-API (--with-gssapi) Print backtrace on crash (--enable-backtrace) Use symbol table for backtrace, named only (--enable-symtable) Dynamically loadable zone (DLZ) drivers: None Features disabled or unavailable on this platform: Fixed RRset order (--enable-fixed-rrset) Automated Testing Framework (--with-atf) AAAA filtering (--enable-filter-aaaa) PKCS#11/Cryptoki support (--with-pkcs11) GOST algorithm support (--with-gost) ECDSA algorithm support (--with-ecdsa) Python tools (--with-python) XML statistics (--with-libxml2) ======================================================================== |
If you require other features, run configure again to enable them. Now, we can actually compile the software with make:
|
1 |
$ make |
This may take a while, depending on your system. Next, install the compiled software to the location we specified with --prefix:
|
1 |
$ su -c “make install” |
Finally, create a symlink as previously outlined:
|
1 |
$ su -c "ln -s /usr/local/bind-9.9.4 /usr/local/named" |
Check that the installation was successful:
|
1 2 3 4 |
$ /usr/local/named/sbin/named -V BIND 9.9.4 (Extended Support Version) <id:8f9657aa> built with \ '--prefix=/usr/local/bind-9.9.4' '--enable-threads' using OpenSSL version: OpenSSL 1.0.0 29 Mar 2010 |
BIND is now built and is ready for configuration. We will move on to compiling Apache next, and then return to configuring and securing our BIND installation.
Prior to building HTTPD, the Apache Portable Runtime needs to be installed. The following process achieves this:
|
1 2 3 4 5 6 7 8 9 10 |
$ cd /usr/local/src $ tar xjf apr-1.4.8.tar.bz2 $ cd apr-1.4.8 $ ./configure --prefix=/usr/local/apr-1.4.8 && make && su -c "make install" $ su -c "ln -s /usr/local/apr-1.4.8 /usr/local/apr" $ tar xjf apr-util-1.5.2.tar.bz2 $ cd apr-util-1.5.2 $ ./configure --prefix=/usr/local/apr-util-1.5.2 \ > --with-apr=/usr/local/apr && make && su -c "make install" $ su -c "ln -s /usr/local/apr-util-1.5.2 /usr/local/apr-util" |
Next, we can compile Apache HTTPD using the same configure, make, make install process. Let’s extract the software:
|
1 2 |
$ cd /usr/local/src $ tar xjf httpd-2.4.6.tar.bz2 |
Configure the software. I will enable a few features for our Apache HTTPD instance. I’ll enable mod_rewrite and a few proxy modules. I’ll also enable SSL and specify the prefork MPM.
|
1 2 3 4 5 |
$ cd httpd-2.4.6 $ ./configure –prefix=/usr/local/httpd-2.4.6 \ > --with-apr-util=/usr/local/apr-util --with-mpm=prefork \ > --enable-proxy --enable-proxy-connect --enable-proxy-http \ > --enable-headers --enable-rewrite --enable-ssl |
Compile the software, and symlink as required:
|
1 2 |
$ make && su -c "make install" $ su -c "ln -s /usr/local/httpd-2.4.6 /usr/local/httpd" |
Apache HTTPD is now fully installed. It’s configuration file is located at /usr/local/http/conf/httpd.conf. Check the version to verify the installation:
|
1 2 3 |
$ /usr/local/httpd/bin/httpd -v Server version: Apache/2.4.6 (Unix) Server built: Nov 5 2013 19:20:40 |
We can now move on to configuring the software.
BIND Chroot
The most important concept in securing BIND is the concept of the chroot (change root). A chroot jail is set up for the calling process (in this case: named) setting the root directory of that process to a specific path, rather than /. The new root directory is inherited by all children of the calling process. This jails the process(es) so that if it is exploited, any potential damage is limited to the confines of the chroot.
I will create a logical volume for the /var/chroot tree. 1GB should be fine for our purposes. This procedure will be different depending upon your OS/volume manager, and you may wish to exclude it if you already have a separate /var filesystem and do not think that a new filesystem is required for /var/chroot. I prefer keeping the chroot on its own volume:
|
1 2 3 4 |
# lvcreate -L 1G -n lv_var_chroot vg_sys Logical volume "lv_var_chroot" created # lvs | fgrep chroot lv_var_chroot vg_sys -wi-a---- 1.00g |
Create a filesystem on the device. Instead of using the indirect block scheme for storing the location of data blocks in an inode, use extents instead. This is a much more efficient encoding which speeds up filesystem access, especially for large files.
|
1 |
# mkfs -t ext4 -O extent /dev/vg_sys/lv_var_chroot |
Create the mountpoint:
|
1 |
# mkdir /var/chroot |
Add to /etc/fstab:
|
1 2 3 |
# vi /etc/fstab # fgrep chroot /etc/fstab /dev/mapper/vg_sys-lv_var_chroot /var/chroot ext4 defaults 1 2 |
Now, the filesystem is ready to be mounted:
|
1 2 3 4 5 |
# mount /var/chroot # df -hT /var/chroot Filesystem Type Size Used Avail Use% Mounted on /dev/mapper/vg_sys-lv_var_chroot ext4 1008M 34M 924M 4% /var/chroot |
We can now begin configuring the chroot tree. Start by creating the directory structure:
|
1 2 |
# mkdir -p /var/chroot/named/{dev,etc,var} # mkdir -p /var/chroot/named/var/{log,named/{pri,sec},run} |
The directories created are as follows:
/var/chroot/named – the top of the chroot tree
/var/chroot/named/dev – directory for device files
/var/chroot/named/etc – directory for configuration files
/var/chroot/named/var – directory for data files
/var/chroot/named/var/log – directory for log files
/var/chroot/named/var/named/pri – directory for primary zone files
/var/chroot/named/var/named/sec – directory for secondary/slave zone files
/var/chroot/named/var/run – directory for state/PID/lock files
Next, we add a non-privileged user and group that BIND can run as. Running BIND as a non-privileged user will prevent the damage that can be done should the named process be exploited. I use the convention of assigning the TCP/UDP port number as the UID and GID, so, first check that 53 isn’t in use as a currently assigned UID/GID:
|
1 2 |
# awk -vFS=: '$3 == 53 {print $3}' /etc/passwd # awk -vFS=: '$3 == 53 {print $3}' /etc/group |
If nothing is returned, go ahead and create the group and user:
|
1 2 3 |
# groupadd -g 53 named # useradd -M -d /var/chroot/named -s /sbin/nologin \ > -u 53 -c “BIND user” -g named named |
We can now use rndc-confgen, a tool provided with BIND, to generate a very basic rndc.conf and named.conf. We will make two copies of the same generated configuration. rndc-confgen produces rndc.conf at the top of the generated file, and a commented named.conf excerpt at the bottom, with the same RNDC key that is generated in the top half, making it easy to get the RNDC configuration correct. RNDC is used for controlling the nameserver.
|
1 2 |
# /usr/local/named/sbin/rndc-confgen | tee /var/chroot/named/etc/rndc.conf \ > > /var/chroot/named/etc/named.conf |
This command may appear to hang as it waits for entropy – if it does you can generate entropy by running a disk I/O intensive command in another terminal, such as:
|
1 |
# find / -print |
Next, we use sed to perform a transformation on our named.conf template. We replace named.conf in place with a new named.conf.new version. The first sed command deletes the last line of input ($d), then each line has the first two characters removed (s/^..//). Then the 15th until last lines are printed (15,$p). All other lines are suppressed (as the –n option has been passed to sed). The result is a bare-bones named.conf, that can replace our original.
|
1 2 3 |
# sed -n -e '$d' -e 's/^..//' -e '15,$p' /var/chroot/named/etc/named.conf \ > > /var/chroot/named/etc/named.conf.new && \ > mv /var/chroot/named/etc/named.conf.new /var/chroot/named/etc/named.conf |
After performing this transformation, the contents of the two files is as follows:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
# cat /var/chroot/named/etc/rndc.conf # Start of rndc.conf key "rndc-key" { algorithm hmac-md5; secret "u0TJathjjtLfEb/461nuQQ=="; }; options { default-key "rndc-key"; default-server 127.0.0.1; default-port 953; }; # End of rndc.conf # Use with the following in named.conf, adjusting the allow list as needed: # key "rndc-key" { # algorithm hmac-md5; # secret "u0TJathjjtLfEb/461nuQQ=="; # }; # # controls { # inet 127.0.0.1 port 953 # allow { 127.0.0.1; } keys { "rndc-key"; }; # }; # End of named.conf # cat /var/chroot/named/etc/named.conf key "rndc-key" { algorithm hmac-md5; secret "u0TJathjjtLfEb/461nuQQ=="; }; controls { inet 127.0.0.1 port 953 allow { 127.0.0.1; } keys { "rndc-key"; }; }; |
With our base configuration in place, we now need to create the appropriate device files within the chroot. BIND requires three on Linux - /dev/{null,random,zero}.
To obtain the correct major and minor device numbers for these devices, and the fact that we need character devices over block devices, you can use ls to view the existing devices under /dev and then translate those into the chrooted device files. Alternatively you can obtain them from the kernel documentation at https://www.kernel.org/doc/Documentation/devices.txt.
|
1 2 3 4 |
# ls -l /dev/{null,random,zero} crw-rw-rw-. 1 root root 1, 3 Nov 5 16:24 /dev/null crw-rw-rw-. 1 root root 1, 8 Nov 5 16:24 /dev/random crw-rw-rw-. 1 root root 1, 5 Nov 5 16:24 /dev/zero |
The leading c indicates a character-based device, all have a major device number of 1, and minor device numbers as shown above.
Use mknod to create /var/chroot/named/dev/{null,random,zero} as follows:
|
1 2 3 |
# mknod /var/chroot/named/dev/null c 1 3 # mknod /var/chroot/named/dev/random c 1 8 # mknod /var/chroot/named/dev/zero c 1 5 |
Copy /etc/localtime into the chroot:
|
1 |
# cp -p /etc/localtime /var/chroot/named/etc |
We now need to create the root.hints file if we’re wishing to provide a recursive service. This list, returned via the dig command for all nameserver resource records (NS) in the root zone (.):
|
1 |
# /usr/local/named/bin/dig NS . > /var/chroot/named/var/named/root.hints |
Next, set appropriate ownership over the chroot tree for our non-privileged account:
|
1 |
# chown -R named:named /var/chroot/named |
As a further step, we can create /dev/log inside our chroot if we are using syslog. I’ll just be using BINDs logging features outside of syslog for the purposes of this article. If you wish to use syslog, then update your syslog configuration (and SELinux if required) to create /dev/log within the chroot tree.
BIND is now ready to run out of the chroot. Specify the non-privileged user (-u option), the chroot tree root (-t option) and the configuration file location relative to the chroot (-c option). Start it in the foreground with the -g option the first time you run it and check for errors. BIND will chroot(2) after initial start up:
|
1 2 3 4 5 6 7 8 |
# /usr/local/named/sbin/named -u named -t /var/chroot/named -g \ > -c /etc/named.conf … 07-Nov-2013 11:07:40.905 command channel listening on 127.0.0.1#953 07-Nov-2013 11:07:40.905 not using config file logging statement for logging due to -g option 07-Nov-2013 11:07:40.906 managed-keys-zone: loaded serial 0 07-Nov-2013 11:07:40.910 all zones loaded 07-Nov-2013 11:07:40.911 running |
If BIND starts correctly at this point, shut it down by hitting ^C. Drop the -g and start BIND in daemon mode, again specifying the chroot with -t:
|
1 |
# /usr/local/named/sbin/named -u named -t /var/chroot/named -c /etc/named.conf |
You can now install an appropriate /etc/init.d script (or control BIND manually with rndc). We can now perform further configuration of BIND and implement further security.
To control BIND manually with rndc, use the following command format:
|
1 2 |
# /usr/local/named/sbin/rndc -s 127.0.0.1 \ > -c /var/chroot/named/etc/rndc.conf <rndc-command> |
Examples include reloading a zone:
|
1 2 |
# /usr/local/named/sbin/rndc -s 127.0.0.1 \ > -c /var/chroot/named/etc/rndc.conf reload example.com |
And reloading the nameserver after a configuration change:
|
1 2 |
# /usr/local/named/sbin/rndc -s 127.0.0.1 \ > -c /var/chroot/named/etc/rndc.conf reconfig |
Read the rndc manual page for more information on this important command.
Securing BIND
We can now begin to tune the BIND configuration for our needs. As each BIND configuration is very different, I will cover core security aspects of it only. DNSSEC is beyond the scope of this article, but should also be considered when securing your nameserver.
Views are an important concept in BIND, and provide a way to offer a different set of zone files to clients matching certain criteria. We can, for example, provide a recursive service with cache query enabled to an internal network of hosts whilst providing a locked-down authoritative service to all other clients. Locking down to whom you provide recursive service is very important. Misconfigured DNS servers can be used in Denial of Service attacks for DNS amplification, so ensure that you only allow hosts to recursively query your nameservers if specifically required to do so.
Let’s consider a network with all local “internal” hosts on 192.168.100.0/24. We’d start by defining an ACL in named.conf:
|
1 2 3 |
acl "mynets" { 127.0.0.0/8; 192.168.100.0/24; }; |
We can then create our two views:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
view "internal" { match-clients { "mynets"; }; allow-query-cache { "mynets"; }; allow-recursion { "mynets"; }; // further configuration and zones }; view "external" { match-clients { any; }; allow-query-cache { none; }; // explicitly deny recursive queries recursion no; // further configuration and zones }; |
With this configuration, any hosts in the mynets ACL can query the DNS cache, and perform recursive queries. External hosts are able to perform non-recursive queries for resource records in any zones defined in view external, and are unable to query the nameserver cache.
You can also specify match-destination as the view match criteria which is useful for situations when different views are being offered via different network interfaces on the system.
The allow-query-cache and allow-recursion options (as well as the more general allow-query directive) can also be placed in the options block for a global default. We can use these directives to define who can query what on our nameserver.
Now that we have secured our nameserver with respect to who can query and how, we should now look towards how zone transfers are secured. You may not need to have zone transfers at all, in which case the allow-transfer { none; }; configuration directive should appear within your options block. If you have slave nameservers, then you can specify an ACL containing your slave servers, and apply that either per-view or per-zone. For example:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
acl "myslaves" { 192.168.100.26; 202.31.123.22; }; options { allow-transfer { none; }; }; view "external" { zone "example.com" { type master; file "pri/example.com.db"; allow-transfer { myslaves; }; }; }; |
We should also restrict dynamic DNS updates by using the allow-update {}; directive. If dynamic updates are required, lock them down as much as possible by turning them off globally, and then only allowing the updates per zone, for example:
|
1 2 3 4 5 6 7 8 |
options { allow-update { none; }; }; zone "example.com" { type master; file "pri/example.com.db"; allow-update { 192.168.100.54; }; }; |
You can also use TSIG (transaction signatures) to secure zone transfers. TSIG can provide security in a pairwise manner between two servers, and can be used to sign and secure zone transfers via a pre-shared key mechanism. TSIG utilises a one-way hash function to provide authentication and data integrity.
For this example, let us presume our master nameserver is ns1.example.com (192.168.100.1), and our slave is ns2.example.com (192.168.100.2). To generate the key, we’ll use the dnssec-keygen command that is installed along with BIND. As the name suggests, the dnssec-keygen command is also used for generating the keys required for DNSSEC operation. Here, we use it to generate the HOST key to be used for our TSIG authenticated transfers.
On the master server, issue the following command to generate the key. You can use a key length of 128, 256 or 512 bits. The longer the key, the harder it will be to crack. Generate the key in the /var/chroot/named/etc directory:
|
1 2 3 |
# cd /var/chroot/named/etc # /usr/local/named/sbin/dnssec-keygen -a HMAC-MD5 -b 512 -n HOST tsigkey Ktsigkey.+157+28164 |
The output is the filename prefix of the .private and .key files that will be generated:
|
1 2 3 |
# ls -l *.key *.private -rw-------. 1 root root 116 Nov 10 10:18 Ktsigkey.+157+28164.key -rw-------. 1 root root 229 Nov 10 10:18 Ktsigkey.+157+28164.private |
The .key file contains a DNS KEY record that can be inserted into the appropriate file when DNSSEC is employed. We are interested in the .private file. Run cat against the file, and note down the key:
|
1 2 3 4 5 6 7 8 |
# cat Ktsigkey.+157+28164.private Private-key-format: v1.3 Algorithm: 157 (HMAC_MD5) Key: tSFWmIVPTaC0vMSZD9wOkMqdrZKUxM2mbpofD2M5MOcPeoeO8cCytbndg9hP/VscozKz7uBWyAxXLkpyUXDerw== Bits: AAA= Created: 20131109231812 Publish: 20131109231812 Activate: 20131109231812 |
On the master server, create the /var/chroot/named/etc/tsig.key file as follows:
|
1 2 3 4 5 6 7 8 |
key “tsigkey” { algorithm hmac-md5; secret “<key from .private file>”; }; // slave server IP server 192.168.100.2 { keys { tsigkey; }; }; |
And on the slave server:
|
1 2 3 4 5 6 7 8 |
key “tsigkey” { algorithm hmac-md5; secret “<key from .private file>”; }; // master server IP server 192.168.100.1 { keys { tsigkey; }; }; |
Next, on both nameservers, include the key file into named.conf. The path should be relative to the chroot:
|
1 |
include “/etc/tsig.key”; |
On the master server, restrict each zone’s ability to be transferred only with the appropriate key:
|
1 2 3 4 5 |
zone "example.com" { type master; file "pri/example.com.db"; allow-transfer { key tsigkey; }; }; |
Restart / reload the BIND instances once configuration is complete.
Logging is also an important step in securing your BIND installation. If you have configured your syslog implementation to provide a /dev/log device within the chroot jail, you can use logging statements such as the following to log to syslog:
|
1 2 3 4 5 6 7 |
logging { channel default_log { syslog local7; severity info; }; category default { default_log; }; }; |
This will log messages at severity info and higher via the syslog facility local7. Ideally your syslog configuration should forward all syslog messages to a centralised remote syslog server.
You can also log locally as follows:
|
1 2 3 4 5 6 7 |
logging { channel default_file { file "/var/log/named.log" versions 5 size 100M; severity info; }; category default { default_file; }; }; |
This will maintain 5 versions up to size 100M of the default log. There are many other categories of logging that can be defined, such as queries and notify. Further information on the logging facilities offered by BIND can be found in the Administrator’s Reference Manual (https://kb.isc.org/article/AA-00845 ).
A final item worth changing is BIND’s version string. By default, the following query will return the version of BIND running, for example:
|
1 2 |
$ dig CH TXT VERSION.BIND @localhost +short "9.9.4" |
This can provide information to somebody looking to exploit a vulnerable BIND version. Therefore, set version to something else within your options block in named.conf, for example:
|
1 |
version “no-such-nameserver-version”; |
Or, just set it to any empty string.
After making any configuration changes to named.conf, either reconfigure or reload BIND with rndc.
Securing HTTPD
Securing Apache HTTPD is a complex subject, and this section will outline a brief selection of “quick-win” security-hardening steps. For a 100-page hardening guide that covers these steps (and much more) in greater detail, download the Apache security benchmark from http://www.cisecurity.org).
As with BIND, we should ensure that our Apache instance runs as a non-root user. Looking at our default httpd.conf supplied at /usr/local/httpd/conf/httpd.conf, User and Group of daemon are specified. I will create a user/group of www, with UID and GID of 80, first checking that the UID and GID are not already in use:
|
1 2 3 4 |
# awk -vFS=: '$3 == 80 {print $3}' /etc/passwd # awk -vFS=: '$3 == 80 {print $3}' /etc/group # groupadd -g 80 # useradd -m -d /var/www -s /sbin/nologin -u 80 -g www -c “Webserver User” www |
Next, update httpd.conf with the correct values:
|
1 2 3 4 5 |
# vi /usr/local/httpd/conf/httpd.conf ... User www Group www ... |
You can now start httpd:
|
1 |
# /usr/local/httpd/bin/apachectl start |
If all is well, you can install a script under /etc/init.d, configure the service to start at the appropriate runlevel as defined by your operating system, etc. You will also need to update the DocumentRoot for the configuration, or for any defined VirtualHosts.
Ensure that logs are configured. Each VirtualHost should log to its own file for ease of auditing. For example:
|
1 2 3 4 5 6 7 |
<VirtualHost *:80> ServerName www.example.com DocumentRoot /var/www/www.example.com/htdocs ServerAlias example.com ErrorLog /var/log/httpd/example.com-error_log CustomLog /var/log/httpd/example.com-access_log combined </VirtualHost> |
You should further ensure that a log rotation mechanism (for example, logrotate on Linux or logadm on Solaris) is employed to rotate the logs at an appropriate interval. Archival should also be configured – depending on your needs, as should the regular removal of old logs. I’ve written plenty of scripts for various log archival needs, and one method is to copy the logs to a central log server, md5sum both the local copy and the new copy, then delete them locally if the md5sums match as it indicates the remote copy on the log server is good.
The Apache instance is only as secure as any dynamic content being served by it via modules such as mod_perl and mod_php. Ensure that those modules are up-to-date and locked down as per any appropriate documentation shipped with the module’s source code.
By default, Apache error pages will give away information regarding the Apache version, OS and IP address of the serving host. This should be disabled by setting the following in httpd.conf:
|
1 2 |
ServerSignature Off ServerTokens Prod |
Now the only information returned will be “Apache”.
To prevent users from uploading their own .htaccess files with snippets of HTTPD configuration in them, consider globally disabling this with:
|
1 2 3 |
<Directory /> AllowOverride None </Directory> |
Ensure that any modules you do not require are disabled in httpd.conf by commenting them out with the # character, e.g.:
|
1 2 3 4 5 6 7 8 9 10 |
# fgrep LoadModule httpd.conf # LoadModule foo_module modules/mod_foo.so LoadModule authn_file_module modules/mod_authn_file.so #LoadModule authn_dbm_module modules/mod_authn_dbm.so #LoadModule authn_anon_module modules/mod_authn_anon.so #LoadModule authn_dbd_module modules/mod_authn_dbd.so #LoadModule authn_socache_module modules/mod_authn_socache.so LoadModule authn_core_module modules/mod_authn_core.so LoadModule authz_host_module modules/mod_authz_host.so … |
You should also consider purchasing SSL certificates and providing HTTPS service if the content is to remain secure – for example if users are entering credit card numbers into forms on your site. If you are running a local application (for example, an intranet-based application within an organisation) it may be enough to create self-signed certificates. Using SSL certificates will also give clients security that they are connecting to the host that they expect, and will also mitigate man-in-the-middle attacks by providing encrypted communication between client and server. Ensure that you monitor the expiration date of certificates in Production so that they are renewed prior to expiry.
SSL can be slightly resource-intensive for large sites. In those environments, a load-balancer with hardware-based SSL-acceleration may be a wise investment.
Apache Options can be used to define options to be applied per directory (for example, FollowSymLinks and Indexes – the defaults). If you don’t wish to give away a directory index listing, you should include
|
1 |
Options -Indexes |
within any appropriate <Directory> stanzas. If you don’t need Apache to follow symbolic links either, it is more desirable to set Options to None.
The HTTP TRACE method is implemented and enabled by default, and this can be open to abuse. Ensure the following is set in httpd.conf:
|
1 |
TraceEnable Off |
After making any changes to httpd.conf, ensure that you reload httpd:
|
1 |
# /usr/local/httpd/bin/apachectl graceful |
If needed, a Web Application Firewall such as mod_security (http://www.modsecurity.org) can be utilised for an array of request filtering rulesets and other security features. One of mod_security’s experimental features is to provide a simple way to chroot Apache HTTPD. Another option for chrooting Apache is mod_chroot (http://www.cyberciti.biz/tips/chroot-apache-under-rhel-fedora-centos-linux.html).
Conclusion
Running web-based services requires a multi-faceted approach to maintain high levels of availability and functionality whilst still being locked-down and secure. This article has described the procedure for compiling and installing BIND and HTTPD from source, and has outlined some major points around securing and hardening the services offered by this software. Whilst it is not exhaustive it should provide food for thought for administrators looking to secure their web-based services.
This article was previously published in Pentest Magazine.
