PHP+Swoole实现web版的shell客户端

2022年7月21日 300点热度 0人点赞 0条评论

点击上方“PHP大神”,选择置顶/星标公众号

重磅干货,第一时间送达

图片

来源:http://suo.nz/1htP5E






本来是想通过PHP的proc_open和进程进行交互,可是中间的坑太多了,不得不转换一下思路,然后想起来宝塔有网页版shell客户端,然后研究了一下,嘿嘿,发现能成 。


一、前期准备


PHP连接ssh是基于第三方拓展库,PECL/ssh2( libssh2的php扩展,允许php程序调用libssh2中的函数)


然后有一个现成的、封装好大部分常用操作的库phpseclib:https://phpseclib.com


通过swoole的协程实现SSH的读和写并发进行以及websocket和浏览器进行通信。


1、安装ssh2拓展库


1.1、Linux安装

首先要安装libssh2(libssh2是一个C 函数库,用来实现SSH2协议。)https://www.libssh2.org

yum install libssh2 libssh2-devel 

然后通过pcel安装ssh2拓展 ,找准版本(https://pecl.php.net/package/ssh2)

pecl install ssh2-1.1.2

当然也可以通过phpize进行手动安装。

1.2、window安装

libssh2好像一般都有,没有就下载丢到系统里,主要是安装ssh2。根据自己PHP的版本去下载,可以看下自己的php版本,以及是32位的还是64位的,32位的下载x86, 64位的下载x64

下载地址:https://windows.php.net/downloads/pecl/releases/

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="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

如有错误请多包涵!右下角点赞给加个鸡腿可好?


51020PHP+Swoole实现web版的shell客户端

这个人很懒,什么都没留下

文章评论