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.