Search

Hiding your NetScaler from mass exploitation with SNI

October 27, 2026

SNI or it didn't happen: Hiding your NetScaler from mass exploitation

TL;DR

  • CVE-2026-88771 and CVE-2026-88772 were exploited for up to three weeks before Citrix’s bulletin was released on September 27, 2026. CVE-2026-88779 followed a week later, also exploited before disclosure. When Citrix issued its warning, many appliances had already been compromised.
  • In cirosec’s incident response cases, attackers mass-scanned IP addresses and fired the exploits at every NetScaler that answered.
  • By default, a NetScaler SSL vserver answers anyone who connects to its IP address. It returns its certificate, and with it the hostname, and serves content for any SNI or Host
  • SNI-only certificate bindings with SNI enforcement in a custom SSL profile close this gap, optionally plus an SSL policy that resets every ClientHello without the correct hostname. An IP-based scanner then gets no certificate and nothing that looks like a NetScaler and moves on.
  • Verify the result with our open-source tool SNItch.
  • Caveat: Certificate Transparency logs publish your hostname. A new, non-obvious hostname with a wildcard certificate keeps it out.

Introduction

For up to three weeks this September, attackers exploited NetScaler appliances that no patch, no CVE and no detection rule could protect. When Citrix published bulletin CTX697096 [11] on September 27, 2026, its two critical remote code execution flaws were already in use as zero-days: CVE-2026-88771 (unauthenticated command injection [9]) and CVE-2026-88772 (pre-auth memory overflow in DTLS [10]) [1][13]. A week later, CVE-2026-88779 followed with bulletin CTX697174 [12]: a SAML memory overflow that crashed even freshly patched appliances, again exploited before disclosure [2].

eSentire saw CVE-2026-88771 exploited from September 5, Google GTIG and Mandiant date CVE-2026-88772 exploitation to early September [5]. Rapid7, GreyNoise and CrowdSec recorded attempts between September 20 and 24 [3][4][5]. Any NetScaler that was reachable from the Internet in that window had to be treated as potentially compromised. Where logging was left in the default state, the logs got rotated out before the investigation even started.

In incident response cases where cirosec was involved, the attackers swept entire address ranges and attacked anything around September 20 that looked like a NetScaler by directly contacting the IP address.

That, oddly, is good news, because you can do something about it. A NetScaler that only talks to clients who know its hostname gives an IP-based mass scan nothing to work with.

In this blog post, we show how to make a NetScaler reject every TLS connection without the correct hostname, how to verify this with SNItch and where the approach reaches its limits.

Your NetScaler talks to strangers

When a browser connects to gateway.example.com, it puts the hostname into the TLS ClientHello as the Server Name Indication (SNI, RFC 6066), and the server uses it to pick the certificate. A mass scanner connecting to X.X.X.X:443 sends no SNI or a random one.

A default NetScaler SSL vserver does not care. It completes the handshake, presents its default certificate and serves content, whatever SNI or Host header arrives. The certificate’s CN/SAN tells the scanner exactly what host it found. Shodan and Censys index this. So do attackers’ scanners. This is a common problem with many improperly configured Internet-facing servers.

You can see how an attacker sees an unhardened Citrix NetScaler host (X.X.X.28:443) in SNItch and Censys:

Category
Date
Navigation
Figure 1: In both cases, using just the IP as input, an attacker gets a valid response from the server. Hence the attacker can clearly see that the system is a Citrix NetScaler and on what domain it is located.

We tested the behavior in our lab on NetScaler CPX 13.0-90.7 and 14.1-73.30:

Client sendsDefault NetScaler
No SNI (connect to the IP)Handshake OK, certificate served, HTTP 200
Wrong SNI (wrong.example)Handshake OK, certificate served, HTTP 200
Correct SNI, different Host headerHTTP 200

Table 1: Behavior of a default NetScaler SSL vserver

Step 0: Switch to SSL profiles

NetScaler configures SSL in one of two modes. In legacy mode, every setting lives on the vserver. In default SSL profile mode, the settings move into profiles that vservers reference, while certificate bindings and SSL policies stay on the vserver. The NetScaler documentation describes profiles as a single point of SSL configuration for all related endpoints, so we use profile mode throughout. We verified the full setup on 14.1-73.30.

The switch affects every SSL vserver and service on the appliance. Switching back can also be tricky, as there is no simple command to disable profile mode again. Back up your configuration, schedule a maintenance window and check the documentation for your release:

set ssl parameter -defaultProfile ENABLED

Until you assign a custom profile, every SSL vserver falls back to the built-in

 ns_default_ssl_profile_frontend, 

which still enables TLS 1.0/1.1 and disables TLS 1.3.

Step 1: Bind certificates as SNI certificates only

The idea, as described by Johannes Norz [6], is simple: if the vserver has no default certificate, a client without a matching SNI gets nothing. The default certificate is any certificate bound to the vserver without -SNICert. In production, that is usually your gateway certificate, such as gw-rsa and gw-ec in our lab. If someone bound the self-signed ns-server-certificate for testing, it counts as well, so unbind every non-SNI certificate, not just the ones in our example. The files are located in /nsconfig/ssl/; the vserver binding decides which one is served.

All commands in this post use placeholder values: replace gateway.example.com, X.X.X.X, vs_tls and the certificate names gw-rsa/gw-ec with your own.

Create a front-end profile with SNI enforcement, assign it to the vserver and rebind the certificates with -SNICert:

# Frontend profile: SNI on, unknown SNI refused, Host header must match the SNI
add ssl profile sslp_fe_hardened -sslProfileType FrontEnd
set ssl profile sslp_fe_hardened -SNIEnable ENABLED -allowUnknownSNI DISABLED
set ssl profile sslp_fe_hardened -SNIHTTPHostMatch STRICT
set ssl profile sslp_fe_hardened -dropReqWithNoHostHeader YES

# Vserver: reference the profile, bind certificates as SNI certificates only
set    ssl vserver vs_tls -sslProfile sslp_fe_hardened
unbind ssl vserver vs_tls -certkeyName gw-rsa
unbind ssl vserver vs_tls -certkeyName gw-ec
bind   ssl vserver vs_tls -certkeyName gw-rsa -SNICert
bind   ssl vserver vs_tls -certkeyName gw-ec  -SNICert

RSA and ECDSA certificates can be bound side by side; NetScaler picks one per negotiated cipher.

The certificate binding is the control that does the SNI magic. But be aware: in our tests, a single non-SNI certificate bound next to the SNI certificates was sufficient: clients without SNI received it and got an HTTP 200 response, regardless of –allowUnknownSNI DISABLED or –defaultSni. We still set -allowUnknownSNI DISABLED as defense in depth. Check every Internet-facing vserver with show ssl vserver vs_tls: each certificate line must read Server Certificate for SNI.

-SNIHTTPHostMatch STRICT rejects requests whose Host header differs from the SNI with HTTP 400, even for another name on the same certificate (the default, CERT, allows other SANs). -dropReqWithNoHostHeader YES rejects HTTP/1.0 requests without a Host header.

With Step 1 alone, a connection without SNI fails with TLS alert 40 (handshake_failure) and a wrong SNI with alert 112 (unrecognized_name). No certificate leaves the appliance, so the hostname stays hidden and direct connections to the IP address are refused. For most setups, that already does the job.

Side note: legacy SSL mode. Can’t switch to profile mode yet? The same protection works in legacy mode, with SNI enabled directly on the vserver. Certificate bindings and the ClientHello policy are identical, and in our tests a Host header that did not match the SNI was still rejected with HTTP 400.

set    ssl vserver vs_tls -SNIEnable ENABLED
unbind ssl vserver vs_tls -certkeyName gw-rsa
unbind ssl vserver vs_tls -certkeyName gw-ec
bind   ssl vserver vs_tls -certkeyName gw-rsa -SNICert
bind   ssl vserver vs_tls -certkeyName gw-ec  -SNICert

Once profile mode is enabled, these vserver commands are refused (“Use set ssl profile”).

Step 2: Reset everything else at the ClientHello (optional)

Step 1 hides the hostname, but the TLS alerts still tell a scanner “TLS service with SNI enforcement here”. That is a fingerprint, and automated tooling may start guessing hostnames. We can remove it.

An SSL policy evaluated at the ClientHello can answer with a plain TCP reset instead. A port scanner still sees the port as open, but no TLS handshake ever completes, so there is nothing to identify it as a NetScaler. In profile mode, SSL policies are still bound to the vserver:

add ssl policy pol_sni_reset -rule "CLIENT.SSL.CLIENT_HELLO.SNI.SET_TEXT_MODE(IGNORECASE).EQ(\"gateway.example.com\").NOT" -action RESET
bind ssl vserver vs_tls -policyName pol_sni_reset -priority 10 -type CLIENTHELLO_REQ

To allow more than one hostname, chain them with .OR(…) or use a pattern set instead of the single-host rule:

add policy patset ps_allowed_sni
bind policy patset ps_allowed_sni gateway.example.com
bind policy patset ps_allowed_sni gateway2.example.com
add ssl policy pol_sni_reset -rule "CLIENT.SSL.CLIENT_HELLO.SNI.SET_TEXT_MODE(IGNORECASE).EQUALS_ANY(\"ps_allowed_sni\").NOT" -action RESET

Each new hostname needs two changes: bind its certificate with -SNICert and add the name to the pol_sni_reset rule or pattern set. Otherwise, clients get a TCP reset. Keep Step 1 in place, too. In our lab, an IP address sent as SNI slipped past the second policy, but the SNI-only certificate binding still rejected it.

Client sendsDefaultStep 1Step 1 + 2
No SNI (connect to IP)cert + 200alert 40, no certTCP RST
Correct SNI200200200
Correct SNI, uppercase200200200
Wrong SNIcert + 200alert 112TCP RST
Subdomain (sub.gateway.example.com)cert + 200alert 112TCP RST
IP literal as SNIcert + 200alert 112alert 112
Correct SNI, other Host header200400400

Table 2: SNI test matrix, identical on CPX 13.0 and 14.1 and in profile mode on 14.1, plain numbers in cells mean HTTP status codes. Alert + <number> mean TLS errors, which in this case are good.

Putting it all together

The SNI hardening is meant as an addition to your existing TLS configuration, not a replacement for it. For protocols, ciphers and renegotiation, we recommend following Citrix’s official guide Networking SSL/TLS Best Practices [8]. That guide does not configure SNI enforcement, so add Steps 1 and 2 on top of it.

The block below combines both: TLS settings aligned with Citrix’s guide, plus the SNI hardening from this post. Replace vs_tls, gw-rsa/gw-ec and gateway.example.com with your own names:

# Global: switch to default SSL profile mode (one-way, affects all SSL vservers, see Step 0)
set ssl parameter -defaultProfile ENABLED
# Cipher group: TLS 1.3 + ECDHE/AEAD only
add ssl cipher CG_MODERN
bind ssl cipher CG_MODERN -cipherName TLS1.3-AES256-GCM-SHA384
bind ssl cipher CG_MODERN -cipherName TLS1.3-CHACHA20-POLY1305-SHA256
bind ssl cipher CG_MODERN -cipherName TLS1.3-AES128-GCM-SHA256
bind ssl cipher CG_MODERN -cipherName TLS1.2-ECDHE-ECDSA-AES256-GCM-SHA384
bind ssl cipher CG_MODERN -cipherName TLS1.2-ECDHE-RSA-AES256-GCM-SHA384
bind ssl cipher CG_MODERN -cipherName TLS1.2-ECDHE-ECDSA-CHACHA20-POLY1305
bind ssl cipher CG_MODERN -cipherName TLS1.2-ECDHE-RSA-CHACHA20-POLY1305
bind ssl cipher CG_MODERN -cipherName TLS1.2-ECDHE-ECDSA-AES128-GCM-SHA256
bind ssl cipher CG_MODERN -cipherName TLS1.2-ECDHE-RSA-AES128-GCM-SHA256

# Frontend profile: SNI enforcement (Step 1) + TLS hardening
add ssl profile sslp_fe_hardened -sslProfileType FrontEnd
set ssl profile sslp_fe_hardened -SNIEnable ENABLED
set ssl profile sslp_fe_hardened -SNIEnable ENABLED -allowUnknownSNI DISABLED
set ssl profile sslp_fe_hardened -SNIHTTPHostMatch STRICT -dropReqWithNoHostHeader YES
set ssl profile sslp_fe_hardened -ssl3 DISABLED -tls1 DISABLED -tls11 DISABLED -tls12 ENABLED -tls13 ENABLED
set ssl profile sslp_fe_hardened -denySSLReneg FRONTEND_CLIENT
set ssl profile sslp_fe_hardened -HSTS ENABLED -maxage 31536000

unbind ssl profile sslp_fe_hardened -cipherName DEFAULT

bind ssl profile sslp_fe_hardened -cipherName CG_MODERN

# Vserver: profile reference, SNI-only certificates (Step 1)
set ssl vserver vs_tls -sslProfile sslp_fe_hardened
unbind ssl vserver vs_tls -certkeyName gw-rsa
unbind ssl vserver vs_tls -certkeyName gw-ec
bind ssl vserver vs_tls -certkeyName gw-rsa -SNICert
bind ssl vserver vs_tls -certkeyName gw-ec -SNICert

# ClientHello reset for wrong or missing SNI (Step 2)

add ssl policy pol_sni_reset -rule "CLIENT.SSL.CLIENT_HELLO.SNI.SET_TEXT_MODE(IGNORECASE).EQ(\"gateway.example.com\").NOT" -action RESET
bind ssl vserver vs_tls -policyName pol_sni_reset -priority 10 -type CLIENTHELLO_REQ
save ns config

# Tested on NetScaler CPX 14.1-73.30 (NS14.1: Build 73.30.nc) and 13.1-63.19 (NS13.1: Build 63.19.nc), in default SSL profile mode.

Before saving, run show ssl vserver vs_tls: every certificate line must read Server Certificate for SNI. If other vservers share the profile, show ssl profile sslp_fe_hardened lists them.

Do a small test with your gateway: if curl https://X.X.X.X/ -v returns a TLS error or connection reset while curl https://your-citrix-jde4.example.com -v works fine, your changes were successful.

Side effects and limitations

  • Anything that connects by IP address only breaks. Health monitors, upstream load balancers, scripts and monitoring checks without SNI now fail (TLS alert, or TCP reset with Step 2). Use the proper hostname instead.
  • Switching to profile mode is global. Every vserver without a custom profile falls back to ns_default_ssl_profile_frontend. Assign profiles to all vservers, not just the gateway.
  • We tested NetScaler CPX. VPX and MPX share the SSL configuration model, but test on your own appliance first.
  • This is not a security patch. It takes you out of the opportunistic first wave. It does nothing against a targeted attacker who knows (or has looked up) your hostname.
  • DTLS (UDP/443) is not covered. We tested TLS over TCP only. CVE-2026-88772 is exploited via DTLS, which is enabled by default on gateway vservers [10], and whether SNI enforcement applies to DTLS handshakes is untested. Check it yourself or disable DTLS if you don’t need it. The hardening still helps: mass scanners mostly identify NetScaler appliances via HTTPS on TCP/443 before they fire the exploit.
  • Other listeners on the same IP address can still leak the hostname. A port-80 vserver that redirects to https://gateway.example.com/ hands out the name in its Location Other SSL vservers with a default certificate on the same IP address, or a management interface reachable from the Internet, do the same. Check every listener on that IP address, not just the gateway.

Verifying it with SNItch and Censys

SNItch [7] is our open-source tool for SNI-based host discovery and testing. It runs a fresh TLS handshake per candidate hostname, tries empty, wrong and IP-literal SNI values and reports how the server responds. That makes it a good way to check how well your NetScaler hides from attackers who don’t know its hostname.

git clone https://github.com/cirosec/SNItch.git
cd SNItch
go build -o SNItch .

Check 1: the mass scanner’s view. Scan only the NetScaler’s IP address:

SNItch -t X.X.X.X:443

On a default NetScaler, the (EMPTY SNI) probe returns a certificate, and SNItch reads your gateway’s hostname straight from it (see Figure 1). On a hardened one, the empty-SNI and IP-as-SNI probes get nothing back: no certificate, no hostname. If SNItch still finds the hostname, it leaked through another channel, such as a PTR record, CT logs or a redirect (see the next section).

Figure 2: Properly hardened NetScaler instance that does not reveal its hostname when scanned with SNItch.

If you search for the same IP on Censys you get the following empty result on the properly hardened NetScaler system (note the .102 IP-Address again):

Figure 3: Properly hardened NetScaler on Censys.

Check 2: legitimate users still get in. Supply the real hostname as a positive control:

SNItch gateway.example.com --no-recon

The hostname should show a valid certificate and an HTTP status, typically 200, or a redirect to the login page. If it doesn’t, recheck the SNI certificate binding.

Scanning the same IP address using the hostname reveals the NetScaler, so the hardening works (see the .102 IP address as above) and users can use it as usual:

Figure 4: Using the hostname as entry point for SNItch reveals the NetScaler again

Your hostname is probably already public

SNI enforcement only helps while the attacker doesn’t know the hostname, and most gateway hostnames are far from secret. Every publicly trusted certificate is registered in the Certificate Transparency (CT) logs, which anyone can search, for example on crt.sh or, if crt.sh is down, on the subdomain index crt.name. A query on crt.sh for %.example.com returns citrix.example.com, vpn.example.com and gateway.example.com. Finding the one that resolves to a NetScaler requires a short script.

An attacker willing to invest a little more effort can build a target list from CT logs instead of scanning IP addresses. We did not see this in the September wave, but it is the obvious next step once SNI enforcement becomes common.

The countermeasure: keep the gateway’s hostname out of CT logs.

  1. Use a new, non-obvious hostname. Not , vpn., remote. or gateway., but something like gateway-k7q2.example.com. A short random suffix is enough: the bare word is on every wordlist, while the suffixed name is not. Users usually rely on bookmarks or direct links in their browser history and rarely type it out once visited.
  2. Use a wildcard certificate (*.example.com). CT then shows only the wildcard, not the hostname. Ideally, use a dedicated wildcard on a subdomain (e.g. *.r.example.com) that is located only on the NetScaler, so a compromised appliance doesn’t leak a key used elsewhere.
  3. Step 2 becomes mandatory. With a wildcard certificate, Step 1’s SNI-only binding accepts any matching subdomain. Only the exact-match ClientHello policy restricts access to the real hostname.
  4. Retire the old name. As long as example.com points to the same IP address with its old certificate, nothing is gained.

One risk remains: the hostname is still in public DNS, and passive DNS databases may pick it up over time. This raises the bar; it does not hide the gateway from a determined, targeted attacker. If the zone is signed with DNSSEC using NSEC instead of NSEC3, the hostname could also be found by walking the zone.

Setting up a new NetScaler? Then start with the stealthiest setup:

  • A dedicated zone: a subdomain of your main domain, e.g. *.gateway.example.com, or a new domain with no link to your company.
  • A wildcard certificate for that zone (*.gateway.example.com), so CT logs never show the real hostname.
  • No A record on the zone itself. Only a hard-to-guess hostname, e.g. workplace-gateway-8b4e.gateway.example.com, resolves to the NetScaler.
  • The ClientHello policy matches only that exact hostname. Everything else gets a TCP reset.

With this setup, bots, mass scanners and opportunistic attackers have very little to go on: no IP response, no hostname in CT logs, nothing to guess from the domain.

Conclusion

The 2026 NetScaler zero-days were exploited for weeks before a patch existed. In our cases, the first wave didn’t need to know its victims. It sprayed IP addresses. A NetScaler that only answers to its hostname was never on that list.

We think that the main takeaway is how cheap this protection is: about ten lines for the SNI part and a one-off change, to significantly reduce the attack surface of a NetScaler:

  1. Use SSL profile mode, enable SNI in a custom front-end profile and bind certificates only with -SNICert.
  2. Optionally, add the ClientHello reset policy (-type CLIENTHELLO_REQ, case-insensitive). With a wildcard certificate, it becomes mandatory.
  3. Verify with SNItch that an IP-address-only scan finds nothing.
  4. Move the gateway to a new, non-obvious hostname behind a wildcard certificate, so CT logs do not give it away.

It won’t stop a targeted attacker, and it doesn’t replace patching. But it can buy you the days between “exploited in the wild” and “patch available”.

References

  1. BleepingComputer: Citrix confirms two NetScaler RCE zero-days exploited in attacks
  2. SecurityWeek: Exploitation of Citrix NetScaler Zero-Day Hits Appliances Patched Days Earlier
  3. Rapid7: Zero-Day Exploitation of Citrix NetScaler ADC and Gateway
  4. GreyNoise: Swarming Against Citrix 0-Day Exploitation
  5. CrowdSec: CVE-2026-88771: Citrix NetScaler Command Injection; timeline incl. eSentire: Ongoing Exploitation of Citrix NetScaler ADC and NetScaler Gateway Vulnerabilities; GTIG and Mandiant findings: Defending Against Active Exploitation of Citrix NetScaler ADC and Gateway Appliances
  6. Johannes Norz: Blocking requests to the IP address in SSL vServers on Citrix NetScaler ADC
  7. cirosec: SNItch on GitHub and Fuzzing vhosts with SNItch
  8. Citrix Tech Zone: Networking SSL/TLS Best Practices (Q3 2026 Edition)
  9. watchTowr Labs: Oh Look, The Foot Gun Went Off Again (Citrix NetScaler PreAuth Command Injection CVE-2026-88771)
  10. watchTowr Labs: Here We Go Again (Citrix NetScaler DTLS Preauth Memory Overflow CVE-2026-88772)
  11. NetScaler security bulletin: CTX697096 (CVE-2026-88771 to CVE-2026-88778)
  12. NetScaler security bulletin: CTX697174 (CVE-2026-88779)
  13. MITRE CVE records: CVE-2026-88771, CVE-2026-88772, CVE-2026-88779

Further blog articles

Pentesting

Fuzzing vhosts with SNI(tch)

June 10, 2026 – Host header fuzzing stops at the HTTP layer and can’t find services hardened at the TLS handshake via SNI validation. SNItch fills that gap by fuzzing the SNI field directly – and doubles as a tool to verify your own servers don’t leak hostnames to IP-based reconnaissance.

Author: Felix Friedberger

Mehr Infos »
Forensic

A collection of Shai-Hulud 2.0 IoCs

November 26, 2025 – Regarding the Node Package Manager (npm) supply chain attack that started November 21, 2025, and affected thousands of packages, we have collected and identified corresponding hashes to make them publicly available in one single place for easier access.

Author: Niklas Vömel, Felix Friedberger

Mehr Infos »
Forensic

IOCs of the npm crypto stealer supply chain incident

September 25, 2025 – Regarding the Node Package Manager (npm) supply chain attack that started September 8, 2025, and affected 27 packages, we have collected and identified corresponding hashes to make them publicly available in one single place for easier access.

Author: Niklas Vömel

Mehr Infos »
Pentesting

TLPT: Bedroh­ungs­­­orientierte Penetra­tions­tests nach DORA

January 24, 2025 – Since January 17, 2025, the Digital Operational Resilience Act (DORA) has been put into practice. One important aspect of DORA is the requirement of regularly performing threat-led penetration tests (TLPT). Only selected entities within the financial sector are required to conduct TLPTs. Even though TLPTs sound like a new concept, they have actually existed in Germany since 2020 in form of TIBER tests. This blog post describes the concepts behind TLPTs and how they are conducted. Furthermore, alternatives for targeted and budget-oriented red team assessments are given.

Author: Michael Brügge

Mehr Infos »
Do you want to protect your systems? Feel free to get in touch with us.
Search
Search