SSH tunnelling from php to multiple nodes - php

I have a central system(web application) which is supposed to connect to a set of nodes one by one (clients), to get a record set from database exposed through mysql views.
I am in a search of best possible implementation for this scenario. Currently I have this flow in my mind.
1. First Approach
Pseudo code
$dataFetched=array();
$clients= getListOfClients();
//
foreach($clients as $client){
// client={details:{id,name,etc....},
// ssh:{pem:'client.pem',localMysqlPort:3307 ,remoteMyqlPort:3306,
// sshUser:'demo#clientHost.com'},
// mysql:{user:'tunnelUser',password:'password'}
// }
//connect ssh connection
$ssh_connection=new Ssh_connect($client->ssh)
if($ssh_connection->status){
$dataFetched[$client->id]=array("status"=>'OK','data'=>fetchMysqlData($client->mysql))
}else{
$dataFetched[$client->id]=array("status"=>'NO_OK','data'=>array())
}
$ssh_connection->destroyTunnel();
unset($ssh_connection);
}
Some Concerns/questions with this approach
Is there a ssh lib/extension to provide me this kind of functionality ?
What is best secure way to store "pem" files for each client ?
Another approach ?
2. Second Approach
Write bash script to implement above pseudo-code
setup cron job to update database
Web application does not know about external clients. It will use table updated
by cron-job to render data.
Some point:
Data size is very light
Both consumer and provider are web-applications
Applications does not know each other except SSH
It should be flexible to add new client node ( I don't known how I will do this in second approach)
Which one will be better from security wise Reverse Tunnel or Forward Tunnel ?
As I know in case of reverse tunnel, echo client knows about server which desires to connect with it. So we need to setup a less privileged user to on each client node to give access to server, which seems more secure as I don't have to keep all pem files in one place at Central server. Correct me if I am getting it wrong.
Your suggestions are much appreciated

There is an SSH library for PHP. It's called ssh2 and it is available on PECL.
It has a dependancy on libssh2, but it should not be too hard to get up and running on a Linux-based machine.
The best way to store the pem files is outside your web directory, with limited permissions - just enough for your web application to be able to read the files. Alternatively, you could set up the ssh-agent daemon to run as your web application.
(I am hoping here that your PHP apps don't all run under the same user. For Apache, there is the little known mod_ruid2 module that allows you to change the user and chroot a PHP web app, without resorting to setting up fastcgi or suExec).
Sadly, ssh2_tunnel only creates a new PHP resource, it's not something that you can use to create a new mysql connection (well, at least not using mysqli_connect & friends).
What you could do is execute the SQL commands on the local server, storing the database password inside ~/.my.cnf
~/.my.cnf:
[client]
password = lolcats1234
And then with PHP & ssh2 you could run the following code once connected:
stream_set_blocking($stream, true);
$stream = ssh2_exec($connection, 'mysql -u db -p db -e "select * from table"');
$first = true;
while($line = fgets($stream)) {
if ($first) {
$first = false;
continue;
}
// process one line of results...
echo $line."<br />";
}
Alternatively, if you just use a Reverse Tunnel, you can just mysql_connect using the local script, without needing to worry about doing any SSH work at all.

Related

ftp_exec(): Unknown SITE command

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.

SSH connections with PHP

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.

How to let apache to do ssh

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

Executing shell commands using PHP, e.g. shell_exec() etc., on a remote host?

Is it possible to execute shell commands on a remote computer (not localhost)? For instance things like
$output = shell_exec("unzip filename.zip");
You can assume that you have the login credentials of a user account on the remote machine, as well as the remote root username, password, and cpanel remote access key.
If you mean "a remote computer" as in "not the client computer", the answer is an unqualified yes; commands run via PHP's exec function will execute on the web server.
If you mean "not the web server", the answer is a slighty-hazier yes. You can only directly execute commands on the server running PHP. However, those commands can then run others on remote machines via mechanisms such as SSH. So, for example, if your web server has passwordless ssh access to the remote machine (a very bad idea), this would work: exec('ssh otherhost someremotecommand');. What solution fits for you depends on your desired usage.
I know this question is pretty old but for the viewers of this question.
You can also use ssh2_exec. http://php.net/manual/en/function.ssh2-exec.php
<?php
$ip = 'ip_address';
$user = 'username';
$pass = 'password';
$connection = ssh2_connect($ip);
ssh2_auth_password($connection,$user,$pass);
$shell = ssh2_shell($connection,"bash");
?>
Yes, and there is no need to change any code to do so. However, if your server puts PHP into safe mode (the lesser shared hosting plans often do) you may not be able to do this.
Tip: You can use this shorthand to get the output of a command:
$output = `command here`

Connect to a MySQL server over SSH in PHP

I'd like to establish an ssh tunnel over ssh to my mysql server.
Ideally I'd return a mysqli db pointer just like I was connecting directly.
I'm on a shared host that doesn't have the SSH2 libraries but I might be able to get them installed locally using PECL.
If there's a way that uses native commands that would be great.
I was thinking something like this, but without those libraries it won't work.
$connection = ssh2_connect('SERVER IP', 22);
ssh2_auth_password($connection, 'username', 'password');
$tunnel = ssh2_tunnel($connection, 'DESTINATION IP', 3307);
$db = new mysqli_connect('127.0.0.1', 'DB_USERNAME', 'DB_PASSWORD',
'dbname', 3307, $tunnel)
or die ('Fail: ' . mysql_error());
Anyone have any ideas? I'm running a shared CentOS linux host at liquidweb.
Any thoughts on making the tunnel persistent? Is it possible to establish it with another script and just take advantage of it in PHP?
Thanks.
I would use the autossh tool to create a persistent ssh tunnel to your mysql database. Then all you have to do in your PHP code is point it to localhost.
You can test this (without automatic restart) by doing:
ssh -L 3306:localhost:3306 user#domain.com
Setup a ssh key so that you don't need to use passwords, and tweak the .ssh/authorized_keys file on the mysql system to only allow it to be used for port forwarding.
For more info on ssh tricks see Brian Hatch's excellent series on SSH and port forwarding.
The tunnel must be keep open during the course of the SQL action(s). The following example from RJMetrics explains:
Here's the generic SSH command syntax:
ssh -f -L bind-ip-address:bind-port:remote-ip-address:remote-port \
username#remote-server [command] >> /path/to/logfile
Here's how to securely establish a remote database connection in just 2 lines of PHP code:
shell_exec("ssh -f -L 127.0.0.1:3307:127.0.0.1:3306 user#remote.rjmetrics.com sleep 60 >> logfile");
$db = mysqli_connect("127.0.0.1", "sqluser", "sqlpassword", "rjmadmin", 3307);
We use the shell_exec() function to create the tunnel with a 60 second opening window, and then use the mysqli_connect() function to open a database connection using the forwarded port. Note that we must use the "mysqli" library here because mysql_connect() does not allow us to specify a port and mysql_* functions are deprecated.
sleep 60: When it comes to tunnel connections, we basically have two options: leave the connection open all the time or open it and close it as needed. We prefer the latter, and as such we don't specify the -N option when establishing a tunnel, which would leave it open until the process is manually killed (bad for automation). Since -N is not specified, our tunnel will close itself as soon as its SSH session isn't being used for anything. This is ideal behavior, except for the few seconds between when we create the tunnel and when we get a MySQL connection up and running via the tunnel. To buy us some time during this period, we issue the harmless sleep 60 command when the tunnel is created, which basically buys us 60 seconds to get something else going through the tunnel before it closes itself. As long as a MySQL connection is established in that timeframe, we are all set.
I tried it by doing SSH both by root credentials and and public private key pair. It allows me to conect through command line but not through PHP code.
I also tried by creating a tunnel (by using SSH2 functions), and running shell commands from PHP code (system, exec, etc.); nothing worked.
Finally I tried SSH2 function to execute shell command and it finally worked :)
Here is my code, if it helps you:
$connection = ssh2_connect($remotehost, '22');
if (ssh2_auth_password($connection, $user,$pass)) {
echo "Authentication Successful!\n";
} else {
die('Authentication Failed...');
}
$stream=ssh2_exec($connection,'echo "select * from zingaya.users where id=\"1606\";" | mysql');
stream_set_blocking($stream, true);
while($line = fgets($stream)) {
flush();
echo $line."\n";
}
Try this if want to use PHP functions specifically.
It is possible, but why? It's more complicated than it needs to be, and error prone.
Can you not run the database locally? If not, can you not use something like SQLite, XML files or something else that doesn't require a separate server daemon?
You really do not want to initialise the tunnel inside the PHP scripts. Initialising an SSH tunnel takes a long time (can easily be a second or two), so that will mean every page that connects to the database will have a 2 seconds delay while loading..
If you have to do this (which I strongly recommend against), the best method would be to have a script that maintains the connection in the background..
Setup a SSH keypair. Then using autossh, or a simple script which would execute the required SSH command, wait till the process died and start it again. It could be more intelligent and try and run a command every 10-20 seconds, reconnecting if it fails.
Using the SSH keypair, you wouldn't have to hardcode the remote users password in the script. I would recommend limiting what the remote user can do (by using a dedicated tunnel user, and giving it a restricted shell, see this question)
In short, I would strongly suggest trying SQLite first, then see how feasible it to store data using XML files, then try bugging your web-host about getting a local MySQL server, then look into the SSH tunnelling option.
Eric,
I think you are out of luck on this one. You can either use the ssh extension in your PHP code, or if you have access to the server, you could try to create a ssh tunnel on the command-line.
You probably need special permissions to do that, though. It also looks like you don't have ssh access to this hosting account.
--Joao
I think your best bet is to find a different hosting provider; I suggest a VPS solution (Virtual Private Server) which gives you full control over your web host. THat way, if you don't like the default configuration, you can upgrade it yourself.

Categories