My friend has some sort of admin panel where he displays information from several servers. He is using cPanel XML api to display several things for example the server load average, the code looks something like this:
include("xmlapi.php");
$conf = simplexml_load_file("servers.xml");
foreach ( $conf->server as $server ) {
$xmlapi = new xmlapi( $server->ip );
$xmlapi->set_debug(1);
$xmlapi->hash_auth( $server->user, $server->accesshash);
$loadavg = $xmlapi->loadavg();
print $server->ip . " - " . $loadavg->one . " - " . $loadavg->five . " - " . $loadavg->fifteen;
}
I've been reading the wiki but the closer thing I found is retrieving an account disk usage. But I need the info that comes from the "df" command ran in ssh as root. What would be the easier and safest way to do this? Since i need to get the df command output from several servers and not from the one running the code.
Why do you need to run df as "root"? I can certainly run it as myself on my Linux machine?
(Yes, that's a question, not an answer)
Assuming you have full access to the machine, you could probably set up a useraccount "df", that is listed in sudoers with !authenticate attribute (so it can sudo without password), and then ssh df#${machine} sudo df
You can also restrict sudo so that the df" user can only do df, and not, for example rm -rf / or adduser blah
And of course set up your ssh to use a public/private key challeng rather than using your php code to have a password in the source - if nothing else because it makes it more flexible, and you don't have to update the source if you decide to change the password.
Related
I'm currently working on a project to make changes to the system with PHP (e.g. change the config file of Nginx / restarting services).
The PHP scripts are running on localhost. In my opinion the best (read: most secure) way is to use SSH to make a connection. I considering one of the following options:
Option 1: store username / password in php session and prompt for sudo
Using phpseclib with a username / password, save these values in a php session and prompt for sudo for every command.
Option 2: use root login
Using phpseclib with the root username and password in the login script. In this case you don't have to ask the user for sudo. (not really a safe solution)
<?php
include('Net/SSH2.php');
$ssh = new Net_SSH2('www.domain.tld');
if (!$ssh->login('root', 'root-password')) {
exit('Login Failed');
}
?>
Option 3: Authenticate using a public key read from a file
Use the PHP SSHlib with a public key to authenticate and place the pubkey outside the www root.
<?php
$connection = ssh2_connect('shell.example.com', 22, array('hostkey' => 'ssh-rsa'));
if (ssh2_auth_pubkey_file($connection, 'username', '/home/username/.ssh/id_rsa.pub', '/home/username/.ssh/id_rsa', 'secret')) {
echo "Public Key Authentication Successful\n";
} else {
die('Public Key Authentication Failed');
}
?>
Option 4: ?
I suggest you to do this in 3 simple steps:
First.
Create another user (for example runner) and make your sensitive data (like user/pass, private key, etc) accessible just for this user. In other words deny your php code to have any access to these data.
Second.
After that create a simple blocking fifo pipe and grant write access to your php user.
Last.
And finally write a simple daemon to read lines from the fifo and execute it for example by ssh command. Run this daemon with runner user.
To execute a command you just need to write your command in the file (fifo pipe). Outputs could be redirected in another pipe or some simple files if needed.
to make fifo use this simple command:
mkfifo "FIFONAME"
The runner daemon would be a simple bash script like this:
#!/bin/bash
while [ 1 ]
do
cmd=$(cat FIFONAME | ( read cmd ; echo $cmd ) )
# Validate the command
ssh 192.168.0.1 "$cmd"
done
After this you can trust your code, if your php code completely hacked, your upstream server is secure yet. In such case, attacker could not access your sensitive data at all. He can send commands to your runner daemon, but if you validate the command properly, there's no worry.
:-)
Method 1
I'd probably use the suid flag. Write a little suid wrapper in C and make sure all commands it executes are predefined and can not be controlled by your php script.
So, you create your wrapper and get the command from ARGV. so a call could look like this:
./wrapper reloadnginx
Wrapper then executes /etc/init.d/nginx reload.
Make sure you chown wrapper to root and chmod +s. It will then spawn the commands as root but since you predefined all the commands your php script can not do anything else as root.
Method 2
Use sudo and set it up for passwordless execution for certain commands. That way you achieve the same effect and only certain applications can be spawned as root. You can't however control the arguments so make sure there is no privilege escalation in these binaries.
You really don't want to give a PHP script full root access.
If you're running on the same host, I would suggest to either directly write the configuration files and (re)start services or to write a wrapper script.
The first option obviously needs a very high privilege level, which I would not recommend to do. However, it will be the most simple to implement. Your other named options with ssh do not help much, as a possible attacker still may easily get root privileges
The second option is way more secure and involves to write a program with high level access, which only takes specified input files, e.g. from a directory. The php-script is merely a frontend for the user and will write said input files and thus only needs very low privileges. That way, you have a separation between your high- and low privileges and therefore mitigate the risk, as it is much easier to secure a program, with which a possible attacker may only work indirectly through text files. This option requires more work, but is a lot more secure.
You can extend option 3 and use SSH Keys without any library
$command = sprintf('ssh -i%s -o "StrictHostKeyChecking no" %s#%s "%s"',
$path, $this->getUsername(), $this->getAddress(), $cmd );
return shell_exec( $command );
I use it quite a lot in my project. You can have a look into SSH adapter I created.
The problem with above is you can't make real time decisions (while connected to a server). If you need real time try PHP extension called SSH2.
ps. I agree with you. SSH seams to be the most secure option.
You can use setcap to allow Nginx to bind to port 80/443 without having to run as root. Nginx only has to run as root to bind on port 80/443 (anything < 1024). setcap is detailed in this answer.
There are a few cavets though. You'll have to chown the log files to the right user (chown -R nginx:nginx /var/log/nginx), and config the pid file to be somewhere else other than /var/run/ (pid /path/to/nginx.pid).
lawl0r provides a good answer for setuid/sudo.
As a last restort you could reload the configuration periodically using a cron job if it has changed.
Either way you don't need SSH when it's on the same system.
Dude that is totally wrong.
You do not want to change any config files you want to create new files and then include them in default config files.
Example for apache you have *.conf files and include directive. It is totally insane to change default config files. That is how I do it.
You do not need ssh or anything like that.
Belive me it is better and safer if you will.
I have been working some time on this and I can't find the solution ( if there is any ).
So the question: Is there a way to do an svn update request via HTML web form?
I have tested multiple web-based SVN clients ( including building my own ), but they all lack the possibility to do an update.
I understand that this is an issue with the user permission that PHP uses, but even if I set all files on my system to 777 ( tried it on a virtual machine ), there still is no way I can archive it.
Anyone has some experience on this?
Thank you already.
I also went over all the directories and set owner to Everyone with full access. Now there can't be a permission issue ... I think.
Last edit:
It seems I can't execute the SVN if the script file is not in the root directory of the svn directory. The final code now looks like this: http://codepad.org/Kb2K8e6m
The following function should more or less do it for you. It also prints out:
The exact command it is executing in shell
The output of the command (including errors) so that it can also be debugged.
function updatesvn() {
if ( isset($_POST) ) {
//Get svn username and password from request parameters
$username = <your_svn_username>;
$password = <your_svn_password>;
//Get version number to move to
$values = $_POST;
$version = $values['revision'];
//validate version number
//Path where you have checked out your project
$rpath = '/var/www/myproj';
//Command to svn update
$cmd = "cd $rpath; svn cleanup; svn --non-interactive --username $username --password $password update -r $version 2>&1";
echo $cmd;
$out = shell_exec($cmd);
echo $out;
}
}
EDIT: Beware of security loopholes in this code. I have simplified it here. We had used it in one of our applications where a user reaching this point was pre-authenticated as an Admin user of the web application. In addition this code was executed in an MVC framework where validations on input were done in central places before executing an action in a controller.
You can just use svn_update() from the PECL svn extension (or exec and command-line svn - there is actually not much difference).
The solution I prefer for permission problems is to set umask 002 for all users who will update (including www-data or whatever your webserver user is), put them into a common phpuser group, set that group for the root directory of the working copy (chgrp phpuser <rootdir> - before the checkout!), make sure it is group writable (chmod g+rwx <rootdir>) and set it to setgid (chmod g+s <rootdir>), but if you don't care about preserving file permissions, then certainly chmod -R 777 <rootdir> is much simpler.
Are you building a continuous integration server? Why would you want to let any Joe Bloggs update your local repository?
You'd have to have some code on the back end to execute the "svn update" in a shell.
Generally, a web-based SVN client would feed directly into the SVN repository and would by default be up-to-date - it should even let you view previous versions and diffs.
I want to run some command from a remote server in my php using exec. Like this:
<? php
exec('ssh user#remote_server command');
?>
My account has access to ssh to that remote_server with public/private key but not apache. Note that I don't have root access to either of the machines. All the answers for Generating SSH keys for 'apache' user need root access.
My recommendation: use phpseclib, a pure PHP SSH implementation. eg.
<?php
include('Net/SSH2.php');
$ssh = new Net_SSH2('www.domain.tld');
if (!$ssh->login('username', 'password')) {
exit('Login Failed');
}
echo $ssh->exec('pwd');
echo $ssh->exec('ls -la');
?>
Web server process is owned by apache user not root .
Make sure that apache user have password less login to remote server
SE linux should be disabled . Refer
I would try:
$identity_file = '/home/you/.ssh/id_rsa'; // <-- replace with actual priv key
exec('ssh -i ' . $identity_file . ' user#remote_server command');
and see if you can authenticate like that. You will have to make sure that the identity file is readable by Apache.
The downside is now the Apache user can read your private key, which means any other site running as the Apache user can as well. But even if you create a private key for the Apache user, the same is true. For better security, see about running PHP as your specific user using suPHP or suExec.
This is a bad idea without root access. To make sure that Apache's user can see your private key, you'll have to make it world-readable: without root you can't chown www-data:www-data, so not only will Apache be able to see it, every user on the system will be able to. Because this is such a bad idea, OpenSSH won't allow it by default - it will refuse to run if your private key has unreasonably open file permissions.
I very strongly advise against doing this. If you really need to be able to have PHP run remote commands with an SSH key, you'll need someone with root access to set up something more secure, along the lines of your link.
A more secure alternative would be to write a PHP script on the target machine that takes an HTTP request containing a password that you define, executes a pre-defined command and returns the output. If this script is written securely and can only execute that one pre-defined command, all an attacker can do is run that same command as you - as long as the password for this script isn't the same as any of your own passwords. It needn't be exactly one command, and it can even take some arguments if you're careful: the important point is that you're not allowing a remote user to execute arbitrary commands on the target machine. If you're sure that the command you want to be able to run isn't potentially harmful, and your script doesn't contain coding errors allowing other commands to be run instead, then this isn't too bad an idea.
Use SSH2.
http://php.net/manual/en/book.ssh2.php
How can I sync local and remote folders using rsync from inside a php script without beeing prompted to type a password?
I have already set up a public key to automate the login on remote server for my user.
So this is running without any problem from cli:
rsync -r -a -v -e "ssh -l user" --delete ~/local/file 111.111.11.111:~/remote/;
But, when I try to run the same from a PHP script (on a webpage in my local server):
$c='rsync -r -a -v -e "ssh -l user" --delete ~/local/file 111.111.11.111:~/remote/';
//exec($c,$data);
passthru($c,$data);
print_r($data);
This is what I receive:
255
And no file is uploaded from local to remote server.
Searching on the net I found this clue:
"You could use a combination of BASh and Expect shell code here, but it wouldn't be very secure, because that would automate the root login. Generate keys for nobody or apache (whatever user as which Apache is being executed). Alternatively, install phpSuExec, Suhosin, or suPHP so that the scripts are run as the user for which they were called."
Well, I don't know how to "automate the root login" for PHP, which is running as "apache". Maybe to make the script run as the actual user is a better idea, I don't know... Thanks!
UPDATE:
- As this works fine:
passthru('ssh user#111.111.11.111 | ls',$data);
Returning a list of the home foder, I can be sure, there is no problem with the automatic login. It mast be something with rsync running from the PHP script.
I also have chmod -R 0777 on the local and remote folders just in case. But I didn't get it yet.
UPDATE:
All the problem is related to the fact that "when ran on commandline, ssh use the keyfiles on $HOME/.ssh/, but under PHP, it's ran using Apache's user, so it might not have a $HOME; much less a $HOME/.ssh/id_dsa. So, either you specifically tell it which keyfile to use, or manually create that directory and its contents."
While I cannot get rsync, this is how I got to transfer the file from local to remote:
if($con=ssh2_connect('111.111.11.111',22)) echo 'ok!';
if(ssh2_auth_password($con,'apache','xxxxxx')) echo ' ok!';
if(ssh2_scp_send($con,'localfile','/remotefolder',0755)) echo ' ok!';
Local file needs: 0644
Remote folder needs: 0775
I guess if the solution wouldn't be to run php with the same user bash does...
#Yzmir Ramirez gave this suggestion: "I don't think you want to "copy the key somewhere where apache can get to it" - that's a security violation. Better to change the script to run as a secure user and then setup the .ssh keys for passwordless login between servers.
This is something I have to invest some more time. If somebody know how to do this, please, it would be of great help.
When I set this same thing up in an application of ours, I also ran into 255 errors, and found that they can mean a variety of things; it's not a particularly helpful error code. In fact, even now, after the solution's been running smoothly for well over a year, I still see an occasional 255 show up in the logs.
I will also say that getting it to work can be a bit of a pain, since there are several variables involved. One thing I found extremely helpful was to construct the rsync command in a variable and then log it. This way, I can grab the exact rsync command being used and try to run it manually. You can even su to the apache user and run it from the same directory as your script (or whatever your script is setting as the cwd), which will make it act the same as when it's run programmatically; this makes it far simpler to debug the rsync command since you're not having to deal with the web server. Also, when you run it manually, if it's failing for some unstated reason, add in verbosity flags to crank up the error output.
Below is the code we're using, slightly edited for security. Note that our code actually supports rsync'ing to both local and remote servers, since the destination is fully configurable to allow easy test installations.
try {
if ('' != $config['publishSsh']['to']['remote']):
//we're syncing with a remote server
$rsyncToRemote = escapeshellarg($config['publishSsh']['to']['remote']) . ':';
$rsyncTo = $rsyncToRemote . escapeshellarg($config['publishThemes']['to']['path']);
$keyFile = $config['publishSsh']['to']['keyfile'];
$rsyncSshCommand = "-e \"ssh -o 'BatchMode yes' -o 'StrictHostKeyChecking no' -q -i '$keyFile' -c arcfour\"";
else:
//we're syncing with the local machine
$rsyncTo = escapeshellarg($config['publishThemes']['to']['path']);
$rsyncSshCommand = '';
endif;
$log->Message("Apache running as user: " . trim(`whoami`), GLCLogger::level_debug);
$deployCommand = "
cd /my/themes/folder/; \
rsync \
--verbose \
--delete \
--recursive \
--exclude '.DS_Store' \
--exclude '.svn/' \
--log-format='Action: %o %n' \
--stats \
$rsyncSshCommand \
./ \
$rsyncTo \
2>&1
"; //deployCommand
$log->Message("Deploying with command: \n" . $deployCommand, GLCLogger::level_debug);
exec($deployCommand, $rsyncOutputLines, $returnValue);
$log->Message("rsync status code: $returnValue", GLCLogger::level_debug);
$log->Message("rsync output: \n" . implode("\n", $rsyncOutputLines), GLCLogger::level_debug);
if (0 != $returnValue):
$log->Message("Encountered error while publishing themes: <<<$returnValue>>>");
throw new Exception('rsync error');
endif;
/* ... process output ... */
} catch (Exception $e) {
/* ... handle errors ... */
}
A couple of things to notice about the code:
I'm using exec() so that I can capture the output in a variable. I then parse it so that I can log and report the results in terms of how many files were added, modified, and removed.
I'm combining rsync's standard output and standard error streams and returning both. I'm also capturing and checking the return result code.
I'm logging everything when in debug mode, including the user Apache is running as, the rsync command itself, and the output of the rsync command. This way, it's trivially easy to run the same command as the same user with just a quick copy-and-paste, as I mention above.
If you're having problems with the rsync command, you can adjust its verbosity without impact and see the output in the log.
In my case, I was able to simply point to an appropriate key file and not be overly concerned about security. That said, some thoughts on how to handle this:
Giving Apache access to the file doesn't mean that it has to be in the web directory; you can put the file anywhere that's accessible by the Apache user, even on another network machine. Depending on your other layers of security, this may or may not be an acceptable compromise.
Depending on the data you're copying, you may be able to heavily lock down the permissions of the ssh user on the other machine, so that if someone unscrupulous managed to get in there, they'd have minimal ability to do damage.
You can use suEXEC to run the script as a user other than the Apache user, allowing you to lock access to your key to that other user. Thus, someone compromising the Apache user simply would not have access to the key.
I hope this helps.
When you let the web server run the command, it runs it as its own user ("nobody" or "apache" or similar) and looks for a private key in that user's home directory/.ssh, while the private key you setup for your own user is in "/home/you/.ssh/keyfile", or however you named it.
Make ssh use that specific key with the -i parameter:
ssh -i /home/you/.ssh/keyfile
and it should work
A Simple but In-direct solution to this problem is here, which i am using.
DO NOT run rsync directly from php, it does have issues as well as it can be security risk to do so.
Rather, just prepare two scripts.
In php script, you just have to change value of one variable on filesystem from 0 to 1.
Now, on ther other side, make an rsync script, that will run forever, and will have following logic.
""
If the file has 0 in it, then do not run rsync, if it is 1, then run the rsync, then change its value back to 0 after successful run of rsync;
""
I am doing it as well.
I'm building a web control that will let our junior IT staff manage firmware on our LifeSize phones. Currently we do this by uploading the new firmware onto a central server, then running this command for each phone we want to upgrade
cat new_firmware.cramfs | ssh -T cli#1.1.1.1 "upgrade all"
This asks me for the password, then uploads the firmware. Works like a champ, but it takes someone comfortable with CLI tools, SSH access to this server, and patience to look up all the IPs of all the phones.
It looks like we're stuck with a password logon, testing with certificates has been disastrous. The device being acted on is not a full-fledged computer, it's a telephone running a tiny, proprietary embedded OS.
I'm working on a PHP script that can iterate over all the phones, but basically duplicate that function. This is what I have so far:
<?php
$firmware_filename = "new_firmware.cramfs";
$firmware_stream = fopen($firmware_filename,"rb");
$ssh_connection = ssh2_connect("1.1.1.1", 22);
ssh2_auth_password($ssh_connection, "cli", "password");
$ssh_stream = ssh2_exec($ssh_connection,'upgrade all');
$written = stream_copy_to_stream($firmware_stream,$ssh_stream,-1);
if($written != filesize($full_filename)){
echo "The file is " . filesize($firmware_filename) . " bytes, I only wrote $written" . PHP_EOL;
}else{
echo "All Good" . PHP_EOL;
}
?>
But this always returns
The file is 26988590 bytes, I only wrote 8192
And the upgrade does not proceed correctly.
Well you could simply call
system('cat new_firmware.cramfs | ssh -T cli#1.1.1.1 "upgrade all"');
and then replace using your vars:
system('cat ' . $firmware . ' | ssh -T ' . $username . '#' . $host . ' "upgrade all"');
is this a solution for you?
you can automate the ssh-access by placing the certificate-file into .ssh-directory. Read about SSH login without password.
regards
There are several things you could try:
Copy the file first, then run the
command on the now-local file.
Assuming that you're filling an 8k buffer, try writing in a loop until you've successfully written the whole file
Take the easy way out, and just set up ssh keys so you don't need to enter a password, and exec shell commands directly from your script