Go实现SSH代理时出现Bad Packet Length错误求助
Go实现SSH代理时出现Bad Packet Length错误的解决方法
错误信息
debug1: SSH2_MSG_KEXINIT sent Bad packet length 1231976033. ssh_dispatch_run_fatal: Connection to ::1 port 8080: message authentication code incorrect
问题代码
func handleSSH(conn net.Conn, r *bufio.Reader, protocol string) { target, err := url.Parse("ssh://localhost:3333") if err != nil { fmt.Println("Error parsing target", err) conn.Close() return } targetConn, err := net.Dial("tcp", target.Host) if err != nil { fmt.Println("error dialing SSH target:", err) conn.Close() return } defer targetConn.Close() var wg sync.WaitGroup wg.Add(2) go func() { _, err := io.Copy(targetConn, conn) if err != nil { fmt.Println("error copying data to target:", err) } wg.Done() }() go func() { _, err := io.Copy(conn, targetConn) if err != nil { fmt.Println("error copying data from target:", err) } wg.Done() }() wg.Wait() conn.Close() } // EDIT func connection(conn net.Conn) { r := bufio.NewReader(conn) protocol, err := r.ReadString('\n') if err != nil { fmt.Println("Error reading first line", err) conn.Close() return } if protocol[0:3] == "SSH" { handleSSH(conn, r, protocol) } } func main() { ln, err := net.Listen("tcp", ":8080") if err != nil { panic(err) } defer ln.Close() for { conn, err := ln.Accept() if err != nil { panic(err) } go connection(conn) } }
问题分析
核心错误是判断SSH协议时,用ReadString('\n')读取了SSH握手的初始数据,但未将这部分数据转发到目标SSH服务器。
SSH初始握手会发送类似SSH-2.0-XXX\r\n的标识字符串,你用ReadString('\n')把这一行读走后,直接使用原始conn做数据转发,但这部分已读取的数据留在了bufio.Reader的缓存中,没有被发送到目标服务器。目标服务器收到的是不完整的握手数据,无法正确解析SSH包结构,从而引发Bad Packet Length错误。
修复方案
方案一:转发已读取的握手数据
修改handleSSH函数,先将读取到的protocol写入目标连接,再用bufio.Reader转发剩余数据:
func handleSSH(conn net.Conn, r *bufio.Reader, protocol string) { target, err := url.Parse("ssh://localhost:3333") if err != nil { fmt.Println("Error parsing target", err) conn.Close() return } targetConn, err := net.Dial("tcp", target.Host) if err != nil { fmt.Println("error dialing SSH target:", err) conn.Close() return } defer targetConn.Close() // 先转发已读取的SSH握手数据 if _, err := targetConn.Write([]byte(protocol)); err != nil { fmt.Println("error writing handshake data:", err) return } var wg sync.WaitGroup wg.Add(2) go func() { // 使用bufio.Reader转发剩余数据,而非原始conn _, err := io.Copy(targetConn, r) if err != nil { fmt.Println("error copying data to target:", err) } wg.Done() }() go func() { _, err := io.Copy(conn, targetConn) if err != nil { fmt.Println("error copying data from target:", err) } wg.Done() }() wg.Wait() conn.Close() }
方案二:避免缓存数据,直接判断协议
取消bufio.Reader的使用,直接从conn读取前4个字节判断是否为SSH协议,同时将读取的字节写回转发:
func connection(conn net.Conn) { // 读取前4个字节判断协议标识 buf := make([]byte, 4) n, err := conn.Read(buf) if err != nil { fmt.Println("Error reading protocol header:", err) conn.Close() return } if string(buf[:n]) == "SSH-" { target, err := url.Parse("ssh://localhost:3333") if err != nil { fmt.Println("Error parsing target", err) conn.Close() return } targetConn, err := net.Dial("tcp", target.Host) if err != nil { fmt.Println("error dialing SSH target:", err) conn.Close() return } defer targetConn.Close() // 先写入已读取的协议标识字节 if _, err := targetConn.Write(buf[:n]); err != nil { fmt.Println("error writing header:", err) return } var wg sync.WaitGroup wg.Add(2) go func() { _, err := io.Copy(targetConn, conn) if err != nil { fmt.Println("error copying data to target:", err) } wg.Done() }() go func() { _, err := io.Copy(conn, targetConn) if err != nil { fmt.Println("error copying data from target:", err) } wg.Done() }() wg.Wait() } conn.Close() }
内容的提问来源于stack exchange,提问作者badman
相关产品推荐
相关产品推荐

