292 lines
22 KiB
Markdown
292 lines
22 KiB
Markdown
# 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.md`](SECURITY.md) before 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](https://github.com/fox-it/citrix-netscaler-triage).
|
|
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](https://developer-docs.netscaler.com/en-us/adc-nitro-api/current-release/configuration/). 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](https://developers.cloudflare.com/dns/proxy-status/limitations/).
|
|
|
|
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](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](https://support.citrix.com/external/article/CTX697096/citrix-netscaler-adc-and-citrix-netscale.htm),
|
|
[CTX696939](https://support.citrix.com/external/article/CTX696939/netscaler-adc-and-netscaler-gateway-secu.html),
|
|
[CTX697174](https://support.citrix.com/external/article?articleNumber=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`,
|
|
nearest `X-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.
|
|
|
|
```sh
|
|
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`](.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, `0600` in a `0700` directory.
|
|
- Event types include `request`, `request_body`, `response*`, `upload*`, `form_fields`,
|
|
`injection_candidate`, `simulated_command`/`unsupported`, `outbound_attempt`,
|
|
`exploit_attempt` (labelled `CVE-2019-19781`, `path-traversal`, `config-read`,
|
|
`management-probe`), `auth_attempt`, `brute_force_suspected` and `payload_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`](SECURITY.md) and
|
|
[`deploy/`](deploy/).
|
|
|
|
## Public deployment
|
|
|
|
1. Terminate TLS at an edge proxy (for example Cloudflare in front of Nginx Proxy Manager).
|
|
2. Forward to the source-preserving ingress and set `INGRESS_ALLOW_FROM` and
|
|
`HONEYPOT_TRUSTED_FORWARDED_FROM` to your edge hop(s) so the real client IP is captured.
|
|
3. Firewall the origin port so only the edge proxy and host can reach it.
|
|
4. Keep simulation mode on; monitor the evidence quota.
|
|
|
|
See [`deploy/VM-ACCESS.md`](deploy/VM-ACCESS.md) for a deployment template.
|
|
|
|
## Tests
|
|
|
|
```sh
|
|
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`](LICENSE). Trademarks belong to their respective owners.
|