Suexec

From DreamHost
Jump to: navigation, search

Overview

suEXEC (which stands for "switch user execution") provides Apache users the ability to run CGI and SSI programs under user IDs different from the user ID of the calling web server. Normally, when a CGI or SSI program executes, it runs as the same user who is running the web server.

As a security precaution, suexec REQUIRES that all cgi scripts and the directories in which they reside not be writable by anyone but the owner user. suEXEC performs checks on the executed CGI to ensure security for the server. It has strict permission checking that results in a "500 Internal Server Error" if the permissions of the script are not set correctly. It also cleans the environment by only passing through those environment variables that are considered safe.

When used properly, this feature can considerably reduce the security risks involved with allowing users to develop and run private CGI or SSI programs.

Script permissions

Because of the way suEXEC handles security, the directory in which your CGI script lives and the script itself must not be writable by anyone but the user. If it is writable, you will receive a "500 Internal Server Error". The script must also be executable, and the easiest way to set permissions is with chmod:

$ chmod 755 script.cgi

Apache module mod_env

Because suEXEC cleans the environment by wiping out all the environment variables except those deemed safe by the server's configuration, the directives provided by mod_env such as SetEnv, PassEnv, and SetEnvIf don't work as expected.

Say you have foobar.cgi installed at both /home/username/example.com and /home/username/example2.com:

#!/usr/bin/perl

use strict;
use CGI;

$ENV{FOOBAR} ||= 'default';
my $q = CGI->new;
print $q->header();
print $ENV{FOOBAR};

In your first domain's directory, add an .htaccess with the directive:

SetEnv FOOBAR foo

and into your second domain's directory, add the following:

SetEnv FOOBAR bar

When you visit example.com/foobar.cgi in your browser, you'd expect it to print foo, and when you visit example2.com/foobar.cgi, you'd expect bar. Instead, default is printed both times.

At first, you might assume mod_env is not installed or that the SetEnv directive is not allowed from .htaccess. In fact, SetEnv is working just fine. It is setting the environment variable; suEXEC just isn't passing it to your CGI script.

On the one hand, if you're developing your own application, you can just keep this in mind while developing it and not design your application to use non-standard environmental variables. If, on the other hand, you are using a third-party application and modifying the source code would be prohibitively difficult and/or time-consuming (or if you just need to have those custom environment variables in the application you're developing yourself), there are a few workarounds which are described in the next section.

Workarounds

Workaround A: Prepending HTTP_ to Variable Name

The way DreamHost configures suEXEC, it allows any environment variable that begins with HTTP_ through. So, if you change your .htaccess to:

SetEnv HTTP_FOOBAR foobar 

and use:

$ENV{HTTP_FOOBAR} 

in your CGI script, it works as expected.

This workaround is probably best suited to applications you're developing yourself. Digging through the source code of third-party applications (especially large applications, which may have hundreds of thousands of lines) and changing each instance of $ENV{FOOBAR} to $ENV{HTTP_FOOBAR} would be both prohibitively complex and time-consuming, not to mention you'd have to redo all that work every time you upgraded the application. This solution is a bit hacky, but it works.

Workaround B: Editing the CGI Script to Set the Variable Itself

You can place $ENV{FOOBAR} = 'foo'; at the top of your script. Like Workaround A, this workaround is best suited to applications you're developing yourself. However, its usefulness is limited: the purpose of using environment variables is to alter the behavior of your script based on the context (or environment) it's executed from. If you set the variable from within the script itself, the behavior of the script is the same regardless of the context it's executed from.

Workaround C: Creating a Wrapper Script

Create wrapper.cgi:

#!/bin/sh

export FOOBAR=foo
exec /home/username/example.com$SCRIPT_URL
  • If you just have one CGI script you wish to set an environmental variable for, you can change the last line of the wrapper to:
exec /home/exampleuser/example.com/foobar.cgi
  • If you need to set an environment variable for dozens or even hundreds of files of a third-party application , $SCRIPT_URL allows you to determine which script was requested and set the environmental variable for that script.

Then, use an .htaccess to redirect requests for the script to wrapper.cgi:

RewriteEngine On
RewriteRule ^application/.*\.cgi /wrapper.cgi
  • application is the directory where your (third-party or in-house developed) app lives.
  • If you're only setting an environment variable for one (or a few) CGI scripts, you can specify the script in the RewriteRule (e.g., RewriteRule ^script.cgi /wrapper.cgi). Just add additional RewriteRules for additional scripts.
  • As you can imagine, simply continuing to add RewriteRules past a few scripts becomes unwieldy. Instead, it's best to match all files ending with a specific file extension. By matching only files ending with .cgi, .pl, .py, .rb, and so on, static files such as HTML documents and images are unaffected.
  • If your wrapper script is in the same directory as the scripts you wish to wrap and you're matching by pattern (this doesn't matter if you are matching a specific file name), your wrapper must have a different file extension than your scripts. For example, if your wrapper ends in .cgi, you might want to use .pl (RewriteRule .*\.pl wrapper.cgi) or .py (RewriteRule .*\.py wrapper.cgi) for your scripts.
  • wrapper.cgi can call a script outside your domain's directory. This is actually what you probably want to do, as the wrapper in both /home/exampleuser/example.com/ and /home/exampleuser/example2.com can refer to the same script(s) in /home/shared.

Workaround C is convoluted but is suitable for a far wider range of cases than either Workaround A or Workaround B. Unlike Workaround A, you can set an environmental variable for hundreds (or thousands) of files by editing just two files (wrapper.cgi and .htaccess. And, when you set an environmental variable for a third-party application, you don't have to modify any files when you upgrade the application. You can have multiple wrapper scripts, so unlike Workaround B, the context (or environment) can actually alter the behavior of the script(s). Simply copy wrapper.cgi and .htaccess from example.com to example2.com, edit wrapper.cgi, and then change export FOOBAR=foo to export FOOBAR=bar, and you're done.

See also