基於Websocket的簡易webshell實現
我們在很多場合都看到過基於瀏覽器的 shell,你可以在裡面輸入一些和你本機相同的命令,然後從遠端伺服器獲得對應的輸出。
本篇文章就是用來講解這個基於 web 的 shell 的實現方法的。我們之所以研究這個問題,另一方面也是因為 kubernetes 的 Dashboard 裡面也包含了這個功能。 在研究 kubernetes 的 Dashboard 的時候,我們會發現那個功能是基於 WebSocket 來實現的。所以本篇也就是講解基於 WebSocket 的 shell 實現方法。
思路
我們如果仔細地思考一下,其實這個 web shell 的主要功能就是將這個命令傳送到遠端伺服器,然後遠端伺服器執行這個命令,然後把結果返回給客戶端就可以了。所以在這個客戶端和伺服器的互動場景下,有很多的方案可以選擇,比如直接使用 HTTP 協議或者使用 TCP 協議,那麼為什麼 kubernetes 在實現的使用使用 web socket協議呢?
在進行一個技術方案的選型的時候,最重要的就是深入瞭解各個方案的利弊,以及它們最適用的場景。所以我們可以對比下基於 TCP,HTTP 和 WebSocket 三種協議實現這個 webshell 的優缺點。
協議 | 類別 | 特點 |
---|---|---|
WebSocket | 七層(應用層) | 相容HTTP的80埠和HTTPS的443埠,可以執行在HTTP或HTTPS協議上,全雙工協議,基於事件驅動的互動方式,客戶端不需要輪詢服務端的執行結果 |
HTTP(s) | 七層(應用層) | HTTP需要保持長連線來維持客戶端和伺服器之間的不斷的命令執行互動,否則頻繁的短連線效能損耗嚴重,另外客戶端需要主動輪訓服務端的執行結果 |
TCP | 四層(傳輸層) | 我就是個裸的傳輸層協議啦,HTTPS(s)和WebSocket都最終依賴我 |
從原理上講,大家最後都是需要依賴TCP協議來進行資料傳輸,所以如果堅持用TCP協議實現 webshell 當然是可以的,沒有任何問題。 但是協議的抽象目的就是簡化問題的解決方案以及解決舊有方案的缺點,HTTP的出現就是一種規範化的TCP協議應用,否則按照大家各自定義自己的資料格式的做法,這個網際網路還是不要搞了,沒法搞。你能腦補出每個公司每天都在互相接入對方的協議開發自己的應用麼?畫面太美,不敢想,不敢想。
那麼我們就看看 WebSokcet 的出現簡化和解決了 HTTP 協議的哪些問題就可以了。
對於互動式場景的應用,最重要的就是等待回覆的不確定性,比如你發個訊息給對方,對方什麼時候回覆是不確定的,你執行了一個遠端的命令,這個命令什麼時候執行完畢也是不確定的,在HTTP協議中,解決這種不確定性的方案是什麼呢?輪詢!你不是沒有辦法告訴我麼?我自己去問行不?可以,來問吧,週期性地詢問一下。
輪詢的做法有什麼問題呢?首先就是輪詢週期的設定,你怎麼設定這個時間呢?週期太短,白白浪費那麼多建立連線,斷開連線的動作,就像很久以前談戀愛的小夥子,每隔一分鐘去問一下傳達室的大爺,今天剛寄出的信有沒有回覆。大爺很快就口吐白沫了。但是要是每隔一天去問,看上去好像不錯,但是萬一那個信是上午到的,結果你下午才去拿,那白白痛苦等待了一個上午不是。所以這個方案不好,但是沒辦法。
剛剛說了輪詢的第一個缺點,第二個就是你總是輪詢,整個路上全是你來來往往的身影,佔用頻寬不是,浪費連線不是。
那麼 WebSocket 的出現,就可以解決這個問題了,大爺說,小夥子信發出去了不著急,等信到了大爺通知你,甚至主動把信送給你,你看好不好。
這就完美解決問題了嘛。
程式碼
BB那麼久,好歹說清楚 WebSocket 哪裡好了,簡單貼個能跑的程式碼吧。能跑就是說能展示原理,但是別直接拷貝就上線了,不好。
先上客戶端,就是模擬每次發一個命令過去,然後命令後面接上換行符,算是簡單的協議格式。
remoteshell-client.go
package main
import (
"flag"
"golang.org/x/net/websocket"
"bufio"
"fmt"
"os"
)
func main() {
var origin string
var url string
flag.StringVar(&origin, "origin", "", "websocket origin")
flag.StringVar(&url, "url", "", "websocket remote url")
flag.Parse()
ws, err := websocket.Dial(url, "", origin)
if err != nil {
panic(err)
return
}
buffer := make([]byte, 40960)
bScanner := bufio.NewScanner(os.Stdin)
fmt.Print("> ")
for bScanner.Scan() {
line := bScanner.Text()
ws.Write([]byte(line + "\r\n"))
num, err := ws.Read(buffer)
if err != nil {
ws.Close()
return
}
fmt.Println(string(buffer[:num]))
fmt.Print("> ")
}
}
執行方法:
$ ./remoteshell-client -url 'ws://localhost:9001/remote/shell' -origin 'http://localhost:9001'
服務端就是處理這個請求並給個回覆了,因為這個連線是一直都在的,所以讀資料就是直接for迴圈去讀。我們在客戶端傳送資料的時候,給每條資料加了一個換行符,所以服務端就可以按行來讀了。
package main
import (
"flag"
"fmt"
"net/http"
"os/exec"
"bytes"
"strings"
"golang.org/x/net/websocket"
"bufio"
"os"
)
func RemoteShell(ws *websocket.Conn) {
bScanner := bufio.NewScanner(ws)
currentWorkingDir, _ := os.Getwd()
fmt.Println("current working dir", currentWorkingDir)
for bScanner.Scan() {
// parse command
cmd := bScanner.Text()
fmt.Println(cmd)
cmdItems := strings.Split(cmd, " ")
cmdName := cmdItems[0]
var cmdArgs []string
if len(cmdItems) >= 2 {
cmdArgs = cmdItems[1:]
}
// execute command
cmdOutput := bytes.NewBuffer(nil)
cmdExec := exec.Command(cmdName, cmdArgs...)
cmdExec.Dir = currentWorkingDir
cmdExec.Stdout = cmdOutput
cmdExec.Stderr = cmdOutput
err := cmdExec.Run()
if err != nil {
fmt.Println(err)
ws.Write([]byte(err.Error()))
} else {
ws.Write(cmdOutput.Bytes())
}
}
}
func main() {
var host string
var port int
flag.StringVar(&host, "host", "0.0.0.0", "host to listen")
flag.IntVar(&port, "port", 9001, "port to listen")
flag.Parse()
//handler
http.Handle("/remote/shell", websocket.Handler(RemoteShell))
//listen
endPoint := fmt.Sprintf("%s:%d", host, port)
err := http.ListenAndServe(endPoint, nil)
if err != nil {
fmt.Println(err)
return
}
}
執行方法:
$ ./remoteshell-server
原文連結:https://blog.duokexuetang.com/post/go-practice-webshell-implementation-on-websocket.html
相關文章
- 基於Vue的簡易MVVM實現VueMVVM
- Django3使用WebSocket實現WebShellDjangoWebshell
- 基於react的hash路由簡易實現React路由
- 基於 WebSocket 的 PPT 遠端控制器簡單實現Web
- 基於 Twirp RPC 的簡易 JSON Api Gateway 實現RPCJSONAPIGateway
- STOMP協議——基於Websocket實現協議Web
- Python基於Socket實現簡易多人聊天室Python
- 基於AOP和Redis實現的簡易版分散式鎖Redis分散式
- VNPY 一種基於統計的交易策略簡易實現
- Go實現基於WebSocket的彈幕服務GoWeb
- 基於FFmpeg和Qt實現簡易影片播放器QT播放器
- 基於 Mysql 實現一個簡易版搜尋引擎MySql
- 基於long pull實現簡易的訊息系統參考
- yii2-websocket | 基於 yii2 實現的 WebSocket 擴充套件Web套件
- 基於websocket與nodejs-websocket的簡單聊天室WebNodeJS
- 基於 Hyperf 實現 RabbitMQ + WebSocket 訊息推送MQWeb
- 基於 swoole 的 websocket 服務實現狀態同步Web
- 基於websocket的簡單廣播系統Web
- gorilla websocket簡易介紹GoWeb
- uniapp專案實踐總結(十五)使用websocket實現簡易聊天室APPWeb
- 基於“結構體”實現簡易版學生管理系統(Golang)結構體Golang
- namedtuple簡易實現
- gin websocket 簡單分散式實現Web分散式
- 手寫實現java棧結構,並實現簡易的計算器(基於字尾演算法)Java演算法
- 如何使用irealtime.js實現一個基於websocket的同步畫板JSWeb
- 基於Redis的簡易延時佇列Redis佇列
- C# WebSocket的簡單使用【使用Fleck實現】C#Web
- java 從零開始手寫 RPC (01) 基於 websocket 實現JavaRPCWeb
- 實現一個簡易的vueVue
- 微信雲託管 WebSocket 實戰:基於模版實現訊息推送Web
- 基於 Session 實現簡訊登入Session
- 簡易版 vue實現Vue
- 基於PHP的Webshell自動檢測芻議PHPWebshell
- 【c#】分享一個簡易的基於時間輪排程的延遲任務實現C#
- 基於 git 打造簡易的 npm 私有倉庫GitNPM
- SpringBoot 實戰 (十六) | 整合 WebSocket 基於 STOMP 協議實現廣播訊息Spring BootWeb協議
- 基於WebSocket的實時訊息傳遞設計Web
- 基於Promise實現對Ajax的簡單封裝Promise封裝