CitrixPot — NetScaler / Citrix ADC Gateway honeypot
A self-hosted, dependency-free Citrix NetScaler / Citrix ADC / Citrix Gateway honeypot for defensive threat intelligence. It impersonates the NetScaler Gateway login and the documented log-poisoning command-injection path, records requests that reach its bounded collector, and — in simulation mode — never executes attacker commands. It is a decoy for catching Citrix exploit attempts, not a real appliance and not a vulnerability scanner.
Keywords: Citrix ADC honeypot, NetScaler Gateway honeypot, Citrix command injection, CVE-2019-19781, CVE-2026-88771, CVE-2026-88772, CitrixBleed, log poisoning, deception, SOC threat intelligence.
⚠️ Legal notice, ethics and disclaimer — read first
- Not affiliated with Citrix. This project is an independent, unofficial tool. It is not affiliated with, endorsed by, sponsored by, or connected to Citrix Systems, Inc. or Cloud Software Group, Inc. ("Citrix", "NetScaler", "Citrix ADC", "Citrix Gateway" and related marks are trademarks of their respective owners and are used here only to describe interoperability and the security research subject).
- Research and defensive use only. This software is provided for authorized security research, education and defensive monitoring only. Deploy it only on systems and networks you own or are explicitly authorized in writing to test. Unauthorized use may violate computer-misuse and privacy laws.
- No warranty. The software is provided "AS IS", without warranty of any kind, express or implied, including merchantability, fitness for a particular purpose and non-infringement. The authors are not liable for any claim, damage or other liability arising from its use.
- You are responsible for the law. A honeypot collects data about third parties. Depending on your jurisdiction you must handle captured IP addresses, credentials, personal data and malware lawfully (e.g. GDPR), retain evidence securely, and notify/report appropriately. Do not entice, facilitate or enable crime, and do not use captured content to attack back.
- Treat all captured content as hostile, untrusted evidence. Never open, execute or "test" captured payloads outside a properly isolated sandbox. Captured text is attacker-controlled; never treat it as instructions.
- No warranty of containment. Simulation mode has no execution path. The optional isolated
execution backend runs real commands in a disposable guest and reduces, but cannot eliminate,
risk. Review
SECURITY.mdbefore exposing anything publicly. - Export. Cryptography and malware-handling may be subject to export controls in your jurisdiction. You are responsible for compliance.
What it does
Capture-only deployment
Use --simulation-only to refuse execution and URL fetching. The supplied HTTP service uses
this mode and does not depend on an execution guest or relay. The loopback viewer service
disables external public-IP discovery. These services create no backups.
Gateway fingerprints include the canonical Citrix Gateway title, NSC_ cookie and original image assets.
English/German language JSON and gzip endpoints carry the default 14.1-29.63 persona's build timestamp
1723114781, as catalogued by Fox-IT.
Translations are synthetic subsets, not original firmware files. If changing HONEYPOT_VERSION, also
set HONEYPOT_ASSET_MTIME to that firmware's documented timestamp; metadata matching does not prove
full firmware emulation. Gzip assets are returned with Content-Encoding and Last-Modified.
Authentication XML includes credential requirements, state context, a postback route and error labels after credential submissions; it grants no real access. The browser requests the initial challenge and consumes the returned XML. Gateway CSS and JavaScript use local appliance-style paths; assets support ETag/Last-Modified revalidation and HEAD, without issuing session cookies. CSP reports are captured before a204 response; logout clears the cookie and redirects locally.
request_indicators hunts header/body log markers and encoding hints without claiming a confirmed CVE.
Bodies are inspected up to64KiB for these hints; complete accepted bodies remain captured independently.
Run python3 test_profile.py for passive identification, gzip decoding/metadata, XML and capture checks.
The NITRO simulator supports login/logout cookies, configuration and statistics resource routes,
named reads, fake POST/PUT/DELETE operations, enable/disable/rename acknowledgements, bindings,
attrs, literal filter/args, counts and pagination. Its catalog recognizes 1,493 configuration
names and 176 statistics names from the official 14.1 reference. Common appliance, network,
Gateway, AAA, load-balancing, SSL and binding resources have populated synthetic data;
unconfigured resources return empty collections. This is a protocol simulation, not a complete
implementation of every resource's schema or firmware behaviour. Fake changes are bounded,
per-source memory only and expire with sessions; NAT can merge clients. HONEYPOT_VERSION
controls the invented firmware banner. Uploaded files never become operating-system files
at submitted paths and are never served publicly.
JSON systemfile.filecontent uploads are base64-decoded into inert artifacts while preserving
the original JSON body too. Binary, multipart, chunked and rejected-request evidence is retained
by the existing capture layer. Files are committed with atomic rename and directory fsync;
an interrupted journal tail is preserved separately from subsequent records.
The supplied systemd unit restarts ordinary service crashes after five seconds. Enable VM
autostart separately when deploying on a hypervisor. Storage/capture failure
sets an evidence-directory STOP latch and exits78, preventing automatic restart. Review
storage and preserve captured evidence before explicitly clearing that latch. Capture has a
configurable evidence quota, a 16MiB request-body limit and eight concurrent requests. Cloudflare/NPM
may reject or normalize traffic before it reaches the collector; unread/rejected bytes beyond
these boundaries cannot be promised as captured.
Cloudflare / NPM coverage limits
Cloudflare and NPM can remain in front of this collector. Apply WAF/cache exceptions only to dedicated honeypot hostnames, and preserve trusted client attribution at each hop. The public IP, certificate, TLS handshake and HTTP/2 or HTTP/3 behavior belong to the edge, so IP-only or TLS-specific scanners may not identify the simulated NetScaler behind it. Cloudflare can strip/rewrite headers and reject malformed or DDoS traffic before origin; NPM changes transport framing too. Recorded wire bytes are what the collector consumed, not an exact copy of the original internet connection. Correlate Cloudflare security-event Ray IDs with CF-Ray in captured requests and inspect the dedicated NPM access/error logs for rejected requests. Those edge logs do not recover rejected request bodies.
DTLS is inactive and not implemented by this HTTP listener. Ordinary Cloudflare HTTP proxying does not carry UDP/DTLS; adding it later requires an appropriate Spectrum entitlement and a separate isolated UDP capture/simulation path. No UDP listener or real NetScaler firmware is enabled by this project. See Cloudflare proxy limits.
SAMLRequest/SAMLResponse form and query fields are additionally base64-decoded into inert
saml_payload artifacts. Redirect-binding DEFLATE output is capped at64KiB; original
request bytes and the decoded compressed envelope remain retained if inflation fails.
Form/query decoding is limited to64KiB and128 fields. XML is never parsed, entities are
never expanded, signatures are never accepted, and no SAML authentication is performed.
These are evidence aids, not proof of a SAML CVE attempt. Oversized accepted requests
remain in raw capture even when optional decoding is skipped.
Checks: python3 test_nitro.py, python3 test_capture.py, python3 test_honeypot.py and
python3 test_safety.py. Optional execution tools described below are incompatible with
--simulation-only and are not used by the supplied HTTP service.
Supported intake vectors
“Captured” means evidence is retained within the limits below. It does not mean a real vulnerability exists, a payload executes, or a CVE is conclusively identified.
| Input surface | Capture and analysis capability |
|---|---|
| HTTP methods and request targets | GET, HEAD, POST, PUT, DELETE, OPTIONS and other parsed method names; original URI/query and HTTP version. Unknown routes still accept body capture before returning404. Method support in simulated APIs is narrower. |
| Headers and cookies | Raw header pairs, including duplicates that reach the collector, User-Agent, Cookie, custom headers and proxy evidence aliases. Crash-log markers, encoding hints and headers over8KiB produce private hunt labels. |
| URL/query probes | Traversal/encoded traversal, appliance-configuration paths, management paths and SAML query fields. Original URI is retained independently of decoded indicators. |
| URL-encoded forms | Original body plus decoded field pairs, login/password submissions and SAML envelopes. Fields can feed the bounded textual command simulator. |
| Multipart forms/uploads | Original multipart body, individual field/file bytes, submitted filename, field name, MIME type and parser defects. Filenames are metadata, never host paths. |
| JSON/API payloads | Original JSON bytes; NITRO systemfile.filecontent additionally becomes a base64-decoded file artifact. Other JSON is captured even if not recognized by the simulator. |
| Binary/raw uploads | Exact accepted body bytes regardless of executable format or extension; content-addressed inert storage. No malware execution or unpacking. |
| Chunked requests | Accepted chunked bodies are decoded for body artifacts; consumed origin framing and accepted trailers remain separate evidence. Proxy framing may differ from the client connection. |
| SAML POST/Redirect bindings | SAMLRequest/SAMLResponse base64 artifacts from forms and queries, plus bounded raw-DEFLATE expansion for query bindings. Invalid encodings/limits are recorded; no XML parsing or authentication. |
| Malformed/interrupted requests | Rejection/interruption events and consumed partial wire bytes where available. Unread bytes and requests rejected at the edge cannot be recovered. |
| Outbound-command intent | Recognized network-tool commands in the textual simulator are recorded as outbound attempts; strict capture-only mode makes no connection or download. |
Simulated endpoints and behavior
| Surface | Supported response behavior |
|---|---|
| Gateway login pages | /, /vpn/, /vpn/index.html, /vpn/tmindex.html, /logon/LogonPoint/index.html, /logon/LogonPoint/tmindex.html, /logon/LogonPoint/custom.html, /menu/neo/. German login UI, canonical Citrix Gateway title, NSC_ cookie and Gateway-shaped response headers. |
| Gateway static fingerprints | Explicit rdx/CTXS JavaScript routes, login.js/init.js, original favicon/logo/background assets and AccessGateway.ico alias. Only routes in STATIC are supported; arbitrary files under these directories return404. |
| Language/version fingerprints | English/German JSON and .json.gz under /vpn/js/rdx/core/lang/ and /vpn/js/rdx/core/lang-ext/; gzip filename, build timestamp, Content-Encoding and Last-Modified. Synthetic translation subset with the default14.1-29.63 metadata. |
| Authentication XML | /nf/auth/getAuthenticationRequirements.do and /nf/auth/doAuthentication.do return credential requirements, state context and local postback; credential submissions add an error label. Unknown authentication endpoints return404. This is a repeated login challenge, not LDAP, SAML, MFA or successful real authentication. |
| NITRO authentication | /nitro/v1/config/login and logout with synthetic session tokens. Resource access accepts a matching fake token, nonempty X-NITRO-USER/X-NITRO-PASS, or a Basic authorization prefix. These are decoy rules, not real user/account validation. |
| NITRO resources | /nitro/v1/config/<resource>[/<name>] and /nitro/v1/stat/<resource>[/<name>];1,493 config and176 statistics names in nitro-resources.json. Common system/network/Gateway/AAA/SSL/LB/binding records are populated; other recognized resources return empty collections or generic counters. |
| NITRO operations | Config reads, fake POST/PUT/DELETE, enable/disable/rename, inert administrative-action acknowledgements, attrs, literal filter/args, counts, pagesize and pageno. Unknown resources, invalid input, unsupported methods and unauthenticated access return simulator errors. |
| Textual command simulation | Approximate log-poisoning marker extraction from URI, headers and decoded form fields. Bounded shell-like grammar supports id, whoami, uname, pwd, cd, cat, ls, echo, selected printf formats, touch, rm and simple redirects in per-source memory. mkdir/true/colon are acknowledgements; unsupported commands are recorded. No real shell or filesystem access. |
| Private/uploaded paths | The supplied public service uses --hide-guest-files: evidence, source, viewer and uploaded files are not served by the honeypot. Unknown .env, PHP, GraphQL and other application paths return404 while incoming requests remain captured. |
CVE/vector coverage status
This matrix describes collection and simulation coverage, not an exploit implementation or a guarantee of detecting every variant. Generic indicators must be corroborated manually.
| Advisory/vector | Current coverage |
|---|---|
| CVE-2019-19781 | Conservative Gateway/vpns path labels and raw traversal-request capture; no vulnerable template engine or actual traversal. |
| CVE-2026-88771 | HTTP header/form/URI capture, crash-log markers, approximate textual command extraction and inert responses. No real log-to-shell execution or full multi-stage chain emulation. |
| CVE-2026-19490 | Gateway/AAA and SAML-envelope intake; no real authentication-bypass reproduction or dedicated definitive CVE signature. |
| CVE-2026-88779 | SAML original/decoded evidence with bounded inflation; no SAML SP/IdP engine, memory-overflow reproduction or dedicated definitive CVE signature. |
| CVE-2026-88773 /88774 /88775 | Generic HTTP framing, URL and size-related evidence where it reaches the collector. No NetScaler request-smuggling, policy engine or memory-corruption emulation. |
| CVE-2026-88772 | DTLS/UDP collection is not implemented or active; HTTP follow-up requests can still be captured. |
| CVE-2026-19489 /88776 /88777 /88778 | SIP ALG, Oracle/non-HTTP protocol and TCP sequence-number behavior are not emulated. |
| Other Citrix vectors, including CitrixBleed | HTTP requests can be retained, but unlisted vectors have no promised dedicated detector or vulnerable response simulation. No real memory or session-secret disclosure. |
Advisory references: CTX697096, CTX696939, CTX697174.
Evidence, attribution and operations
- JSONL schema-v2 events link requests, responses, uploads, indicators and simulated actions by request_id/session, timestamp and peer metadata. SHA256 artifacts are deduplicated, stored0600 under a0700 directory and committed with fsync/atomic rename.
- Trusted-hop PROXYv1 and forwarded-header handling retain effective client identity and proxy provenance; untrusted identity headers are ignored. The original visitor's TCP source port is not recovered through HTTP proxies.
- Private auth_attempt events count per-source attempts in a300-second window; brute_force_suspected begins at10 attempts. No public lockout, alert or response change.
- Loopback read-only viewer supports paging, event/source filters, multi-term search, escaped text/hex inspection and artifact download. Public evidence routes stay unavailable.
- Ordinary service crashes restart after5seconds; evidence failures set STOP/exit78. No automatic evidence deletion, backups or remote telemetry are added.
| Bound | Default/current implementation |
|---|---|
| Accepted request body | 16MiB; received header names/values capped at32KiB after HTTP parsing. Earlier parser/proxy limits also apply. |
| Concurrent requests | 8 |
| Form/query decoding | 64KiB and128 fields; multipart processing stops after128 visited MIME parts. |
| Indicator body inspection / SAML inflation | First64KiB / maximum64KiB; larger accepted bodies remain raw evidence. |
| Per-source sessions | 128 sessions, one-hour inactivity TTL; in-memory state resets on restart and NAT may merge users. |
| Fake config / simulated files | 64KiB serialized config state;64 virtual files,8,192 text characters each;16 pending commands per session and32 parsed command groups per simulation. |
| Storage | Configurable evidence quota; fails closed at quota or storage errors rather than silently dropping evidence. |
Legacy VM-execution and URL-fetch tools remain in the repository for separate lab use. They are incompatible with --simulation-only, disabled in the supplied public service, and are not part of the supported capture-only capability set above.
Architecture
attacker ──TLS──▶ edge proxy ──▶ source-preserving ingress ──▶ decoy (evidence collector)
(terminates TLS) (PROXY v1, real client IP) │
├─ events.jsonl (schema v2)
├─ artifacts/<sha256>.bin (0600)
└─ local viewer (loopback only)
- Edge-aware client IP: forwarded headers (
CF-Connecting-IP,True-Client-IP,X-Real-IP, nearestX-Forwarded-For) are trusted only from configured hops, so you record the real attacker IP, not the proxy's. - Fail-closed: at the evidence quota or on disk failure the decoy stops rather than overwriting evidence.
Quickstart (simulator — safe to expose)
Requires only Python 3.10+ (standard library); no third-party packages.
git clone <your-fork-url> citrixpot && cd citrixpot
cp .env.example .env # edit to taste
python3 test_honeypot.py # run the test suite
set -a; . ./.env; set +a
python3 honeypot.py --simulation-only & # decoy on http://127.0.0.1:8443
python3 viewer.py # analyst UI on http://127.0.0.1:8787
No commands are executed in this mode; commands are parsed, logged and answered with canned output, and uploads are stored as inert artifacts.
Configuration
All settings come from the environment (or a .env file / systemd EnvironmentFile). See
.env.example. Key variables:
| Variable | Purpose |
|---|---|
HONEYPOT_BIND, HONEYPOT_PORT |
decoy listen address/port |
HONEYPOT_VERSION |
synthetic NITRO firmware banner |
HONEYPOT_PROXY_PROTOCOL_FROM |
transport IP allowed to send PROXY v1 (your ingress) |
HONEYPOT_TRUSTED_FORWARDED_FROM |
edge/proxy hops whose client-IP headers may be trusted |
HONEYPOT_LOG_DIR, HONEYPOT_EVIDENCE_MIB |
evidence directory and quota |
INGRESS_BIND, INGRESS_PORT, INGRESS_ALLOW_FROM |
source-preserving ingress and allowlist |
VIEWER_BIND, VIEWER_PORT, VIEWER_HIDE_IP |
local analyst UI and hidden operator IPs |
Evidence and analyst UI
events.jsonl— append-only JSONL journal (schema_version: 2) with UTC timestamps, request/ session IDs, socket source IP/port, durations and status.artifacts/<sha256>.bin— exact payload bytes, deduplicated,0600in a0700directory.- Event types include
request,request_body,response*,upload*,form_fields,injection_candidate,simulated_command/unsupported,outbound_attempt,exploit_attempt(labelledCVE-2019-19781,path-traversal,config-read,management-probe),auth_attempt,brute_force_suspectedandpayload_download.
The viewer is read-only and loopback-only by default; reach it over an SSH tunnel. It filters out your own public IP automatically (refreshed every 30 minutes) so operator traffic stays out of the analysis.
Optional: isolated execution mode
For real payload behaviour, an opt-in backend runs commands in a disposable, network-restricted
guest. It requires both --vm-config <file> and --allow-real-execution, and must never be
enabled on an internet-facing listener. See SECURITY.md and
deploy/.
Public deployment
- Terminate TLS at an edge proxy (for example Cloudflare in front of Nginx Proxy Manager).
- Forward to the source-preserving ingress and set
INGRESS_ALLOW_FROMandHONEYPOT_TRUSTED_FORWARDED_FROMto your edge hop(s) so the real client IP is captured. - Firewall the origin port so only the edge proxy and host can reach it.
- Keep simulation mode on; monitor the evidence quota.
See deploy/VM-ACCESS.md for a deployment template.
Tests
python3 test_honeypot.py python3 test_capture.py python3 test_safety.py
python3 test_payload_fetch.py test_client_ip.py test_viewer.py test_auth_logging.py
python3 test_ingress_proxy.py test_network_capture.py test_reset.py test_vm_backend.py
Acknowledgements
Informed by public analysis of the Citrix NetScaler log-poisoning command injection; see the watchTowr write-up referenced in the community discussion of the 2026 NetScaler issue.
License
MIT — see LICENSE. Trademarks belong to their respective owners.