Step-by-Step Guide to Building a Simple and Secure Socks5 Proxy in Python

Socks5 proxy is a network protocol that allows clients to forward network connection requests through a proxy server. Compared with Socks4, Socks5 provides a wider range of authentication methods and address type support, including IPv6 and domain name resolution. Creating a simple and secure Socks5 proxy in Python requires support for authentication and correct protocol handling. Here is a step-by-step guide and code examples:

Step 1: Install necessary libraries

Use the standard library socketserver and struct, no additional installation is required.

Step 2: Write Socks5 proxy server code

import socket
import struct
import select
from socketserver import ThreadingTCPServer, BaseRequestHandler

class Socks5ProxyHandler(BaseRequestHandler):
    username = 'admin'  # Change to a safe username
    password = 'password'  # Change to a strong password

    def handle_auth(self):
        data = self.request.recv(1024)
        if not data or data[0] != 0x05:
            self.request.close()
            return False

        # Check whether username and password authentication is supported
        nmethods = data[1]
        methods = data[2:2 + nmethods]
        if 0x02 not in methods:
            self.request.sendall(struct.pack('!BB', 0x05, 0xFF))
            return False

        # Select Username/Password Authentication
        self.request.sendall(struct.pack('!BB', 0x05, 0x02))

        # Handling Authentication
        auth_data = self.request.recv(1024)
        if not auth_data or auth_data[0] != 0x01:
            return False

        ulen = auth_data[1]
        uname = auth_data[2:2 + ulen].decode('utf-8')
        plen = auth_data[2 + ulen]
        passwd = auth_data[3 + ulen:3 + ulen + plen].decode('utf-8')

        if uname == self.username and passwd == self.password:
            self.request.sendall(struct.pack('!BB', 0x01, 0x00))
            return True
        else:
            self.request.sendall(struct.pack('!BB', 0x01, 0x01))
            return False

    def handle_request(self):
        # Receiving client requests
        data = self.request.recv(1024)
        if not data or len(data) < 4:
            return False

        ver, cmd, _, atyp = struct.unpack('!4B', data[:4])
        if ver != 0x05 or cmd != 0x01:  # Only handle CONNECT requests
            self.request.sendall(struct.pack('!8B', 0x05, 0x07, 0x00, 0x01, 0, 0, 0, 0))
            return False

        # Resolve the target address and port
        if atyp == 0x01:  # IPv4
            target_addr = socket.inet_ntop(socket.AF_INET, data[4:8])
            port = struct.unpack('!H', data[8:10])[0]
        elif atyp == 0x03:  # domain name
            domain_len = data[4]
            target_addr = data[5:5 + domain_len].decode('utf-8')
            port = struct.unpack('!H', data[5 + domain_len:5 + domain_len + 2])[0]
        else:
            self.request.sendall(struct.pack('!8B', 0x05, 0x08, 0x00, 0x01, 0, 0, 0, 0))
            return False

        # Connect to the target server
        remote = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        try:
            remote.connect((target_addr, port))
        except Exception as e:
            self.request.sendall(struct.pack('!8B', 0x05, 0x03, 0x00, 0x01, 0, 0, 0, 0))
            return False

        # Response to client connection success
        bind_addr = remote.getsockname()
        response = struct.pack('!4B', 0x05, 0x00, 0x00, 0x01)
        response += socket.inet_aton(bind_addr[0]) + struct.pack('!H', bind_addr[1])
        self.request.sendall(response)

        # Data forwarding
        try:
            while True:
                r, _, _ = select.select([self.request, remote], [], [])
                if self.request in r:
                    data = self.request.recv(4096)
                    if not data:
                        break
                    remote.sendall(data)
                if remote in r:
                    data = remote.recv(4096)
                    if not data:
                        break
                    self.request.sendall(data)
        except:
            pass
        finally:
            remote.close()
            return True

    def handle(self):
        if not self.handle_auth():
            return
        self.handle_request()

if __name__ == '__main__':
    # Start the proxy server on local port 1080
    with ThreadingTCPServer(('0.0.0.0', 1080), Socks5ProxyHandler) as server:
        print("Socks5 proxy server started, listening on port 1080...")
        server.serve_forever()

Step 3: Security Enhancements

  • Strong Password Policy: Modify the default username and password in the example to a complex combination.

  • Restrict Access: Bind to a specific IP (such as 127.0.0.1) instead of 0.0.0.0 to avoid exposure to the public network.

  • Log Monitoring: Add logging capabilities to track connection attempts.

  • Use TLS Tunnel: Wrap proxy traffic with tools such as stunnel to achieve encrypted transmission.

Step 4: Test the Proxy

Use curl to test whether the proxy is working:

curl --socks5 admin:password@127.0.0.1:1080 https://example.com

Notes

  • Protocol support: The example only handles TCP CONNECT requests and is applicable to HTTP/HTTPS.

  • Performance optimization: For high-concurrency scenarios, it is recommended to use asynchronous libraries such as asyncio.

  • Production environment: It is recommended to use mature libraries (such as python-socks) or dedicated proxy software.

Conclusion

This article introduces the need to support authentication and correctly handle protocols to create a simple and secure Socks5 proxy in Python. However, to create a simple and secure Socks5 proxy, you also need to consider the security of authentication mechanisms, encrypted communications, logging, and monitoring.