golang通过websocket实现ssh、telnet、正向shell(bind shell)终端交互

1. gin框架注册路由

func init() {
	routerCheckRole = append(routerCheckRole, registerTgWsRouter)
}

// 需认证的路由代码
func registerTgWsRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) {
	api := apis.TgWs{}
	r := v1.Group("")
	{
		// 协议、IP、端口
		r.GET("/tgws/:proto/:ipaddr/:port", api.TgWsWeb)
	}
}

2. 逻辑实现

package apis

import (
	"fmt"
	"io"
	"net"
	"net/http"
	"os"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/go-admin-team/go-admin-core/sdk/api"
	"github.com/gorilla/websocket"
	"github.com/ziutek/telnet"
	"golang.org/x/crypto/ssh"
	"golang.org/x/crypto/ssh/terminal"
)

type TgWs struct {
	api.Api
}

var upGrader = websocket.Upgrader{
	ReadBufferSize:  1024,
	WriteBufferSize: 1024 * 1024 * 10,
	CheckOrigin: func(r *http.Request) bool {
		return true
	},
}

type ShellInfoStruct struct {
	Proto  string `json:"proto"`
	IpAddr string `json:"ipaddr"`
	Port   string `json:"port"`
}

type wsWrapper struct {
	*websocket.Conn
}

func (e TgWs) TgWsWeb(c *gin.Context) {
	// 初始化返回信息
	err := e.MakeContext(c).Errors
	if err != nil {
		e.Logger.Error(err)
		e.Error(500, err, fmt.Sprintf(" %s ", err.Error()))
		return
	}

    // 升级为websocket
	wsConn, err := upGrader.Upgrade(c.Writer, c.Request, nil)
	if err != nil {
		e.Logger.Error(err)
		e.Error(500, err, "websocket client connect error")
		return
	}
	defer wsConn.Close()

	proto := c.Param("proto")
	ipaddr := c.Param("ipaddr")
	port := c.Param("port")
	shellinfo := ShellInfoStruct{
		Proto:  proto,
		IpAddr: ipaddr,
		Port:   port,
	}
	quitChan := make(chan bool, 1)
	go websocketHandle(wsConn, shellinfo, quitChan)
	<-quitChan
}

func websocketHandle(con *websocket.Conn, shellinfo ShellInfoStruct, exitCh chan bool) {
	defer setQuit(exitCh)
	rw := io.ReadWriter(&wsWrapper{con})
	webprintln := func(data string) {
		rw.Write([]byte(data + "\r\n"))
	}
	con.SetCloseHandler(func(code int, text string) error {
		con.Close()
		return nil
	})
	switch shellinfo.Proto {
	case "ssh":
		sshHandle(rw, shellinfo.IpAddr, shellinfo.Port, "XXX", "XXX", webprintln)
	case "telnet":
		telnetHandle(rw, shellinfo.IpAddr, shellinfo.Port, webprintln)
	case "bind_shell":
		bindShellHandler(rw, shellinfo.IpAddr, shellinfo.Port, webprintln)
	default:
		webprintln("Not Support Protocol '" + shellinfo.Proto + "'")
	}
	return
}

func (wsw *wsWrapper) Write(p []byte) (n int, err error) {
	writer, err := wsw.Conn.NextWriter(websocket.TextMessage)
	if err != nil {
		return 0, err
	}
	defer writer.Close()
	return writer.Write(p)
}

func (wsw *wsWrapper) Read(p []byte) (n int, err error) {
	for {
		msgType, reader, err := wsw.Conn.NextReader()
		if err != nil {
			return 0, err
		}
		if msgType != websocket.TextMessage {
			continue
		}
		return reader.Read(p)
	}
}

// SSH连接
func sshHandle(rw io.ReadWriter, ip, port, user, passwd string, errhandle func(string)) {
	sshConfig := &ssh.ClientConfig{
		User:    user,
		Auth:    []ssh.AuthMethod{ssh.Password(passwd)},
		Timeout: 6 * time.Second,
	}
	sshConfig.HostKeyCallback = ssh.InsecureIgnoreHostKey()
	if port == "" {
		ip = ip + ":22"
	} else {
		ip = ip + ":" + port
	}
	client, err := ssh.Dial("tcp", ip, sshConfig)
	if err != nil {
		errhandle(err.Error())
		return
	}
	defer client.Close()
	session, err := client.NewSession()
	if err != nil {
		errhandle(err.Error())
		return
	}
	defer session.Close()
	fd := int(os.Stdin.Fd())
	session.Stdout = rw
	session.Stderr = rw
	session.Stdin = rw
	modes := ssh.TerminalModes{
		ssh.ECHO:          1,
		ssh.TTY_OP_ISPEED: 14400,
		ssh.TTY_OP_OSPEED: 14400,
	}
	termWidth, termHeight, err := terminal.GetSize(fd)
	err = session.RequestPty("xterm", termHeight, termWidth, modes)
	if err != nil {
		errhandle(err.Error())
	}
	err = session.Shell()
	if err != nil {
		errhandle(err.Error())
	}
	err = session.Wait()
	if err != nil {
		errhandle(err.Error())
	}
	return
}

// telnet连接
func telnetHandle(rw io.ReadWriter, ip, port string, errhandle func(string)) {
	if port == "" {
		ip = ip + ":23"
	} else {
		ip = ip + ":" + port
	}
	con, err := telnet.Dial("tcp", ip)
	if err != nil {
		errhandle(err.Error())
		return
	}
	defer con.Close()
	buf := make([]byte, 16*1024)

    // 从远端读取返回结果并回显页面
	go func() {
		for {
			n, err := con.Read(buf)
			if err != nil {
				errhandle(err.Error())
				break
			}
			_, err = rw.Write(buf[:n])
			if err != nil {
				errhandle(err.Error())
				break
			}
		}
	}()

	for {
        // 从页面读取命令
		n, err := rw.Read(buf)
		if err != nil {
			errhandle(err.Error())
			break
		}
		if buf[0] == 13 { // 处理换行
			data := []byte{telnet.CR, telnet.LF}
			_, err = con.Write(data)
		} else {
			_, err = con.Write(buf[:n])
		}
		if err != nil {
			errhandle(err.Error())
			break
		}
	}
	return
}

// 正向shell
func bindShellHandler(rw io.ReadWriter, ip, port string, errhandle func(string)) {
	server := ip + ":" + port
	//获取命令行参数 socket地址
	addr, err := net.ResolveTCPAddr("tcp4", server)
	if err != nil {
		errhandle(err.Error())
		return
	}
	//建立tcp连接
	con, err := net.DialTCP("tcp4", nil, addr)
	if err != nil {
		errhandle(err.Error())
		return
	}
	rw.Write([]byte("reverse shell connected " + "\r\n"))
	defer con.Close()
	buf := make([]byte, 16*1024)

	go func() {
		for {
			n, err := con.Read(buf)
			if err != nil {
				errhandle(err.Error())
				break
			}
			_, err = rw.Write(buf[:n])
			if err != nil {
				errhandle(err.Error())
				break
			}
		}
	}()

	for {
		n, err := rw.Read(buf)
		if err != nil {
			errhandle(err.Error())
			break
		}
		_, err = rw.Write(buf[:n])
		if err != nil {
			errhandle(err.Error())
			break
		}

		if buf[0] == 13 {
			data := []byte{telnet.LF}
			_, err = con.Write(data)
			rw.Write([]byte("\r\n"))
		} else {
			_, err = con.Write(buf[:n])
		}
		if err != nil {
			errhandle(err.Error())
			break
		}
	}
	return
}

func setQuit(ch chan bool) {
	ch <- true
}

3. 测试方法

  1. ssh连接

    开启虚拟机,url种协议为ssh,传入IP和端口,程序中写入用户名密码

  2. telnet连接

    开启虚拟机,url种协议为telnet,传入IP和端口

  3. bind shell连接

    开启虚拟机,url种协议为bind_shell,传入IP和端口

    # 虚拟机安装nc服务
    yum install -y nc
    
    # 开启nc,监听端口为1234
    nc -lvn 1234 -e /bin/bash
    

你可能感兴趣的:(golang,golang,websocket,ssh)