
Forensic triage toolkit for Citrix NetScaler devices, featuring a Dissect-based IOC scanner for webshells, timestomping, and suspicious binaries, plus a remote version scanner that identifies CVE vulnerabilities.
This repository contains triage scripts for Citrix NetScaler devices:
iocitrix.py -- a Dissect script to Triage a Citrix NetScaler image/target.scan-citrix-netscaler-version.py -- Scan and fingerprint the version of a NetScaler device over HTTP(s).You can use this script to scan and determine the version of a Citrix NetScaler device over HTTP(s). It will also determine if the NetScaler is vulnerable to specific CVEs based on the version.
scan-citrix-netscaler-version.pyUse the following steps if you are using pip:
In case of uv, you can run the script directly using:
Example usage:
$ python3 scan-citrix-netscaler-version.py 192.168.1.10 192.168.1.12
192.168.1.10 (*.local.domain) is running Citrix NetScaler version 13.1-51.15 (VULNERABLE)
192.168.1.12 (*.local.domain) is running Citrix NetScaler version 12.1-55.330 (NOT VULNERABLE)
Or get the results in JSON:
$ python3 scan-citrix-netscaler-version.py https://192.168.1.11 --json | jq
{
"scanned_at": "2025-09-03T23:11:36.864228+00:00",
"target": "https://192.168.1.11",
"tls_names": "my-first-netscaler.local",
"rdx_en_stamp": 1702886392,
"rdx_en_dt": "2023-12-18T07:59:52+00:00",
"version": "12.1-55.302",
"error": null,
"vulnerable": {
"CVE-2025-5349": true,
"CVE-2025-5777": true,
"CVE-2025-6543": false,
"CVE-2025-7775": true,
"CVE-2025-7776": true,
"CVE-2025-8424": true
},
"is_vulnerable": true
}
It's also possible to limit vulnerability status checks to specific CVEs by using the --cve flag:
$ python3 scan-citrix-netscaler-version.py https://192.168.1.11 --cve CVE-2025-6543 --json | jq
{
"scanned_at": "2025-09-03T23:16:36.573016+00:00",
"target": "https://192.168.1.11",
"tls_names": "my-first-netscaler.local",
"rdx_en_stamp": 1702886392,
"rdx_en_dt": "2023-12-18T07:59:52+00:00",
"version": "12.1-55.302",
"error": null,
"vulnerable": {
"CVE-2025-6543": false
},
"is_vulnerable": false
}
To get the results in CSV format, just use the --csv flag.
For more options see --help.
You can use iocitrix.py to check for known Indicators of Compromise on a NetScaler Dissect target. It checks for the following things:
Note that this script is meant to run on forensic disk images of Citrix NetScaler devices and not on the device itself. Also see the Creating Citrix NetScaler disk images section on how to create forensic disk images of your Citrix NetScaler.
Ensure that you have the latest version of Dissect, support for Citrix NetScaler was added in this PR: https://github.com/fox-it/dissect.target/pull/357
Disclaimer: While this tool strives for accuracy, it is possible for it to produce false positives or false negatives. Users are advised to cross-check results and use their own judgement before making any decisions based on this tool's output.
iocitrix.pyUse the following steps:
Note that step 4 will print the following error, but you can ignore it:
ERROR: pip's dependency resolver does not currently take into account all the packages that are installed. This behaviour is the source of the following dependency conflicts.
You can then run iocitrix.py <TARGETS> to start an IOC check against one or more forensic images. The script accepts any input that dissect can read as a Target, such as a .VMDK, or a raw disk image. Some examples are provided below.
python3 iocitrix.py image.vmx
python3 iocitrix.py image.vmdk
If you have also created a forensic image of the RAM disk, you can utilize iocitrix.py to incorporate volatile data in its triage as such:
python3 iocitrix.py md0.img+image.vmx
python3 iocitrix.py md0.img+image.vmdk
python3 iocitrix.py md0.img+da0.img
The + (plus) sign will load the two disk images as a single Dissect Target.
A Citrix NetScaler exposes two important block devices which can imaged for offline forensic analysis. These block device files can be found at the following paths:
/dev/md0: The disk that holds the root (/) directory. This is a RAM disk/dev/da0 or /dev/ada0: The disk that holds the /var and /flash directories. This is a persistent disk.The root directory (/) of Citrix NetScaler is a RAM disk, meaning that this is a volatile disk. This disk can be found at /dev/md0 when the NetScaler is powered-on and running, and will be unavailable when the NetScaler is powered-off. The /var and /flash directories reside on the /dev/da0 or /dev/ada0 disk as two separate partitions and are persistent.
The following commands can be used on a local Linux machine to create disk copies of your NetScaler over SSH. If you are using Windows, you can use WSL (wsl.exe) or Git Bash (git-bash.exe) to use the same commands. Refrain from using native PowerShell commands, as PowerShell doesn't properly handle streaming raw binary data over SSH.
This will show the available disks, including whether your persistent disk is /dev/da0 or /dev/ada0.
Then check the exact size:
local ~ $ ssh nsroot@<YOUR-NETSCALER-IP> shell diskinfo /dev/da0
local ~ $ ssh nsroot@<YOUR-NETSCALER-IP> shell diskinfo /dev/ada0
For the RAM disk:
local ~ $ ssh nsroot@<YOUR-NETSCALER-IP> shell diskinfo /dev/md0
The resulting da0.img or ada0.img will be approximately the size of the respective device, and md0.img approximately the size of /dev/md0.
local ~ $ df -h .
Make sure that there is enough space available to accomodate the disk image files, whose file sizes were calculated in the previous step.
/dev/da0 or /dev/ada0 disk to your local machineDepending on your disk name, execute either
local ~ $ ssh nsroot@<YOUR-NETSCALER-IP> shell dd if=/dev/da0 bs=10M | tail -c +7 | head -c -6 > da0.img
or
local ~ $ ssh nsroot@<YOUR-NETSCALER-IP> shell dd if=/dev/ada0 bs=10M | tail -c +7 | head -c -6 > ada0.img
Do note, that this can take some time to complete. No progess is shown when using dd.
It is adviced to wait until you gain control back over the prompt. This is an indication that dd finished.
The tail and head commands are used to strip out the NetScaler SSH banners from the bytestream. Be aware that NetScaler SSH banners may change over time, in which case you should manually edit the command.
/dev/md0 disk to your local machineThis step could be skipped, though this can cause iocitrix.py to miss certains incicators of compromise. Therefore it is recommended to create disk images of both /dev/md0 and /dev/da0 or /dev/ada0.
local ~ $ ssh nsroot@<YOUR-NETSCALER-IP> shell dd if=/dev/md0 bs=10M | tail -c +7 | head -c -6 > md0.img
iocitrix.py on your images