← Back to Attack Research

A perfect storm to root: unauthenticated deserialization RCE in Cisco ISE (CVE-2025-20281 / CVE-2025-20337)

Two companion flaws on one Cisco ISE API endpoint turn a single unauthenticated request into root on the identity system every switch, VPN, and wireless controller in your network trusts. Both are CVSS 10.0, both are in CISA KEV, and Cisco confirmed active exploitation. Here is the full chain, from the exposed pre-auth method to a shell as root, and what closes it.

The Zero Day Initiative researcher who found it called it a perfect storm, and that is exactly right. A single API endpoint on Cisco Identity Services Engine answers before authentication, hands attacker-controlled bytes to a Java deserializer, and lets the resulting data flow into an operating-system command that runs as root. No credentials. No configuration prerequisite. One request from the network to a shell on the box that arbitrates who is allowed onto your network at all. That is CWE-502 (Deserialization of Untrusted Data) feeding CWE-78 (OS Command Injection), tracked as CVE-2025-20281 and CVE-2025-20337, both scored CVSS 10.0.

Cisco Identity Services Engine, or ISE, is the policy brain of a lot of enterprise networks. It is the RADIUS and TACACS+ server that decides whether a laptop, a phone, a switch admin, or a VPN session is allowed on and what it is allowed to reach. ISE-PIC, the passive-identity companion, feeds the same policy plane. When a bug lets an unauthenticated attacker run code as root on that appliance, the attacker does not have a foothold near your network. They have a foothold inside the thing that defines your network's trust. This dossier walks the chain the public research describes, explains why it took two CVEs and two patches to close, and lays out how to determine your own exposure without firing a single payload. Verifiable security.

The affected surface, stated precisely

The flaws affect Cisco ISE and ISE-PIC releases 3.3 and 3.4, regardless of configuration. That last clause matters. There is no non-default toggle that has to be enabled, no optional persona that has to be running. If you run an affected release with the API reachable, you are exposed. Cisco's fix is ISE 3.3 Patch 7 and ISE 3.4 Patch 2. The ordering is the interesting part: the first advisory addressed CVE-2025-20281, researchers then bypassed that fix through the deserialization path, and CVE-2025-20337 was assigned to cover the bypass. The later patch is the one that fully closes the second CVE. A partially patched appliance, one that took the first fix but not the second, is still reachable.

Both CVEs carry a CVSS base score of 10.0, the maximum. There is a related third issue, CVE-2025-20282, an arbitrary-file-write variant on a different internal API that lets an attacker plant a file and execute it as root. It is a distinct bug with its own root cause but lands in the same patch train and the same threat model, so we note it for completeness and keep the focus on the 20281 / 20337 pair, which share one endpoint and one story. All three are in the CISA Known Exploited Vulnerabilities catalog, added 28 July 2025, and Cisco has confirmed exploitation in the wild. AWS threat-intelligence reporting later tied CVE-2025-20337 to an advanced actor that chained it alongside a Citrix zero-day, which tells you the class of adversary this is worth defending against.

Root cause: a pre-auth method that should never have been reachable

The Zero Day Initiative writeup traces the entry point to a method named enableStrongSwanTunnel(), invoked through a class called DescriptionRegistrationListener. The method is reachable through an exposed API endpoint, and here is the defining problem: it answers before authentication and performs no meaningful validation of the input it accepts. That is the whole ballgame for this class of bug. The endpoint exists to accept input from callers, it is one of the surfaces that talks to the network by design, and it takes a caller's data into internal logic without first proving the caller is anyone at all.

An unauthenticated, exposed, input-accepting method is not automatically a vulnerability. It becomes one when the input reaches something dangerous. The reason the ZDI researcher reached for the phrase perfect storm is that every condition lined up: the method was pre-auth, the input was attacker-controlled, validation was absent, and the downstream handling turned that input first into a deserialized Java object and then into an operating-system command. Remove any one link and there is no root shell. All four were present.

Link one: the deserialization primitive (CVE-2025-20337)

The endpoint does not treat the incoming request as an inert blob. On the path from the pre-auth method, attacker-supplied bytes are handed to a Java object deserializer, which reconstructs a live object from the wire format. Java deserialization is one of the most durable footguns in enterprise software because deserializing untrusted bytes is not merely parsing data, it is executing the reconstruction logic that the byte stream describes. An attacker who controls the bytes controls what gets built and, depending on the classes available on the server's classpath, what runs during that construction.

In this chain the attacker crafts an object, for example a String[], whose values they fully control, and gets the server to deserialize it. That gives the attacker a controlled data structure sitting inside the application at a point where the application then goes on to use those values. The deserialization step is the pivot that converts bytes on a socket into attacker-chosen arguments inside a trusted process. On its own, deserializing a string array is not yet code execution. The severity comes from where those attacker-chosen strings flow next.

CVE-2025-20281 + CVE-2025-20337 : ONE ENDPOINT, ONE CHAIN unauth request --> [ exposed API endpoint ] no auth, no validation | | | enableStrongSwanTunnel() reachable PRE-AUTH | DescriptionRegistrationListener v | attacker bytes --> [ Java deserializer ] --------> attacker-built String[] (20337) | v [ Runtime.exec( ... ) ] <-------- args tokenized here | ${IFS} defeats arg-splitting defense (20281) | v arbitrary command ==> ROOT in the ISE container

The two CVEs describe two links of the same chain: a deserialization primitive that lands attacker-controlled data, and a command-injection sink that runs it as root. Illustrative of the public research, not a payload.

Link two: from deserialized data to root (CVE-2025-20281)

The strings the attacker landed through deserialization do not stay data. They flow into a command-execution path, ultimately reaching a Runtime.exec() call, the Java API for spawning an operating-system process. When user-controlled values reach a command sink, you have the classic command-injection question: can the attacker break out of the intended argument and have the shell interpret their input as something more than a single token?

The intended defense here is argument tokenization, splitting the command into discrete arguments so that a value cannot introduce a new command or a shell metacharacter that changes the meaning of the line. The researcher defeated that defense with one of the oldest tricks in the injection playbook: the ${IFS} shell variable. IFS is the Internal Field Separator, the character the shell uses to split a line into words, whitespace by default. When a naive tokenizer strips or filters literal spaces, an attacker can substitute ${IFS} where a space would go, and the shell expands it back into a field separator at execution time. The filter sees no space and lets the value through; the shell reconstitutes the separation and runs the attacker's words as distinct arguments. That is enough to turn a constrained Runtime.exec() into arbitrary command execution.

Because the ISE application process runs with high privilege inside its container, the injected command executes as root. There is no privilege-escalation second act to write. The position was already privileged the moment the endpoint answered. Unauthenticated request in, root command out, on the identity appliance.

The incomplete-fix story: why it took two CVEs

This is a clean, instructive case of an incomplete fix, and it is worth studying because the pattern recurs across the industry. Cisco's first remediation addressed CVE-2025-20281 by closing the command-injection surface, the sink side of the chain. That is a reasonable first move: block the dangerous characters, tighten the tokenization, stop the ${IFS} trick from reaching the shell. But the fix treated the symptom at the sink rather than the disease at the source.

Researchers came back through the deserialization path. The endpoint was still deserializing untrusted input pre-auth, which meant the attacker still controlled data inside the process; the bypass reached command execution by a route the first patch had not fully accounted for. Cisco assigned CVE-2025-20337 to the bypass and shipped a second patch, which is why full closure requires the later build, ISE 3.3 Patch 7 or ISE 3.4 Patch 2, and not merely the first fix.

Patch the sink and you close a symptom. Patch the pre-auth deserialization and you close the disease. This chain needed both, in that order, which is why it needed two CVEs.

The lesson for defenders is not to assume that the presence of a patch means the presence of safety. When a vulnerability is a chain, a fix that severs one link can leave the others intact and reachable by a slightly different route. The durable fix for a deserialization-fed command injection is to stop deserializing untrusted input on a pre-auth surface at all, or to authenticate before the dangerous handling runs. Everything downstream of that decision is a filter, and filters get bypassed.

Why the blast radius is catastrophic

Severity is not just the CVSS number. It is where the compromised host sits. ISE is the identity chokepoint. It answers the RADIUS and TACACS+ requests that gate 802.1X network access, administrative logins to switches and routers, VPN authentication, and wireless association across the enterprise. It commonly integrates with the directory that holds every corporate identity. Root on ISE is not a server compromise in the ordinary sense; it is control of the machine that decides who is trusted and, in many designs, the credentials and policy that decision rests on.

From that position an attacker can mint or alter access policy, harvest the secrets ISE holds to talk to network devices and directories, pivot laterally by authorizing themselves onto segments that should be closed, and do all of it wearing the identity plane's own authority so that the activity looks, to everything downstream, like ISE working normally. That an advanced actor was reported chaining CVE-2025-20337 with a Citrix zero-day is consistent with this being a high-value beachhead in a larger intrusion, not a smash-and-grab.

Detection: what to hunt for

Because this is unauthenticated and pre-auth, you cannot rely on failed-login telemetry. Hunt at the process and file layer on the appliance and at the request layer in front of it.

What to do about it

Close CVE-2025-20281 / CVE-2025-20337 on your ISE estate

How Celvex catches this

Find. Prove. Fix. Verify.

Find

A read-only sweep maps your internet-facing and management-plane appliances, fingerprints the ISE release and patch level, and cross-references it against the known-exploited catalog, flagging exposed 3.3 and 3.4 nodes without sending a single malicious payload.

Prove

A confirmed exposed, catalog-listed node becomes an Ed25519-signed Proof Capsule carrying the host, the version and patch evidence, and the matching CVE-2025-20281 / 20337 entries, reproducible offline by you or your auditor.

Fix

The capsule's remediation block names the steps: apply ISE 3.3 Patch 7 or 3.4 Patch 2, confirm the second patch closed CVE-2025-20337, and pull the admin and API plane off any untrusted network.

Verify

A fresh sweep confirms the node is on the fully fixed build and no longer reachable from untrusted space. The finding closes and the verified-fix event is recorded for the audit trail.

The reason chains like this one land is rarely that the defenses are unknown. Deserialization of untrusted data and shell-metacharacter injection are two of the most documented weaknesses in the field. They land because a security appliance is filed under trusted infrastructure, so the pre-auth surface that would get hard scrutiny on a public web app gets waved through on the identity server. The discipline is to apply the boundary question everywhere a stranger's input can reach an interpreter, and to keep applying it after a patch ships, because a chain closed at one link may still be open at another. Ask, of every trust anchor: which endpoints answer before login, what do they hand the input to, and at what privilege does that run.

Verifiable security. Find it. Prove it. Fix it. Verify the fix held. That is what we ship.

Sources

Is your identity plane on a patched, unreachable build?

Free Exposure Check, no signup required. We fingerprint your ISE and ISE-PIC nodes against the known-exploited catalog, tell you which are on affected 3.3 or 3.4 builds and reachable, and ship a signed Proof Capsule for the highest-confidence finding.

Run a Free Scan →