目录
本来是想通过PHP的proc_open和进程进行交互,可是中间的坑太多了,不得不转换一下思路,然后想起来宝塔有网页版shell客户端,然后研究了一下,嘿嘿,发现能成 。
一、前期准备
PHP连接ssh是基于第三方拓展库,PECL/ssh2( libssh2的php扩展,允许php程序调用libssh2中的函数)
然后有一个现成的、封装好大部分常用操作的库phpseclib
通过swoole的协程实现SSH的读和写并发进行以及websocket和浏览器进行通信。
1、安装ssh2拓展库
1.1、Linux安装
首先要安装libssh2(libssh2是一个C 函数库,用来实现SSH2协议。)
yum install libssh2 libssh2-devel
然后通过pcel安装ssh2拓展 ,找准版本
pecl install ssh2-1.1.2
当然也可以通过phpize进行手动安装。
1.2、window安装
libssh2好像一般都有,没有就下载丢到系统里,主要是安装ssh2。根据自己PHP的版本去下载,可以看下自己的php版本,以及是32位的还是64位的,32位的下载x86, 64位的下载x64
php.ini中加入 extension=php_ssh2.dll ,完事。
2、swoole安装
参考官网:https://wiki.swoole.com/#/environment
3、phpseclib
官网:https://phpseclib.com,composer安装即可:
composer require phpseclib/phpseclib:~3.0
二、编写代码
测试Demo:http://cname.teiao.com:5707/
通过swoole创建一个websocket,连接成功时创建一个协程专门读取ssh返回的内容发送到websocket,客户端发送消息时转发给shell。
以下是简单的功能实现,不可应用于生产,经测试,实际使用过程中某些命令的输出需要进行特殊处理。
1、swoole.php
<?php include_once \'include/functions.php\'; include_once \'vendor/autoload.php\'; use Swoole\\Http\\Request; use Swoole\\Http\\Response; use Swoole\\WebSocket\\CloseFrame; use Swoole\\Coroutine\\Http\\Server; use Swoole\\Coroutine; use function Swoole\\Coroutine\\go; use function Swoole\\Coroutine\\run; use function Swoole\\Coroutine\\defer; use phpseclib3\\Net\\SSH2; /* * 设置协程运行相关的参数 * */ Co::set([ \'socket_timeout\'=>-1, //tcp超时 \'hook_flags\' => SWOOLE_HOOK_ALL //HOOK函数范围 ]); /* * 创建协程容器 * */ run(function () { /* * 第三个参数 代表是否开启ssl * */ $server = new Server(\'0.0.0.0\', 5707, false); $server->handle(\'/ws\', function (Request $request, Response $ws) { /*websocket协议*/ $ws->upgrade(); /*连接ssh*/ $ssh = new SSH2(\'localhost\',22); /*如果登录失败*/ if (!$ssh->login(\'root\', \'Qq461625091@\')) { $ws->close(); return; } /*命令输出内容的读取时间*/ $ssh->setTimeout(0.1); /* * 创建协程,专门输出命令行内容 * */ $subscribe=function () use($ws,$ssh){ /* * 保存id,用于取消协程 * */ $ws->Gid = go(function () use ($ws,$ssh){ /* * 协程退出时清理 * */ defer(function () use ($ssh,$ws) { /* * 退出 * */ logs($ws->qq.\',已断开链接!\'); $ssh->disconnect(); }); try { while (true){ $msg=$ssh->read(\'username@username:~$\'); if(!empty($msg)){ $ws->push($msg); } } } catch (\\Throwable $e) { logs(\'读取异常\'); } }); }; /* * 清理 * */ $quit=function ($log) use ($ws){ logs($log);//记录退出原因 /* * 如果协程已经运行 * */ if(isset($ws->Gid)){ Coroutine::cancel($ws->Gid); //关闭协程 } $ws->close(); //断开ws }; /* * 正常处理逻辑 * */ $subscribe(); //开始订阅 $cmd=[ \'ps -ef\', \'ping 127.0.0.1\', \'ifconfig\', \"\\x03\" ]; while (true) { $frame = $ws->recv(); //阻塞接收消息 if ($frame === \'\') { $quit(\"断开连接,收到空数据!\"); break; } else if ($frame === false) { $quit(swoole_last_error()); break; } else { if ($frame->data == \'close\' || get_class($frame) === CloseFrame::class) { $quit(\"用户主动关闭\\n\"); break; } /* * 如果不在测试命令,则终止 * */ if(!in_array($frame->data,$cmd)){ continue; } $ssh->write($frame->data.\"\\n\"); // note the \"\\n\" } } }); /* * 输出默认测试模板 * */ $server->handle(\'/\', function (Request $request, Response $response) { $response->end(getTest()); }); $server->start(); });
2、function.php
<?php /* * 打印测试的html模板 * */ function getTest(): string { $test = <<<HTML <!DOCTYPE html> <html lang=\"zh-cn\" xmlns=\"http://www.w3.org/1999/html\"> <head> <meta charset=\"UTF-8\"/> <meta charset=\"UTF-8\"/> <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no\"> <title>Web SSH客户端</title> <link href=\"https://nicen.cn/wp-content/themes/document/favicon.ico\" rel=\"external nofollow\" rel=\"shortcut icon\" type=\"image/x-icon\"/> <script src=\"https://lf3-cdn-tos.bytecdntp.com/cdn/expire-1-M/jquery/3.6.0/jquery.min.js\" type=\"application/javascript\"></script> <script src=\"https://lf3-cdn-tos.bytecdntp.com/cdn/expire-1-M/keyboardjs/2.6.2/keyboard.min.js\" type=\"application/javascript\"></script> <style> body{ background-color: #000000; color: #e2e2e2; padding: 15px; } input{ background-color: black; border: none; color: white; outline: none; font-size: 17px; } </style> </head> <body> <h1>Web SSH测试</h1> <div>须知:测试环境只支持:ps -ef、ping 127.0.0.1、ifconfig,三个命令。</div> <div>提示:回车提交、ctrl+c中断(终端现在连接的是网站的主机)</div> <br /> <main> <span id=\"content\"></span> <input type=\"text\"> </main> </body> <script> window.onload=function (){ let content=$(\"#content\"); let input= $(\'input\'); let wsServer = \'ws://cname.teiao.com:5707/ws\'; let websocket = new WebSocket(wsServer); websocket.onopen = function (evt) { content.append(\"Connected to WebSocket server.<br />\"); }; websocket.onclose = function (evt) { content.append(\"Disconnected.<br />\"); }; websocket.onmessage = function (evt) { content.append(evt.data.replaceAll(\"\\\\n\",\'<br />\')); input.val(\"\"); $(window).scrollTop(document.documentElement.scrollHeight) }; websocket.onerror = function (evt, e) { content.append(\"Error occured: \" + evt.data+\"<br />\"); }; input.focus(); /* * 自动聚焦 * */ $(window).on(\"click\",function (){ input.focus(); }) /* * 回车提交 * */ keyboardJS.bind(\'enter\', (e) => { websocket.send(input.val()); }); /* * ctrl+c * */ keyboardJS.bind(\'ctrl > c\', (e) => { websocket.send(\"\\x03\"); }); } </script> HTML; return $test; } /* * 记录日志 * */ function logs(string $log, bool $flag = true): void { $time = date(\"Y-m-d H:i:s\", time()); if ($flag) { echo $time . \',\' . $log . \"\\n\"; } else { file_put_contents(\'log.txt\', $time . \',\' . $log . \"\\n\", FILE_APPEND); } }
3、运行
php swoole.php
以上就是PHP+Swoole实现web版的shell客户端详解的详细内容,更多关于PHP Swoole shell客户端的资料请关注其它相关文章!
暂无评论内容