Centos – How to allow unprivileged apache/PHP to do a root task (CentOS)

apache-2.2centosselinuxsftpsudo

I am setting up a sort of personal dropbox for our customers on a CentOS 6.3 machine. The server will be accessible thru SFTP and a proprietary http service base on PHP. This machine will be in our DMZ so it has to be secure. Because of this I have apache running as an unprivileged user, hardened the security on apache, the OS, PHP, applied a lot of filtering in iptables and applied some restrictive TCP Wrappers. Now you might have suspected this one was coming, SELinux is also set to enforcing.

I'm setting up PAM to use MySQL so my users in the web application can login.
These users will all be in a group that can use SSH only for SFTP and users will be chrooted to their own 'home' folder.

To allow this SELinux wants the folders to have the user_home_t tag. Also the parent directory needs to be writable by root only. If these restrictions are not met SELinux will kill the SSH pipe immediately.

The files that need to be accessible thru both http and SFTP so I have made a SELinux module to allow Apache to search/attr/read/write etc. to directories with the user_home_dir_t tag.

As sftp users are stored in MySQL I want to setup their home dirs upon user creation. This is a problem since Apache has no write access to the /home dir, it's only writable by root since it's required to keep SELinux and OpenSSH happy.

Basically I need to let Apache do only a few tasks as root and only within /home. So I need to somehow elevate the privileges temporarily or let root do these tasks for apache instead.

What I need to have apache do with root privileges is the following.

mkdir /home/userdir/
mkdir /home/userdir/userdir
chmod -R 0755 /home/userdir
umask 011 /home/userdir/userdir
chcon -R -t user_home_t /home/userdir
chown -R user:sftp_admin /home/userdir/userdir
chmod 2770 /home/userdir/userdir

This would create a home for the user, now I have an idea that might work, cron.
That would mean the server needs to check for users that have no home every minute, then when creating users the interface would freeze for an average of 30 seconds before the account creation can be confirmed which I do not prefer. Does anybody know if something can be done with sudoers? Or any other idea's are welcome…

Thanks for your time!

Best Answer

Create a script that implements the commands you describe, and run setuid on it to enable it to run as root when anyone invokes it. See http://en.wikipedia.org/wiki/Setuid for more information on setuid.

From PHP, you can then just call system('/bin/setupNewUser'); as usual and the script will be ran as root.


Alternative solution that limits the injection opportunities and works on systems with setuid execution on scripts disabled:

Create a small program that does have setuid. An example listed below:

#include <stdlib.h>
#include <iostream>
#include <string>

using namespace std;

int main(int argc, char* argv[])
{
    if (argc != 2)
    {
        cout << "Invalid arguments";
        return 1;
    }

    //make sure the argument passed to the script has only [a-z]
    for (char* i = argv[1]; *i != '\0'; i++)
    {
       //check if the current character falls outside an allowed range
       if (!(
           //Allowed ranges:
           // between a and z
           ('a' <= *i && *i <= 'z') 
           // between 0 and 9
           || ('0' <= *i && *i <= '9')
           // '_' or '-'
           || ('_' == *i)
           || ('-' == *i)
       ))
       {
           cout << "Illegal character in username: " << *i << endl;
           return 2;
       }
    }

    //append the username
    string command = string("/test/setupUser.sh ");
    command += string(argv[1]);

    //execute the command and return the result transparently
    // return system(command.c_str());
    // * OR *
    // call mkdir directly
}

Call the setuid program from php

system('/test/setupUser '.escapeshellarg($username));