feat(citrix-honeypot): KEV-metadata coverage for the NetScaler zero-day RCE pair (#3467) - #3469
Merged
Merged
Conversation
Dependency Review✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.Scanned FilesNone |
…ay RCE pair (#3467) CVE-2026-88771 and CVE-2026-88772 are a Citrix NetScaler ADC/Gateway zero-day RCE pair, added to CISA KEV on 2026-09-27, each independently sufficient for RCE. This is detection coverage for citrix-honeypot, and it is deliberately narrower than the issue's Signal A/B/C sketch. There is no payload signature in this change, because none is published. CTX697096 and the KEV catalog give preconditions, CVSS vectors, CWEs and fixed builds, and nothing else: no path, method, header, parameter, body field or byte sequence for either CVE. The "provided IOCs" both sources point at are, per watchTowr, run as an IOC scan on the NetScaler console advisory page or requested from Citrix Support -- appliance-console artifacts gated on a build this decoy does not run, not wire patterns. Shipping an invented signature here would produce detection events an analyst trusts and that correspond to nothing, so the deliverable is KEV-metadata-driven coverage instead. Verified this run against the live KEV feed (catalog 2026.09.27, 1728 entries; both CVEs dateAdded 2026-09-27, dueDate 2026-09-30, forensicTriage Yes) and against CTX697096. What ships: - netscaler_kev_exposure, once per process start, one event per CVE carrying the verified KEV/bulletin metadata and a decoy_exercises_precondition field. The CVE id goes in `path` so `path: "CVE-2026-88771"` is a working query. Uses the existing emit path; no new fields on the shared event struct. - netscaler_cmd_metachar_shape_inferred, an INFERRED classifier over the one documented primitive ("an unauthenticated attacker to execute arbitrary commands"). The primitive is documented; where the unvalidated input lands is not, so the event name carries "inferred" and the code comment says so. Two tiers: high-conviction tokens (backtick, $(, ${, LF, CR) fire alone; low-conviction ones (&&, ||, ;, |, >, <, &) need two distinct, because & is the query separator and ; is the Jetty/Tomcat matrix-parameter form. A bare "/vpn/;id" deliberately does not classify -- it is indistinguishable from "/store;jsessionid=..." by shape alone, and that miss is recorded in a comment rather than left to be discovered. CVE-2026-88772 has no classifier. Its precondition is DTLS, a UDP transport, and this decoy is TCP-only (net.Listen("tcp") behind tls.NewListener, fronted tcp:4443 -> 10.8.0.2:443:pp), so the precondition is structurally unobservable here. The gap is recorded as queryable data in a precondition_gap field rather than papered over. Also recorded: KEV lists cwes ["CWE-119"] for both entries, contradicting CTX697096's CWE-20 for 88771. The code uses the vendor's own classification of its own CVE and notes the discrepancy in a comment. Tests: every classifier has a CAN-fire and a does-not-fire test, the benign corpus drawn from traffic this decoy actually serves (SAML wctx/SAMLRequest base64, JWT bearer, a=1&b=2, /store;jsessionid=, newbm.pl, an ordinary credential POST). Four mutants run and reverted: emptying the high tier (7 CAN-fire failures), reverting to per-token strings.Contains (5 benign failures -- the Contains scan made "a=1&&b=2" match both & and && and fire on the very artifact the low tier exists to tolerate), moving the classifier after the early-returning auth block (the ordering test fails), and bolting a fabricated payload= field into the KEV metadata (the anti-fabrication guard fails on both CVEs). Out of scope, noted in the file and the doc rather than coded: 88772 needs a UDP/DTLS listener, which is a new exposed surface and its own issue; 88773 request smuggling belongs in http-honeypot per #3464; 88774-88778 are configuration-dependent and not reported exploited; a "no preceding session" classifier needs per-source state this package does not hold. Not verified: nothing deployed, so no live detection rate is claimable. The Citrix IoC blog section could not be read (community.citrix.com returns 403 to fetch and to a browser-UA curl), so the IoC conclusion rests on watchTowr's description of where those IOCs live, not on having read the list. No live Suricata ruleset re-audit. No traffic sent anywhere: all fixtures are inert strings in unit tests.
Xore
force-pushed
the
oc/3467-netscaler-cve
branch
from
September 28, 2026 09:08
3fcc7f0 to
c69eb86
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
feat(citrix-honeypot): KEV-metadata coverage for the NetScaler zero-day RCE pair (#3467)
CVE-2026-88771 and CVE-2026-88772 are a Citrix NetScaler ADC/Gateway
zero-day RCE pair, added to CISA KEV on 2026-09-27, each independently
sufficient for RCE. This is detection coverage for citrix-honeypot, and
it is deliberately narrower than the issue's Signal A/B/C sketch.
There is no payload signature in this change, because none is published.
CTX697096 and the KEV catalog give preconditions, CVSS vectors, CWEs and
fixed builds, and nothing else: no path, method, header, parameter, body
field or byte sequence for either CVE. The "provided IOCs" both sources
point at are, per watchTowr, run as an IOC scan on the NetScaler console
advisory page or requested from Citrix Support -- appliance-console
artifacts gated on a build this decoy does not run, not wire patterns.
Shipping an invented signature here would produce detection events an
analyst trusts and that correspond to nothing, so the deliverable is
KEV-metadata-driven coverage instead.
Verified this run against the live KEV feed (catalog 2026.09.27, 1728
entries; both CVEs dateAdded 2026-09-27, dueDate 2026-09-30,
forensicTriage Yes) and against CTX697096.
What ships:
netscaler_kev_exposure, once per process start, one event per CVE
carrying the verified KEV/bulletin metadata and a
decoy_exercises_precondition field. The CVE id goes in
pathsopath: "CVE-2026-88771"is a working query. Uses the existing emitpath; no new fields on the shared event struct.
netscaler_cmd_metachar_shape_inferred, an INFERRED classifier over the$(, $ {, LF, CR) fire alone; low-conviction ones
one documented primitive ("an unauthenticated attacker to execute
arbitrary commands"). The primitive is documented; where the
unvalidated input lands is not, so the event name carries "inferred"
and the code comment says so. Two tiers: high-conviction tokens
(backtick,
(&&, ||, ;, |, >, <, &) need two distinct, because & is the query
separator and ; is the Jetty/Tomcat matrix-parameter form. A bare
"/vpn/;id" deliberately does not classify -- it is indistinguishable
from "/store;jsessionid=..." by shape alone, and that miss is recorded
in a comment rather than left to be discovered.
CVE-2026-88772 has no classifier. Its precondition is DTLS, a UDP
transport, and this decoy is TCP-only (net.Listen("tcp") behind
tls.NewListener, fronted tcp:4443 -> 10.8.0.2:443:pp), so the
precondition is structurally unobservable here. The gap is recorded as
queryable data in a precondition_gap field rather than papered over.
Also recorded: KEV lists cwes ["CWE-119"] for both entries, contradicting
CTX697096's CWE-20 for 88771. The code uses the vendor's own
classification of its own CVE and notes the discrepancy in a comment.
Tests: every classifier has a CAN-fire and a does-not-fire test, the
benign corpus drawn from traffic this decoy actually serves (SAML
wctx/SAMLRequest base64, JWT bearer, a=1&b=2, /store;jsessionid=,
newbm.pl, an ordinary credential POST). Four mutants run and reverted:
emptying the high tier (7 CAN-fire failures), reverting to per-token
strings.Contains (5 benign failures -- the Contains scan made "a=1&&b=2"
match both & and && and fire on the very artifact the low tier exists to
tolerate), moving the classifier after the early-returning auth block
(the ordering test fails), and bolting a fabricated payload= field into
the KEV metadata (the anti-fabrication guard fails on both CVEs).
Out of scope, noted in the file and the doc rather than coded: 88772
needs a UDP/DTLS listener, which is a new exposed surface and its own
issue; 88773 request smuggling belongs in http-honeypot per #3464;
88774-88778 are configuration-dependent and not reported exploited;
a "no preceding session" classifier needs per-source state this package
does not hold.
Not verified: nothing deployed, so no live detection rate is claimable.
The Citrix IoC blog section could not be read (community.citrix.com
returns 403 to fetch and to a browser-UA curl), so the IoC conclusion
rests on watchTowr's description of where those IOCs live, not on having
read the list. No live Suricata ruleset re-audit. No traffic sent
anywhere: all fixtures are inert strings in unit tests.