The bug is one missing comparison
In iked v12.11.3, the function watchTowr identifies as ike2_ProcessPayload_CERT does this:
memcpy(identification, pIDPld.identification.buffer, pIDPld.identification.length);
identification is a 512-byte stack buffer. pIDPld.identification.length is parsed straight from the wire. There is no length check. An attacker who sends an IDENTIFICATION_INITIATOR payload (type 35) with a length field above 512 writes past the end of the stack frame.
The patch in v12.11.4 is one statement:
+ if (pIDPld.identification.length > 0x200) return -1;
memcpy(identification, pIDPld.identification.buffer, pIDPld.identification.length);
0x200 is 512. The buffer is 512 bytes. The check is a hardcoded constant, not sizeof(identification). If a future maintainer enlarges the buffer, the check stays at 512 and the gateway gets truncation. If a future maintainer shrinks the buffer, the check stays at 512 and the bug returns. The patch is a magic number for what should have been a sizeof.
The boundary is observable from outside the box. The watchTowr verifier sends an ID payload of exactly 512 bytes, then 513:
# Send a valid auth packet to ensure the service is running as expected
trigger_status = Runner.trigger(ike, b'A' * 512)
...
# Send an invalid auth packet, if the service responds then it is vulnerable
trigger_status = Runner.trigger(ike, b'A' * 513)
512 bytes returns. 513 bytes does not. One byte over the buffer is the difference between the daemon answering and the daemon dying. The patch boundary is reachable, observable, and unauthenticated.
ike2_ProcessPayload_CERT runs before the certificate is validated
IKEv2 has two exchanges. IKE_SA_INIT establishes session keys without authentication, anyone with UDP/500 reachability can complete it. IKE_AUTH is where the gateway is supposed to verify the client by checking the AUTH payload's signature against the certificate the client supplied.
The vulnerable handler runs inside the IKE_AUTH exchange, on the IDENTIFICATION_INITIATOR payload, before the AUTH payload's signature has been validated against the cert. The data that names the client is parsed and copied into a stack buffer before any proof-of-identity check fires. WatchGuard's own advisory WGSA-2025-00015 confirms the reachability shape: "An Out-of-bounds Write vulnerability in the WatchGuard Fireware OS iked process may allow a remote unauthenticated attacker to execute arbitrary code."
In Firebox's deployment model, "remote unauthenticated attacker" is a synonym for "anyone on the internet who can route a UDP packet to the appliance." Mobile User VPN and Branch Office VPN with IKEv2 dynamic gateway peer terminate IKE on the public interface by design. The threat model that allows the bug to be reached is the threat model the product is sold under.
The vendor ID is the recon payload
Here is what an unsolicited IKE_SA_INIT to a vulnerable Firebox returns. The PoC parser:
# From the PoC packet handler, IKE_SA_INIT response parsing:
if len(vendor) > 32 and vendor[:8].hex() == 'bfc22e9856ba9936':
watchguard_data = base64.b64decode(vendor[32:].decode('ascii')).decode()
match = re.search(r"VN=([0-9\.]+) BN=([0-9]+)", watchguard_data)
if match:
FW_VERSION = match.group(1)
BUILD_NUMBER = match.group(2)
The first 8 bytes of the vendor ID (bfc22e9856ba9936) are WatchGuard's brand magic. The bytes after that are a base64-encoded ASCII string of the form VN=12.11.3 BN=719894. Version number, build number. Sent unsolicited, on every IKE_SA_INIT, before the handshake authenticates anything.
The PoC's exploit module is keyed on exactly that string:
class WatchGuardFw:
ADDRESSES = {
'12.11.3': {
'pop_rcx_ret': 0x4225ab,
'mov_rax_rcx_ret': 0x5a4fac,
'mov_rbp_rsp_call_rax': 0x42008d,
'pop_r13_ret': 0x594ac4,
'mov_rax_rbp_pop_rbx_pop_rbp_ret': 0x598d69,
'sub_rax_rcx_ret': 0x5a4fd8,
'push_rax_mov_rax_rbx_pop_rbx_ret': 0x5a4468,
'mov_rdi_rbx_call_rax': 0x42fce4,
'pop_rsi_ret': 0x508ece,
'pop_rdx_ret': 0x483a4a,
'mov_rax_rax_ret': 0x5b145e,
'jmp_rax': 0x41908f,
'jmp_rbx': 0x449ba3,
'offset_data': 0x00,
'offset_shellcode': 0x30,
'offset_stack': 0x340,
'offset_stack_page_aligned': 0x0cc8,
'offset_bind_mprotect': 0x5ea0,
'got_bind': 0x658028,
},
}
One firmware version. One fixed gadget table. The exploit calls WatchGuardFw.has(target_fw_version) to check whether a build is supported, and reads wg.get('pop_rcx_ret') to assemble the chain. The lookup is deterministic because the lookup key arrives unauthenticated, in advance, in the same protocol the attacker is about to abuse.
This is the recon primitive defenders normally make attackers earn. Banner-grabbing, retry-and-time, error-message harvesting, version-fingerprint heuristics, every one is an opportunity for the gateway to refuse to answer or answer ambiguously. iked answers cleanly. The build number is a fielded value in a documented vendor ID block, sent on every IKE_SA_INIT, in the same UDP exchange the attacker is going to use to reach the bug.
The build is unmitigated
The gadget table reads 0x4225ab, 0x5a4fac, 0x594ac4, 0x508ece. These are absolute virtual addresses inside the iked binary. They are stable across reboots. They are stable across deployments of the same build. They mean iked is not compiled with PIE.
The PoC takes that absence as load-bearing. Every gadget address in the chain is the address you find on every Firebox running 12.11.3, so the chain that works on one box works on every box. The version-leak channel and the unmitigated build compose: one fingerprint, one gadget table, one ROP chain, and the only branch in the exploit is the if version not in self.ADDRESSES early-out for builds the attacker hasn't pre-computed yet.
The pattern is unmitigated-binary in our catalog. It surfaced earlier this year on ASUSTOR's vpnupload.cgi: "Eight identical bugs in one function is not a bug count. It is a parser style". The shape is the same. The build flags are the substrate that decides whether memory-corruption bugs are RCE. The watchTowr exploit lifting cleanly from a static gadget table, instead of needing to leak a base address first, is the substrate's confession.
The patch closes the overflow. It does not close the channel.
WatchGuard patched CVE-2025-9242 in their September 2025 release. watchTowr published the writeup in mid-October. CISA added it to the Known Exploited Vulnerabilities catalog on November 12, 2025, citing active exploitation. As of that date, Shadowserver counted 54,300 Fireboxes still vulnerable, down from 75,955 in mid-October. Six weeks of patch availability bought roughly 21,000 patched appliances and left 54,000 not.
The patch is the magic-number check above. It is correct. It is also the only thing the patch changes.
The vendor-ID block that tells the attacker which build the device is running is not modified. The lack of PIE in iked is not modified. The pre-cert reachability of the IDENTIFICATION_INITIATOR handler inside IKE_AUTH is not modified.
Two months after CVE-2025-9242 was patched, WatchGuard disclosed CVE-2025-14733: another out-of-bounds write in iked, also pre-authentication, also exploitable from the public interface. Same daemon. Same broad reachability surface. The recon primitive that fed the 9242 exploit is in place to feed the 14733 exploit, on every Firebox that has not been rebuilt with PIE and a vendor-ID block that does not announce the build.
11.x is end-of-life. WatchGuard explicitly does not patch 11.x. Devices on 11.10.2 through 11.12.4_Update1, listed by WatchGuard themselves as affected, will remain vulnerable to CVE-2025-9242 until they are decommissioned. The vendor-ID block on those devices announces a build that has no available fix, on a binary with no mitigations, exposing a daemon that has now had at least two pre-auth memory-corruption CVEs in a quarter.
This is security-tool-as-primitive with a particular shape. The Firebox's defensive role is to terminate IPsec from the public internet, gating remote users into the enterprise's internal network. That role is the attack surface. The same UDP/500 listener that serves remote-access VPN to authorized employees serves CVE-2025-9242 to anyone with a Python interpreter and the watchTowr repository. Fortinet's fgfmd had the same property when CVE-2024-47575 inverted FortiManager into the attacker's policy-push primitive. The deployment model that makes the appliance valuable is the deployment model that makes the bug carry.
The Firebox is configured, by its own protocol implementation, to volunteer the information an attacker uses to weaponize any future iked CVE. That is not a side effect of CVE-2025-9242. It is the substrate that makes any iked memory-corruption bug ship-ready on the day the CVE drops.
PoC: projectdiscovery/nuclei-templates, based on watchTowr's Detection Artifact Generator.
The patch closes the overflow. It does not close the channel that handed the attacker the build.