SELinux fix: allowing write to /var/lib/mod_security/

There’s a long-standing bug that prevents mod_security from writing to /var/lib/mod_security/.

According to Red Hat Bugzilla this bug should been fixed around May 2013, but it still exists – on fully patched CentOS 6.5. From /var/log/audit/audit.log:

type=AVC msg=audit(1411718594.811:7017): avc: denied { write } for pid=28144 comm="httpd" name="global.dir" \
dev=dm-0 ino=1577960 scontext=unconfined_u:system_r:httpd_t:s0 tcontext=unconfined_u:object_r:var_lib_t:s0 tclass=file

type=AVC msg=audit(1411718594.812:7018): avc: denied { write } for pid=28144 comm="httpd" name="ip.dir" \
dev=dm-0 ino=1577962 scontext=unconfined_u:system_r:httpd_t:s0 tcontext=unconfined_u:object_r:var_lib_t:s0 tclass=file

To relabel this directory with the proper “httpd_var_lib_t” context, run the following as root:

semanage fcontext -a -t httpd_var_lib_t "/var/lib/mod_security(/.*)?"
restorecon -Rv /var/lib/mod_security

SELinux context for website with FTP access

So, you have decided to leave SELinux enabled. Congratulations, you have just taken a major step in securing your Internet-facing system.

Let’s say you are hosting a website that needs to be updated using FTP. By default, webserver content is labeled as:


This context prevents you from updating files using the FTP server. If both HTTP (Apache) and FTP (vsftpd) access is needed, the SELinux context should be:


You can either run “chcon” to temporarily fix this, or make the changes permanent by adding a proper local SELinux rule:

semanage fcontext -a -t public_content_rw_t "/var/www/html(/.*)?"
restorecon -Rv /var/www/html

Replace “/var/www/html” by your actual DocumentRoot as defined in Apache. The “semanage” command merely adds the rule to the SELinux database. The “restorecon” command performs the actual relabeling of your files.

Verify your changes using “ls -lZ”:

[root@webserver www]# ls -lZ
drwxr-sr-x. ed www unconfined_u:object_r:httpd_sys_content_t:s0 cgi-bin
drwxr-sr-x. ed www unconfined_u:object_r:httpd_sys_content_t:s0 error
drwxr-sr-x. ed www unconfined_u:object_r:public_content_rw_t:s0 html
drwxr-sr-x. ed www unconfined_u:object_r:httpd_sys_content_t:s0 icons


PNP4Nagios with SElinux on CentOS / RHEL 6

PNP4Nagios is commonly used to add performance graphs to a Nagios installation.

For additional security, SElinux is enabled on the monitoring host. There is no standard SElinux policy for applications like PNP4Nagios, so we need to develop a custom policy. This sounds harder than it actually is:

  • Run the software as you normally would (SElinux will interfere, so prepare for errors)
  • Extract audit messages and use them to create or update a local SElinux policy for the software
  • Repeat until everything works

In this example, I am running Nagios 3.2.3 with PNP4Nagios 0.6.16 on EL6, 64-bit.

After configuring Nagios and PNP4Nagios integration in Synchronous Mode (see documentation), I noticed that PNP4Nagios is not logging any performance data to /var/lib/pnp4nagios/.

Normally, PNP4Nagios should automatically create directories and files under /var/lib/pnp4nagios as performance data is received by Nagios. This smells of an SElinux issue, so check /var/log/audit/audit.log for suspicious messages. Sure enough, several audit messages have been logged. They look like this:

type=AVC msg=audit(1329129875.344:198212): avc:  denied  { getattr } for  pid=26692 comm="process_perfdat" \
    path="/var/lib/pnp4nagios/orac/Root_Partition.xml.26692" dev=dm-0 ino=1444378 \
    scontext=unconfined_u:system_r:nagios_t:s0 tcontext=unconfined_u:object_r:var_lib_t:s0 tclass=file
type=SYSCALL msg=audit(1329129875.344:198212): arch=c000003e syscall=5 success=no exit=-13 a0=3 a1=25440a0 \
    a2=25440a0 a3=0 items=0 ppid=26691 pid=26692 auid=0 uid=498 gid=498 euid=498 suid=498 fsuid=498 egid=498 \
    sgid=498 fsgid=498 tty=(none) ses=14942 comm="process_perfdat" exe="/usr/bin/perl" subj=unconfined_u:system_r:nagios_t:s0 key=(null)

Create a policy

You can run the “audit2allow” command (part of the policycoreutils-python RPM) to display suggested policy improvements based on the audit log:

audit2allow -a

The output can be saved in a file, for example local_nagios.te:

grep nagios_t /var/log/audit/audit.log | audit2allow -l -v -m local_nagios > local_nagios.te

This generates an output file suitable for compiling into a custom SElinux module.

Note: ALWAYS prefix the policy name with something like local_ to prevent overwriting system policies!

Test and refine the policy

Compile and load the SElinux policy module:

checkmodule -M -m -o local_nagios.mod local_nagios.te
semodule_package -o local_nagios.pp -m local_nagios.mod
semodule -v -i local_nagios.pp

Note: The above tools can be found in the checkpolicy and policycoreutils RPMs.

Re-run the software and check for SElinux audit messages. New issues can be captured and translated into a new policy:

grep nagios_t /var/log/audit/audit.log | audit2allow -l -v -m local_nagios > local_nagios.te_NEW

Merge the new results (in local_nagios.te_NEW) with your existing policy (in local_nagios.te). Compile and reload the module.

Lather, rinse, repeat ;-)


After some iterations, your local_nagios.te file will look something like this:

module local_nagios 1.0;

require {
    type nagios_t;
    type var_log_t;
    type var_lib_t;
    class dir { write create add_name remove_name };
    class file { create getattr ioctl lock open read rename unlink write };

#============= nagios_t ==============
allow nagios_t var_lib_t:dir { add_name create remove_name write };
allow nagios_t var_lib_t:file { create getattr ioctl lock open read rename unlink write };
allow nagios_t var_log_t:file { read rename unlink };

If all is well, the audit.log should not show any new messages for nagios_t:

clear;tail -f /var/log/audit/audit.log |grep nagios_t

Note: The new SElinux policy will survive reboots; it is automatically copied to /etc/selinux/targeted/modules/active/modules/local_nagios.pp.


Tip: Encrypted passwords, just add salt

You can generate encrypted password strings (hashes) using the openssl utility.

You need to supply both a “salt” string and the password you wish to encrypt:

  # openssl passwd -1 -salt MoreSalt ThePassword

The following command line generates a random 8-character salt string:

  # openssl rand -base64 6

Combine these into a single command line that uses a different random salt on each invocation:

  # openssl passwd -1 -salt $(openssl rand -base64 6) ThePassword

Read on for a comparison between old crypt()-style passwords and the current md5-style shadow passwords. Continue reading “Tip: Encrypted passwords, just add salt”

Annoyed by phpMyAdmin scans? Set up a tarpit with mod_security!

Note: Rho’s excellent blog post pointed me in the right direction – credit where credit is due.

On my websites, I’ve seen a lot of scanning for vulnerable phpMyAdmin installations.

As a matter of policy, I don’t run phpMyAdmin on any Internet-facing web server. The scans won’t find anything, but the log entries are annoying so I decided to take action.

Some information about the attacks:

  • The scans originate from a variety of sources, so an IP-address block will not work.
  • The scans typically probe IP-addresses (not hostnames) for phpMyAdmin installations. We will use this to our advantage.

The scans cannot be prevented, but at least we can slow them down to make them less effective.


What do we need?

On CentOS / Red Hat Enterprise Linux, these are installed using the “httpd” and “mod_security” RPMs.


On Apache, it is common to use “Name-Based Virtual Hosting”. This technique allows you to run many websites on a single IP-address.

The first “Virtualhost” you define in Apache becomes the default host that is used if the request is made to the IP-address ( instead of the hostname ( Most probes will hit this default host as they use an IP-address instead of a hostname.

We will set up a ModSecurity rule on this Virtualhost to catch anyone looking for “phpmyadmin”, “phpMyAdmin” etc. This will require case-insensitive matching. I modified the SecRule in Rho’s blog entry by including a “to lowercase” function:

  SecRule REQUEST_URI phpmyadmin t:lowercase,pause:5000,log,noauditlog,status:402,deny

The rule is now no longer case-sensitive and will catch all variations of upper- and lower-case letters.

My default virtualhost configuration looks like this:

# The first VirtualHost is special; it is the default
# It is mostly accessed by IP-based clients (or port scanners)
# Therefore, this is an ideal candidate for a "tarpit" or "honeypot"

# Allow Apache to access the DocumentRoot
<Directory "/var/www/virtual/defaultsite/html">
 Options None
 AllowOverride None
 Order Deny,Allow
 Allow from All

# Define the virtual host
<VirtualHost *:80>
 ServerAdmin webmaster@localhost
 DocumentRoot /var/www/virtual/defaultsite/html

 # Set up a tarpit for all those pesky phpMyAdmin scanners out there
 # Inspired by
 <IfModule mod_security2.c>
 SecRuleEngine On
 SecDefaultAction log,allow,status:406,phase:2
 SecRule REQUEST_URI phpmyadmin t:lowercase,pause:5000,log,noauditlog,status:402,deny

 SecDebugLog /var/log/httpd/modsec_tarpit.log
 SecDebugLogLevel 1

 ErrorLog /var/log/httpd/defaultsite-error_log
 CustomLog /var/log/httpd/defaultsite-access_log common

If all is well (service httpd configtest), restart Apache (service httpd graceful) and watch the tarpit logfile:

[14/Aug/2010:00:58:49 +0200] [][rid#2ba3523ad573][/pHpMyAdMin][1] Access denied with code 402 (phase 2). Pattern match "phpmyadmin" at REQUEST_URI.

Good! The attacker was delayed for 5 seconds (5000 msec). You can now feel good about yourself, knowing that you have made the Internet a safer place ;-)

Remember that each tarpitted connection ties up an Apache child process for the duration of the delay. This may impact your web server, so keep an eye on those logs.

Happy tarpitting!