I'm a sysadmin for a small server at my university, which is used by students to experiment and run long jobs at their will.

I enabled PHP in user directories so they could experiment with websites and all with some comfort.

Recently, I realized that all PHP code in user directories are run by the web server user (www-data) which has some privileges that I obviously don't want to give to the users. A vulnerable site or malicious user could delete the server's regular website.

Is there a way to run user's PHP with the user's privileges?

I thought of a workaround to this that is giving the webserver almost zero privileges so a breach into it could not compromise anything serious. This is doable in my situation since the website is very very simple (plain html index).

The server is running Debian Jessie with Apache 2.4.10.

  • I might be misunderstanding, but why can't you give www-data only read permission on these folders?
    – harrymc
    Nov 9 '14 at 20:41
  • That would prevent it from deleting the site, but it could still kill the webserver or some other stuff like that. Maybe forbidding all kinds of exec() could work
    – Guido
    Nov 9 '14 at 22:23

I have done this a number of times using nginx, lighttpd, and apache. The concept is basically the same in all of them: [Fast]CGI processes that run as the user.

If you want to save yourself a lot of trouble, try messing with permissions as other people have suggested. In particular, if your user websites are owned by the users and your main site is owned by root or similar, you will be in decent shape. But users will still not be segregated and could read eachother's files etc (database passwords?). Running separate virtual servers for each user/site will give you the most security, and it is certainly simpler than trying to segregate users with apache.

The standard accepted way of segregating PHP users in apache is via SuExec (there is also mpm-itk). If you Google for apache fastcgi suexec or similar, you should get plenty of results. I think it would be impractical to include full directions here. It can vary based on various design decisions and the distribution in use. Instead, I will try to provide some points I consider important and a brief description of my configuration. My setup gets pretty complicated (and on top of this, I run a custom SELinux module). Here is a [hopefully] brief overview:

  • Name based virtual hosts by pointing *.somedomain to the server: user.somedomain is the name of the virtual host (I assume you could do the same thing with user directories?).
  • One /etc/httpd/vhosts.d/user.conf for each user.
  • One directory in /var/www/vhosts_config for each user.
    • /var/www/vhosts_config/$USER/wrapper-bin contains the wrapper to execute PHP (more on this later)
    • /var/www/vhosts_config/$USER/php/php.ini: I like to have a separate php.ini per user.
    • /var/www/vhosts_config/$USER/php/ext/*.ini: One ini file per available extension
    • /var/www/vhosts_config/$USER/php/ext-active/*.ini: symbolic links back to ../ext for each enabled extension (so I can enable/disable extensions per user too)
  • /var/www/vhosts/user contains the actual content for the vhost. I recommend always using a public_html subdirectory as the document root so users have a directory that is outside of the document root to store configuration files etc.
  • Users should not be able to read each other's files. You need to allow the webserver +x on the users'. There are a variety of ways you could do this. I use POSIX ACLs.

The per-user httpd config looks something like this (altered for brevity):

<VirtualHost *:80>
        ServerName someuser.somedomain
        DocumentRoot /var/www/vhosts/someuser/public_html

        SuexecUserGroup someuser someuser    

        FcgidWrapper /var/www/vhosts_config/someuser/wrapper-bin/php .php

        #max of 5 cgis per vhost
        FcgidMaxProcessesPerClass 5
        FcgidMinProcessesPerClass 0

        AddHandler php-fcgi .php
        Action php-fcgi /wrapper-bin/php

        Alias /wrapper-bin/ /var/www/vhosts_config/someuser/wrapper-bin/

        <Location /wrapper-bin/>
                Options +ExecCGI
                SetHandler fcgid-script

suexec is a setuid root program that apache invokes to start processes as a given user. It is pretty fussy. It requires that the program it executes is owned by the user and contained in a subdirectory of /var/www (this varies on different Linux distributions and is set at compile time) directory that is owned by the user. This is why each user needs his own PHP wrapper script.

/var/www/vhosts_config/someuser/wrapper-bin/php looks something like:

export PHPRC=/var/www/vhosts_config/someuser/php
export PHP_INI_SCAN_DIR=$PHPRC/ext-active/
umask 0077
exec /usr/bin/php-cgi
  • PHP_INI_SCAN_DIR sets a directory to look for additional php.ini files, which I use for loading extension-specific configuration.
  • PHPRC lists the directory in which to find the php.ini file.

You do not have to use a separate PHP configuration for each user, and doing so certainly complicates things. I like to give users a stripped down configuration and enabled only what is necessary (and a few common extensions by default). I also load suhosion with different configurations for each user (different encryption keys).


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.