include("site.inc"); $template = new Page; $template->initCommon(); $template->displayHeader(); ?>
For some, the default Apache HTTP policy may actually not be expressive enough. This section discusses extending and fundamentally rewriting the Apache HTTP policy for even stronger security. It is assumed that you are familiar with the concepts discussed earlier. This section will generally only outline solutions, instead of going into step-by-step detail.
Using the default policy, a compromise of one CGI script means a compromise of all of them. This is because they all run in the same domain (httpd_sys_script_t) with access to the same other types (e.g. httpd_sys_script_rw_t). Put a different way, types are security equivalence classes.
Most of the type definitions and permissions regarding CGI scripts are in /etc/selinux/policyname/src/policy/macros/program/apache_macros.te. Open that file up in your favorite editor. This entire file is definining an m4 macro called apache_domain.
Understanding m4 in one paragraph | |
---|---|
The define(`apache_domain',` begins the macro definition. Inside the definition, the $1 represents the parameter passed to the macro. |
If you then look in /etc/selinux/policyname/src/policy/domains/program/apache.te, you'll see the following invocation:
apache_domain(sys)
This single line then generates a large set of types and rules, substituting sys for every $1. Suppose that you have two CGI programs you want to protect: a blog installation, and a wiki installation. Because these domains will be largely similar, you will create a macro. Open up the file you are using for local policy, /etc/selinux/policyname/src/policy/domains/misc/local.te.
In order to separate the blog and the wiki, you need to create separate types (domains) for them, allow httpd_t the ability to transition to those domains, and create new derived types to label their data. This is very similar to what most of apache_domain already does, but unfortunately that macro does too much for your simple needs. However, you can use that macro as a guide for creating your macro.
Start with the macro skeleton:
define(`apache_script_domain',` # macro content goes here ') dnl end of apache_script_domain
Define types for the CGI script executables (similar to httpd_sys_script_exec_t), and domains for the script processes (similar to httpd_sys_script_t). You also need to authorize the system_r role for the type.
type httpd_sys_$1_exec_t, file_type, sysadmfile; type httpd_sys_$1_t, domain, privmail; role system_r types httpd_sys_$1_t;
Allow Apache HTTP to execute the script, and cause it to transition to the script domain. You also want it to be able to signal your CGI script.
domain_auto_trans(httpd_t, httpd_sys_$1_exec_t, httpd_sys_$1_t) allow httpd_t httpd_sys_$1_t:process { signal sigkill sigstop };
Define new types for content. The following demonstrates a read-only type and a read-write type, but you can easily define different, more refined types. After defining them, grant your CGI domain the access to them that you want.
type httpd_sys_$1_ro_t, file_type, sysadmfile; type httpd_sys_$1_rw_t, file_type, sysadmfile; r_dir_file(httpd_sys_$1_t, httpd_sys_$1_ro_t) create_dir_file(httpd_sys_$1_t, httpd_sys_$1_rw_t)
User domains in strict policy | |
---|---|
On a strict policy system, you also need to allow the user domain to manage files with your new types. See apache_domain. |
Now at this point, you need to give your new domain permissions. There are a lot of permissions defined in apache_domain. You can copy the core of those.
# Copied from apache_macros, more is needed allow httpd_sys_$1_t httpd_t:fd use; allow httpd_sys_$1_t httpd_t:process sigchld; uses_shlib(httpd_sys_$1_t) can_network(httpd_sys_$1_t) can_ypbind(httpd_sys_$1_t) allow httpd_sys_$1_t { usr_t lib_t }:file { getattr read ioctl }; allow httpd_sys_$1_t usr_t:lnk_file { getattr read }; allow httpd_sys_$1_t self:process { fork signal_perms }; ...
There are actually more permissions likely to be necessary; they have been omitted for brevity.
You can now use your new macro to create the actual types:
apache_script_domain(wiki) apache_script_domain(blog)
Looking at your macro definition, you can mentally substitute wiki and blog for $1 and see what has happened. You can easily define quite a bit of policy, having built the macro.
Execute a make reload in the source directory. Debug any problems that occur.
Finally, you need to label your data. Use the chcon command to do this. For example, the wiki CGI executable should be labeled as httpd_sys_wiki_exec_t, its read-only data as httpd_sys_wiki_ro_t, and its database as httpd_sys_wiki_rw_t, etc. Similarly for the blog.
In the virtual hosting setup outlined in Section 6, “Virtual Hosting, CGI scripts and suEXEC”, a compromise or misconfiguration of the main Apache HTTP (httpd_t) means all sites are affected. It would be nice if you could actually have multiple Apache HTTP servers, each running in their own domain; for example, httpd_site_t. This subsection simply discusses the problems and outlines possible solutions.
Looking at the current policy, it is fairly obvious that in order to accomplish this goal, you will need to essentially rewrite it from scratch. The entire policy is predicated on a single httpd_t. The main idea would be to change it into a macro, along the lines of what you did with apache_script_domain in Section 10.1, “Individual Domains for Particular CGI Scripts”. In fact, if you also want to have individual domains on CGI scripts, you will end up with types of the form: httpd_vhost_sys_script_t. For example, httpd_examplecom_sys_wiki_t would be a wiki CGI for example.com.
Assuming that you have a macro apache_server_domain which defines all of the desired types and rules. If you are fortunate enough that you have an IP address per domain, one approach is simply to copy the /usr/sbin/httpd binary multiple times, but label each one differently:
ls -aZ /usr/sbin/httpd.* -rwxr-xr-x root root system_u:object_r:httpd_examplecom_exec_t /usr/sbin/httpd.examplecom -rwxr-xr-x root root system_u:object_r:httpd_barorg_exec_t /usr/sbin/httpd.barorg
Next, have your init scripts give each one a separate configuration, for example /usr/sbin/httpd.foocom -f foocom.com.
If you have to serve multiple virtual sites from one IP address, one approach might be to modify the Apache HTTP source code so that once it has read the desired domain from a requesting client, it passes off the request to a child which is running in the domain for that site.
This guide has posited that, from a security point of view, a misconfigured Apache HTTP is no different from a compromised one. SELinux deliberately separates the security policy from the configuration of every daemon on the system. One neat consequence of this is that it allows you to actually create a webmaster role that is allowed to modify the Apache HTTP configuration file /etc/httpd/conf/httpd.conf, restart the daemon, etc. But no matter how the webmaster changes the configuration, httpd is still restricted by the security policy.
In order to do this, you would need to use the strict policy. The overall approach is to create a new account with a uid of 0, but only allowed to log in with the webmaster_r role, which is only authorized for the webmaster_t type. You could create webmaster_t with full_user_role(webmaster), and then proceeed to add a few permissions, such as:
create_dir_file(webmaster_t, httpd_config_t)
Since you are giving uid 0 (root) access to the webmaster, in this setup you are entirely relying on the SELinux policy. There is more to do here (making init scripts work will be slightly tricky), but this outlines the general idea. The remainder is left as an exercise for you.