【问题标题】:How To Execute SSH Commands Via PHP如何通过 PHP 执行 SSH 命令
【发布时间】:2011-09-10 08:32:25
【问题描述】:

我希望通过 PHP 进行 SSH 连接。最好/最安全的方法是什么?我知道我能做到:

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

还有更好的吗?感觉很“淘气”:)。

【问题讨论】:

  • 我能知道您为什么认为这不是最好的安全方式吗?我很好奇!

标签: php ssh shell-exec


【解决方案1】:

你有可用的 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');

【讨论】:

  • 无法启动该死的 SSH2 模块,在错误日志中显示以下内容: PHP 警告:PHP 启动:ssh2:无法初始化模块 使用模块 API 编译的模块 = 20050922 使用模块 API 编译的 PHP =20090626 这些选项需要在第0行的Unknown中匹配
  • 什么操作系统和版本,什么 PHP 版本?
  • 另外请注意,您必须拥有 OpenSSL 和 libssh2 才能使 ssh2 模块正常运行。如果您手动构建 PHP,则必须确保您拥有两个必需模块的兼容版本。此错误表示版本不匹配。
  • ssh2_ 功能很差,我自己用了很多次,就是不能正常工作很不稳定,在服务器上创建了很多ssh会话,真的,我不推荐使用 ssh2_ 函数。
【解决方案2】:

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

【讨论】:

    【解决方案3】:

    我在 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 );
        }
    }
    

    【讨论】:

    • 谢谢!这个帮助很大。我重写了它以使用公钥/私钥身份验证,但是 sleep(1) 扼杀了我的工作效率。如果我有时间想出一个解决办法,我会告诉你的。谢谢!
    • 缺少logAction的定义?
    【解决方案4】:

    我会使用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');
    ?>
    

    【讨论】:

    • 这些可以是异步的吗?我需要一次与多个系统对话——我可以启动多个 ssh 会话,然后在所有系统上“选择”(或轮询)吗?..
    • 有点。真的,您的问题将更适合作为新问题而不是答复,但无论如何。您可以为每个 $ssh 实例执行 $ssh-&gt;setTimeout(...) 并循环浏览它们。交互模式也可能对你更好,因为阅读和写作有点脱节。同样,您可以执行 enablePTY() 我猜想将 $ssh-&gt;exec()$ssh-&gt;read() / $ssh-&gt;write() 一起使用。
    • 我不认为 SFTP 作为一种协议支持 readlink()。如果您知道任何支持 readlink() 的 SFTP 实现,我很想知道它们。
    • @MikhailT:您可以设置对同一个 PHP 文件的 AJAX 调用,以异步方式运行这些命令。
    • @Alexander - 我在 github 上订阅了 phpseclib,看起来刚刚提交了一个添加 readlink() 支持:github.com/phpseclib/phpseclib/pull/386
    【解决方案5】:

    对于那些使用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');
    

    【讨论】:

    • 声明:这与 Symfony 无关。如果您有任何 PHP 项目,您可以添加/附加 composer 依赖项并使用上面的代码。
    • 您可能需要使用use phpseclib3\Net\SSH2;
    【解决方案6】:

    //更新2018,作品//

    方法1:

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

    附言如果您收到“连接超时”,则可能是 HOST/FIREWALL(本地或远程)或类似问题,而不是脚本错误。

    【讨论】:

    • 2021 年仍可使用,但您需要更新到 v3。 include ('vendor/autoload.php'); $loader = new \Composer\Autoload\ClassLoader(); use phpseclib3\Net\SSH2;
    猜你喜欢
    • 1970-01-01
    • 2019-12-06
    • 1970-01-01
    • 2013-08-24
    • 1970-01-01
    • 1970-01-01
    • 2011-09-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多