Entry
How to enable suexec for php scripts outside ScriptAlias directory of VirtualHost
Why are the files I create in PHP owned by "nobody"?
Dec 18th, 2003 04:23
Alexander Amelkin, Simon "janus" Dassow
Apache's mod_php executes php scripts under the UID of the apache
process itself (usually 'nobody'), which is a potential security
problem. There are solutions for enabling suexec on php scripts, but
they require all your scripts are in the ScriptAlias'ed directory of a
VirtualHost and the first line of your php scripts is a magic line
(#!/usr/bin/php). However, if you provide virtual hosting for your
customers, it's not too good to ask them to edit their php scripts to
conform with those rules. That's why I developed the following small C
program acting as a PHP wrapper. Using this program will enable UID/GID
setting on php scripts, while keeping them look like they are processed
by mod_php. The original version has been slightly advanced by
Simon "janus" Dassow to fix certain problems with scripts running
outside the cgi-bin directory. Since I keep getting questions about the
wrapper, I decided to post Simon's version here instead of the original
one. Later I found that both the original script and the Simon's
version suffer from memory allocation errors sometimes. So here is the
latest updated and advanced version of the wrapper:
/*******************************************************
* This is a new version of the wrapper originally written
* by me and then slightly modified by Simon 'janus' Dassow.
* The new version fixes memory allocation problems that
* lead to sigfaults sometimes. It also introduces
* debug information output to apache's error logs and the
* output is made more "apache-like".
*
* Alexander Amelkin <spirit@reactor.ru>
* 23.05.2021
*******************************************************
*/
#define VERSION "1.2"
/* Previous info:
*
* This is a modified version of the PHP wrapper from
* Alexander Amelkin.
*
* I modified it to run under conditions where php is
* running outside cgi-bin.
*
* Now the wrapper fixes the environmentvariables
* SCRIPT_NAME and SCRIPT_FILENAME.
*
* Wed Aug 21 16:46:46 CEST 2002
* Simon "janus" Dassow, janus@linux-de.org
*
*
* (c) 2002, Alexander Amelkin, spirit@reactor.ru
*
* -----------------------------------------------------
* Installation Manual
* -----------------------------------------------------
* To enable suexec php scripts, first of all you must
* disable php processing by mod_php. To do so, comment
* out all php-related strings in your httpd.conf
*
* Add the following strings to httpd.conf:
* AddType application/x-httpd-php .php .php4 .php3 .phtml
* Action application/x-httpd-php /cgi-bin/php_cgi
*
* Do the following:
* gcc php_cgi.c -o php_cgi
* chmod 755 php_cgi
*
* Copy (do not use ln) php_cgi to the ScriptAlias /cgi-bin/ directory
* of each VirtualHost, where php must be enabled. chown php_cgi
* to whatever is set by User and Group directives for each
* VirtualHost. You may also want to 'chattr +i php_cgi' in order
* to prevent user from deleting or modifying the wrapper.
*
* You're done. No modification to the existing php scripts
* is required.
*
* To enable debug information output, create a file called
* 'php_debug' in the directory where php_cgi resides.
*
*******************************************************/
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <libgen.h>
#include <time.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
// PHP binary file
#define PHPPATH "/usr/bin/php"
void report(char *s)
{
time_t t;
char *tm;
char ss[2048];
time(&t);
tm=ctime(&t);
tm[strlen(tm)-1]=0;
fprintf(stderr, "[%s] [debug] php_cgi %s\n", tm, s);
}
void reperror(char *s)
{
time_t t;
char *tm;
char ss[2048];
time(&t);
tm=ctime(&t);
tm[strlen(tm)-1]=0;
sprintf(ss, "[%s] [error] php_cgi %s", tm, s );
perror(ss);
}
extern char **environ;
int main(int argc, char * argv[])
{
char ss[2048];
char *buf;
char *script_name;
char *script_filename;
char *document_root;
int i=0;
int debug=0;
struct stat flag;
time_t t;
sprintf(ss, "PHP wrapper version %s started", VERSION);
report(ss);
sprintf(ss,"%s/php_debug",dirname(argv[0]));
if( ! stat(ss,&flag) ) // File exists
debug=1;
if(debug)
{
report("Reporting environment variables:");
while( environ[i] != NULL )
report(environ[i++]);
report("End of environment");
}
// Check if a script name was supplied
if(getenv("PATH_TRANSLATED")!=NULL) {
// Allocate some memory for a temporary buffer
if(debug) report("Allocating memory for
PATH_TRANSLATED");
buf=strdup(getenv("PATH_TRANSLATED"));
if(buf==NULL) {
printf("Content-Type: text/plain\n\n");
reperror("step 1 memory allocation error");
return 1;
}
// Get the script's working directory
dirname(buf);
// chdir there
if(debug)
{
sprintf(ss,"Changing working directory to %s",buf);
report(ss);
}
if(chdir(buf)==-1) {
printf("Content-Type: text/plain\n\n");
reperror("can't chdir");
return 1;
}
// We don't need the buffer anymore
free(buf);
// Reset broken SCRIPT_* vars
// Allocate memory
if(debug) report("Allocating memory for REQUEST_URI and
DOCUMENT_ROOT");
script_name=strdup(getenv("REQUEST_URI"));
document_root=strdup(getenv("DOCUMENT_ROOT"));
if((script_name==NULL)||(document_root==NULL)) {
printf("Content-Type: text/plain\n\n");
reperror("step 2 memory allocation error");
return 1;
}
if(debug) report("Resetting REQUEST_URI and
DOCUMENT_ROOT");
setenv("REQUEST_URI",script_name,1);
if(strchr(script_name,'?')) rindex(script_name,'?')[0]
=0;
setenv("SCRIPT_NAME",script_name,1);
if(debug) report("Allocating memory for
SCRIPT_FILENAME");
script_filename=(char*)malloc((strlen(document_root)
+strlen(script_name)+1));
if(script_filename==NULL) {
printf("Content-Type: text/plain\n\n");
reperror("step 3 memory allocation error");
return 1;
}
sprintf(script_filename,"%s%
s",document_root,script_name);
if(debug)
{
sprintf(ss,"SCRIPT_FILENAME=\"%
s\"\n",script_filename);
report(ss);
}
// Invoke php to run the script, all CGI parameters are
left
// untouched in the system environment
setenv("SCRIPT_FILENAME",script_filename,1);
if(debug) report("Freeing script_name...");
free(script_name);
if(debug) report("Freeing script_filename...");
free(script_filename);
if(debug) report("Freeing document_root...");
free(document_root);
sprintf(ss,"executing [%s]...", getenv
("PATH_TRANSLATED"));
report(ss);
execl(PHPPATH,PHPPATH,getenv("PATH_TRANSLATED"),NULL);
return 0;
}
printf("Content-Type: text/plain\n\n");
sprintf(ss,"wrong script requested [%s]",getenv
("PATH_TRANSLATED"));
printf("%s",ss);
reperror(ss);
return 1;
}