将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流量,正确流程如下:
- 完成SOCKS5握手,获取客户端要访问的目标
dest_host和dest_port。 - 作为中间代理,主动与目标WSS服务器建立SSL连接。
- 在客户端侧,伪装成目标服务器,用自有证书和客户端完成SSL握手。
- 握手完成后,双向转发客户端与目标服务器的加密流量,同时在中间拦截解析消息。
代码修正示例
调整后的核心逻辑代码:
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
相关产品推荐
相关产品推荐

