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
Related
I need to run a linux command from php. So I used ftp_exec() function.
$command='ls -al> /ftp_test/t.log';
if (ftp_exec($ftp_conn,$command))
{
echo "$command executed successfully.";
}
else
{
echo "Execution of $command failed.";
}
But it gives me warning
Warning: ftp_exec(): Unknown SITE command
I have googled and found for ftp_exec "execution via FTP isn't very widely supported. Check that it works on the servers that you intend to connect to before you start coding something that requires this."
Can anybody give me a idea to run a linux command from php ?
If you have the appropriate authorization you may do so via SSH:
$file_list = shell_exec('ssh user#site "ls -la"');
You'll need for user to have an authorized ssh key for site, and the user must be accessible from whatever user is running PHP. This usually boils down to using user wwwrun for both.
Or you can use sudo for added security, by placing the command into a script of its own, then sudoing it:
$file_list = shell_exec('sudo /usr/local/bin/ssh-ls-site');
Now user wwwrun can be allowed to run ssh-ls-site but can't modify its contents, so he can't run arbitrary commands, nor has he access to the ssh authorization key.
The ssh-ls-site can log the request as well as updating a local marker file, and exiting immediately if the file is newer than a certain guard time. This will prevent possible DoS attacks against site (running lots of allowed commands, exhausting resources), and also improve performances; if for example you need to run the command often, you can save the results into a temporary file. Then if this file is found to exist, and is not too old, you just read back its contents instead of asking it to #site, effectively caching the command locally.
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'm trying to run a command on a remote server via SSH from a PHP script. Here's the snippet:
$ssh_command = "ssh -F keys/config -o UserKnownHostsFile=keys/known_hosts -i keys/deployment_key -p $ssh_port $r
$git_fetch = "git --git-dir=$remote_path/.git --work-tree=$remote_path fetch 2>&1";
exec("$ssh_command '$git_fetch' 2>&1", $out);
The script works fine if I run it from the command line, because it's running as a user with a regular login shell and their own .ssh directory. When I try to run the script through the Web interface, it fails because SSH can't create its .ssh directory in the Apache user's home directory.
Is it possible to use SSH without the .ssh directory or is there another alternative for running SSH as the Apache user? I'd prefer not to create a .ssh folder for the Apache user.
I'm able to get around this restriction with one small issue/side-effect.
Setting the options UserKnownHostsFile=/dev/null and StrictHostKeyChecking=no, you can trick SSH into not actually storing or requiring verification of a host key.
Setting StrictHostKeyChecking to no allows the connection to the server without first knowing or verifying its key; and using /dev/null for UserKnownHostsFile just reads and writes to nothing so no values are ever read of saved.
The caveat was that SSH still tries to create the .ssh directory and fails, but the failure results in a warning and it will continue with the connection. The warning WILL be included in your output (unless you were to suppress warnings).
Here is an example. Note: For this example I didn't set up any authentication so it will try to use password auth and fail, but since you are using an identity you should be able to connect just fine.
<?php
$ssh_command = "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no "
."-p 2223 user#host.localdomain";
exec("$ssh_command ls 2>&1", $out);
var_dump($out);
// output when called from browser running as daemon(1)
array(5) {
[0]=>
string(44) "Could not create directory '/usr/sbin/.ssh'."
[1]=>
string(110) "Warning: Permanently added '[host.localdomain]:2223,[192.168.88.20]:2223' (RSA) to the list of known hosts."
[2]=>
string(36) "Permission denied, please try again."
[3]=>
string(36) "Permission denied, please try again."
[4]=>
string(82) "Received disconnect from 192.168.88.20: 2: Too many authentication failures for user"
}
Your output would most likely only include the first warning about not being able to create the .ssh directory, followed by the warning about permanently adding the host to the list of known hosts (/dev/null), followed by the output from your command; so you would have to check if the first line was this warning, and shift it from the $out array.
Another note: This does open up the possibility of man-in-the-middle attacks or a DNS/IP hack to get you to try to connect to a rogue server.
See this article on SSH Host Key Protection from Symantec.
I'd do it with phpseclib, a pure PHP SSH implementation:
<?php
include('Net/SSH2.php');
$key = new Crypt_RSA();
//$key->setPassword('whatever');
$key->loadKey(file_get_contents('privatekey'));
$ssh = new Net_SSH2('www.domain.tld');
if (!$ssh->login('username', $key)) {
exit('Login Failed');
}
echo $ssh->read('username#username:~$');
$ssh->write("ls -la\n");
echo $ssh->read('username#username:~$');
?>
The only other thing I can imagine people referring to when they say PHP SSH library is the PECL extension. I'd personally recommend against using that as it's pretty badly written. The fact that it's hard to install and badly supported aside it requires you provide public and private keys. phpseclib only requires the private key. This makes sense because the private keys normally contain the public key embedded within them. The PECL SSH2 extension requires it be extracted separately, which is silly.
phpseclib also supports a ton more formats than the PECL SSH2 library does. phpseclib supports the PuTTY key format, XML Signatures keys, etc.
The problem is that the first thing that SSH looks for is the user's configuration file. You can use the -F flag to specify the location of your own configuration file, which ought to solve the problem, though I'm rushing out to work right now, so I haven't double-checked this. You'll need to specify the location of other things like the identity files, &c., within it, mind. The file will need to have the correct ownership and permissions or SSH will complain.
That said, using an SSH library like SSH2 is a far superior solution to shelling out if at all possible.
No, you need a .ssh in the user's home when using an OpenSSH client. There's no way to avoid this - it's meant to be a security feature since it contains integral settings. Simply keep an empty dummy directory - the client won't write there - but you are required to have adequate user permissions on it.
Solution:
DON'T DO THIS. I haven't used PHP in years, but in any modern, general purpose programming language, this would be a huge WTF. I noticed one user commneted about using the PHP SSH2 library... this would be the way to go, IMHO.
Jumping out to shell to execute a command should ONLY occur when you're producing a side-effect where the result is inconsequential. If you're relying on this for anything important, or if the result of the command should modify program state in any way, you either need to find an internal library to handle this, or extend the language to do this properly via the appropriate C libraries. Otherwise, you're setting yourself up for a world of hurt.
You can control where ~/.ssh will land using the HOME environment variable. That way you can be sure it won't be a directory Apache serves.
I know this question has been asked before in many different ways but I'm still scratching my head over why I can't get this to work.
Firstly I have two SLES servers setup, these are Server A & Server B which are both running on a small private network which is only accessed by a dedicated team.
Server A is configured as a web server which is running Apache, PHP, MYSQL and ssh all of which are running problem free.
Server B is used to run menial tasks with ssh also installed and activated.
I have created my rsa key on Server A and installed it on Server B which when run at the command line logs me in straight away with out asking for a password. I have repeated this process for both root & nobody accounts on Server A.
I have added this a PHP page to Server A which looks like:
<?php
shell_exec('ssh root#192.162.0.5 ./StartTest.sh');
header("Location: archive.php?page=home");
?>
But when I run it it does not create my folder. If I run this from the command line it works for both (I think both, I can't recall if I did try this for the nobody account on the cli now) root & the nobody account. I even went as far as adding the nobody account to the root group but still no joy.
Have I missed some thing here. All I would like to do is connect from Server A to Server B via php & ssh to execute one command and redirect to a another page on the web site.
Any help would be graciously appreciated as my paracetamol stock is running low.
The built-in SSH support George Cummins speaks of is non-existent. It is an extension to PHP that's not included by default. It has to be compiled separately and is notoriously difficult to setup / use. My recommendation would be to use phpseclib, a pure PHP SSH implementation:
<?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');
?>
You said "I have added this a PHP page", so I will assume that you are executing this script via your web server, rather than as a standalone script.
As such, the script may not be running from the directory you expect. You should use absolute (rather than relative) paths to ensure that the script finds the ssh binary and your script:
shell_exec('/path/to/ssh root#192.162.0.5 /home/yourdirectory/scripts/StartTest.sh');
You will also need to confirm that the webserver user had permissions to execute ssh and the StartTest.sh script.
I know that I'm too late at this answer but maybe can help someone:
To use shell_exec and ssh you need to add as parameter to ssh these
ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o LogLevel=quiet
So the command doesn't try to create .ssh folder and you have a clear output without log of ssh
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.