SSL密钥协商导致抓包失败的原因分析

前几天逛L站的时候,看到有网友发了一个咨询帖子,说他有一个个人网站,想用Reqable抓包分析一下流量,但是开启代理后浏览器无法访问,关闭后可以正常访问,不确定是哪里的原因。我排查定位了下,这里给大家做个简单的分享。

1. 问题复现

问题还是很容易复现的,打开Reqable启用系统代理并抓包,使用Chrome浏览器访问网站,看到浏览器报错ERR_CONNECTION_CLOSED,Reqable中能看到此域名的流量,但是状态全是黄色警告,点开一条记录提示是服务器SSL握手失败。

我们经常会遇到的是客户端SSL握手失败,往往是客户端不信任中间人签发的证书,中断请求。这里的服务器SSL握手失败,表明是服务器拒绝了握手,导致连接失败。

服务器拒绝SSL连接的原因下面这些:

  • 启用了双向验证。需要客户端内置证书才能访问,浏览器这里绝无可能,我们可以排除掉。
  • 启用了SSL指纹校验。一般为了防抓包,服务器会校验客户端请求的指纹是否和预期一致。这位网友说只是Nginx部署了一个静态网页项目,没有这种可能。
  • SSL/TLS版本不一致的问题。比如服务器强制使用了TLS 1.3,用到一些比较新的加密套件算法,密钥协商算法等。Reqable可能由于版本过低,或者算法不支持导致服务器SSL握手失败。

Reqable使用的是OpenSSL作为SSL支持,编译的是1.1.1n版本,OpenSSL现在主要是支持3.x版本,已经更新到3.6了。

我怀疑问题可能出现在这里,下面来验证一下。

2. 原因排查

我看了下电脑安装的OpenSSL版本恰好是最新的3.6版本。

yaml 复制代码
openssl -vOpenSSL 3.6.0 1 Oct 2025 (Library: OpenSSL 3.6.0 1 Oct 2025)

下面用OpenSSL命令来测试下这个网站的域名。

bash 复制代码
openssl s_client -connect xxx.top:443

控制台输出能看到SSL正常连接,没什么问题:

vbnet 复制代码
Connecting to xxx.xxx.xxx.xxx
CONNECTED(00000005)
depth=2 C=US, O=Internet Security Research Group, CN=ISRG Root X1
verify return:1
depth=1 C=US, O=Let's Encrypt, CN=R12
verify return:1
depth=0 CN=xxx.top
verify return:1

下面我们开始搬出大杀器Wireshark,来分析具体的SSL/TLS层协议,看看Reqable和浏览器哪里不同。

主要是看ClientHello包,里面包含了客户端的SSL各种信息。上图左边是浏览器的数据包,右边是Reqable的数据包。重点观察的部分,我已经用红框标出来了,主要有三个部分。

  • Cipher Suites,加密套件。如果服务器指定了某个加密套件,但是Reqable支持的加密套件列表里面不包含,就可能出现SSL握手失败的情况。从浏览器访问回复的ServerHello包来看,选择的是加密套件是Cipher Suite: TLS_AES_128_GCM_SHA256 (0x1301),已经在Reqable支持的列表中,故而可以排除掉。
  • Extension: signature_algorithms,支持算法列表。Reqable提供了19种算法,浏览器提供了8种算法,仔细对比发现,Reqable并没有缺少的部分。
  • Extension: key_share 密钥协商算法(也被称为密钥协商)。这是TLS 1.3新增的重要扩展,用于减少握手步骤和增加前向安全。浏览器支持X25519MLKEM768和X25519,Reqable只支持X25519。

我们怀疑是密钥协商算法不支持X25519MLKEM768导致的,这个算法从OpenSSL 3.5版本才开始支持的。为了验证这个问题,我们可以在OpenSSL的命令中使用-curves来指定。

arduino 复制代码
openssl s_client -connect xxx.top:443 -curves X25519

我们发现SSL握手失败了。

bash 复制代码
Connecting to xxx.xxx.xxx.xxx
CONNECTED(00000005)
4061CE0C02000000:error:0A000410:SSL routines:ssl3_read_bytes:ssl/tls alert handshake failure:ssl/record/rec_layer_s3.c:916:SSL alert number 40

重新用-curves来指定为X25519MLKEM768:X25519则可以正常握手(注意中间用冒号隔开)。

arduino 复制代码
openssl s_client -connect xxx.top:443 -curves X25519MLKEM768:X25519

3. 修复方案

我们接下来考虑如何在Reqable中修复这个问题,首先需要更新OpenSSL版本到3.5以上。这个比较麻烦,由于我们C++库使用CMake构建并编译,OpenSSL并不支持CMake,更新比较麻烦。不过有个暂时取巧的办法,编译时直接链接到本机已安装的OpenSSL库。

作为临时测试,我们修改代码,强行设置密钥交换算法为X25519MLKEM768:X25519。如果要完整处理,我们需要还原客户端的密码交换算法。

c++ 复制代码
SSL_set1_groups_list(ssl, "X25519MLKEM768:X25519");

重新编译,再次运行Reqable开始抓包,看起来没有问题了。

4. 更新计划

目前的3.0版本我们还没有修复这个问题,计划在后面3.1版本处理。3.0后续还有几个小版本,预计在12月之内完成,明年正式开始3.1版本的开发。

3.1版本主要会优化抓包部分的功能和实现,包括集成最新OpenSSL库、还原SSL指纹、增强模式优化和重写依赖原始服务器响应等问题。

Reqable,最强API抓包和测试工具,欢迎访问我们的官网:reqable.com

相关推荐
dly_blog3 小时前
ref 与 reactive 的本质区别(第3节)
前端·javascript·vue.js
前端不太难10 小时前
从 Navigation State 反推架构腐化
前端·架构·react
前端程序猿之路10 小时前
Next.js 入门指南 - 从 Vue 角度的理解
前端·vue.js·语言模型·ai编程·入门·next.js·deepseek
大布布将军10 小时前
⚡️ 深入数据之海:SQL 基础与 ORM 的应用
前端·数据库·经验分享·sql·程序人生·面试·改行学it
川贝枇杷膏cbppg11 小时前
Redis 的 RDB 持久化
前端·redis·bootstrap
JIngJaneIL11 小时前
基于java+ vue农产投入线上管理系统(源码+数据库+文档)
java·开发语言·前端·数据库·vue.js·spring boot
天外天-亮11 小时前
v-if、v-show、display: none、visibility: hidden区别
前端·javascript·html
jump_jump11 小时前
手写一个 Askama 模板压缩工具
前端·性能优化·rust
be or not to be12 小时前
HTML入门系列:从图片到表单,再到音视频的完整实践
前端·html·音视频
90后的晨仔12 小时前
在macOS上无缝整合:为Claude Code配置魔搭社区免费API完全指南
前端