Python新手向:如何用Python编写SNI代理扫描工具
Python新手向:如何用Python编写SNI代理扫描工具
建议把百度换成知乎或者其他没有上 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字段。工作原理:
- 创建代理服务器监听指定端口
- 客户端连接时,截取TLS握手包
- 解析SNI扩展(类型0x0000)
- 记录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 也无所谓。
TLS
话说 SNI 代理到底是干嘛的?
就是代理啊 你用 hosts 强制定过去后 他转发,证书还是目标网站的,优势是国内部署私人 DNS 小范围分享方便
有些 ip 只是握手,却不能代理实际内容?
我改了改,支持了。
发了个 pull request,你可以合并一下哈哈。
怎么扫描 sni
似乎扫描的 ip 段一长 就完全失效了 有人成功扫过长 ip 段吗
好评,挂输没排上了
#18 *树莓派 fixed

