Citrix NetScaler – Reported Unpatched RCE Zero-Days Under Active Exploitation

Threat Overview

On September 27, 2026, The Hacker News reported that watchTowr had identified two previously undisclosed remote-code-execution vulnerabilities affecting Citrix NetScaler ADC and NetScaler Gateway during forensic investigations. watchTowr characterized both flaws as unpatched and exploited before fixes existed. As of the report, Citrix had not published a security bulletin, assigned CVEs, confirmed affected builds, disclosed exploit paths, or released indicators of compromise. This is a high-consequence edge-device warning, but the available public evidence remains incomplete. The report should therefore drive emergency exposure validation and compromise assessment, not unsupported signature claims.

References

Impacted Systems

  • Vendor/product: Citrix NetScaler ADC and NetScaler Gateway.
  • Role: Internet-facing application delivery, VPN/remote access, load balancing, and authentication edge appliances.
  • Deployment: Physical MPX, SDX-hosted VPX, and standalone VPX may be relevant; the public report does not confirm an affected deployment model.
  • Affected versions/builds: Not confirmed. Citrix had not stated whether August builds 14.1-73.32 and 13.1-63.21 or later builds are affected.
  • Exploit prerequisite: Reported as remote code execution; authentication and configuration prerequisites are not publicly confirmed.
  • Exposure: Internet-reachable NetScaler services present the highest concern. Management services should not be Internet-exposed.
  • Fixed versions: None confirmed as of September 27, 2026.
  • Distinction: These reported zero-days are separate from CVE-2026-19490, which Citrix fixed on August 19, 2026.

Why this matters

NetScaler appliances commonly terminate remote access and authentication at the enterprise perimeter. Successful code execution can expose credentials, session material, certificates, private keys, backend connectivity, and an opportunity to persist on a device that conventional endpoint agents may not monitor.

Exploitation Status

  • Researcher statement: watchTowr reported two RCE flaws discovered during forensic investigations and said both had been exploited before fixes existed.
  • Vendor status: Citrix had not confirmed the flaws or published patches, affected-version guidance, workarounds, or IOCs as of the reporting cutoff.
  • Evidence limitation: No victim, exploit request, file path, process name, source address, or artifact was publicly substantiated. Claims beyond reported active exploitation would be speculative.

What this hunt looks for

The hunt content is designed to review available Microsoft Sentinel telemetry for NetScaler log presence, new or rare administrative sources, suspicious appliance log terms, management-plane connections, unusual outbound appliance traffic, and authentication anomalies adjacent to NetScaler use.

Required logs

Conclusions are limited to data sources and retention available in Microsoft Sentinel. NetScaler appliance syslog, NetScaler Console logs, firewall/flow telemetry, and identity telemetry must be ingested for relevant visibility. Absence of matching activity does not confirm absence of exploitation, especially because the exploit path and IOCs are not public and the appliance may not forward forensic artifacts.

1. First Pass – NetScaler Appliance Log Activity and High-Risk Terms

let Lookback = 14d;
union isfuzzy=true
(
    Syslog
    | where TimeGenerated >= ago(Lookback)
    | extend Device=tostring(Computer), Message=tostring(SyslogMessage), SourceTable="Syslog"
),
(
    CommonSecurityLog
    | where TimeGenerated >= ago(Lookback)
    | extend Device=coalesce(tostring(DeviceName), tostring(DestinationHostName)), Message=strcat(tostring(Activity), " ", tostring(Message), " ", tostring(DeviceCustomString1), " ", tostring(DeviceCustomString2)), SourceTable="CommonSecurityLog"
)
| where Message has_any ("NetScaler", "Citrix ADC", "NSCONFIG", "ns.conf", "NITRO", "shell", "command", "unauthorized", "authentication failure", "login failed")
| project TimeGenerated, SourceTable, Device, Message
| order by TimeGenerated desc

2. New or Rare Source Addresses in NetScaler-Related Logs

let BaselineStart = ago(30d);
let RecentStart = ago(48h);
let Logs = union isfuzzy=true
(
    Syslog
    | where TimeGenerated >= BaselineStart
    | extend Message=tostring(SyslogMessage), SrcIP=extract(@"(?i)(?:src|source|client|remote)(?:ip)?[=: ]+([0-9]{1,3}(?:\.[0-9]{1,3}){3})", 1, SyslogMessage), Device=tostring(Computer)
),
(
    CommonSecurityLog
    | where TimeGenerated >= BaselineStart
    | extend Message=strcat(tostring(Activity), " ", tostring(Message)), SrcIP=tostring(SourceIP), Device=coalesce(tostring(DeviceName), tostring(DestinationHostName))
)
| where Message has_any ("NetScaler", "Citrix ADC", "Gateway", "AAA", "NITRO");
let Baseline = Logs | where TimeGenerated < RecentStart | where isnotempty(SrcIP) | summarize by SrcIP;
Logs
| where TimeGenerated >= RecentStart and isnotempty(SrcIP)
| join kind=leftanti Baseline on SrcIP
| summarize FirstSeen=min(TimeGenerated), LastSeen=max(TimeGenerated), Events=count(), Devices=make_set(Device, 20), Samples=make_set(Message, 5) by SrcIP
| order by Events desc

3. NetScaler Administrative or Configuration Events

union isfuzzy=true
(
    Syslog
    | where TimeGenerated >= ago(14d)
    | extend Device=tostring(Computer), Message=tostring(SyslogMessage), SrcIP=extract(@"([0-9]{1,3}(?:\.[0-9]{1,3}){3})", 1, SyslogMessage)
),
(
    CommonSecurityLog
    | where TimeGenerated >= ago(14d)
    | extend Device=coalesce(tostring(DeviceName), tostring(DestinationHostName)), Message=strcat(tostring(Activity), " ", tostring(Message), " ", tostring(DeviceCustomString1), " ", tostring(DeviceCustomString2)), SrcIP=tostring(SourceIP)
)
| where Message has_any ("NITRO", "CONFIG", "COMMAND", "CLI", "SSH", "SCP", "SFTP", "ns.conf", "add system user", "save ns config", "shell")
| project TimeGenerated, Device, SrcIP, Message
| order by TimeGenerated desc

4. Connections to NetScaler Management Interfaces

let NetScalerIPs = dynamic(["REPLACE_WITH_NETSCALER_IP"]);
CommonSecurityLog
| where TimeGenerated >= ago(14d)
| where DestinationIP in (NetScalerIPs)
| where DestinationPort in (22, 80, 443, 3010)
| summarize FirstSeen=min(TimeGenerated), LastSeen=max(TimeGenerated), Connections=count(), Actions=make_set(DeviceAction, 10) by SourceIP, DestinationIP, DestinationPort, DeviceVendor, DeviceProduct
| order by Connections desc

5. Unusual Outbound Traffic From NetScaler Appliances

let NetScalerIPs = dynamic(["REPLACE_WITH_NETSCALER_IP"]);
let BaselineStart = ago(30d);
let RecentStart = ago(48h);
let Baseline = CommonSecurityLog
| where TimeGenerated between (BaselineStart .. RecentStart)
| where SourceIP in (NetScalerIPs)
| summarize by DestinationIP, DestinationPort;
CommonSecurityLog
| where TimeGenerated >= RecentStart
| where SourceIP in (NetScalerIPs)
| join kind=leftanti Baseline on DestinationIP, DestinationPort
| summarize FirstSeen=min(TimeGenerated), LastSeen=max(TimeGenerated), Connections=count(), BytesOut=sum(tolong(SentBytes)), Actions=make_set(DeviceAction, 10) by SourceIP, DestinationIP, DestinationPort
| order by Connections desc

6. Authentication Failures Followed by Success From the Same Source

let Window = 30m;
let Failures = Syslog
| where TimeGenerated >= ago(14d)
| where SyslogMessage has_any ("NetScaler", "AAA", "Gateway")
| where SyslogMessage has_any ("fail", "denied", "invalid")
| extend SrcIP=extract(@"([0-9]{1,3}(?:\.[0-9]{1,3}){3})", 1, SyslogMessage)
| where isnotempty(SrcIP)
| project FailureTime=TimeGenerated, Computer, SrcIP, FailureMessage=SyslogMessage;
let Successes = Syslog
| where TimeGenerated >= ago(14d)
| where SyslogMessage has_any ("NetScaler", "AAA", "Gateway")
| where SyslogMessage has_any ("success", "accepted", "authenticated")
| extend SrcIP=extract(@"([0-9]{1,3}(?:\.[0-9]{1,3}){3})", 1, SyslogMessage)
| where isnotempty(SrcIP)
| project SuccessTime=TimeGenerated, Computer, SrcIP, SuccessMessage=SyslogMessage;
Failures
| join kind=inner Successes on SrcIP, Computer
| where SuccessTime between (FailureTime .. FailureTime + Window)
| summarize FailedAttempts=count(), FirstFailure=min(FailureTime), SuccessTime=min(SuccessTime), FailureSamples=make_set(FailureMessage, 3), SuccessSamples=make_set(SuccessMessage, 3) by Computer, SrcIP
| order by FailedAttempts desc

Detection Notes

  • Query 1 is the fastest inventory and triage pass; it is not an exploit signature.
  • Queries 4 and 5 require the actual NetScaler IP addresses to replace the placeholder.
  • Raw NetScaler messages vary by firmware, feature, and connector. Validate local message formats before relying on parsed source addresses or success/failure keywords.
  • The absence of published exploit paths and IOCs sharply limits signature-based detection. Appliance snapshots, support bundles, core dumps, and local filesystem review are outside Sentinel unless their results are ingested.
  • Expected noise includes legitimate administrators, configuration automation, vulnerability scanners, health checks, and normal backend traffic.