+
26
-

回答

阿里云fc函数计算web请求只支持HTTP或https,所以传统的tcp代理是无法通过第一层防护墙的,我们要采取另外一个加密方式来访问:

800_auto

点击查看全文

HTTP 隧道 (HTTP Tunneling)。与TCP 裸连不同,HTTP 隧道要求客户端和服务端之间的通讯必须看起来像标准的 HTTP 协议。通常的做法是:客户端向服务端发送一个 POST 请求,将实际的流量伪装成 HTTP 请求的 Body(请求体),服务端则将回包伪装成 HTTP 响应的 Body。

核心设计思路

伪装

Client -> Server:客户端发起一个 HTTP POST 请求(例如 POST /api/v1/sync),在这个请求的 Header 中放入加密的目标地址信息,后续所有的浏览器流量都加密后作为这个 POST 请求的 Body 发送。

Server -> Client:服务端收到 Header 后,解析出目标,回复 HTTP/1.1 200 OK,后续目标网站返回的数据加密后作为 Response Body 返回。

加密

Header 中的目标信息(IP:Port)需要加密并 Base64 编码,防止明文泄露。

Body 中的流量流(Stream)全量进行 XOR 加密。

1. 公共工具模块 (utils.py)

为了代码清晰,将加密和 HTTP 解析逻辑放这里。你可以单独存文件,也可以分别复制到 Client 和 Server 代码顶部。

import base64
import json

# ================= 配置 =================
PASSWORD = b'my_secret_http_tunnel_key'  # 密钥

class Cipher:
    """ 简单的 XOR 加密,用于流数据混淆 """
    def __init__(self, key):
        self.key = key
        self.key_len = len(key)

    def xor_data(self, data):
        if not data: return data
        return bytes([b ^ self.key[i % self.key_len] for i, b in enumerate(data)])

    def encrypt_info(self, info_dict):
        """ 加密字典信息用于放入 HTTP Header """
        json_bytes = json.dumps(info_dict).encode('utf-8')
        xor_bytes = self.xor_data(json_bytes)
        # 转为 base64 以便放入 HTTP Header
        return base64.b64encode(xor_bytes).decode('utf-8')

    def decrypt_info(self, b64_str):
        """ 解密 HTTP Header 中的信息 """
        xor_bytes = base64.b64decode(b64_str)
        json_bytes = self.xor_data(xor_bytes)
        return json.loads(json_bytes.decode('utf-8'))

def read_http_header(sock):
    """ 读取 HTTP 头部直到 \r\n\r\n """
    header_data = b''
    while True:
        chunk = sock.recv(1)
        if not chunk:
            raise ConnectionError("Connection closed while reading header")
        header_data += chunk
        if header_data.endswith(b'\r\n\r\n'):
            break
    return header_data.decode('iso-8859-1')

2. 服务端代码 (server.py)

部署在拥有公网 IP 的服务器上。它伪装成一个 Web 服务器,接收 POST 请求。

import socket
import threading
import select
from utils import Cipher, read_http_header, PASSWORD

# ================= 配置 =================
SERVER_IP = '0.0.0.0'
SERVER_PORT = 9999
# =======================================

def forward_data(source, destination, cipher, decrypt=False):
    """ 数据转发管道 """
    try:
        while True:
            data = source.recv(4096)
            if not data: break

            if decrypt:
                converted = cipher.xor_data(data)
            else:
                converted = cipher.xor_data(data)

            destination.sendall(converted)
    except Exception:
        pass
    finally:
        source.close()
        destination.close()

def handle_client(client_sock, addr):
    cipher = Cipher(PASSWORD)
    remote_sock = None

    try:
        # 1. 模拟 Web Server,读取 HTTP 请求头
        header_str = read_http_header(client_sock)
        # print(f"[Server] 收到 HTTP 请求头:\n{header_str}")

        # 2. 从 Header 中提取加密的目标信息
        # 我们约定 Header 里的 'X-Token' 字段存放加密的目标地址
        headers = {}
        for line in header_str.split('\r\n')[1:]:
            if ': ' in line:
                key, value = line.split(': ', 1)
                headers[key] = value

        if 'X-Token' not in headers:
            print(f"[Server] 非法请求,缺少 Token: {addr}")
            client_sock.close()
            return

        # 解密目标地址
        target_info = cipher.decrypt_info(headers['X-Token'])
        target_host = target_info['host']
        target_port = target_info['port']

        # print(f"[Server] 目标解析成功: {target_host}:{target_port}")

        # 3. 连接真实目标
        remote_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        remote_sock.connect((target_host, target_port))

        # 4. 回复 HTTP 200 OK,表示隧道建立成功
        # 之后的数据流将被视为 Body
        response = "HTTP/1.1 200 OK\r\nServer: PythonProxy\r\n\r\n"
        client_sock.sendall(response.encode('utf-8'))

        # 5. 双向转发 (Body 部分全是密文)
        # Client(密文Body) -> 解密 -> Target
        t1 = threading.Thread(target=forward_data, args=(client_sock, remote_sock, cipher, True))
        # Target -> 加密 -> Client(作为响应Body)
        t2 = threading.Thread(target=forward_data, args=(remote_sock, client_sock, cipher, False))

        t1.start()
        t2.start()

        t1.join()
        t2.join()

    except Exception as e:
        print(f"[Server] 处理异常: {e}")
        if client_sock: client_sock.close()
        if remote_sock: remote_sock.close()

def main():
    server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    server.bind((SERVER_IP, SERVER_PORT))
    server.listen(100)
    print(f"[*] HTTP隧道服务端 运行在 {SERVER_IP}:{SERVER_PORT}")

    while True:
        client, addr = server.accept()
        t = threading.Thread(target=handle_client, args=(client, addr))
        t.daemon = True
        t.start()

if __name__ == '__main__':
    main()

3. 客户端代码 (client.py)

本地运行。它接收浏览器的标准代理请求,然后将其包装成一个发往服务端的 HTTP POST 请求。

import socket
import threading
from utils import Cipher, read_http_header, PASSWORD

# ================= 配置 =================
LOCAL_IP = '127.0.0.1'
LOCAL_PORT = 8080

# 你的服务端 IP
REMOTE_HOST = '127.0.0.1' 
REMOTE_PORT = 9999
# =======================================

def forward_data(source, destination, cipher, encrypt=False):
    """ 数据转发管道 """
    try:
        while True:
            data = source.recv(4096)
            if not data: break

            if encrypt:
                converted = cipher.xor_data(data)
            else:
                converted = cipher.xor_data(data)

            destination.sendall(converted)
    except Exception:
        pass
    finally:
        source.close()
        destination.close()

def handle_browser(browser_sock):
    proxy_sock = None
    cipher = Cipher(PASSWORD)

    try:
        # 1. 也是先读取浏览器的请求头(明文)
        # 必须使用 MSG_PEEK 预览,或者读出来后处理
        # 这里为了简单,我们读出来解析,然后根据情况决定是否转发
        header_buffer = b''
        while True:
            chunk = browser_sock.recv(1)
            if not chunk: break
            header_buffer += chunk
            if header_buffer.endswith(b'\r\n\r\n'): break

        if not header_buffer: return
        header_str = header_buffer.decode('iso-8859-1')

        # 2. 解析浏览器的目标
        first_line = header_str.split('\r\n')[0]
        method, url, _ = first_line.split(' ')

        # 提取 Host 和 Port
        if method == 'CONNECT':
            # HTTPS: CONNECT www.google.com:443 HTTP/1.1
            host_part = url
            if ':' in host_part:
                target_host, target_port = host_part.split(':')
                target_port = int(target_port)
            else:
                target_host = host_part
                target_port = 443
        else:
            # HTTP: GET http://www.google.com/abc HTTP/1.1
            # 简单解析
            temp = url.replace('http://', '').replace('https://', '')
            path_idx = temp.find('/')
            if path_idx != -1:
                host_part = temp[:path_idx]
            else:
                host_part = temp

            if ':' in host_part:
                target_host, target_port = host_part.split(':')
                target_port = int(target_port)
            else:
                target_host = host_part
                target_port = 80

        # print(f"[Client] 浏览器想要去: {target_host}:{target_port}")

        # 3. 连接远程服务端,并构建伪装的 HTTP POST 请求
        proxy_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        proxy_sock.connect((REMOTE_HOST, REMOTE_PORT))

        # 加密目标信息
        encrypted_token = cipher.encrypt_info({"host": target_host, "port": target_port})

        # 构造 HTTP 隧道握手包
        # 看起来像是一个普通的 POST 上传请求
        fake_request = (
            f"POST /tunnel_stream HTTP/1.1\r\n"
            f"Host: {REMOTE_HOST}\r\n"
            f"User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)\r\n"
            f"X-Token: {encrypted_token}\r\n"  # 关键加密参数
            f"Content-Type: application/octet-stream\r\n"
            f"Connection: keep-alive\r\n"
            f"\r\n"
        )
        proxy_sock.sendall(fake_request.encode('utf-8'))

        # 4. 等待服务端回复 HTTP 200 OK
        remote_header = read_http_header(proxy_sock)
        if "200 OK" not in remote_header:
            print("[Client] 服务端拒绝了隧道连接")
            proxy_sock.close()
            return

        # 5. 处理浏览器协议差异
        if method == 'CONNECT':
            # HTTPS: 代理已经连通,必须告诉浏览器 Connection Established
            browser_sock.sendall(b'HTTP/1.1 200 Connection Established\r\n\r\n')
        else:
            # HTTP: 刚才为了解析读走了 Header,现在必须把这个 Header 发给目标
            # 注意:这个 Header 是明文的 HTTP 请求,需要加密后发给服务端
            encrypted_header = cipher.xor_data(header_buffer)
            proxy_sock.sendall(encrypted_header)

        # 6. 开始数据流转发
        # 浏览器 -> (加密) -> 隧道 Body -> 服务端
        t1 = threading.Thread(target=forward_data, args=(browser_sock, proxy_sock, cipher, True))
        # 服务端 -> (隧道 Body) -> (解密) -> 浏览器
        t2 = threading.Thread(target=forward_data, args=(proxy_sock, browser_sock, cipher, False))

        t1.start()
        t2.start()

        t1.join()
        t2.join()

    except Exception as e:
        print(f"[Client] 错误: {e}")
        if proxy_sock: proxy_sock.close()
        if browser_sock: browser_sock.close()

def main():
    server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    server.bind((LOCAL_IP, LOCAL_PORT))
    server.listen(100)
    print(f"[*] HTTP隧道客户端 监听在 {LOCAL_IP}:{LOCAL_PORT}")

    while True:
        client, addr = server.accept()
        t = threading.Thread(target=handle_browser, args=(client,))
        t.daemon = True
        t.start()

if __name__ == '__main__':
    main()

代码变化点与实现原理

协议层的伪装

TCP 版本:连接建立后直接发送自定义的二进制结构(Length + JSON)。

HTTP 隧道版本

连接建立后,Client 发送标准的 HTTP 文本:POST /tunnel_stream HTTP/1.1 ...。

防火墙看到的只有 HTTP POST 请求头。

真正的数据流紧跟在 HTTP 头部 \r\n\r\n 之后。这在 HTTP 协议中被视为 Request Body(虽然我们没有指定 Content-Length 或使用了 Chunked,但在 TCP流中,如果不关闭连接,这就是无限长的 Body)。

Server 端的行为

Server 不再直接 recv(4) 读长度,而是使用 read_http_header 逐字节读取直到遇到双换行符。

Server 解析 Header,验证 X-Token,如果合法则回复 HTTP/1.1 200 OK。这让防火墙认为这是一个合法的 HTTP 响应。

Client 端的行为

Client 在向 Server 发送数据前,先发送 HTTP Header 握手。

Client 等待 Server 回复 200 OK 后,才开始正式的双向数据转发。

使用方法

保存:将 utils.py 保存,然后将 server.py 和 client.py 放在同一目录下(或者确保它们都能 import utils)。

配置:修改 client.py 中的 REMOTE_HOST 为服务端 IP。

运行服务端:python server.py

运行客户端:python client.py

浏览器设置:HTTP 代理 127.0.0.1:8080。

这样,你的流量在网络上传输时,看起来就像是一个持续不断的 HTTP POST 数据流,且内容是加密的。

网友回复

我知道答案,我要回答