Appliance Mode Was Not the Boundary

Appliance Mode Was Not the Boundary

On September 22, 2026, F5 published advisory K000162605 for a heap overflow that lets an unauthenticated attacker run code inside the Traffic Management Microkernel of a BIG-IP system running Access Policy Manager. It carries CVSS 3.1 base 9.8 and CVSS 4.0 base 9.3, both Critical, on a vector of network reachable, low complexity, no privileges and no user interaction. CISA added it to the Known Exploited Vulnerabilities catalog the same day, with a federal due date three days later and forensic triage flagged.

The line worth reading twice is not the score. It is this one, from the CVE record: “The BIG-IP system in Appliance mode is also vulnerable. This is a data plane issue; there is no control plane exposure.”

Appliance mode is the profile an operator turns on when a BIG-IP faces untrusted networks. It is also, for this vulnerability, irrelevant, and the reason is structural rather than embarrassing. The thing that failed was not the administrative surface. It was the request path in front of the authentication decision.

What the record actually says

The vulnerability is assigned CVE-2026-94127, CWE-122 heap-based buffer overflow, published by F5’s own CNA at 15:17 UTC on 2026-09-22 and updated the next day. The credit line reads “F5 finder” and the record references a single external source, the vendor advisory, which matches Kudelski Security’s summary that the issue was found internally rather than reported from outside.

The precondition is a configuration, not a product:

FieldValue
Affected productBIG-IP APM only, when configured as an OAuth Authorization Server
PreconditionAn APM access policy and an OAuth profile on the same virtual server
Not affectedAPM used strictly as OAuth Client or Resource Server
Affected 21.x21.1.0, fixed in Hotfix-BIGIP-21.1.0.2.0.30.22-ENG
Affected 17.x17.5.0 through 17.5.1 and 17.1.0 through 17.1.3, fixed in Hotfix-BIGIP-17.5.1.9.0.160.12-ENG and Hotfix-BIGIP-17.1.3.5.0.41.14-ENG
ExposureData plane only, unauthenticated, no control plane exposure
Out of scopeVersions past End of Technical Support are not evaluated

The fix ships as engineering hotfixes rather than a point release, and “not evaluated” is doing real work in that last row: for anyone running a version past end of support, absence from the affected list is not a statement about their build.

There is also a radar-side claim worth separating from the record. The account that surfaced this story on X, @The_Cyber_News, posted on September 23 that “F5 published advisory K000162605 on September 22, 2026, after learning that attackers had already weaponized the issue,” and gave the internal tracking ID 2524777. The tracking ID checks out against Kudelski’s advisory summary, which lists F5 Product Development ID 2524777. The “weaponized” framing is that account’s characterization. F5’s own wording, as reproduced by Kudelski, is narrower: “We have learned that this vulnerability has been exploited.” Confirmed: the vendor says it was exploited, and CISA listed it. Alleged: any specific campaign, tooling or actor behind it.

The check ran after the copy

The mechanism is documented in watchTowr Labs’ writeup, which patch-diffed tmm64 between BIG-IP 21.1.0 build 0.0.38 and the fixed build 0.30.22. This is researcher reproduction, not a vendor-confirmed account of the in-the-wild chain, and the difference matters enough to say out loud.

The vulnerable handler is the OAuth UserInfo path, reached at /f5-oauth2/v1/userinfo by default, a value that comes from the OAuth profile’s configurable userinfo-url setting. In the decompiled handler, TMM allocates a fixed buffer, reads the Authorization header length out of parsed header metadata, and copies that many bytes into the buffer:

v8 = (char *)umalloc(0x4100, 66, 0);   // 16,640 byte heap buffer
v20 = (unsigned int)(v19 - v18);       // length from parsed header metadata
if ( memcpy_wrapper(v72, v81, v82, v8, v20, 0) == v20 )
{
  if ( v20 <= 6 || memcmp(v8, "Bearer ", 7u) )   // the prefix check, after the copy

The patched binary adds one comparison ahead of the copy, with its own error string:

if ( v20 > 0x4100 )
{
  v15 = 5;
  v26 = 29;
  v27 = "Authorization header too big.";

Two details do the damage. First, the bound is exact and the overrun is cheap: a Bearer prefix plus 16,634 ASCII bytes totals 0x4101, one byte past the end of the allocation. Second, the check that rejects a non-Bearer header runs on the buffer after the bytes have already been written into it. The handler validated the content of a header it had not yet bounded.

What follows in the watchTowr reproduction is a chain that is long only in its middle. The overflow corrupts heap metadata, ufree hits an assert and the process crashes. On the target build, system wide ASLR is on, NX is on, and there is no executable heap or stack, but the binary is not PIE, so gadget addresses are stable. In roughly 90 percent of their runs, an object carrying a function pointer landed just past the buffer at buffer + 0x4ff8; overwriting that callback turned a call r9 into a jump into attacker-chosen instructions, and a stack pivot set up the arguments for a return-oriented chain.

SELinux stopped the obvious ending. execvp("/bin/sh") returned -1, errno=13 (EACCES), and the web server was confined too, so dropping a shell failed. The chain instead called open() and write() on a file the platform runs itself:

fd = open("/etc/bigstart/scripts/tmm.finish", O_WRONLY | O_APPEND);
write(fd, "/usr/bin/touch /watchTowr.txt;", 30);

tmm.finish is a bash script BIG-IP invokes when TMM crashes. The next crash runs it, with the appended command inside, and the last hop from code execution to a command on disk is a lifecycle hook that exists for reliability.

Hardening the shell you can see

F5’s own description of Appliance mode (K12815) is about administrative access: it limits a BIG-IP to the access model of a typical network appliance, disables bash shell access, prevents root login by any means including console, and restricts administrative functions to the Configuration utility and tmsh. That is a control over the interactive surface, and it is a good one.

Put it next to the exploitation path and the two facts read as one sentence: Appliance mode takes the administrator’s shell away, and the chain gets a script interpreter anyway, through a file the platform executes on crash. This is inference from the two sources rather than a claim F5 makes, and it is the reason the CVE text says what it says. Hardening what a logged-in operator can do does not shrink what reachable, unauthenticated parsing code can reach, and the execution surface that remains is the one nobody audited, because it was built as plumbing rather than as an interface.

Exposure was a topology question, not a version

No scanner query returns “the virtual servers that carry both an APM access policy and an OAuth profile with APM acting as the authorization server”. Product and version data answer a different question, and the answer for this bug is wider than the truth. The check that decides whether you are exposed is two bindings on the same object, which means it is answered from the configuration, not the inventory. The operator shortcut published by hol.org’s fix summary is the shape of it:

tmsh show sys version
tmsh list ltm virtual one-line | grep -E 'profiles|access-policy|oauth'

Any virtual server that shows an access policy and an OAuth profile together is in the blast radius until it holds the matching engineering hotfix. Grepping a UCS or SCF export for oauth profiles attached beside APM access policies is the same check without an interactive session, and removing one of the two bindings is a real reduction in exposure if a change window is not available.

The remediation assumes the box was already reachable

The CISA KEV entry was added the day the advisory published, due three days later, with forensicTriage set to Yes and ransomware use unknown. Its notes say to apply the vendor-provided iRule for temporary mitigation “to allow for proactive forensic triage”, then install the final vendor patch as soon as possible.

Read the ordering. The iRule is not primarily a way to hold the perimeter while a change window is scheduled. It is a way to stop the bleeding so that evidence survives long enough to be collected. Patch an unauthenticated memory corruption bug on an appliance that was reachable from the internet and you have closed the door. You have not undone the visit.

F5’s indicator pattern, reproduced by Kudelski and by RedLegg’s bulletin, is deliberately a combination rather than a single alarm. Repeated UserInfo failures in /var/log/apm, ten or more in one log, especially from a single address, with invalid_token errors, followed by suspicious commands and then a TMM SIGABRT, is the sequence that warrants human review. The counters and the core files are cheap to check:

tmctl global_oauth_stat -s total_requests,total_userinfo_requests,total_failed

An unexplained rise in total_failed, audit entries around those timestamps, and the presence of TMM core files are the three signals F5 says should be read together. Any one alone is noise. The combination close in time is the reason to assume a breach and work backwards.

The blast radius question is not only “what could that process read”. An authorization server holds the material that makes trust work, and per watchTowr’s framing of the platform, this class of device sits at the edge, terminates TLS and sees the traffic that flows through it in the clear. Whatever mints tokens also holds the keys and secrets those tokens depend on. That makes key rotation and token invalidation part of the incident response rather than a follow-up task.

Why this is agent infrastructure

Agent deployments are moving identity outward. MCP servers, agent gateways and service accounts increasingly authenticate through an OAuth authorization server, which turns the token issuer into a shared dependency of everything that reads a token from it. A pre-auth memory bug in that component is not one application’s perimeter problem. It is a control plane problem for every agent whose trust starts there, and the failure arrived at the one point in the loop that is supposed to be in front of the gate.

The patch itself is the lesson agent builders keep relearning. The fix is one comparison placed before the copy, and the vulnerability is what the code did when the comparison was not there. That ordering rule is the same one that decides whether an agent loop is safe: validate the input before you act on it, not after. A check that runs behind the operation it is meant to guard is not a weaker control. It is decoration with a stack trace.

Appliance mode is a control over who can log in and what they can run from a shell, and it is worth having. CVE-2026-94127 was reachable by anyone who could send an HTTP request to an SSO virtual server, and the execution it reached did not need a login. The boundary that failed was the one in the request path, one comparison wide, in the component that decides who gets a token.

Sources

Keep reading