Execute shell script from php, as root user?

26,786

Solution 1

For security reasons you should never try to execute something with the user www-data with more privileges than it naturally has. There was a reason why some times ago the Apache access was moved to www-data. If you need to do something on your machine as root - and changing passwords or creating accounts is this 'something' - you should build an interface. Let the php-script put something somewhere and scan this via scripts executed from root and handle it there. You could f.e. create a file containing the users to add in a directory where www-data has access, and then perform this via root-cronjob every 5 minutes (or less) and move the file to a done-folder with timestamp to have control over what is happening.

Solution 2

I strongly disagree that running something with higher privs from a web server is always a bad idea.

The security problems from a web application come from trusting the user-supplied data, not from the exact process that's using that data.

In other words, if you don't validate and sanitise your data, it's no safer to pass it to a root-owned process via named-pipe than it is to write a wrapper script to process that data and allow the www-data user to run it via sudo.

Actually, most of the processing/validation/sanitising of data should be done before the data is passed to the sudo script, which should a) do a final check on the data, and b) perform only the operations that need higher privs.

In fact, I'd argue that it's possibly safer to do the latter because it's a simpler solution that's easier to understand....the more complicated you make something, the more likely you are to make a mistake.

Whichever method you use (named-pipe, sudo wrapper, or whatever) the crucial thing is that you examine the user supplied data and make sure it fits very narrowly defined criteria before doing anything with it.

There are numerous articles and howtos on CGI security on the web, including several here on the stackexchange sites, but some of the common themes are:

  • regard the data as 'tainted' and untrustworthy until you've checked it and/or modified it to make it safe.
  • check the data - e.g. test if it matches a regexp of allowed or prohibited characters. safest option is to abort if there's anything odd, otherwise transform it with a regexp or something to remove potentially unsafe elements.
  • always quote the data if you pass the data to an external shell script. e.g. the shell script should use double-quotes around the variables.
  • similarly, when inserting data into database you should use a database library that supports placeholder values rather than relying on escaping or quoting. Here is a humorous illustration of why.

I could go on, but there's too much to summarise in an answer here - CGI Security is a broad topic. Try a google search for CGI Security or Validating CGI Input


With your 'I don't care because it's only me using it' attitude to security, I kind of feel like I'm giving you a loaded shotgun to blow off your feet with, but shooting yourself in the foot is a valuable learning experience. Here's a simple script to create a user account. It requires root privs.

The script relies on adduser, which is available on debian and debian-derived systems and maybe others. modify to use useradd instead if it's not on your system.

#! /bin/bash 

# make the script abort on any error
set -e

U="$1"
P="$2"

[ -z "$U" ] && echo "Error: No username provided" >&2 && exit 1
[ -z "$P" ] && echo "Error: No password provided" >&2 && exit 1

# simple check - only allow lower-case letters and digits in usernames.
[ "$U" !~ '^[a-z0-9]*$' ] && echo "Error: Invalid Username" >&2 && exit 1

# create user if they don't already exist
if ! getent passwd "$U" > /dev/null ; then

   # create the user using adduser, must provide the gecos field 
   # and disable the password so adduser doesn't ask for them. 
   adduser --gecos "$U" --disabled-password "$U"

   # now change the password
   echo "$U:$P" | chpasswd
else
    echo "Error: Username already exists" >&2
    exit 1
fi

Save the script somewhere, and make it executable with chmod.

To allow www-data to run it as root without a password, edit /etc/sudoers with visudo and add the following:

Cmnd_Alias APACHEADDUSER = /path/to/makeaccount.sh

www-data ALL = NOPASSWD: APACHEADDUSER
Share:
26,786

Related videos on Youtube

qwerty
Author by

qwerty

Updated on September 18, 2022

Comments

  • qwerty
    qwerty over 1 year

    Need to execute the following line from PHP:

    $res = shell_exec('sudo sh /home/nicklas/cronjobs/make_account.sh username password');

    The problem is nothing happens on execution. If i try to echo $res it comes out blank. I've tried using system() also, same result. I'm guessing it doesn't work because i need to run the scrip with root access, and the www-data user doesn't have that by default. I added the following line to /etc/sudoers in hope of getting access:

    www-data ALL=(ALL:ALL) NOPASSWD: /home/nicklas/cronjobs/make_account.sh

    But no success. I've tried restarting apache inbetween, doesn't change anything.

    Am i missing something?

    • Admin
      Admin over 11 years
      if you use shell_exec() or similar, remember to use escapeshellcmd() or escapeshellarg() to escape any shell metachars in the data.
  • qwerty
    qwerty over 11 years
    I already knew it was a security breach, but since i'm just experimenting on a development server to learn i was going to let it pass. But fair enough, i'll do it the way it should be done. Your idea sounds fine except i need to create the user instantly, it has to be done as soon the PHP script goes off. Any ideas how to solve that? Thank you btw!
  • wolf
    wolf over 11 years
    Sounds like this could be something for a named pipe. Named pipes are typical methods for inter-process-communication on Unix (and meanwhile on other systems too). For an introduction you may read en.wikipedia.org/wiki/Named_pipe
  • qwerty
    qwerty over 11 years
    I've been fibbling with named pipes for some time now and i really can't figure out how to actually use it for my purpose. I understand the principle of named pipes, but can't apply it to my needs. This is how i imagine it would work: Send data (in my case username and password) to named pipe from PHP, no root needed. I have another process running (with root access) which constantly reads from the pipe, and then executes the script with root access. Am i on the right track? If so, my question is, how do i create that other process that runs with root access and constantly reads from the pipe?
  • qwerty
    qwerty over 11 years
    The problem is not about making the data secure by sanitizing/validating it before using it. I'm not concerned at all about the security since only i will be using it. Either way i still sanitize and validate the data before passing it on to the shell script, so that should work fine. The problem is that i don't know to execute a shell script with root access (without entering password) from a user that has no root access. You mentioned a "wrapper script", can you explain a bit more about that?
  • Alessio
    Alessio over 11 years
    I'll add an example to my answer.
  • qwerty
    qwerty over 11 years
    Sorry, i didn't mean to sound ignorant, i know security is very important but it just wasn't what i was looking for. I do very much appreciate your help and will keep your suggestions in mind. I already have a bash script that creates a user, but the last code block sounds very interesting. I've tried to add www-data to the sudoers list, but it didn't seem to work (as written in the question), but i will try the code you suggested.
  • Alessio
    Alessio over 11 years
    sorry, my comment was meant as a slightly humorous warning, not as a criticism. have you checked your apache error log to find out whether it's your script failing or the sudo failing? the sudoers entry you posted looked fine, but also check your auth.log.
  • qwerty
    qwerty over 11 years
    My bad, heh. I'm new to Linux, so i wasn't even aware that log existed, although i assumed there was a log somewhere. The log says auth could not identify password for [www-data] so i'm pretty sure it's sudo failing. I even tried doing system("sudo ls"); from PHP just to see if sudo worked. It didn't. Anyways, i added the code to /etc/sudoers as you advised but it still doesn't seem to be working. I have triple checked the path, so that's not the problem. If it helps, here is my sudoers file: pastebin.com/FNhdaJzL
  • qwerty
    qwerty over 11 years
    Also, if it matters, permissions for the make_account.sh file is -rwxrwxrwx which probably isn't very smart, but at least that confirms the problem isn't related to file permissions, right?
  • Alessio
    Alessio over 11 years
    did you check the log like i suggested. and "it doesn't work" is not a good description of the problem (or any problem). what isn't working? what is happening? how does that differ from what you expect to happen? most importantly, post relevant extracts from the log files.
  • qwerty
    qwerty over 11 years
    "Did you check the log like i suggested?" If you read my previous commend you'll see that i quoted an important piece of the log file. The command "sudo ls" didn't work, for the same reason the adduser.sh script didn't work. At the absolute top of the PHP file i run this line: echo system('sudo ls'); die(); to which i get a completely blank respond. So, no respond in PHP. All i know is what's written in the log, and i don't know how to fix that. This is exactly what's written to auth.log on every page load: pastebin.com/raw.php?i=wfn4htxS
  • qwerty
    qwerty over 11 years
    And as for what i expect to happen, is for it to echo out index.php index.php on the page. That's what happens if i remove sudo before the ls command. I don't need sudo to list a directory, i just use it to see if sudo works. I don't know why it echos index.php two times, as there is only one index.php file, but that's irrelevant.
  • Alessio
    Alessio over 11 years
    sorry, i must be going blind, i somehow missed the log entry. was expecting you to add them to your question, not post to an external site. anyway, try moving the lines you added to /etc/sudoers to the bottom of the file (after the #include line). The order of sudoers rules is significant, and the last match always wins, even if the last-match is less specific than a previous match. some rule/default in a file in /etc/sudoers.d/ might override the prior rules for www-data. I've seen this catch people before - it's vital information buried in a wall-of-text in the man page.
  • Alessio
    Alessio over 11 years
    for example, with the sudoers file you posted, if www-data is a member of either the admin or sudo group, then the rules of those groups will override the earlier rule for www-data - and since they don't specify NOPASSWD:, a password will be required. it can't be emphasised enough: with sudo, the order of rules is important, and the last match always wins.
  • qwerty
    qwerty over 11 years
    No worries! Oh, i'll post it in the question in the future. That's a good point. I tried moving it to the absolute bottom, then restarted apache in case it was needed, but it still doesn't work. Log looks the same as before. I also checked /etc/sudoers.d/ to see if something might be overwriting it, but it was empty. Is it possible it might be overwritten from somewhere else?
  • qwerty
    qwerty over 11 years
    I looked at the path i added to the sudoers file and realized the path i wrote was to the add_user.sh script, which isn't being used now. I'm just calling sudo ls from index.php as i said earlier, so add_user.sh has nothing to do with it. I tried changing the path to /var/www/index.php instead but that didn't work either. When i think about it though, the script isn't really being run from the index.php file itself, right? Apache/PHP reads the file and then procceses the contents, right? Maybe need to add permission to some apache/php path in the sudoers file?
  • qwerty
    qwerty over 11 years
    No other ideas huh?