74

I am looking to SSH out via PHP. What is the best/most secure way to go about this? I know I can do:

shell_exec("SSH user@host.com mkdir /testing");

Anything better? That feels so 'naughty' :).

Justin
  • 42,716
  • 77
  • 201
  • 296

6 Answers6

82

I would use phpseclib, a pure PHP SSH implementation. An example:

<?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');
?>
  • 1
    Can these be asynchronous? I need to talk to multiple systems at once -- can I initiate multiple ssh-sessions and then "select" (or poll) on all of them?.. – Mikhail T. Nov 01 '13 at 04:25
  • 2
    Kinda. Really, you're question would be better suited as a new one instead of a reply but whatever. You could do `$ssh->setTimeout(...)` for each $ssh instance and cycle through them. Interactive mode might be better for you too because there reading and writing are kinda disjoint. Similarly, you could do enablePTY() I guess to use `$ssh->exec()` with `$ssh->read()` / `$ssh->write()`. – neubert Jan 02 '14 at 21:57
  • I don't think SFTP as a protocol supports readlink(). If you know of any SFTP implementations that support readlink() I'd be curious to hear of them. – neubert Apr 02 '14 at 20:16
  • @MikhailT: You can set up AJAX calls to the same PHP file to run these commands in an asynchronous manner. – Jacob Mulquin May 19 '14 at 03:36
  • 1
    @Alexander - I'm subscribed to phpseclib on github and it looks like a commit was just made adding `readlink()` support: https://github.com/phpseclib/phpseclib/pull/386 – neubert Jun 18 '14 at 16:43
  • Example above should be improved by adding this before the first line: `set_include_path(get_include_path() . PATH_SEPARATOR . '/path/to/your/phpseclib/');` – TheStoryCoder Jun 15 '15 at 07:20
25

Do you have the SSH2 extension available?

Docs: http://www.php.net/manual/en/function.ssh2-exec.php

$connection = ssh2_connect('shell.example.com', 22);
ssh2_auth_password($connection, 'username', 'password');

$stream = ssh2_exec($connection, '/usr/local/bin/php -i');
Chris Baker
  • 49,926
  • 12
  • 96
  • 115
  • 1
    Can't get the bloody SSH2 module going, getting the following in the error log: PHP Warning:PHP Startup: ssh2: Unable to initialize module Module compiled with module API=20050922 PHP compiled with module API=20090626 These options need to match in Unknown on line 0 – Justin Jun 07 '11 at 19:34
  • What OS and version, and what PHP version? – Chris Baker Jun 07 '11 at 19:39
  • 2
    Also note, you have to have OpenSSL and libssh2 for the ssh2 module to function correctly. If you're manually building PHP, you have to make sure you have compatible versions of the two required modules. This error is indicative of a mismatch in versions. – Chris Baker Jun 07 '11 at 19:41
  • ssh2_ functions are pretty bad, I myself use try to use it so many times, it just not work properly is very unstable, create a lot of ssh session in the server, really, I do not recommend to use ssh2_ functions. – devasia2112 Feb 05 '14 at 19:50
16

I've had a hard time with ssh2 in php mostly because the output stream sometimes works and sometimes it doesn't. I'm just gonna paste my lib here which works for me very well. If there are small inconsistencies in code it's because I have it plugged in a framework but you should be fine porting it:

<?php

class Components_Ssh {

    private $host;

    private $user;

    private $pass;

    private $port;

    private $conn = false;

    private $error;

    private $stream;

    private $stream_timeout = 100;

    private $log;

    private $lastLog;

    public function __construct ( $host, $user, $pass, $port, $serverLog ) {
        $this->host = $host;
        $this->user = $user;
        $this->pass = $pass;
        $this->port = $port;
        $this->sLog = $serverLog;

        if ( $this->connect ()->authenticate () ) {
            return true;
        }
    }

    public function isConnected () {
        return ( boolean ) $this->conn;
    }

    public function __get ( $name ) {
        return $this->$name;
    }

    public function connect () {
        $this->logAction ( "Connecting to {$this->host}" );
        if ( $this->conn = ssh2_connect ( $this->host, $this->port ) ) {
            return $this;
        }
        $this->logAction ( "Connection to {$this->host} failed" );
        throw new Exception ( "Unable to connect to {$this->host}" );
    }

    public function authenticate () {
        $this->logAction ( "Authenticating to {$this->host}" );
        if ( ssh2_auth_password ( $this->conn, $this->user, $this->pass ) ) {
            return $this;
        }
        $this->logAction ( "Authentication to {$this->host} failed" );
        throw new Exception ( "Unable to authenticate to {$this->host}" );
    }

    public function sendFile ( $localFile, $remoteFile, $permision = 0644 ) {
        if ( ! is_file ( $localFile ) ) throw new Exception ( "Local file {$localFile} does not exist" );
        $this->logAction ( "Sending file $localFile as $remoteFile" );

        $sftp = ssh2_sftp ( $this->conn );
        $sftpStream = @fopen ( 'ssh2.sftp://' . $sftp . $remoteFile, 'w' );
        if ( ! $sftpStream ) {
            //  if 1 method failes try the other one
            if ( ! @ssh2_scp_send ( $this->conn, $localFile, $remoteFile, $permision ) ) {
                throw new Exception ( "Could not open remote file: $remoteFile" );
            }
            else {
                return true;
            }
        }

        $data_to_send = @file_get_contents ( $localFile );

        if ( @fwrite ( $sftpStream, $data_to_send ) === false ) {
            throw new Exception ( "Could not send data from file: $localFile." );
        }

        fclose ( $sftpStream );

        $this->logAction ( "Sending file $localFile as $remoteFile succeeded" );
        return true;
    }

    public function getFile ( $remoteFile, $localFile ) {
        $this->logAction ( "Receiving file $remoteFile as $localFile" );
        if ( ssh2_scp_recv ( $this->conn, $remoteFile, $localFile ) ) {
            return true;
        }
        $this->logAction ( "Receiving file $remoteFile as $localFile failed" );
        throw new Exception ( "Unable to get file to {$remoteFile}" );
    }

    public function cmd ( $cmd, $returnOutput = false ) {
        $this->logAction ( "Executing command $cmd" );
        $this->stream = ssh2_exec ( $this->conn, $cmd );

        if ( FALSE === $this->stream ) {
            $this->logAction ( "Unable to execute command $cmd" );
            throw new Exception ( "Unable to execute command '$cmd'" );
        }
        $this->logAction ( "$cmd was executed" );

        stream_set_blocking ( $this->stream, true );
        stream_set_timeout ( $this->stream, $this->stream_timeout );
        $this->lastLog = stream_get_contents ( $this->stream );

        $this->logAction ( "$cmd output: {$this->lastLog}" );
        fclose ( $this->stream );
        $this->log .= $this->lastLog . "\n";
        return ( $returnOutput ) ? $this->lastLog : $this;
    }

    public function shellCmd ( $cmds = array () ) {
        $this->logAction ( "Openning ssh2 shell" );
        $this->shellStream = ssh2_shell ( $this->conn );

        sleep ( 1 );
        $out = '';
        while ( $line = fgets ( $this->shellStream ) ) {
            $out .= $line;
        }

        $this->logAction ( "ssh2 shell output: $out" );

        foreach ( $cmds as $cmd ) {
            $out = '';
            $this->logAction ( "Writing ssh2 shell command: $cmd" );
            fwrite ( $this->shellStream, "$cmd" . PHP_EOL );
            sleep ( 1 );
            while ( $line = fgets ( $this->shellStream ) ) {
                $out .= $line;
                sleep ( 1 );
            }
            $this->logAction ( "ssh2 shell command $cmd output: $out" );
        }

        $this->logAction ( "Closing shell stream" );
        fclose ( $this->shellStream );
    }

    public function getLastOutput () {
        return $this->lastLog;
    }

    public function getOutput () {
        return $this->log;
    }

    public function disconnect () {
        $this->logAction ( "Disconnecting from {$this->host}" );
        // if disconnect function is available call it..
        if ( function_exists ( 'ssh2_disconnect' ) ) {
            ssh2_disconnect ( $this->conn );
        }
        else { // if no disconnect func is available, close conn, unset var
            @fclose ( $this->conn );
            $this->conn = false;
        }
        // return null always
        return NULL;
    }

    public function fileExists ( $path ) {
        $output = $this->cmd ( "[ -f $path ] && echo 1 || echo 0", true );
        return ( bool ) trim ( $output );
    }
}
Romeo M.
  • 3,218
  • 8
  • 35
  • 40
  • 1
    Thanks! this one helped quite a bit. I rewrote it to use public / private key authentication but the sleep(1) kills my productivity. If I have time to come up with a work around I'll let you know. thanks! – jbrahy Oct 13 '14 at 22:01
  • 1
    it's missing `logAction`'s definition? – php_nub_qq Aug 17 '18 at 06:08
10

//Update 2018, works//

Method1:

Download phpseclib v1 and use this code:

<?php
set_include_path(__DIR__ . '/phpseclib1.0.11');
include("Net/SSH2.php");

$key ="MyPassword";
  /* ### if using PrivateKey ### 
  include("Crypt/RSA.php");
  $key = new Crypt_RSA();
  $key->loadKey(file_get_contents('private-key.ppk'));
  */

$ssh = new Net_SSH2('www.example.com', 22);   // Domain or IP
if (!$ssh->login('your_username', $key))  exit('Login Failed');

echo $ssh->exec('pwd');
?>

or Method2:

Download newest phpseclib v2 (requires composer install at first):

<?php

set_include_path($path=__DIR__ . '/phpseclib-master/phpseclib');
include ($path.'/../vendor/autoload.php');

$loader = new \Composer\Autoload\ClassLoader();

use phpseclib\Net\SSH2;

$key ="MyPassword";
  /* ### if using PrivateKey ### 
  use phpseclib\Crypt\RSA;
  $key = new RSA();
  $key->load(file_get_contents('private-key.ppk'));
  */

$ssh = new SSH2('www.example.com', 22);   // Domain or IP
if (!$ssh->login('your_username', $key))   exit('Login Failed'); 

echo $ssh->exec('pwd');
?>

p.s. if you get "Connection timed out" then it's probably the issue of HOST/FIREWALL (local or remote) or like that, not a fault of script.

T.Todua
  • 53,146
  • 19
  • 236
  • 237
9

For those using the Symfony framework, the phpseclib can also be used to connect via SSH. It can be installed using composer:

composer require phpseclib/phpseclib

Next, simply use it as follows:

use phpseclib\Net\SSH2;

// Within a controller for example:
$ssh = new SSH2('hostname or ip');
if (!$ssh->login('username', 'password')) {
    // Login failed, do something
}

$return_value = $ssh->exec('command');
ViliusL
  • 4,589
  • 26
  • 28
Ziad Akiki
  • 2,601
  • 2
  • 26
  • 41
4

Use the ssh2 functions. Anything you'd do via an exec() call can be done directly using these functions, saving you a lot of connections and shell invocations.

Marc B
  • 356,200
  • 43
  • 426
  • 500