Research · Incident

Detecting NetScaler log poisoning before the web shell hides as CSS

Last reviewed:

Join NetScaler access-log staging, ns.log command fragments, CSS-like web-shell traffic and downstream identity use, and preserve the evidence before patching removes it.

In this research
  1. Contribution
  2. The pattern
  3. Why it matters to cloud defenders
  4. ATT&CK mapping
  5. Detection guidance
  6. What to do now
Research snapshot
Type
Incident
Reviewed
2026-10-02
ATT&CK
T1190, T1059

Contribution

This post adds an original three-part detector for the NetScaler zero-day activity: pair the poisoned access-log entry with the later ns.log command fragment, confirm the resulting Apache configuration or web-shell access, then review use of every identity and certificate exposed through the appliance. Unit 42 publishes a query for the command fragment, GreyNoise publishes the attempted persistence playbook, and Citrix explains which secrets and connected systems need investigation. The added value is the join between them, run in an order that preserves evidence before patching, and a way to distinguish an exploit attempt from established persistence without depending on attacker IP addresses.

The pattern

On 27 September 2026 Citrix disclosed eight vulnerabilities, CVE-2026-88771 to CVE-2026-88778, in NetScaler ADC and NetScaler Gateway. Two were already being exploited. Citrix's bulletin describes CVE-2026-88771 as unauthenticated command execution through improper input validation, affecting all deployments including default configurations. CVE-2026-88772 is a memory overflow that can lead to remote code execution or denial of service when DTLS is enabled, which Citrix notes is the default on VPN virtual servers. Fixed builds are 14.1-73.37 and 13.1-64.23 or later, with 14.1-73.37 for 14.1-FIPS and 13.1-37.279 for 13.1-FIPS and NDcPP. CISA reports exploitation globally and added both CVEs to the Known Exploited Vulnerabilities catalogue the same day.

The activity started well before disclosure. Unit 42's threat brief traces version fingerprinting back to 21 August, with requests for /admin_ui/common/css/ns/ui.css and /vpn/js/rdx/core/lang/rdx_en.json.gz, and web-shell retrieval from 4 September. GreyNoise recorded a sensor being exploited on 24 September by 149.104.78.141, which moved from login-panel crawling to command injection within seconds. GreyNoise labelled the source suspicious, then malicious, from behaviour alone, and only tagged the stored sessions as CVE-2026-88771 retrospectively once a CVE-specific detector existed on 27 September. A defender who keeps raw edge sessions can apply the same idea: retain suspicious login activity and re-query it when a vendor discloses a new flaw.

Unit 42 separated two web-shell delivery paths. The DTLS path placed malicious .deb files in /vpn/scripts/linux/, a directory normally used for Citrix client installation packages. The second path turned two ordinary NetScaler logs into a delayed command-execution mechanism, in three stages. First, the attacker sends an HTTP request with a Base64-encoded dropper in the User-Agent header. NetScaler returns a 404, but writes the header to /var/log/httpaccess-vpn.log. Second, a crafted login request causes text after pitboss PPE missed too many heartbeatsNSPPE; to enter /var/log/ns.log; note there is no space before NSPPE, and variants such as PPE unexpectedly died and NSPPE-00; also occur. Third, the vulnerable Perl script /netscaler/ns_monuploadd_err.pl later extracts the text after NSPPE; and runs it as part of a shell command, which searches the access log for the staged Base64, decodes it, and pipes it to sh or php.

The exploit is therefore split in time. A WAF or analyst may see a harmless-looking 404 in one log and a failed login in another. Neither event needs to contain a complete payload. Execution only becomes clear when the two records are joined and the appliance's later processing step is understood.

The observed payload changed /bin/sh permissions, created /var/netscaler/logon/LogonPoint/custom/.ctxs.receiver, enabled PHP in Apache and patched /etc/httpd.conf. The patch exposed the hidden PHP file through /logon/LogonPoint/custom/receiver.min.css and an AliasMatch for receiver\.min\.[0-9a-f]+\.css, so a request for a stylesheet could execute commands. The shell required a fixed, per-implant CsrfToken value and carried URL-encoded commands in the NSC_TASS cookie, a name chosen to resemble normal NetScaler session cookies. GreyNoise saw the same playbook attempted on its sensor, including setting setuid and setgid on /bin/sh and killing httpd to load the new configuration; Unit 42's recovered payload used kill -HUP for a graceful reload. The attempt on the GreyNoise sensor failed, but the structure is a durable hunt target.

Unit 42 also analysed a PHP shell distributed as nsg64.deb. It took commands through custom HTTP headers and supported command execution, upload, file-size checks, and chunked download capped at 1,048,576 bytes per request. It escalated through the NetScaler SUID helper /var/netscaler/.ns_suidcmd. That gives the incident a clear payoff: local files and appliance-held secrets could be read or transferred after the public-facing exploit.

Infrastructure is useful for scoping but should not be the centre of the detector. Unit 42 observed VPS addresses, Cloudflare WARP egress and round-the-clock requests to /logon/LogonPoint/Authentication/GetUserName, and warned that activity after disclosure may not match the original indicators. A path, log relationship, configuration change or implausible cookie on a CSS request survives infrastructure rotation better than an IP blocklist.

Why it matters to cloud defenders

NetScaler often sits at the trust boundary between the internet and private applications. A VPX instance may run in a cloud virtual network while terminating VPN, load-balancing application traffic, or brokering authentication to LDAP, RADIUS, OAuth and AAA services. Compromising that boundary gives the attacker a place where authentication traffic, private keys, service credentials and routes to internal systems meet.

Citrix's compromise guidance treats the appliance as a credential-exposure boundary. It tells responders to change service-account passwords, RADIUS shared secrets, OAuth tokens, API keys and SNMP community names stored on the NetScaler. It also calls for revoking certificates and private keys, resetting accounts that authenticated through Gateway or AAA virtual servers, and investigating every connected authentication server, sensitive system, web tier and management jump host.

That advice creates a cloud detection problem even when the appliance itself is not a cloud service. A stolen OAuth token may next appear in Entra sign-in or SaaS audit data. An API key may be used against a cloud control plane. A certificate copied from the appliance may support a trusted connection after the NetScaler has been patched. The appliance log proves entry; the identity or cloud audit log proves whether the compromise crossed into another plane. Do not reverse that logic. An unusual cloud sign-in alone does not prove NetScaler exploitation. Use the appliance evidence to define the affected identities and the time window, then query those identities across the systems they can reach.

Patching is also an evidence event. CISA warns that applying updates may remove forensic visibility, and Unit 42 notes that patching does not remove persistence already created. A current version proves only that the software was updated. It does not prove that .ctxs.receiver was never created, that the web-server configuration was untouched, or that no administrative session was established before the update. Isolate the appliance from untrusted networks, preserve its state, then patch or rebuild. The distinction is between stopping exposure and overwriting evidence.

Virtual appliances add a recovery advantage and a trap. Citrix recommends recording system time, timezone and NTP settings, taking a VPX snapshot before isolation, and preserving remote syslog, NetScaler Console data, a technical support bundle and a packet-engine core dump. A snapshot keeps volatile evidence that disappears during an upgrade, but it can also keep attacker persistence if restored without scrutiny. Citrix recommends replacing and restoring a VPX instance, then rotating restored secrets and certificates, rather than assuming a firmware update cleans the host.

ATT&CK mapping

The entry maps to T1190, Exploit Public-Facing Application. Both vulnerabilities reach an internet-facing NetScaler service, and CVE-2026-88771 does so without authentication. The DTLS memory-corruption path and the HTTP log-poisoning path differ technically, but each turns exposed appliance traffic into code execution.

The command stage maps to T1059, Command and Scripting Interpreter. The observed chain decodes attacker-controlled text and pipes it to sh or php, and the web shell later passes operator commands to the shell. T1059 is listed in the metadata because it has a reproducible signal: the command fragment after NSPPE;, followed by interpreter execution or the resulting filesystem changes.

Persistence through .ctxs.receiver maps to T1505.003, Web Shell. The Apache Alias and AliasMatch directives make the implant reachable under a CSS-like path, while the PHP-engine change makes it executable. It stays in the narrative rather than the metadata because the a13e technique page does not yet have a hand-authored answer box. Treat the public URL and hidden backing file as one persistence object. The setuid change to /bin/sh is an attempted privilege mechanism alongside it.

The file-download feature maps to T1005, Data from Local System. It describes capability, not confirmed exfiltration for every victim. Evidence of download commands, repeated fixed-size responses or access to sensitive local paths is needed before stating that data was taken. Likewise, Valid Accounts or Steal Application Access Token may apply later if identity records show an exposed account or token in use. Citrix's rotation advice establishes risk, not observed use.

Detection guidance

Preserve first. Forward /var/log/httpaccess-vpn.log and /var/log/ns.log to storage outside the appliance, export remote syslog and NetScaler Console records, and capture the snapshot, support bundle and packet-engine state before changing the device. Hash every collected file and record the appliance's timezone and NTP state before correlating events. A compromised gateway is not a trustworthy archive. Normalise source IP, request path, method, User-Agent, account, result, raw message, appliance identifier and event time. Start the hunt window no later than 21 August.

The first detector looks for staging in the access log. Alert on long or high-entropy User-Agent values attached to 404 responses, especially strings that decode into shell or PHP text. Base64 alone is not enough because legitimate clients and scanners use encoded values. Raise confidence when the same appliance in a short window also produces an ns.log record containing a pitboss PPE or PPE unexpectedly died message followed by NSPPE; and non-empty text. Unit 42's own query keys on pitboss; a hit there shows log poisoning was attempted, not that it executed on a patched device.

Code block TEXT
stage_a = httpaccess-vpn where status = 404
          and user_agent has high_entropy_or_base64_shape
stage_b = ns.log where message matches
          "(pitboss PPE missed too many heartbeats|PPE unexpectedly died)\s?NSPPE(-00)?;.+"
alert when stage_a then stage_b on appliance_id within 30 minutes

Do not require the same source address. Reverse proxies, WARP, NAT or infrastructure changes can break that join. The appliance identifier and timing are the stable keys. Preserve the raw strings because decoding and shell metacharacter review happen after the initial match. GreyNoise also observed ${IFS} used in place of spaces and abrupt shifts from panel discovery to malformed login requests, both worth flagging in edge sessions.

The second detector asks whether execution or persistence followed. Check for the exact path /var/netscaler/logon/LogonPoint/custom/.ctxs.receiver and the SHA-256 published by GreyNoise, 6f5a2a452a7901323abd21879c6cecccb47c06aeeaccb1b467212f3b11e4b1e7. Then go structural: dot files under the custom LogonPoint tree, an Alias or AliasMatch mapping a .css route to a hidden file, PHP switched from engine off to engine on, changes to /etc/httpd.conf, setuid or setgid on /bin/sh, unexpected .deb files under /vpn/scripts/linux/, and an httpd kill or HUP outside an approved change. Compare the live configuration and portal tree with a known-good appliance on the same release. A setuid shell or the hidden receiver should trigger incident response on their own. Where raw appliance records reach a Splunk-style SIEM, this hunt groups the structural indicators by host:

Code block SPL
index=netscaler earliest=-60d
(
  ".ctxs.receiver" OR "receiver.min." OR
  ("AliasMatch" AND "LogonPoint/custom") OR
  ("/bin/sh" AND ("chmod" OR "setuid" OR "setgid")) OR
  ("httpd" AND ("kill" OR "HUP" OR "restart")) OR
  ("engine on")
)
| eval indicator=case(
    like(_raw,"%.ctxs.receiver%"),"hidden receiver",
    like(_raw,"%AliasMatch%"),"CSS alias",
    like(_raw,"%/bin/sh%"),"shell permission change",
    like(_raw,"%engine on%"),"PHP enabled",
    like(_raw,"%httpd%"),"web service reload",
    true(),"receiver route")
| stats min(_time) as first_seen max(_time) as last_seen
        values(indicator) as indicators by host
| where mvcount(indicators) >= 2

Replace the index with local values, and treat it as a hunt rather than a drop-in alert: raw command records may not reach remote syslog, and a compromised appliance can alter local logs, which is why file-system comparison stays an independent check.

Network and HTTP telemetry can catch the implant even when file monitoring is weak. Alert on requests to /logon/LogonPoint/custom/receiver.min.css or receiver.min.<hex>.css. A request for a stylesheet becomes high confidence when it carries an NSC_TASS cookie, a CsrfToken, non-browser timing, or a response that does not resemble CSS. Store request headers for this narrow path. For the DTLS route, review creation and retrieval of nsgclient18.deb, nsgser18.deb, nsgsupport.deb, nsgpackage64.deb, nsgbuild.deb or nsg64.deb from /vpn/scripts/linux/. Legitimate client packages live there, so compare hashes and package contents with the shipped build rather than trusting the filename.

The third detector crosses the appliance boundary. Build a list of LDAP service accounts, RADIUS secrets, OAuth tokens, API keys, certificates and users that authenticated through the affected virtual server. From the earliest suspicious appliance event until rotation completes, query identity and cloud logs for first-seen source networks, unfamiliar user agents, unusual token audiences, certificate authentication from a new host, or access to resources the principal rarely touches. Join those to new administrative and VPN sessions on the appliance in one timeline.

False positives differ by stage. Security scanners create malformed logins and odd headers. Administrators reload Apache or alter custom portal content. Citrix package updates change .deb files. Legitimate users generate new sign-in locations. The sequence is what separates these cases: staged encoded input, poisoned NSPPE; text, unapproved executable configuration, shell-like CSS requests, then identity use. A change ticket, expected file hash and known administrator are stronger tuning evidence than a path exclusion; never suppress all changes under the custom LogonPoint directory.

What to do now

  1. Identify every NetScaler ADC and Gateway, including cloud-hosted VPX instances, and compare each with the fixed builds in Citrix's bulletin. Do not rule out CVE-2026-88772 on the assumption that DTLS is off; it is on by default for VPN virtual servers.
  2. Move traffic away from exposed or suspect appliances where redundancy permits. Before changing a device, preserve the snapshot or hardware evidence, remote syslog, Console records, support bundle, packet-engine memory, system time, timezone and NTP settings.
  3. Hunt from 21 August onwards without relying on published IP addresses. Search the two logs for paired staging and NSPPE; records, compare /etc/httpd.conf and the custom portal tree with a clean build, check /bin/sh permissions, inspect the client-package directory, and search for requests to the CSS-like receiver paths. Re-query retained edge sessions with current detections.
  4. Treat a confirmed web shell or configuration change as root-level appliance compromise. Isolate the device and replace or rebuild it from known-good media. Patching closes the vulnerability but does not remove a web shell, altered Apache configuration, changed shell permissions or copied credentials.
  5. Rotate the trust material that passed through the appliance: LDAP and other service-account credentials, RADIUS secrets, OAuth tokens, API keys, local passwords and key-encryption keys. Revoke and replace certificates and private keys, and reset affected user accounts according to the authentication paths they used.
  6. Investigate connected systems and cloud identities from the first suspicious appliance event through completed rotation. Record whether each principal was used, from where, against which resource, and with which token or certificate. Keep suspected exposure separate from confirmed downstream use, but do not stop the investigation at the patched gateway.

01 ATT&CK references