Using suexec to run PHP under a different account

Learn Web Development!
I wanted to run PHP for some virtual hosts on a webserver using the user's account for her websites that were handled in Apache's httpd.conf via virtual hosts. To do so, I planned to use suEXEC.

Apache suEXEC is a feature of the Apache Web server. It allows users to run CGI and SSI applications as a different user - normally, all web server processes run as the default web server user (often wwwrun, Apache or nobody). The suEXEC feature consists of a module for the web server and a binary executable which acts as a wrapper. suEXEC was introduced in Apache 1.2 and is often included in the default Apache package provided by most Linux distributions.

If a client requests a CGI and suEXEC is activated, it will call the suEXEC binary which then wraps the CGI scripts and executes it under the user account of the server process (virtual host) defined in the virtual host directive.

Additionally, suEXEC perform a multi-step check on the executed CGI to ensure security for the server (including path-checks, a limit of permitted commands, etc.)

It took me quote some time to get it working, but eventually I succeeded.

I verifed that Apache on the system was compled with suexec support.

 httpd -V | grep suexec
 -D SUEXEC_BIN="/usr/sbin/suexec"

I checked the suexec settings on the system.

# suexec -V
 -D AP_DOC_ROOT="/var/www"
 -D AP_GID_MIN=100
 -D AP_HTTPD_USER="apache"
 -D AP_LOG_EXEC="/var/log/httpd/suexec.log"
 -D AP_SAFE_PATH="/usr/local/bin:/usr/bin:/bin"
 -D AP_UID_MIN=500
 -D AP_USERDIR_SUFFIX="public_html"
# ls -l /usr/sbin/suexec
-r-s--x--- 1 root apache 14264 Aug 30 12:32 /usr/sbin/suexec
I placed the following line in the virtualhost section for the website in Apache's httpd.conf file:
SuexecUserGroup jdoe jdoe

I also added the line below to the same virtualhost section:

ScriptAlias /cgi-bin/ "/home/jdoe/public_html/examplesite/cgi-bin/"

I then needed to copy the php-cgi file located in /usr/bin in the user's cgi-bin directory and verify that the ownership of the file was set for the user's account.

[jdoe@frodo cgi-bin]$ pwd
/home/jdoe/public_html/moonwillowsrealm/cgi-bin
[jdoe@frodo cgi-bin]$ cp /usr/bin/php-cgi .
[amy@frostdragon cgi-bin]$ ls -l php-cgi
-rwx------ 1 jdoe jdoe 2872768 Feb 21 22:57 php-cgi

I placed a PHP file, whoami.php, and a Perl file whoami.pl, in the cgi-bin directory to use for testing. Both files were owned by the user's account and had the group set to the user's group. The .php file had permissions of 644 and the .pl file had permissions of 700.

Whenever I checked whoami.php in the cgi-bin directory, I saw the following:

uid=48(apache) gid=48(apache) groups=48(apache)

Whenever I used whoami.pl, I saw the following:

Internal Server Error

The server encountered an internal error or misconfiguration and was unable to complete your request.

When I checked the error log file for the website, I saw the following:

[Fri Feb 11 21:45:26 2011] [error] [client 72.45.13.244] suexec policy violation: see suexec log for more details
[Fri Feb 11 21:45:26 2011] [error] [client 72.45.13.244] Premature end of script headers: whoami.pl

When I checked the /var/log/httpd/suexec.log, I saw the following entries corresponding to times I attempted to access whoami.pl

[2011-02-11 21:45:26]: uid: (501/jdoe) gid: (501/501) cmd: whoami.pl
[2011-02-11 21:45:26]: command not in docroot (/home/jdoe/public_html/examplesite/cgi-bin/whoami.pl)

From the error message I realized the problem was due to the fact that suexec had been compiled to use /var/www for AP_DOC_ROOT as could be seen by issuing the command suexec -V [Note: suexec -V can only be run as root].

# suexec -V
 -D AP_DOC_ROOT="/var/www"
 -D AP_GID_MIN=100
 -D AP_HTTPD_USER="apache"
 -D AP_LOG_EXEC="/var/log/httpd/suexec.log"
 -D AP_SAFE_PATH="/usr/local/bin:/usr/bin:/bin"
 -D AP_UID_MIN=500
 -D AP_USERDIR_SUFFIX="public_html"

Any program that will be run by suexec must reside within suEXEC's document root. On this system, user's websites are within /home/username/public_html. So I needed the document root to be /home, instead. Unfortunately, the only way to change an suexec option is to recompile the software.

So I followed the instructions found at Setting up suEXEC for changing the value of AP_DOC_ROOT.

I first determined the RPM for Apache that was installed on the system with rpm -qi httpd. You can also get that information by using httpd -V.

# httpd -V | grep "Server version"
Server version: Apache/2.2.3

Since the system was running CentOS, I downloaded the approprate source RPM from http://mirror.centos.org/centos/5/updates/SRPMS/. Update: (2012-02-21) Check the readme.txt file at this location for the current location of SRPMS files. On February 21, 2012, the SRPMS are at http://vault.centos.org/5.7/updates/SRPMS/. The latest SRPM file as of February 21, 2012 for CentOS 5.7 is httpd-2.2.3-53.el5.centos.3.src.rpm. You would need to get the appropriate file for whatever distribution and version of Linux you are using.

Update for CentOS 7 on 2015-07-25: For CentOS 7, the source file for Apache 2.4.6 can be found at CentOS Mirror. I checked the version of the httpd package on the system and found it was 2.4.6-31.el7.

# rpm -qi httpd | grep "Source RPM"
Source RPM  : httpd-2.4.6-31.el7.centos.src.rpm

There were httpd-2.4.6-18.el7.centos.src.rpm and httpd-2.4.6-19.el7.centos.src.rpm at the CentoS Mirror site, vault.centos.org. I downloaded the "-19" one.

I then extracted the files from the source package using rpm -ivh package.

# rpm -ivh httpd-2.2.3-43.el5.centos.3.src.rpm 
   1:httpd                  warning: user mockbuild does not exist - using root
warning: group mockbuild does not exist - using root

I received a lot of "warning: group mockbuild does not exist - using root" messages, but those weren't of concern to me.

With CentOS 5.7, I found the source files in /usr/src/redhat/SOURCES. Within that directory, I found the file httpd-2.2.3.tar.gz. I unzipped and untarred it. I then made the working directory the one created when I extracted the files from the .rpm.gz file.

I used the following command to extract the files with CentOS 5.7:

# tar -zxvf httpd-2.2.3.tar.gz
# cd httpd-2.2.3

With CentOS 7, there was no /usr/src/redhat/SOURCES directory, but I found the file I needed, which was a .bz2 file in /root/rpmbuild/SOURCES/, instead. With the bz2 file on CentOS 7, I had to subsitute a "j" for the "z" in the command to extract the contents of the .bz2 file:

[root@localhost temp]# ls /root/rpmbuild/SOURCES/httpd-2.4.6.tar.bz2
/root/rpmbuild/SOURCES/httpd-2.4.6.tar.bz2
[root@localhost temp]# cp /root/rpmbuild/SOURCES/httpd-2.4.6.tar.bz2 .
[root@localhost temp]# tar -jxvf httpd-2.4.6.tar.bz2

I needed to edit the suexec.h file, so I looked for it and found it within the support subdirectory.

# find . -name suexec.h
./support/suexec.h

I then looked for the location where AP_DOC_ROOT was set within suexec.h and found the following:

The Basics of PHP for Web Development
The Basics of PHP for Web Development
1x1 px

/*
 * DOC_ROOT -- Define as the DocumentRoot set for Apache.  This
 *             will be the only hierarchy (aside from UserDirs)
 *             that can be used for suEXEC behavior.
 */
#ifndef AP_DOC_ROOT
#define AP_DOC_ROOT DEFAULT_EXP_HTDOCSDIR
#endif

I changed the define AP_DOC_ROOT line so it read as follows:

#define AP_DOC_ROOT "/home"

Since the default value in suexec.h for HTTPD_USER I had seen when I ran suexec -V earlier was not what it was for suexec on the system, I changed that value as well, so it would be as it was before for the system when I recompiled.

/*
 * HTTPD_USER -- Define as the username under which Apache normally
 *               runs.  This is the only user allowed to execute
 *               this program.
 */
#ifndef AP_HTTPD_USER
#define AP_HTTPD_USER "www"
#endif

I changed #define AP_HTTPD_USER "www" to be #define AP_HTTPD_USER "apache", since that was the user under which Apache was running on the system and was the value I had seen when I ran suexec -V.

I also changed the value for AP_UID_MIN from the default value in suexec.h to the one that was present for suexec on the system.

Learn PHP as a master
Learn PHP as a master
1x1 px

/*
 * UID_MIN -- Define this as the lowest UID allowed to be a target user
 *            for suEXEC.  For most systems, 500 or 100 is common.
 */
#ifndef AP_UID_MIN
#define AP_UID_MIN 100
#endif

I changed the value for AUP_UID_MIN from 100 to 500.

I also needed to change the variable that points to the location of suexec's log file.

/*
 * LOG_EXEC -- Define this as a filename if you want all suEXEC
 *             transactions and errors logged for auditing and
 *             debugging purposes.
 */
#ifndef AP_LOG_EXEC
#define AP_LOG_EXEC DEFAULT_EXP_LOGFILEDIR "/suexec_log" /* Need me? */
#endif

I changed the value of AP_LOG_EXEC to be "/var/log/httpd/suexec.log", so it matched the file where logging was shown when I issued the suexec -V command when I was running CentOS 5.7. I didn't see any value for AP_LOG_EXEC when I ran the command on the CentOS 7 system:

# suexec -V
 -D AP_DOC_ROOT="/var/www"
 -D AP_GID_MIN=100
 -D AP_HTTPD_USER="apache"
 -D AP_LOG_SYSLOG
 -D AP_SAFE_PATH="/usr/local/bin:/usr/bin:/bin"
 -D AP_UID_MIN=500
 -D AP_USERDIR_SUFFIX="public_html"

Note: this value is derived from the include/ap_config_layout.h, so it could also be changed there.

I then ran configure. Note: the instructions at Setting up suEXEC suggest using ./configure --prefix=/usr/local/apache2, but Apache isn't installed in /usr/local/apache2 on a CentOS system. Instead, it is installed in /usr/sbin.

# ./configure

Note: run ./configure from the top-level directory in which you extraced the contents of the .gz file or .bz2 file, not the support subdirectory where suexec.h is located.

On the CentOS 7 system, when I ran ./configure, I encountered the error message "configure: error: APR not found. Please read the documentation." I was able to resolve the problem by downloading Apache Portable Runtime Project files.

I then ran make suexec. Note: If later you change any of the options in suexec.h and need to recompile suexec again, you will need to use make --always-make suexec to have any subsequent changes incorporated after running make suexec the first time.

# make suexec

I then had a new binary version of suexec in the support subdirectory with AP_DOC_ROOT now set to /home.

I then needed to change permissions on the suexec file thst was just created.

# chmod 4510 support/suexec

I replaced the existing version of suexec in /usr/sbin with the new version.

[root@frodo httpd-2.4.6]# cp support/suexec /usr/sbin/suexec
cp: overwrite `/usr/sbin/suexec'? y
[root@frodo httpd-2.4.6]# ls -l support/suexec
-r-s--x---. 1 root root 35347 Jul 25 22:17 support/suexec
[root@frodo httpd-2.4.6]# support/suexec -V
 -D AP_DOC_ROOT="/home"
 -D AP_GID_MIN=100
 -D AP_HTTPD_USER="apache"
 -D AP_LOG_EXEC="/usr/local/apache2/logs/var/log/httpd/suexec_log"
 -D AP_SAFE_PATH="/usr/local/bin:/usr/bin:/bin"
 -D AP_UID_MIN=500
 -D AP_USERDIR_SUFFIX="public_html"

I noticed that the AP_LOG_EXEC path was /usr/local/apache2/logs/var/log/httpd/suexec_log, though I had the following line in support/suexec.h:

#define AP_LOG_EXEC DEFAULT_EXP_LOGFILEDIR "/var/log/httpd/suexec_log"

I found the following line in include/ap_config_layout.h beneath the directory where I had extracted the files in the .bz2 file:

#define DEFAULT_EXP_LOGFILEDIR "/usr/local/apache2/logs"

Since there is no /usr/local/apache2 directory on the system, I modified that line as shown below, instead:

#define DEFAULT_EXP_LOGFILEDIR "/var/log/httpd/"

I then changed the AP_LOG_EXEC line in support/suexec.h to be as follows:

#define AP_LOG_EXEC DEFAULT_EXP_LOGFILEDIR "suexec_log"

I then reran ./configure and make suexec.

Since when I had previously checked for suexec support within Apache, I had found it was already loaded, I didn't have to modify the Apache etc/httpd/conf/httpd.conf file by adding LoadModule suexec_module modules/mod_suexec.so to include suexec support.

httpd -V | grep suexec
-D SUEXEC_BIN="/usr/sbin/suexec"

And I had already added SuexecUserGroup username groupname to the virtualhost section for the website in Apache's httpd.conf file.

But when I attempted to access a Perl script in the website's cgi-bin directory, I got a "500 Internal Server Error" message with "The server encountered an internal error or misconfiguration and was unable to complete your request."

When I checked the website's error log, I saw the following:

[Sun Feb 13 16:54:12 2011] [error] [client 72.45.13.244] (13)Permission denied: exec of '/usr/sbin/suexec' failed
[Sun Feb 13 16:54:12 2011] [error] [client 72.45.13.244] Premature end of script headers: whoami.pl

I checked the suexec file's permissions again and found what what appeared to be the source of the problem:

# ls -l /usr/sbin/suexec
-r-s--x--- 1 root root 29343 Feb 12 22:31 /usr/sbin/suexec

I then realized that the group for suexec previously had been apache, so I changed it from root to apache.

# chgrp apache /usr/sbin/suexec

Attempting to access the Perl script again resulted in the same error, so I restarted apache, but then saw another error.

# apachectl restart
Warning: SuexecUserGroup directive requires SUEXEC wrapper

Looking at the permissions on suexec again, I saw that they had been reset to 710. I changed the permissions to what was needed and restarted Apache.

# ls -l /usr/sbin/suexec
-r-x--x--- 1 root apache 29343 Feb 12 22:31 /usr/sbin/suexec
# chmod 4510 /usr/sbin/suexec
# ls -l /usr/sbin/suexec
-r-s--x--- 1 root apache 29343 Feb 12 22:31 /usr/sbin/suexec
# apachectl restart

But the "500 Internal Server Error" message reappeared when I tried accessing the Perl script again. Checking the website's error log file, I saw the following:

[Sun Feb 13 19:21:09 2011] [error] [client 72.45.13.244] suexec policy violation: see suexec log for more details
[Sun Feb 13 19:21:09 2011] [error] [client 72.45.13.244] Premature end of script headers: whoami.pl

Checking the /var/log/httpd/suexec.log file, I saw the following:

[2011-02-13 19:21:09]: uid: (501/jdoe) gid: (501/501) cmd: whoami.pl
[2011-02-13 19:21:09]: directory is writable by others: (/home/jdoe/public_html/examplesite/cgi-bin)

Checking the permissions on the cgi-bin directory, I saw the following:

$ ls -ld cgi-bin
drwxrwxr-x 2 jdoe jdoe 4096 Feb 13 15:51 cgi-bin

I changed the permissions to be 700.

$ chmod 700 cgi-bin

But that resulted in a "403 Forbidden" message with "You don't have permission to access /cgi-bin/whoami.pl on this server." when I atttmpted to access the webpage for the Perl script via a browser.

I changed the permissions on the directory to 711 and the permissions on the whoami.pl file to be 700. I was then able to access the Perl script, after correcting a coding error in it, in the cgi-bin directory, which was defined in the virutalhosts section for that particular website in httpd.conf with ScriptAlias /cgi-bin/ "/home/jdoe/public_html/examplesite/cgi-bin/".

<VirtualHost *:80>
    ServerName example.com
    ServerAlias www.example.com
    ScriptAlias /cgi-bin/ "/home/jdoe/public_html/examplesite/cgi-bin/"
    ServerAdmin webmaster@example.com
    DocumentRoot /home/jdoe/public_html/examplesite
    ErrorLog /home/jdoe/public_html/examplesite/logs/example-error.log
    CustomLog /home/jdoe/public_html/examplesite/logs/example-transfer.log common
    SuexecUserGroup jdoe jdoe
</VirtualHost>

The Perl script, whoami.pl, which had a userid and groupid that matched the user's account, had permissions 700 and showed that it was running under the user's account. The script whoami.pl contained the following lines:

#!/usr/bin/perl -w

print "Content-type: text/html\n\n";

print "<html><head><title>Whoami Perl Test</title></head>\n<body>\n";
system ("id -a");
print "</body>\n</html>"

It produced the following output showing me that Apache was running under the user's account for the relevant website:

uid=501(jdoe) gid=501(jdoe) groups=501(jdoe),509(test)

A whoami.php file I put in the same directory with similar code, produced the same output.

The code for whoami.php was as follows:

<html>

<body>

<?php
system("id -a");
?>

</body>

</html>

Note: When I much later upgraded Apache on the system, I had to go through the same steps for recompiling suexec again and replacing the one in usr/bin with the one I created after again modifying suexec.h.

References:

  1. suEXEC
    Wikipedia, the free encyclopedia
  2. Using suexec To Secure A Shared Server
    Stuard Herbert’s Ramblings
  3. Setting up suEXEC
    CosmicScripts.com - Free CGI Scripts in Perl from CosmicScripts.com
  4. RPM RedHat Package Manager
    Unofficial SUSEFAQ