Protecting web servers with mod_selinux and SEPostgreSQL

Web Wall


Web application firewalls might defend your web server against known attacks, but they can't protect you from the latest intrusion tricks. Apache's mod_selinux module helps you plug the gaps.

By Thorsten Scherf

Mary Lane, 123RF

Web-based applications are an increasingly popular attack vector for computer system intrusions (Figure 1). Legacy firewall systems can't provide any protection against attacks such as SQL injection and cross-site scripting, and this means that an error in a web application can have fatal consequences. For one thing, web applications are extremely dynamic and complex; for another, the developer of a web application can't look into the future to anticipate and eliminate potential dangers before they happen.

Figure 1: The percentage of network attacks directed at web servers increases every year. (Source: Little Earth Corporation)

ModSecurity

Apache's ModSecurity module [1], which uses rulesets to monitor the incoming data stream for suspicious content, has become a popular tool for keeping intruders from getting in. Data packets that do not match the permissible patterns are discarded.

Although this approach catches a large percentage of malicious packets before they are passed in to the web application, what happens with the packets that get through? Systems that rely on pattern matching will only detect known patterns and thus will fail when faced with previously unknown exploits. Additionally, once the intruder gets in, the possibilities for extending the attack are nearly endless.

At the operating system level, administrators can resolve this issue by introducing a Mandatory Access Control (MAC) scheme that allows the administrator to fine-tune the access permissions applied to specific resources. Over the past few years, SELinux has established itself as an important tool for applying mandatory access controls in highly secure environments, but the problem is that SE only restricts access to system resources. The newer, web-based attacks tend to focus on web application servers and databases.

Security Enhanced PostgreSQL (SEPostgreSQL) [2] and mod_selinux [3] extend the protection offered by SELinux further to database objects and web applications.

How it Works

SEPostgreSQL is an extension of the popular PostgreSQL database system that supports the assignment of an SELinux security context to individual objects. In a classic PostgreSQL database, a user logs in with a username and authenticates by entering a password. The user is then given access to all the objects available to the user account. With SEPostgreSQL, the accessing client socket receives a security context that can be either the context of a user seeking access or the context of the accessing process.

Because the SEPostgreSQL administrator can apply security labels to individual tables and any objects they contain, the centralized SELinux ruleset thus defines which user or process can access, or not access, which database object.

As is typically the case with SELinux, two access controls must be negotiated before the user or process receives access to the requested object. First, credentials are checked through the database-based authentication system, and in a second step, the system then checks to see whether the security label of the accessing socket has sufficient privileges for the requested object.

Both tests need to complete successfully before the database serves up the requested object. Even the database administrator can only access a specific object if the security administrator has explicitly allowed this access through an SELinux rule.

Unlike legacy system objects, such as files or directories, in which the security context is stored in the extended attributes, SEPostgreSQL stores the security context for a database object in a special system catalog. System catalogs for relational database systems typically include the schema data and other meta information.

SEPostgreSQL introduces an additional catalog titled sg_security and uses the catalog to store mappings between the security context of an object and the matching Object Identifier (OID). When a new database object is created, a numeric OID is assigned to it. The database itself handles resolution of the OID into a more conventional string required for a security context.

mod_selinux Module

Apache's mod_selinux module allows the administrator to start different web server instances with an individual security context instead of using the same context for multiple processes. The security context assigned to a web server instance depends on the accessing user.

The approach is similar to that of a classic shell login. After successfully authenticating via login or ssh, the user's shell is assigned a security context. The user can now work with the system within the security constraints of that context. If the user attempts to do something that the context does not permit, SELinux will prevent it from happening.

In the case of mod_selinux, it is the Apache web server that acts as an agent or proxy for an accessing user. If the user logs in to a user account, the web server process will apply the user's context.

To view the rights assigned to a process, you can query the kernel-based security server and see the objects for which the user is allowed or denied access. If SEPostgreSQL is used as a back end for the web application, access controls are extended to individual objects within the database.

Setting mod_selinux

Before starting with the configuration steps, you first need to install the mod_selinux package. Most major Linux distributions will have it in their software repositories; if your distribution does not offer a version, you can download it from the Fedora Project package database [3]. After completing the installation on a Fedora 11 system, you will find mod_selinux.conf in the /etc/httpd/conf.d directory.

Get a Context

Once you finish installing mod_selinux, the next question is how the users will get a security context for a web application. mod_selinux provides various approaches for assigning a context, all of which are described in the mod_selinux configuration file. First of all, you need to tell your Apache server the directory or location for which you require users to authenticate. Listing 1 shows an example that authenticates users by referencing a local file with the usernames and MD5 user password hashes.

Listing 1: /etc/httpd/conf.d/mod_selinux.conf
01 <Directory "/var/www/html">
02 # HTTP Basic Authentication
03 AuthType      Basic
04 AuthName      "Secret Zone"
05 AuthUserFile  /var/www/htpasswd
06 Require       valid-user
07 </Directory>

The following command adds a user to the password file:

# htpasswd -m /var/www/htpasswd foo

Listing 2 shows more settings. The selinuxDomainMap instruction specifies a local file that assigns a security context to each user; as an alternative, you could use selinuxDomainVal to set a default context. A mapping file would look something like Listing 3.

After authentication, the web server processes for the two user requests both reside in the SELinux user_webapp_t domain with SELinux multi-level security (MLS) sensitivity levels of s0 and the Multi-Category Security (MCS) categories of c0 (foo) and c1 (bar). The file provides access to objects that belong in these categories and can access the user_webapp_t domain. The MCS categories for file objects are set by the chcat tool (Listing 4).

If a user is not explicitly listed in the mapping file, access is only via the user_webapp_t domain unless the MCS category is set. The sesearch tool tells you that access to the objects in the user_webapp_t domain is allowed (Listing 5). These rules are added to the SELinux policy by the mod_selinux.pp policy package. The package is automatically loaded after installing mod_selinux, as a call to semodule confirms:

# semodule -l | grep mod_selinux
mod_selinux 2.2

Of course you can add your own rules to a policy, but you will need to wrap them in a policy package. Any non-authenticated access to the web server is routed to the SELinux anon_webapp_t domain, as specified in the mapping file.

Listing 2: /etc/httpd/conf.d/mod_selinux.conf
01 ### mod_selinux.conf can access a local file to
02 ### map users with security contexts
03
04 selinuxDomainMap    /var/www/mod_selinux.map
05 selinuxDomainVal    anon_webapp_t:s0
06 selinuxDomainEnv    SELINUX_DOMAIN
Listing 3: /var/www/mod_selinux.map
01 foo                *:s0:c0
02 bar                *:s0:c1
03 __anonymous__      anon_webapp_t:s0
04 *                  user_webapp_t:s0
Listing 4: chcat
01 # chcat -L
02 s0
03 s0-s0:c0.c255     SystemLow-SystemHigh
04 s0:c0.c255        SystemHigh
05 s0:c1             Marketing
06 s0:c2             Payroll
07 s0:c3             IT
08
09 chcat +IT /var/www/virtual/www1/index.html
10 ls -lZ /var/www/virtual/www1/index.html
11 -rw-rw-r--  root  apache  root:object_r:httpd_sys_content_t:IT  /var/www/virtual/www1/index.html
Listing 5: sesearch
01 # sesearch --allow -s user_webapp_t
02 Found 120 semantic av rules:
03 allow user_webapp_t public_content_t : file { ioctl read getattr lock open} ;
04 allow user_webapp_t public_content_t : dir { ioctl read getattr lock search open } ;
05 allow user_webapp_t public_content_t : lnk_file { read getattr } ;
06 allow user_webapp_t sysctl_kernel_t : file { ioctl read getattr lock open } ;
07 allow user_webapp_t sysctl_kernel_t : dir { ioctl read getattr lock search open } ;
08 ...

Setting Up SEPostgreSQL

You will probably prefer to store users in a database for a larger domain and to use the database for authentication, especially if a database system must exist to host your web application. The following example uses SEPostgreSQL as the database.

The advantage that this offers is that the administrator can map all objects in the database to a security context, which is not possible with other RDBMS systems.

With most Linux distributions, you can use the standard software repository for the install; failing this, the source code is available through Google Code [4]. The following Yum command line installs SEPostgreSQL on a Fedora 11 system:

# yum install sepostgresql

After the installation, you need to initialize and start the database, and the following commands will take care of this for you:

# /etc/init.d/sepostgresql initdb
# /etc/init.d/sepostgresql start

A standard user account (sepgsql) is defined for administrative access to the database. This account allows the administrator to create an initial database and store the user objects in it (Listing 6).

Listing 6: uaccount Table
01 # su sepgsql
02 # createdb web
03 # psql web
04 ...
05
06 web=# CREATE TABLE uaccount (
07 web(#         uname       TEXT PRIMARY KEY,
08 web(#         upass       TEXT,
09 web(#         udomain     TEXT
10 web(# );
11
12 web=# INSERT INTO uaccount VALUES ('foo', 'pass', 'user_webapp_t:s0:c0');
13 web=# INSERT INTO uaccount VALUES ('bar', 'pass', 'user_webapp_t:s0:c1');

Then you can use the mod_selinux.conf configuration file to access these objects in order to authenticate the users of a web application. The matching security context for each user object already exists in the database, thus removing the need for mapping. The commands that are required for configuring mod_selinux are given in Listing 7.

Listing 7: /etc/httpd/conf.d/mod_selinux.conf
01 LoadModule dbd_module        modules/mod_dbd.so
02 LoadModule authn_dbd_module  modules/mod_authn_dbd.so
03
04 # Parameters for database connection
05
06 DBDriver    pgsql
07 DBDParams   "dbname=web user=apache"
08
09 # Digest authentication
10
11 <Directory "/var/www/html">
12     AuthType               Digest
13     AuthName               "Secret Zone"
14     AuthDigestProvider     dbd
15     AuthDBDUserRealmQuery  \
16         "SELECT md5(uname || ':' || $2 || ':' || upass), udomain, \
17                 %s=%s as dummy FROM uaccount WHERE uname = $1"
18
19 # SELinux context mapping
20
21 selinuxDomainEnv         AUTHENTICATE_UDOMAIN
22 selinuxDomainVal         anon_webapp_t:s0
23
24 </Directory>

First you need to load the Apache modules required to access the database. Then the next two parameters specify the driver names and credentials that are required to access PostgreSQL. These parameters are followed by user authentication, in which the AUTHENTICATE_UDOMAIN variable is used to pass the authentication information in to mod_selinux. A user who is unable to log in can still use the anon_webapp_t security context as a fallback.

SEPostgreSQL with MAC Protection

The following sections describe the SELinux configuration for a relational database management system using SEPostgreSQL as an example. Listing 8 shows the schematic workflow. First of all, you need to log in to the RDBMS with administrative privileges and create a new database - footballdb in this case. After that, you use the Psql client application to connect to the database and create a new table - clubs in this example. A single record is then added to the table.

Listing 8: Security Context for Database Records
01 # su sepgsql
02 # createdb footballdb
03 # psql footballdb
04 Welcome to psql 8.3.7, the PostgreSQL interactive terminal.
05
06 Type:  \copyright for distribution terms
07        \h for help with SQL commands
08        \? for help with psql commands
09        \g or terminate with semicolon to execute query
10        \q to quit
11
12 footballdb=# CREATE TABLE clubs (
13 footballdb(# id      integer primary key,
14 footballdb(# name    varchar(32),
15 footballdb(# platz   integer,
16 footballdb(# punkte  integer
17 footballdb(# );
18
19 footballdb=# INSERT INTO clubs (id, name, platz, punkte)
20 footballdb-# VALUES (1, 'Manchester United', 1, 72);
21
22 footballdb=# SELECT security_context, * FROM clubs;
23
24              security_context             | id |  name   | platz | punkte
25 ------------------------------------------+----+---------+-------+--------
26  unconfined_u:object_r:sepgsql_table_t:s0 |  1 | Manchester United |     1 |     72
27 (1 row)
28
29 footballdb=# UPDATE clubs SET security_context = 'system_u:object_r:public_content_t:s0' WHERE  name='Manchester United';
30
31 footballdb=# SELECT security_context, * FROM clubs;
32
33            security_context            | id |  name   | platz | punkte
34 ---------------------------------------+----+---------+-------+--------
35  system_u:object_r:public_content_t:s0 |  1 | Manchester United |     1 |     72
36 (1 row)

The first select statement outputs this record. As you can see, it already has a security context. The type for this default security context is sepgsql_table_t for access from the unconfined_t domain. A simple update statement is all it takes to change the context. The final call to select verifies that the record now has a new security context type.

A context can be applied to individual columns as well as database records (Listing 9. The listing shows a table with employee data; the idea is to apply the sepgsql_secret_table_t security context to this column. Regular SELinux user domain users are not allowed to access it, as is confirmed by the last two select statements. SELECT sepgsql_getcon() outputs the security context of the accessing socket; in this case, it is the user_t user domain. The final select attempts to access all the columns in the employee table. This provokes an SELinux error, because access to a sepgsql_secret_table_t type database object is not permitted from the user_t domain. The rules required for this are added to the sepostgresql-devel.pp policy package, the global SELinux ruleset when installing SEPostgreSQL. As with mod_selinux, you can add your own rulesets. The sepostgresql man page lists all supported SELinux types and their permissions.

Listing 9: Security Context for Columns
01 foo=# CREATE TABLE employee (
02 foo(# mid integer primary key,
03 foo(# mname varchar(32),
04 foo(# esalary varchar(32)  CONTEXT = 'system_u:object_r:sepgsql_secret_table_t:s0'
05 foo(# );
06
07 foo=# GRANT ALL ON employee TO PUBLIC;
08
09 foo=# SELECT sepgsql_getcon();
10      sepgsql_getcon
11 -------------------------
12  user_u:user_r:user_t:s0
13 (1 row)
14
15 foo=# SELECT * FROM employee;
16 ERROR:  SELinux: denied { select }                            \
17         scontext=user_u:user_r:user_t:s0                      \
18         tcontext=system_u:object_r:sepgsql_secret_table_t:s0  \
19         tclass=db_column name=employee.esalary

SEPostgreSQL always uses the SELinux context of the accessing client socket to make access decisions for objects in the database. This is either the security context of the accessing process (e.g., httpd_t), or the user's shell context (e.g., user_t). psql uses a SELECT sepgsql_getcon(); statement to output the context of the accessing socket. If access originates on an Apache server or another machine, rather than on the local machine, SEPostgreSQL will obviously only see the context of the accessing network socket. But labeled networking gives you an elegant solution for this by supporting arbitrary IPSec tunnels between various systems to extend the security context of a process beyond network borders.

Conclusions

Thanks to mod_selinux, it is now possible to start individual web server processes (or web server threads, to be more precise) with an individual security context. The administrator can then use SELinux rules to define the objects that are allowed to access these individual threads. If you take a closer look at the documentation, you will notice that there are many more applications for this. For example, you can use mod_selinux to start individual, virtual Apache hosts with their own security contexts, or you can set the context based on the IP address of the accessing host.

mod_selinux offers a more granular approach to accessing SELinux objects than ever. Although AppArmor provides a similar solution, it requires a special Apache server, which is not the case with mod_selinux. And if SEPostgreSQL is used as the database, mandatory access control can be extended to the database objects. A combination of both systems promises major security benefits when deploying web applications.

INFO
[1] ModSecurity: http://www.modsecurity.org/
[2] SEPostgreSQL: Security Enhanced PostgreSQL, http://wiki.postgresql.org/wiki/SEPostgreSQL
[3] mod_selinux: https://admin.fedoraproject.org/pkgdb/packages/name/mod_selinux
[4] Download SEPostgreSQL: http://code.google.com/p/sepgsql/