Python新手向:如何用Python编写SNI代理扫描工具

用于发现 SNI 代理服务器, sni 代理的作用你懂的

食用方法简单

https://github.com/garsonbb/sni-detecter


Python新手向:如何用Python编写SNI代理扫描工具
20 回复

建议把百度换成知乎或者其他没有上 cdn 的测试目标,否则会扫到大量云加速和 bfe 。我之前也写过 go 的版本但是后来发现其实 zmap 可以扩展 go 的插件来实现。。。


要写一个SNI代理扫描工具,核心是解析TLS握手包里的SNI字段。我直接给你个实用的代码:

import socket
import ssl
import struct
from concurrent.futures import ThreadPoolExecutor
import ipaddress

def parse_tls_handshake(data):
    """解析TLS握手包,提取SNI"""
    try:
        # TLS记录层
        if data[0] != 0x16:  # Handshake
            return None
        
        # 跳过记录层头部
        pos = 5
        
        # Handshake类型
        if data[pos] != 0x01:  # Client Hello
            return None
        
        # 跳过Handshake头部
        pos += 4
        
        # 跳过协议版本
        pos += 2
        
        # 读取随机数
        pos += 32
        
        # 会话ID长度
        session_id_len = data[pos]
        pos += 1 + session_id_len
        
        # 密码套件长度
        cipher_suites_len = struct.unpack('>H', data[pos:pos+2])[0]
        pos += 2 + cipher_suites_len
        
        # 压缩方法长度
        compression_methods_len = data[pos]
        pos += 1 + compression_methods_len
        
        # 扩展长度
        extensions_len = struct.unpack('>H', data[pos:pos+2])[0]
        pos += 2
        
        # 遍历扩展
        end_pos = pos + extensions_len
        while pos < end_pos:
            ext_type = struct.unpack('>H', data[pos:pos+2])[0]
            ext_len = struct.unpack('>H', data[pos+2:pos+4])[0]
            
            if ext_type == 0x0000:  # SNI扩展
                sni_len = struct.unpack('>H', data[pos+6:pos+8])[0]
                sni = data[pos+8:pos+8+sni_len].decode('utf-8')
                return sni
            
            pos += 4 + ext_len
        
        return None
    except Exception:
        return None

def handle_client(client_sock, target_host, target_port):
    """处理客户端连接"""
    try:
        # 接收客户端数据
        data = client_sock.recv(4096)
        if not data:
            return
        
        # 解析SNI
        sni = parse_tls_handshake(data)
        if sni:
            print(f"[+] SNI found: {sni}")
        
        # 转发到目标服务器
        server_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        server_sock.connect((target_host, target_port))
        server_sock.send(data)
        
        # 双向转发
        while True:
            rlist, _, _ = select.select([client_sock, server_sock], [], [])
            
            for sock in rlist:
                data = sock.recv(4096)
                if not data:
                    return
                
                if sock is client_sock:
                    server_sock.send(data)
                else:
                    client_sock.send(data)
                    
    except Exception as e:
        print(f"[-] Error: {e}")
    finally:
        client_sock.close()

def start_proxy(listen_port, target_host, target_port):
    """启动代理服务器"""
    server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    server.bind(('0.0.0.0', listen_port))
    server.listen(5)
    
    print(f"[*] Proxy listening on port {listen_port}")
    
    with ThreadPoolExecutor(max_workers=10) as executor:
        while True:
            client_sock, addr = server.accept()
            print(f"[*] Connection from {addr[0]}:{addr[1]}")
            executor.submit(handle_client, client_sock, target_host, target_port)

if __name__ == "__main__":
    # 使用示例:监听8443端口,转发到example.com:443
    start_proxy(8443, "example.com", 443)

这个工具的核心是parse_tls_handshake函数,它解析TLS Client Hello包提取SNI字段。工作原理:

  1. 创建代理服务器监听指定端口
  2. 客户端连接时,截取TLS握手包
  3. 解析SNI扩展(类型0x0000)
  4. 记录SNI并转发流量

要扫描,你可以把代理部署在中间,让流量经过你的工具。或者批量处理pcap文件:

from scapy.all import *

def scan_pcap(pcap_file):
    packets = rdpcap(pcap_file)
    for pkt in packets:
        if pkt.haslayer(TLS):
            # 提取TLS数据并调用parse_tls_handshake
            pass

注意:这需要理解TLS协议格式,重点在解析扩展列表。实际使用要考虑性能,大流量时用异步IO。

一句话建议:先搞懂TLS握手协议格式再动手。

建议换成 google ,有些 sni proxy 会使用白名单, nginx 之类的貌似默认没这个功能

zgrab ?

谢谢提醒

还是要低调点 :)

灰常棒

IP 段不支持 CIDR ?

不支持哦,你可以自己改一下

TSL 握手阶段的 server name 既然可以被中间人看到,是否可能被 reset ?

我想多了, TSL 协商阶段的证书本来就可以被看到,多一个 SNI 也无所谓。

话说 SNI 代理到底是干嘛的?

就是代理啊 你用 hosts 强制定过去后 他转发,证书还是目标网站的,优势是国内部署私人 DNS 小范围分享方便

有些 ip 只是握手,却不能代理实际内容?


我改了改,支持了。


发了个 pull request,你可以合并一下哈哈。

怎么扫描 sni

似乎扫描的 ip 段一长 就完全失效了 有人成功扫过长 ip 段吗

好评,挂输没排上了

#18 *树莓派 fixed

回到顶部