homepage Welcome to WebmasterWorld Guest from 54.204.127.191
register, free tools, login, search, pro membership, help, library, announcements, recent posts, open posts,
Pubcon Platinum Sponsor 2014
Home / Forums Index / Code, Content, and Presentation / PHP Server Side Scripting
Forum Library, Charter, Moderators: coopster & jatar k

PHP Server Side Scripting Forum

    
exec("chattr. ") doesnt work - how do I assign/override permissions?
httpwebwitch




msg:4294677
 5:04 pm on Apr 9, 2011 (gmt 0)

I need to lock a file so it can't be deleted, renamed, or modified while a process is happening.

At the Linux command line, logged in as root, I can do that with this command:

# chattr +i filename.txt

So, I added this line of PHP to my script:

exec("chattr +i filename.txt");

but alas, it doesn't work.

I can see that it didn't work by showing it:
echo exec("lsattr filename.txt");

and the output is this:

--------------- /path/to/file/filename.txt


If the command worked, I would see this instead:

----i---------- /path/to/file/filename.txt


I'm aware that this is likely a problem with permissions, ownership, et al.

I've tried adding "sudo" to the command, and fiddling with chown and chmod; changing the owner of the file to "root" and "apache" and "nobody"... no luck.

What I need is just to let my PHP script, which is run by apache, execute that chattr command to lock that file.

oh, I tried shell_exec() too. No difference.

I also wrote a bash script that does the chattr, then ran "exec()" to call that, with and without "sudo"... no luck. I get the output from the bash script, but the file's "immutable" setting doesn't change.

I'm running out of ideas.

 

phranque




msg:4294900
 8:20 am on Apr 10, 2011 (gmt 0)

perhaps PHP is running in safe mode?

have you tried capturing and displaying the return status of the chattr command?

if there is no solution to using chattr in PHP you might try the copy/process/replace workaround i suggested in your other thread.

incrediBILL




msg:4294912
 10:12 am on Apr 10, 2011 (gmt 0)

I get the output from the bash script, but the file's "immutable" setting doesn't change.


Have you captured the output to see it it runs? "chattr +i filename.txt > chattrout"

Is /usr/sbin/ in your default path for PHP?

Did you try an explicit path to the command and the file?

try something like: echo exec("/usr/bin/lsattr /usr/www/vhosts/somepath/domain.com/filename.txt");

if that doesn't work, move lsattr to your FTP account, change the ownership (chown/chmod) to your local account, try it again as not all commands are authorized for global execution.

httpwebwitch




msg:4295012
 5:37 pm on Apr 10, 2011 (gmt 0)

this:
echo exec("whoami");

echoes "apache". So I know that a page hit by the browser is being run by the "apache" user.

So, I copied /usr/bin/chattr to /var/www, then did chown apache:apache chattr, chmod 777 chattr.

Then this:
exec("/var/www/chattr +i example.txt");

No change to the file. The lsattr output is still "---------------"

when I execute the same command from the linux CLI, it works.

# cd /var/www
# ./chattr +i example.txt
# lsattr example.txt

output is : "----i----------"


Dang, I really thought that was going to work

incrediBILL




msg:4295045
 6:41 pm on Apr 10, 2011 (gmt 0)

try echo exec("/var/www/chattr +i example.txt"); and see what it says!

it may be telling you it requires root to run, something simple you need to know

try this, make a multi-line script that attempts privilege escalation, change to root before running chatter and see what happens

brotherhood of LAN




msg:4295046
 6:44 pm on Apr 10, 2011 (gmt 0)

When you invoked the wrapper script that uses exec, did you try using sudo for that, rather than the command within the script?

If you're trying to lock the file to prevent race conditions, e.g. the script running more than once... you may want to edit this snippet which I used to prevent the same script running twice, as its run from cron every 5 minutes but may still be running.

<?php

function check_isrunning($name,$die)
{
global $tmpfile;
$tmpfilename = $name.'.pid'; // Create a temporary .pid file to lock for checking if script is running
   if(!($tmpfile = fopen($tmpfilename,"a"))) // Create file is this is the first time running
   {
      if($die)
         die('Script is already running (1)');
      else
         return false;
   }

   if(@!flock($tmpfile,LOCK_EX | LOCK_NB,$wouldblock) || $wouldblock) // If file cant be locked, another process is using it
   {
   @fclose($tmpfile);
      if($die)
         die('Script is already running (2)');
      else
         return false;
   }
return true;
}

   if(!check_isrunning('myscript.php',1)) // returns false or exits if script is running
      die('SCRIPT ALREADY RUNNING'."\n");
// Script can execute here because it is not already running
?>

httpwebwitch




msg:4295099
 9:31 pm on Apr 10, 2011 (gmt 0)

@iBill, output from echo exec("/var/www/chattr +i example.txt"); is nothing - no output.

nothing in the PHP error log either.

@B'o'Lan, This is a script that's being triggered by Apache, i.e. the user is making it happen by loading a PHP page in their browser. I've tried adding "sudo" inside the exec() command, but it makes no difference.

I can describe less vaguely what I'm doing, in point form...

1) user connects to my server via FTP to their designated folder, and uploads some files.
2) user goes to the web interface, and sees their list of files (I've got that working fine). There's a checkbox beside each one, and a Submit button that says "Process These"
3) The processing of those files could take a few seconds, or an hour or more, if the files are large or plentiful.

While that processing is taking place, I can't allow the user to be messing with the files.

When my script is finished processing the file(s), it deletes them.

I've considered making a copy of the file and processing that copy, but it's so much cleaner if I use the original and lock it.

incrediBILL




msg:4295262
 8:05 am on Apr 11, 2011 (gmt 0)

While that processing is taking place, I can't allow the user to be messing with the files.


Just rename it when processing starts to originalfilename.processing then you can still show the user the file exists but is currently being processed, and don't allow anything with ".processing" to be selected for processing.

httpwebwitch




msg:4295482
 4:44 pm on Apr 11, 2011 (gmt 0)

seems like moving and renaming the file may be the simplest but least elegant option, eg the advice from @phranque.

I was saving that as a last resort; it complicates the processing steps somewhat, and I need to think about the consequences of many user processing files with the same name... lots of edge cases... etc

Before I give up on PHP invoking chattr, I'm going to try something daemonic; I'll have PHP write the file name into a queue (txt or database, whatever), then a daemon can read that queue and process the file using root ownerships and permissions.

so crazy it just might work.

Little_G




msg:4297135
 8:37 pm on Apr 13, 2011 (gmt 0)

Hi,

After running the code
echo shell_exec('sudo chattr +i /tmp/test.txt 2>&1'); (redirecting strerr to stdout) I got the error sudo: no tty present and no askpass program specified, to get around this I edited the sudoers file to allow chattr to be run by the apache user without a password, like this:

www-data ALL=NOPASSWD: /usr/bin/chattr

www-data is the apache username on Ubuntu, you might need to change that.

Andrew

incrediBILL




msg:4297220
 11:26 pm on Apr 13, 2011 (gmt 0)

I need to think about the consequences of many user processing files with the same name


Why would any user process the same file name?

Get a new unique file name with tempnam(), move the file to this new name, then symlink() the original file name to the tempnam() for tracking purposes.

100% unique, tracked and tied back to what the original owner uploaded.

httpwebwitch




msg:4297564
 2:04 pm on Apr 14, 2011 (gmt 0)

Little_G, is there a similar file that controls permissions for non-sudo commands? I'd just like to give apache the ability to run chattr.

Little_G




msg:4297732
 6:10 pm on Apr 14, 2011 (gmt 0)

Hi,

I don't think it's possible to give the apache user the permission to change extended file attributes directly, but the sudo command does essentially that, just a little safer. The entry I posted states that user
www-data (the apache user on Ubuntu) can run from an host (ALL) using no password (NOPASSWD) the command /usr/bin/chattr as root.

Andrew

httpwebwitch




msg:4305140
 2:21 am on Apr 28, 2011 (gmt 0)

I've figured out a way to do it. It's a total hack/workaround, but the technique opens up some other interesting possibilities...

1) Install Gearman [gearman.org]
2) Install the Gearman PECL extension for PHP
3) write a Worker Job that does the Chattr command
4) Have PHP send the Chattr command to Gearman, so Gearman does the work.

The permission limitations of PHP don't limit the job that Gearman will do on its behalf. The result: a PHP script with which I can set the immutable bit on a file uploaded via FTP.

hooray!

httpwebwitch




msg:4305189
 4:30 am on Apr 28, 2011 (gmt 0)

Sorry that was a very quick note, I didn't have time to expand it.

Since this post is already ranking for "chattr php" and other keywords, this is what people are going to find when they confront this problem. So, I'll explain in more detail how I solved it.

Firstly, I knew from all my previous experiments that in no way would PHP/Apache ever let me run a chattr command. I tried changing permissions, ownership, sudo in shell_exec(), and a few other more arcane hacks, and nothing worked.

I created a script called "chattr.php", which had just one line: the shell_exec('chattr +i myfile.txt');

if I was on the command line and executed it:
# php chattr.php
then it worked great. But if I hit that same script from a browser, it would do nothing.

I realized that I'd have to have something powerful, running as root, waiting for a trigger to do the chattr command. I considered dropping a row into a MySQL database, and having some kind of daemon waiting and polling for it; but that solution seemed a bit too kludgy. Besides, writing a daemon in PHP is not a trivial task. There are some open-source classes that claim to make it easier, but OMG they are either buggy or way too complicated to bother with.

So, I discovered Gearman, and it looked like exactly what I needed. And after a few problems setting up Gearman, I finally got something that works.

First, install Gearman, and get it running.
# yum install libgearman libgearman-devel gearmand
# service gearmand start
# chkconfig gearmand on


I found that the PECL extension for Gearman Does Not Work. I installed the darn thing and had all the pieces for a "hello world" demo, but all it gave back was a Segmentation Fault.

But the PEAR Net_Gearman module does work. The syntax for using it is a little awkward, but hey. It's better than segfaults.

So, install the PEAR module:

# pear install Net_Gearman


Now, of all the resources I scoured for advice getting this running, this one was golden:

Building a distributed app with PHP and Net_Gearman [smorgasbork.com]

Follow the tutorial. It'll have you build a class called "gm" in "gm_shared.php" - my example references that later on so just grab that code now and customize the settings in it to fit your server environment.

Using the sample code in that tutorial, I was able to build this "Job" called "Chattr"...

<?php
class Net_Gearman_Job_Chattr extends Net_Gearman_Job_Common{
public function run($arg){
$filepath = $arg[0]['fullpath'];
shell_exec("chattr +i ".escapeshellarg($filepath));
}
}
?>


The tutorial has you create a script called "gm_worker.php". When you have that set up, you'll want to give your worker the ability to do the Job you defined. Find the lines where the worker is given "abilities", and add yours:

try{
gm::log_msg("[gm_worker] starting worker...");
$worker = new Net_Gearman_Worker(gm::$servers);
$worker->addAbility('Chattr');
$worker->beginWork();
}


Now your worker is programmed to know how to do a chattr. Next we need to turn it on. In a terminal window, start the worker as a foreground process:

# php gm_worker.php


When you run this thing in prod, you'll want that to be a background process, so it just keeps running and running after you close the terminal. But while debugging, it's good to have it in the foreground, because you can use echo and print_r() commands, and you can see the output in the terminal running the worker. Very handy, that.

To start it later as a background process, add the "&":

# php gm_worker.php &


You may want to make sure your worker is always running. One hack I love is writing a little script that checks "ps" to see if the worker process is going. If it's not, start it. Put it on a cron, to run every few minutes depending on your tolerance for downtime. The cronned restart-if-it-died trick is like having a dude with a defibrillator all warmed up and standing by while you're working out. Your home made daemons will always revive if they die (for whatever reason).

Now you should have a worker waiting for work. The awesome thing is that this worker is capable of doing the chattr command, on a file path that you pass to the worker as $arg.

You need a client. The client gives work to the worker, by passing an $arg to one of its Jobs. The client will be running as the Apache user - really, this is the PHP file that I'm hitting with my browser. The code is pretty simple:


<?php
$fullpath = $_GET['filepath'];

require_once ('/path/to/gm_shared.php');
require_once ('Net/Gearman/Client.php');
$gmclient = new Net_Gearman_Client(gm::$servers);
$gmclient->Chattr(array(
array(
"fullpath"=>$fullpath
)
));
?>


No wait - in reality, I'm not going to be accepting the file path from $_GET. That's silly. I'm actually going to be getting the file path a completely different way, but $_GET suffices for a quick demo. I do not recommend that you pass stuff right from $_GET into a Gearman worker, unless the input is being thoroughly validated and cleansed.

This is pretty powerful stuff. By tossing a file path into $_GET, I'm able to make the file immutable by giving the job to a Gearman worker. No PHP script executed by apache is capable of doing that. Gearman makes it possible.

As with any power tool, you could do a lot of damage with this. But used responsibly, I'm satisfied that this is going to solve my chattr problem, and furthermore I'm already thinking of ways I can leverage the distributed worker paradigm to simplify and scale up my app. In situations where I'm already doing asynchronous processing using popen(), Gearman will do the same thing elegantly.

Oh, didn't I mention, I can have multiple servers full of workers, accepting jobs from load-balanced clients? The way this thing scales horizontally is sick.

Little_G




msg:4305620
 9:28 pm on Apr 28, 2011 (gmt 0)

Hi,

I'm glad you found a way to get it working, but for anyone else reading this in the future I have to disagree with one thing you said:

I knew from all my previous experiments that in no way would PHP/Apache ever let me run a chattr command.

By adding the config line I mentioned above to the sudoers file I was able to get a php script (executed via apache when visited in a browser) to add the immutable bit to a test file by calling sudo chattr via shell_exec, so it is possible.
I'm sorry if my previous posts where a little cryptic, and I'm happy to write a more legible how-to if you decide to move away from Gearman.

Andrew

henry0




msg:4305844
 12:38 pm on Apr 29, 2011 (gmt 0)

Hi Little_G, I'll be interested in your solution, no rush, just when you get the chance...
By advance thanks!

httpwebwitch




msg:4306057
 6:31 pm on Apr 29, 2011 (gmt 0)

sorry Little_G,
I glossed by your suggestion because I don't know lick about editing a "sudoers file".

I don't know where that file is, nor do I understand the consequences of editing it.

A how-to would definitely be appreciated

Leosghost




msg:4306068
 6:52 pm on Apr 29, 2011 (gmt 0)

Another interested lurker here on this thread for that "how to" if you get the time.. Little_G :)

and httpwebwitch ..thanks for all the detail in #:4305189 :)

Little_G




msg:4306117
 9:09 pm on Apr 29, 2011 (gmt 0)

Hi,

Ok, well I've never really written a how-to before so here goes and thanks for the interest.

Running a shell command from Apache/PHP with elevated privileges

One of the most important security measures taken by Apache is to run itself and any child processes it starts under a limited user account, this means that any vulnerabilities in scripts or the server itself or malicious scripts are limited in their access to sensitive parts of the file system and powerful utilities.
I'm going to cover configuring a Linux system to allow specific shell commands to be executed with root privileges using the
sudo command, how to call a command from PHP and security implications.

The
sudo command, unlike the similar su command is commonly configured to request the password of the user running it, rather than the password of the target user. The benefit of this is that a user can be granted the privileges of a user (for example root) without giving out that users password. Another powerful feature is the fine grain control it's config file gives over which users can run what commands and as which user.

Running commands in PHP
There are a number of functions in PHP for executing external commands, I'll use shell_exec [php.net] in the following examples because it's nice and simple and returns the whole output of the command in one string.

When running commands that affect files on the system it is more common to change the permissions of the file(s) to match the server, however in the case of the
chattr command mentioned above the command modifies extended file system attributes of a file and cannot be run by a non-root user even on files they own.

So running the following code will display an error:
<?php
echo shell_exec('chattr +i /tmp/testfile.txt 2>&1');
?>
chattr: Permission denied while setting flags on /tmp/testfile.txt
The
2>&1 at the end of the command redirects stderr into stdout allowing you to see the error message.

The sudoers file
The sudoers file is the configuration file for the
sudo program, it has a quite complex syntax and is a little difficult to get to grips with at first but it is powerful and allows for fine grain control over what commands can be run and by which users.
The file is usually located at
/etc/sudoers

To grant the Apache user permission to run the
chattr program we add the following line to the end of the sudoers file:
apache ALL=NOPASSWD: /usr/bin/chattr

This grants the user apache permission to run (without providing a password) the program
chattr.
Two parts of the config line above may need to be adjusted for different systems. Firstly the Apache user is often apache but on Ubuntu is www-data and may be different again on others. The user your script is running as can be discovered by running the code:
<?php
echo shell_exec('whoami');
?>

Secondly it is possible, though unlikely that
chattr is at a different location, the following shell command will find it:
whereis chattr

After modifying the sudoers file we can change our php to the following:
<?php
echo shell_exec('sudo chattr +i /tmp/testfile.txt 2>&1');
?>

And run the
chattr command with root privileges.

Security
There are always security implications when running shell commands from a publicly accessible script, especially when any part of the the command is defined by the user, in this particular case the risks can be easily mitigated. The worst case scenario would be allowing a malicious user to inject a pipe character into the command, this could allow a specially crafted request to execute arbitrary shell commands on your server. The second worst case scenario would be allowing a malicious user to run the command against any file on the system, allowing them to lock any file of their choice.

We can filter user input by using functions such as str_replace [php.net], preg_replace [php.net] or functions from the Filter extension [php.net]. In the case of validating file names and paths we can use the realpath [php.net] function to get a absolute path which will only work on files that exist and makes restricting the command to a particular directory easier.

Tying all the above together you get the following code:
define('UPLOAD_DIR', realpath('/tmp'));

function immutable($file){
$path = realpath(UPLOAD_DIR . '/' . $file);
//is the file within the upload directory and is it readable.
if($path !== false && strpos($path, UPLOAD_DIR . '/') === 0 && is_readable($path)){
$r = shell_exec('sudo chattr +i ' . $path . ' 2>&1');
//chattr returns nothing on success.
if(empty($r)) return true;
}
return false;
}

Filtering special characters shouldn't be necessary when using
realpath but could be included if you're feeling paranoid, also is_writable [php.net] could be used instead of is_readable so the command will only be run against file the script can write.

Any suggestions or comments regarding any of the above are appreciated.

Andrew

henry0




msg:4306903
 12:53 pm on May 2, 2011 (gmt 0)

Little_G, Thanks a lot for taking time to write it down!
Guess everyone took off for the weekend.
Hope we'll get some feedbacks.
I plan to play with it.

httpwebwitch




msg:4306920
 1:50 pm on May 2, 2011 (gmt 0)

oh, me too - I'll be attempting this on one of my sandbox servers this week. If all goes well, this technique is going to enable some amazingly cool features in my app

Global Options:
 top home search open messages active posts  
 

Home / Forums Index / Code, Content, and Presentation / PHP Server Side Scripting
rss feed

All trademarks and copyrights held by respective owners. Member comments are owned by the poster.
Home ¦ Free Tools ¦ Terms of Service ¦ Privacy Policy ¦ Report Problem ¦ About ¦ Library ¦ Newsletter
WebmasterWorld is a Developer Shed Community owned by Jim Boykin.
© Webmaster World 1996-2014 all rights reserved