suEXEC overview

Overview

suEXEC (which stands for "switch user execution") allows Apache users to run CGI and SSI programs under different users. When used properly, it considerably reduces the security risks involved with allowing users to develop and run private CGI or SSI programs.

Security

Script permissions

suEXEC requires all CGI scripts and the directories they reside to only be writable by the owner. If permissions are not set correctly, a 500 Internal Server Error is thrown.

The script must also be executable. You can set permissions by logging into your server via SSH and running the chmod command. For example:

[server]$ chmod 755 script.cgi

Environment variable checking

suEXEC only passes through environment variables that are considered safe.

In the following examples, username would be your Shell user and example.com your website.

suEXEC example

Because suEXEC only allows variables deemed safe by the server's configuration, the directives provided by mod_env such as SetEnv, PassEnv, and SetEnvIf don't work as expected.

The following steps create a simple example of how suEXEC functions at DreamHost.

 

Step 1 — Add a file to your site

Create a file named foobar.cgi with the following content.

#!/usr/bin/perl

use strict;
use CGI;

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

Place this file in two different websites under the same username. For example:

  • /home/username/example.com
  • /home/username/example2.com

Step 2 — Add an .htaccess file to each directory

In the example.com directory, add an .htaccess with the following directive:

SetEnv FOOBAR foo

In the example2.com directory, add an .htaccess with the following directive:

SetEnv FOOBAR bar

Step 3 — Visit the files in a browser

Visit example.com/foobar.cgi in your browser. You'd expect it to print foo.

Visit example2.com/foobar.cgi in your browser. You'd expect it to print bar.

Instead, default is printed on both pages.

Explanation

SetEnv is working correctly to set the environment variable. The problem is that suEXEC isn't passing it to your CGI script.

Workarounds

The following sections offer you a few ways to get your environment variables working as intended.

Option 1: Prepend HTTP_ to the variable name

DreamHost's configuration of suEXEC allows any environment variable beginning with HTTP_ through.

Change your .htaccess file to the following:

SetEnv HTTP_FOOBAR foobar

then use the following in your CGI script.

$ENV{HTTP_FOOBAR} 

This allows it to work as expected.

When to use this option

This workaround is 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.

Option 2: Edit the CGI script to set the variable itself

Place $ENV{FOOBAR} = 'foo'; at the top of your script. For example:

#!/usr/bin/perl

use strict;
use CGI;

$ENV{FOOBAR} = 'foo';

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

When to use this option

Like Option 1, this workaround is best suited to applications you're developing yourself.

However, keep in mind 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.

Option 3: Create a wrapper script

Create a file named wrapper.cgi with the following content:

#!/bin/sh

export FOOBAR=foo
exec /home/username/example.com/foobar.cgi

Then add the following to your .htaccess file to redirect requests for the script to wrapper.cgi:

RewriteEngine On
RewriteRule ^script.cgi /wrapper.cgi
  • 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 the following for .pl and .py extensions.
    RewriteRule .*\.pl wrapper.cgi
    RewriteRule .*\.py wrapper.cgi
  • 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/username/example.com/ and /home/username/example2.com can refer to the same script(s) in /home/username.
  • If you're setting environment variables for an application, you can specify the application directory instead. For example:
    RewriteEngine On
    RewriteRule ^application/.*\.cgi /wrapper.cgi

When to use this option

Option 3 is convoluted but is suitable for a far wider range of cases

Unlike Option 1, 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 Option 2, 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

Did this article answer your questions?

Article last updated PST.

Still not finding what you're looking for?