By Thorsten Scherf
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.
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.
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.
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.
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.
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 ... |
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.
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.
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/ |