sni

检查 SNI(Server Name Indication)的配置和通信是否正常,可以通过 **Python 代码** 或 **Shell 工具** 实现。以下是具体方法:

---

### **1. 使用 Shell 工具检查 SNI**
#### **(1) 使用 `openssl s_client`**
通过 OpenSSL 的 `s_client` 工具手动发起 TLS 连接并指定 SNI,观察服务器返回的证书信息:

```bash
# 检查 SNI 是否被正确响应(替换 example.com 和 443)
openssl s_client -connect example.com:443 -servername example.com -showcerts </dev/null 2>&1 | grep -A 1 "Certificate chain"

# 检查服务器返回的证书域名是否匹配 SNI
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>&1 | openssl x509 -noout -text | grep "Subject Alternative Name" -A 1
```

**关键参数**:
- `-servername`: 指定 SNI 名称(必须与目标域名一致)。
- `-showcerts`: 显示服务器返回的证书链。
- `</dev/null`: 避免等待输入,直接结束连接。

---

#### **(2) 使用 `curl` 测试 SNI**
通过 `curl` 的 `-v` 参数查看详细的 TLS 握手过程,确认 SNI 字段:

```bash
curl -v -I https://example.com 2>&1 | grep "TLS handshake"
```

输出中应包含 `TLS handshake, Server Name Indication (SNI)` 字段:

```
* TLS handshake, Server Name Indication (SNI): example.com
```

---

### **2. 使用 Python 检查 SNI**
#### **(1) 使用 `ssl` 模块验证 SNI**
通过 Python 的 `ssl` 库发起 TLS 连接并检查证书信息:

```python
import socket
import ssl

def check_sni(hostname, port=443):
context = ssl.create_default_context()
conn = context.wrap_socket(
socket.socket(socket.AF_INET),
server_hostname=hostname
)
try:
conn.connect((hostname, port))
cert = conn.getpeercert()
print("SNI 检查成功!证书信息:")
print(f"Subject: {cert['subject']}")
print(f"SANs: {cert.get('subjectAltName', [])}")
except Exception as e:
print(f"SNI 检查失败: {e}")
finally:
conn.close()

# 示例:检查 example.com 的 SNI
check_sni("example.com")
```

**关键点**:
- `server_hostname=hostname`:指定 SNI 名称。
- `getpeercert()`:获取服务器返回的证书信息。

---

#### **(2) 使用 `requests` 库测试 SNI**
通过 `requests` 库发起请求并捕获证书信息:

```python
import requests

def check_sni_with_requests(url):
try:
response = requests.get(url)
cert_info = response.connection.sock.getpeercert()
print("SNI 检查成功!证书信息:")
print(f"证书域名: {cert_info['subjectAltName']}")
except Exception as e:
print(f"请求失败: {e}")

# 示例:检查 HTTPS 站点的 SNI
check_sni_with_requests("https://example.com")
```

---

### **3. 检查 SNI 的常见场景**
#### **场景 1:验证服务器是否支持 SNI**
- **方法**:使用不同 `-servername` 值测试同一 IP 地址,观察返回的证书是否不同。
```bash
openssl s_client -connect 1.2.3.4:443 -servername site1.com
openssl s_client -connect 1.2.3.4:443 -servername site2.com
```

#### **场景 2:客户端是否发送 SNI**
- **方法**:在服务器端抓包(如 `tcpdump` 或 Wireshark),过滤 TLS 握手流量,查看 `Client Hello` 报文是否包含 SNI 扩展。

---

### **4. 自动化工具推荐**
#### **(1) `testssl.sh`**
功能强大的命令行工具,支持全面检查 SNI 和证书配置:
```bash
# 下载并运行
git clone https://github.com/drwetter/testssl.sh.git
cd testssl.sh
./testssl.sh --sni example.com
```

#### **(2) `sslyze`**
Python 编写的 SSL/TLS 扫描工具,支持 SNI 检测:
```bash
pip install sslyze
sslyze --sni=example.com example.com:443
```

---

### **5. 常见问题排查**
#### **问题 1:服务器未响应 SNI**
- **表现**:无论 `-servername` 指定什么值,返回的证书始终相同。
- **解决**:确保服务器配置了基于 SNI 的虚拟主机(如 Nginx 的 `server_name`)。

#### **问题 2:客户端未发送 SNI**
- **表现**:服务器返回默认证书或错误。
- **解决**:检查客户端代码或工具是否显式设置了 SNI(如 Python 的 `server_hostname` 参数)。

---

通过以上方法,您可以快速验证 SNI 的配置和通信是否正常。优先推荐使用 `openssl s_client` 或 `testssl.sh` 进行快速测试,Python 代码适合集成到自动化脚本中。

 

 

```mermaid
sequenceDiagram
participant Client
participant Server

Note over Client: 需要访问 https://example.com
Client->>Server: ClientHello
Note left of Client: TLS握手第一步
Note right of Server: 携带SNI扩展<br/>(Server Name: example.com)

Server->>Client: ServerHello
Note right of Server: 根据SNI选择对应证书
Note left of Client: 收到example.com的证书

Client->>Server: 验证证书 + 发送加密密钥
Server->>Client: 确认加密完成
Note over Client,Server: TLS握手完成,开始加密通信
```

### 关键流程说明:
1. **SNI触发时机**
└── 在TLS握手**第一个数据包**(ClientHello)中携带
└── 位于明文传输的握手阶段(未加密)

2. **SNI核心作用**
```diff
+ 解决单IP多证书问题
+ 允许服务器识别客户端请求的具体域名
- 未启用SNI的旧客户端(如IE6)无法支持
```

3. **技术特征对比**
| 特性 | SNI扩展 | 传统HTTPS |
|---------------|----------------------------------|----------------------------|
| 多域名支持 | ✅ 单IP托管多个证书 | ❌ 需独立IP或通配符证书 |
| 隐私性 | ❗ 暴露访问域名(明文传输) | 相同 |
| 兼容性 | 要求客户端支持TLS扩展(现代浏览器)| 通用 |

### 抓包示例(Wireshark)
```
Transport Layer Security
TLSv1.2 Record Layer: Handshake Protocol: Client Hello
Handshake Protocol: Client Hello
Extension: server_name (len=21)
Server Name Indication extension
Server Name: example.com 👈 可见明文域名
```

### 相关安全实践
1. **SNI加密方案**
- ESNI (Encrypted SNI) -> 已被放弃
- ECH (Encrypted Client Hello) -> TLS 1.3+ 新标准

2. **防火墙限制**
```bash
# 使用iptables匹配SNI(需CONFIG_NF_CONNTRACK_SNIME支持)
iptables -A INPUT -p tcp --dport 443 -m conntrack --ctproto tls --sni example.com -j ACCEPT
```

posted on 2025-04-22 11:23  吃草的青蛙  阅读(151)  评论(0)    收藏  举报

导航