Using PHP and Bash to log into an external server via SSH - php

I'm trying to set up a centralized server which is in charge of monitoring my other servers. This centralized server needs to be able to collect particular information/metrics about a specific server (such as df -h and service httpd status); but it also needs to be able to restart Apache if needed.
If it wasn't for the Apache restart, I could write a listening script to provide a means of giving the centralized server the data it needs without having to SSH in. But because I also want it to be able to restart Apache, it needs to be able to log in and initiate scripts through a combination of PHP and Bash.
At the moment, I'm using PHP's shell_exec to execute this (very simple) Bash script:
#!/bin/sh
ssh -i /path/to/keyFile.pem ec2-user#x.x.x.x;
I'm accessing the external server (which is an EC2 instance) through a private IP. If I launch this script, I can log in without any problem - the problem comes, however, when I then want to send back the output for commands like the ones I've listed above.
In a Bash script, how would I output a command like df -h after SSHing into another server? Is this possible?

There is a PECL extension for SSH.
Other than that you'll probably want to either use the &$output parameter of exec() to grab the output:
$output = array();
exec('bash myscript.sh', $output);
print_r($output);
Or use output redirection
$output = '/path/to/output.txt';
exec("bash myscript.sh > $output");
if( file_exists($output) && is_readable($output) ) {
$mydata = file_get_contents($output);
}
and, of coure, this all assumes your script looks like what jeroen has in his answer.

You could use:
ssh -i /path/to/keyFile.pem ec2-user#x.x.x.x 'df -h'
or for multiple commands:
ssh -i /path/to/keyFile.pem ec2-user#x.x.x.x 'ls -al ; df -h'
That works from the command line but I have not tried it via php's exec (nor on Amazon to be honest...).

If you're doing ssh I'd suggest phpseclib, a pure PHP SSH implementation. It's a ton more portable than the PECL SSH extension and more reliable too.

Related

How i can run function exec in PHP

I can't add * to my code to find file
this code work
exec("mediaconvert -t wav -i /home/20220228/11/23401.rec -o /var/www/html/test.mp3");
if i add a the *, it don't work
exec("mediaconvert -t wav -i /home/20220228/11/*01.rec -o /var/www/html/test.mp3");
p.s. in path is only one file, when i try execute this code from shell it work. Pls help me)
Filename expansion and other bash-specific features may/will not work in other shells (e.g. standard POSIX). If your command with * is not executed in bash/compatible, it won't work as expected. You need to verify the environment/shell that your PHP installation executes commands in.
Run the following test script:
<?php
exec('echo "$SHELL"', $out);
var_dump($out);
When I run the PHP script directly on CLI, I get "/bin/bash" for the shell that's called. When I run it via browser, curiously I get "/sbin/nologin" instead. There are different environments for user apache that executes PHP via browser calls, and the "actual" user logging in via SSH. Bash shell is not available for the Apache user by default.
These results are from a Centos7 server with Apache 2.4/PHP 8.1.4 running. Your mileage may vary. Bottom line: if the command you are executing depends on bash-specific features, it must execute in a bash environment, or another shell that supports the required features.
If bash is not available, your other option is using e.g. glob to get files matching the pattern in your directory, and then loop over them while executing the command for each specific file.
Edit: As pointed out by #Sammitch (see comments), /sbin/nologin is a common "shell name" choice for non-login users, and most likely uses /bin/sh. This should still allow for filename expansion/globbing. Testing browser script call with exec('ls *.php', $out); the wildcard functions as expected.
You may find this question/answer relevant: Use php exec to launch a linux command with brace expansion.
I recommend you do the opposite. First, get the files you want to input then you exec. For instance:
$input_files = ...
exec("mediaconvert -t wav -i " . $input_files . " -o /var/www/html/test.mp3");
You can try to find files with glob() function and after that you can use exec(). You can try a similiar solution with the following code:
$input_files = '/home/20220228/11/*01.rec';
foreach (glob($input_files) as $filename) {
exec("mediaconvert -t wav -i " . $filename . " -o /var/www/html/test.mp3");
}

How to execute a local Shell Script with SSH commands on server?

I want to run a local shell script that have SSH commands on the server using PHP. And inside the script i am using ssh to run a command like ls -lart and save the result on a log file in the server, and then using scp to copy the remote file to my local host. Something like this:
/// my_local_shell.sh
#!/bin/bash
host=$1
user=$2
port=$3
ssh -p $port $user#$host 'ls -lart >> /home/remote/file.log'
scp -P $port $user#$host:/home/remote/file.log /home/local/file.log
If i run the script using the terminal user#local_host:~$ ./my_local_shell.sh everything works just fine. But if i use shell_exec() to execute the script using PHP like this:
/// index.php
$output = shell_exec("my_local_shell.sh 192.168.1.1 root 2222");
echo <pre>$output</pre>;
Nothing is printed on screen and the SSH commands inside the file are not executed.
I know I can use ssh2_shell(), but by using it I would have to send the commands inside the PHP, and it's not what i want.
I already gave the permissions needed to index.php and my_local_shell.sh
Any ideas how I can do this?
Apparently scp uses some sort of ncurses that you can't capture, so you could add the -v flag to your scp command in the shell script
scp -v -P $port $user#$host:/home/remote/file.log /home/local/file.log
or alternatively, since scp returns 0 on success you could write
scp -P $port $user#$host:/home/remote/file.log /home/local/file.log && echo Success
As for the PHP please check you have PHP opening and closing tags and correct your echo statement
echo "<pre>".$output."</pre>";

How to create an API of some shell commands, and allow access to them via PHP?

I want to do some tasks on my server that require root shell access. I want to make a simple API that I can access from PHP.
The things I want to achieve is:
clone from one database to another. The databases are owned by different users:
mysqldump -h localhost -u SOURCE_USER -pSOURCEPASSWD SOURCE_DB | mysql -h localhost -u DEST_USER -pDEST_PASS DEST_DB
copy files from one user public_html to another:
cp -R SOURCE_DIR DEST_DIR
I have working bash-scripts for both those tasks.
I do not want to give PHP full root access to the server, since that would be crazy, but instead:
How can I make specified bash-scripts executable from a PHP-file in one linux-user's public_html directory?
Alternatively: How can I give root shell access (via shell_exec) to ONE specified PHP-file on a server.
You could use this project: Github. It allows PHP to obtain and interact with a real Bash shell even as root without running the web server as root.
After composer/downloading you would simply use the following code:
//read the documentation: here you get a root shell if you allowed sudo
$shellObj = \MTS\Factories::getDevices()->getLocalHost()->getShell("bash", true);
//OR if you did not want to give the webserver sudo access, then you can use this syntax:
$shellObj = \MTS\Factories::getDevices()->getLocalHost()->getShell("bash", false);
\MTS\Factories::getActions()->getRemoteUsers()->changeUser($shellObj, 'root', 'rootPassword');
//In both cases you now have a shell as root. This really is a bash shell, its not just wrapping the PHP shell functions.
//All you have left is to issue commands just like you would on a bash prompt
$strCmd = "mysqldump -h localhost -u SOURCE_USER -pSOURCEPASSWD SOURCE_DB | mysql -h localhost -u DEST_USER -pDEST_PASS DEST_DB";
//for the vast majority of commands that finish within 10 sec you need only issue the command
$return = $shellObj->exeCmd($strCmd);
echo $return;// return from your command
//However if your command runs for more than 10 sec, you must set a timeout. e.g.
//timeout in miliseconds
$timeout = 20000;
$return = $shellObj->exeCmd($strCmd, null, $timeout);
echo $return;// return from your command
Feel free to issue more commands on the $shellObj, its a bash shell ready to take orders and as i said ready the documentation.
After an useful comment from arkascha I researched sudo and found Ezequiel Hdez's answer in sudo in php exec() to be useful in finding the answer to my question:
Allow myuser to sudo my shell script by adding in sudoers file (access it by typing visudo):
myuser ALL = NOPASSWD: /path/to/myscript.sh
Make script executable:
chown u+x /path/to/myscript.sh
Then in PHP (from "myuser")
exec("sudo /path/to/myscript.sh argument1 argument2");
Now I was able to do whatever I want from PHP, but ONLY via /path/to/myscript.sh

Run docker in shell using PHP

Well, I need to run a Docker using a PHP function. I have a web page where pushing a link I execute a shell order using shell_exec or exec. This works for me if the execution is like an ls or something that expects a result. The problem is that if the command is to run the Docker (or for example a ping) it doesn't work.
What I want is when the user clicks the link, the shell will execute a command to run Docker in the browser, and the page will be redirected there.
For exemple, if I use shell_exec('firefox'); this should open a new firefox browser, but it doesn't work. It seems that the browser is opened but few seconds later is closed.
This is the Docker execution that doesn't work.
public function executeDocker() {
$result = shell_exec('docker run --rm -p 3838:3838 shiny/gsva_interactive /usr/bin/shiny-server.sh');
echo "<br><br>Execution: ".$result;
}
shell_exec will only return the output of a, in this case Docker, command only when the command has exited completely. In the case of ping (it will just keep pinging) and probably in the case of your Docker image, the process will never exit by itself, so it will never give a response.
Using passthru instead of shell_exec should give you the commandline output of your Docker script right back as a response.
If the Docker container is not meant to exit you should probably start it in detached mode with $containerId = shell_exec('docker run -d --rm -p 3838:3838 shiny/gsva_interactive /usr/bin/shiny-server.sh'), so the docker run command will exit. This will return the container id, which you can use with $result = shell_exec("docker ps -f \"id=$containerId\"") to check if the container is running correctly and redirect the user if it is.
i was having the same issue running docker exec via shell_exec.
shell_exec('docker exec -it containerid /usr/bin/cmd);
Getting rid of the -i option worked for me.
Finally I solved it. The problem was in the user group and permissions. In the system that I was using, CentOS, apache server uses a user called apache. This user needs to be in the docker group and reboot the services.
Now it works. Thanks to everyone who helped me.

How can I use PHP to setup an interactive SSH session?

I'm trying to establish an interactive SSH connection to a remote server using PHP via the command line on Mac OS X 10.6. I'm currently using PHP's proc_open function to execute the following command:
ssh -t -t -p 22 user#server.com
This almost works. The -t -t options are supposed to force a pseudo terminal which they almost do. I am able to enter the SSH password and press enter. However, after pressing enter the terminal appears to simply hang. No output, no nothing - it's as if the SSH session has failed. I can't run commands or anything and have to kill the whole thing using Ctrl+C. I know the login is successful because I can execute a command like ssh -t -t -p 22 user#server.com "ls -la" and get the correct output.
I thought the problem must be related to the fact that I was using standard pipes in my proc_open call, so I replaced them with pty. I get the following error: "pty pseudo terminal not supported on this system..."
Does Mac OS X simply not support pty or pseudo terminals? (I'm pretty new at using all this shell terminology).
Here's the PHP code:
$descriptorspec = array(0 => array("pty"), 1 => array("pty"), 2 => array("pty"));
$cwd = getcwd();
$process = proc_open('ssh -t -t -p 22 user#server.com', $descriptorspec, $pipes, $cwd);
if (is_resource($process))
{
while (true)
{
echo(stream_get_contents($pipes[1]));
$status = proc_get_status($process);
if (! $status["running"])
break;
}
}
(Sorry - cannot for the life of me figure out SO's formatting instructions...)
What am I doing wrong? Why can't I use pty? Is this just impossible on Mac OS X? Thanks for your help!
You should use public key authentication rather than trying to programmatically bypass interactive password authentication.
The password prompt is supposed to be used from a tty and I believe it was made intentionally difficult to use otherwise. Also the -t -t argument only takes effect once you are connected to the remote host. And I don't believe the PHP function proc_open() can run a command inside a virtual terminal.
To setup public key authentication:
# Generate keypair
ssh-keygen -t rsa
# Copy public key to server
scp ~/.ssh/id_rsa.pub example.com:.ssh/authorized_keys
# Now you shouldn't be prompted for a password when connecting to example.com
# from this host and user account.
ssh example.com
# Since the web server (and thus PHP) probably has its own user account...
# Copy the ~/.ssh/id_rsa file somewhere else
cp ~/.ssh/id_rsa /some_path/id_rsa
# Change ownership of the file to the web server account
chown www-data:www-data /some_path/id_rsa
# Fix the file permissions (ssh ignore the keyfile if it is world readable)
chown 600 /some_path/id_rsa
# Try connecting to the server through the web server account
su -c "ssh -i /some_path/id_rsa -o UserKnownHostsFile=/some_path/known_hosts example.com" www-data
# Add the host to the known hosts file when prompted
Alternately, you could use plink (part of PuTTY for Linux) instead of OpenSSH as it can take the password on the command line plink -pw password example.com. But doing so presents a security risk as anyone who runs ps aux on the server can see the password in the process list.
There is also a program called sshpass that takes the password from an environment variable or command argument and passes it to ssh.
It looks like the problem is best solved using PHP's passthru() function. After alot more (rather painful) research I was able to issue a command through this function and could interact with the remote server through the terminal as if I had run ssh and svn export by hand (they both require passwords, therefore were good tests). What I'm going to have to do is construct a (potentially very long) string of commands separated by && and attach them to the end of the ssh command: ssh -t -t -p 22 hostname command1 && command2 ... The output will be sent to my terminal in Mac OS X even though the commands are being executed on the remote server. Looks like this is the solution I was looking for the whole time - pretty simple really! Thanks to everyone who helped me with this. I gave Alexandre the "green checkmark" because he was the only one who kept responding and was quite helpful in deducing the final answer to the problem. Thanks Alexandre!
This is old, but for any googlers out there, here is an actual solution using proc_open:
Pty descriptors are available in PHP, but have to be configured during compilation (see this 10yr old bug report https://bugs.php.net/bug.php?id=33147)
But in python however, we don't have that problem. So instead of running the ssh command directly, run this python script:
import sys
import pty
args = " ".join(sys.argv[1:])
pty.spawn(['/usr/bin/ssh', args])
About pty.spawn from python docs:
Spawn a process, and connect its controlling terminal with the current
process’s standard io. This is often used to baffle programs which
insist on reading from the controlling terminal.
Have you tried the PHP SSH2 extension?
Have you tried 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->read('username#username:~$');
$ssh->write("ls -la\n");
echo $ssh->read('username#username:~$');
?>
I wrote a ssh client on php with ssh2 extension, you can take a look to the source code on the github page https://github.com/roke22/PHP-SSH2-Web-Client
Please send some feedback.

Categories