如何通过PHP执行SSH命令

74

我想通过PHP进行SSH连接。最好/最安全的方法是什么?我知道可以这样做:

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

还有更好的方法吗?那感觉太“淘气”了:)。


我可以知道你为什么认为这不是最安全的方式吗?我很好奇! - Meet Vora
6个回答

82

我会使用phpseclib,一个纯PHP的SSH实现。一个例子:

<?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');
?>

2
这些能异步执行吗?我需要同时与多个系统通信,我可以启动多个ssh会话,然后在所有会话上“选择”(或轮询)吗? - Mikhail T.
2
有点儿。实际上,你的问题最好作为一个新问题而不是回复,但无论如何。你可以为每个$ssh实例执行$ssh->setTimeout(...)并循环遍历它们。交互模式对你来说可能更好,因为读取和写入有点不连贯。同样,我猜你可以使用enablePTY()来使用$ssh->exec()$ssh->read()/$ssh->write() - neubert
我认为SFTP协议不支持readlink()。如果您知道任何支持readlink()的SFTP实现,我很想了解。 - neubert
1
@MikhailT:您可以设置AJAX调用同一PHP文件,以异步方式运行这些命令。 - Jacob Mulquin
1
@Alexander - 我当前在github上关注phpseclib,看起来最近进行了一次提交,添加了 readlink() 支持:https://github.com/phpseclib/phpseclib/pull/386 - neubert
将以下内容添加到第一行之前可以改进上面的示例: set_include_path(get_include_path() . PATH_SEPARATOR . '/path/to/your/phpseclib/'); - TheStoryCoder

25

你是否已安装SSH2扩展?

文档: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');

1
无法启动SSH2模块,错误日志中出现以下内容:PHP警告:PHP启动:ssh2:无法初始化模块 模块编译时使用的模块API = 20050922 PHP编译时使用的模块API = 20090626 这些选项需要匹配,在未知行上。 - Justin
操作系统和版本以及PHP版本是什么? - Chris Baker
2
请注意,为了使ssh2模块正常工作,您必须拥有OpenSSL和libssh2。如果您正在手动构建PHP,则必须确保您拥有两个所需模块的兼容版本。此错误表明版本不匹配。 - Chris Baker
ssh2_函数非常糟糕,我自己尝试使用了很多次,但它并不能正常工作,非常不稳定,在服务器上创建了许多ssh会话,真的,我不建议使用ssh2_函数。 - devasia2112

16

我在使用php中的ssh2时遇到了一些困难,主要原因是输出流有时候有效,有时候无效。我会在这里粘贴我的库,它对我非常有效。如果代码存在一些小的不一致性,那是因为我将其插入了一个框架中,但你应该可以轻松地移植它:

<?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 );
    }
}

1
谢谢!这个对我帮助很大。我重写了它,使用公钥/私钥身份验证,但是 sleep(1) 影响了我的生产力。如果我有时间想出解决方法,我会告诉你的。谢谢! - jbrahy
1
缺少logAction的定义? - php_nub_qq

10

//更新2018年,工作内容//

方法一:

下载 phpseclib v1 并使用以下代码:

<?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');
?>

或者方法2:

下载最新的phpseclib v2(需要首先执行composer install):

<?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');
?>

顺便提一下,如果你看到“连接超时”这个错误提示,那很可能是主机/防火墙(本地或远程)等问题造成的,而不是脚本的错误。


1
仍然适用于2021年,但您需要更新到v3。 include ('vendor/autoload.php'); $loader = new \Composer\Autoload\ClassLoader(); use phpseclib3\Net\SSH2; - tibelchior
我们需要在服务器上安装phpseclib吗? - Aref Solaimany

9

对于使用Symfony框架的用户来说,phpseclib也可以用来通过SSH连接。可以使用composer进行安装:

composer require phpseclib/phpseclib

接下来,只需按如下方式使用它:
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');

4
记录一下:这与Symfony无关。如果你有任何PHP项目,你可以添加/追加Composer依赖,并使用上述代码。 - Nick Andriopoulos
你可能需要使用 use phpseclib3\Net\SSH2; - ViliusL

4
使用ssh2函数。您通过exec()调用执行的任何操作都可以直接使用这些函数完成,这将节省大量连接和shell调用。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接