You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

将SOCKS5连接转换为SSL时握手出错,寻求技术协助

问题说明

我用Python实现了一个SOCKS5服务器,想通过自有证书拦截处理WSS(基于SSL的WebSocket)消息,但在客户端Hello阶段前执行wrap_socket(sock, server_side=True)时,出现ssl.SSLError: ssl-wrong-version-number错误,随后触发握手超时_ssl.c:985: The handshake operation timed out。确认证书和TLS版本都没问题,试过所有可用TLS版本仍未解决。想知道:能否将SOCKS5连接转换为SSL?若可行,麻烦分析错误原因。

期望:在SOCKS5服务器内用有效证书完成WSS消息的拦截与处理。

相关代码:

def establish_socks5(self, sock):
    dest_host, dest_port = None, None
    try:
        ver, nmethods, methods = sock.recv(1), sock.recv(1), sock.recv(1)
        sock.sendall(VER + METHOD)
        ver, cmd, rsv, address_type = sock.recv(1), sock.recv(1), sock.recv(1), sock.recv(1)
        dst_addr = None
        dst_port = None
        if address_type == ADDR_TYPE_IPV4:
            dst_addr, dst_port = sock.recv(4), sock.recv(2)
            dst_addr = '.'.join([str(chr_to_int(i)) for i in dst_addr])
        elif address_type == ADDR_TYPE_DOMAIN:
            addr_len = ord(sock.recv(1))
            dst_addr, dst_port = sock.recv(addr_len), sock.recv(2)
            dst_addr = ''.join([chr(chr_to_int(i)) for i in dst_addr])
        elif address_type == ADDR_TYPE_IPV6:
            dst_addr, dst_port = sock.recv(16), sock.recv(2)
            tmp_addr = []
            for i in range(len(dst_addr) // 2):
                tmp_addr.append(chr(dst_addr[2 * i] * 256 + dst_addr[2 * i + 1]))
            dst_addr = ':'.join(tmp_addr)
        dst_port = chr_to_int(dst_port[0]) * 256 + chr_to_int(dst_port[1])
        server_sock = sock
        server_ip = ''.join([chr(int(i)) for i in socket.gethostbyname("172.16.8.151").split('.')])
        if cmd == CMD_TYPE_TCP_BIND:
            print('TCP Bind requested, but is not supported by socks5_server')
            sock.close()
        elif cmd == CMD_TYPE_UDP:
            print('UDP requested, but is not supported by socks5_server')
            sock.close()
        elif cmd == CMD_TYPE_CONNECT:
            sock.sendall(VER + SUCCESS + b'\x00' + b'\x01' + encode_str(server_ip +
                                                                       chr(9001 // 256) + chr(
                9001 % 256)))
            dest_host, dest_port = dst_addr, dst_port
        else:
            print('Unsupported/unknown SOCKS5 command requested')
            sock.sendall(VER + UNSUPPORTED_CMD + encode_str(server_ip + chr(9001 // 256) +
                                                           chr(9001 % 256)))
            sock.close()
    except KeyboardInterrupt as e:
        print('Error in SOCKS5 establishment: %s' % e)

    return dest_host, dest_port

def decorateSocket(self, sock):
 try:
    sslsock = self.context.wrap_socket(sock, server_side=True)
    return sslsock
 except ssl.SSLError as e:
       print(e)
       return None

sock, address = self.serversocket.accept()
self.establish_socks5(sock)
newsock = self.decorateSocket(sock)
newsock.setblocking(0)
可行性与错误原因分析

可行性结论

完全可以将SOCKS5连接转换为SSL来拦截WSS流量,但必须遵循正确的交互顺序。

错误核心原因

代码逻辑顺序错误:

  • SOCKS5握手完成后,客户端会直接向目标服务器发送明文的HTTP/WSS握手请求,而非SSL Client Hello包。
  • 此时调用wrap_socket(server_side=True),SSL库会等待客户端发送SSL握手数据,但实际收到的是普通HTTP请求,无法识别就会抛出wrong-version-number错误,最终触发握手超时。
修正思路

要拦截WSS流量,正确流程如下:

  1. 完成SOCKS5握手,获取客户端要访问的目标dest_host和dest_port。
  2. 作为中间代理,主动与目标WSS服务器建立SSL连接。
  3. 在客户端侧,伪装成目标服务器,用自有证书和客户端完成SSL握手。
  4. 握手完成后,双向转发客户端与目标服务器的加密流量,同时在中间拦截解析消息。
代码修正示例

调整后的核心逻辑代码:

def establish_socks5(self, sock):
    dest_host, dest_port = None, None
    try:
        ver, nmethods, methods = sock.recv(1), sock.recv(1), sock.recv(1)
        sock.sendall(VER + METHOD)
        ver, cmd, rsv, address_type = sock.recv(1), sock.recv(1), sock.recv(1), sock.recv(1)
        dst_addr = None
        dst_port = None
        if address_type == ADDR_TYPE_IPV4:
            dst_addr, dst_port = sock.recv(4), sock.recv(2)
            dst_addr = '.'.join([str(chr_to_int(i)) for i in dst_addr])
        elif address_type == ADDR_TYPE_DOMAIN:
            addr_len = ord(sock.recv(1))
            dst_addr, dst_port = sock.recv(addr_len), sock.recv(2)
            dst_addr = ''.join([chr(chr_to_int(i)) for i in dst_addr])
        elif address_type == ADDR_TYPE_IPV6:
            dst_addr, dst_port = sock.recv(16), sock.recv(2)
            tmp_addr = []
            for i in range(len(dst_addr) // 2):
                tmp_addr.append(chr(dst_addr[2 * i] * 256 + dst_addr[2 * i + 1]))
            dst_addr = ':'.join(tmp_addr)
        dst_port = chr_to_int(dst_port[0]) * 256 + chr_to_int(dst_port[1])
        server_ip = ''.join([chr(int(i)) for i in socket.gethostbyname("172.16.8.151").split('.')])
        if cmd == CMD_TYPE_TCP_BIND:
            print('TCP Bind requested, but is not supported by socks5_server')
            sock.close()
        elif cmd == CMD_TYPE_UDP:
            print('UDP requested, but is not supported by socks5_server')
            sock.close()
        elif cmd == CMD_TYPE_CONNECT:
            sock.sendall(VER + SUCCESS + b'\x00' + b'\x01' + encode_str(server_ip +
                                                                       chr(9001 // 256) + chr(
                9001 % 256)))
            dest_host, dest_port = dst_addr, dst_port
        else:
            print('Unsupported/unknown SOCKS5 command requested')
            sock.sendall(VER + UNSUPPORTED_CMD + encode_str(server_ip + chr(9001 // 256) +
                                                           chr(9001 % 256)))
            sock.close()
    except Exception as e:
        print('Error in SOCKS5 establishment: %s' % e)
        sock.close()

    return dest_host, dest_port

def handle_wss_proxy(self, client_sock, dest_host, dest_port):
    try:
        # 伪装成目标服务器,与客户端完成SSL握手
        ssl_client_sock = self.context.wrap_socket(client_sock, server_side=True, server_hostname=dest_host)
        
        # 连接目标WSS服务器并建立SSL连接
        target_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        target_sock.connect((dest_host, dest_port))
        ssl_target_sock = ssl.create_default_context().wrap_socket(target_sock, server_hostname=dest_host)

        # 双向转发流量,可在此添加拦截解析逻辑
        while True:
            # 读取客户端数据转发到目标服务器
            client_data = ssl_client_sock.recv(4096)
            if not client_data:
                break
            # 示例:打印客户端数据,可替换为WSS消息解析逻辑
            # print(f"Client -> Target: {client_data}")
            ssl_target_sock.sendall(client_data)

            # 读取目标服务器数据转发到客户端
            target_data = ssl_target_sock.recv(4096)
            if not target_data:
                break
            # 示例:打印目标服务器数据,可替换为WSS消息解析逻辑
            # print(f"Target -> Client: {target_data}")
            ssl_client_sock.sendall(target_data)
    except Exception as e:
        print(f"Proxy error: {e}")
    finally:
        ssl_client_sock.close()
        ssl_target_sock.close()

# 主逻辑调整
sock, address = self.serversocket.accept()
dest_host, dest_port = self.establish_socks5(sock)
if dest_host and dest_port:
    self.handle_wss_proxy(sock, dest_host, dest_port)
关键注意点
  • 证书需匹配客户端访问的目标域名,否则客户端会提示证书错误(除非客户端已信任你的根证书)。
  • 双向转发需处理非阻塞IO或使用多线程/多进程,避免单线程阻塞导致死锁。
  • WSS基于HTTP升级协议,可在握手阶段解析Upgrade: websocket头部,确认是WSS流量后再进行后续处理。

内容的提问来源于stack exchange,提问作者wdiwei

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 03:58:10