Table of Contents


History

DateChanges
2025/07/20Initial publication
2025/07/21Update script to remove double echo to log-file, replaced by tee command
2025/07/21Fix T33 for -newermt parameter
2025/07/21Make script result stand out more in output
2025/07/21Added comment on setup type (standalone, ha, cluster)
2025/07/21Update script, thanks for the feedback Anton van Pelt
2025/07/21Added check 35
2025/07/22Added check 36 and 37

Introduction

In the fallout of the latest vulnerabilities affecting NetScaler, namely CVE-2025-5777 and CVE-2025-6543, a lot of people have started reaching out regarding the availability of “indicators of compromise” (IoC).

Unfortunately, there is also a lot of FUD going around.

To simplify a lot of the checks, I’ve adapted an IoC-script from an older CVE.
For details about the old CVE, check Citrix Support.

I’ve taken the liberty to take the commands from my fellow former Citrix CTP, Manuel Winkel, and incorporated them into my old script. For more information, check the original article:
https://www.deyda.net/index.php/en/2025/06/26/checklist-for-netscaler-citrix-adc-cve-2025-5777/.

Use this script at your own discretion/risk, as it comes without any guarantees!

  • Note that I had to create this script in a hurry for a customer, so I realize it is not perfect form at the moment.
  • Note that the outcome of the script might require you to go through the generated log-file to assess if a possible indicator of compromise is real and not a false flag. This will still require knowledge on how NetScaler operates.
  • Always contact your local expert or myself when in doubt.
If you want full coverage, make sure to run this script on all nodes in your setup (standalone, high-availability, cluster)!!
If you have any comments or suggestions, please add them on GitHub.

Back to top

Script

The latest version of the script is always available on GitHub

Usage

Copy the script to your NetScaler instance, and put it in /var/tmp/, or just copy the contents in /var/tmp/ioccheck.sh.

The script requires two command-line arguments:

  • Name of the environment
  • Date of the last upgrade, to validate modified files.

Invoke the script, replace the date with the date of your last upgrade +1, for example:

sh /var/tmp/ioccheck.sh $(hostname) 2025-07-15

Always double check the output log, even if the scan result says “OK”.
Some commands just log some output, which needs to be validated manually!

The log file can be found at the following location: /var/tmp/<hostname>.log

Content (GitHub Gist)

#!/bin/sh
NAME=$1
UPDATE=$2
OUTLOG=/var/tmp/$NAME.log
echo "Sending output to $OUTLOG"

echo "RUNNING CHECKS FOR $NAME AGAINST DATE $UPDATE" | tee -a $OUTLOG

echo "##### 01 - Check for modified files in /var/nsinstall with extension .php #####" | tee -a $OUTLOG
T01=$(find /var/nsinstall/ -type f -iname '*.php' -exec ls -al {} \; 2>>$OUTLOG)
echo "#############################################################" >> $OUTLOG
echo "" >> $OUTLOG

echo "##### 02 - Check for modified files in /var/nsproflog with extension .php #####" | tee -a $OUTLOG
T02=$(find /var/nsproflog/ -type f -iname '*.php' -newermt $UPDATE -exec ls -al {} \; 2>>$OUTLOG)
echo "#############################################################" >> $OUTLOG
echo "" >> $OUTLOG

echo "##### 03 - Check for modified files in /var/netscaler/ns_gui/ with extension .php #####" | tee -a $OUTLOG
T03=$(find /var/netscaler/ns_gui/ -type f -iname '*.php' -newermt $UPDATE -not -path "/var/netscaler/ns_gui/admin_ui/*" -exec ls -al {} \; 2>>$OUTLOG)
echo "#############################################################" >> $OUTLOG
echo "" >> $OUTLOG

echo "##### 04 - Check for modified files in /var/netscaler/gui/ with extension .php #####" | tee -a $OUTLOG
T04=$(find /var/netscaler/gui/ -type f -iname '*.php' -newermt $UPDATE -not -path "/var/netscaler/gui/admin_ui/*" -exec ls -al {} \; 2>>$OUTLOG)
echo "#############################################################" >> $OUTLOG
echo "" >> $OUTLOG

echo "##### 05 - Check for modified files in /var/vpn/ #####" | tee -a $OUTLOG
T05=$(find /var/vpn/ -type f -newermt $UPDATE -exec ls -al {} \; 2>>$OUTLOG)
echo "#############################################################" >> $OUTLOG
echo "" >> $OUTLOG
 
echo "##### 06 - Check for modified files in /var/netscaler/logon/ #####" | tee -a $OUTLOG
T06=$(find /var/netscaler/logon/ -type f -newermt $UPDATE -not -path "/var/netscaler/logon/themes/*/resources/*.xml" -exec ls -al {} \; 2>>$OUTLOG)
echo "#############################################################" >> $OUTLOG
echo "" >> $OUTLOG

echo "##### 07 - Check for modified files in /var/netscaler/logon/ with extension .php #####" | tee -a $OUTLOG
T07=$(find /var/netscaler/logon/ -type f -iname '*.php' -newermt $UPDATE -exec ls -al {} \; 2>>$OUTLOG)
echo "#############################################################" >> $OUTLOG
echo "" >> $OUTLOG

echo "##### 08 - Check for modified files in /var/python/ #####" | tee -a $OUTLOG
T08=$(find /var/python/ -type f -newermt $UPDATE -exec ls -al {} \; 2>>$OUTLOG)
echo "#############################################################" >> $OUTLOG
echo "" >> $OUTLOG

echo "##### 09 - Check files in /netscaler/ns_gui/ with extension .php #####" | tee -a $OUTLOG
echo "WARNING ---> These files may change timestamps during system reboots — use this only as an indicator, not proof." >> $OUTLOG
T09=$(find /netscaler/ns_gui/ -type f -iname '*.php' -newermt $UPDATE -not -path "/netscaler/ns_gui/admin_ui/*" -exec ls -al {} \; 2>>$OUTLOG)
echo "#############################################################" >> $OUTLOG
echo "" >> $OUTLOG

echo "##### 10 - Check files in /netscaler/gui/ with extension .php #####" | tee -a $OUTLOG
T10=$(find /netscaler/gui/ -type f -iname '*.php' -newermt $UPDATE -not -path "/netscaler/gui/admin_ui/*" -exec ls -al {} \; 2>>$OUTLOG)
echo "#############################################################" >> $OUTLOG
echo "" >> $OUTLOG

echo "##### 11 - Check files in /netscaler/portal/ with extension .php #####" | tee -a $OUTLOG
T11=$(find /netscaler/portal/ -type f -iname '*.php' -newermt $UPDATE -exec ls -al {} \; 2>>$OUTLOG)
echo "#############################################################" >> $OUTLOG
echo "" >> $OUTLOG

echo "##### 12 - Check for 'Graceful' in /var/log/httperror.log #####" | tee -a $OUTLOG
T12=$(grep "Graceful" /var/log/httperror.log | grep -v ":00:" >> $OUTLOG)
echo "#############################################################" >> $OUTLOG
echo "" >> $OUTLOG

echo "##### 13 - Check for 'Graceful' in /var/log/httperror.log.* (compressed files) #####" | tee -a $OUTLOG
T13=$(gzcat /var/log/httperror.log.*.gz | grep Graceful | grep -v ":00:" >> $OUTLOG)
echo "#############################################################" >> $OUTLOG
echo "" >> $OUTLOG

echo "##### 14 - Check for suspicious pipes #####" | tee -a $OUTLOG
T14=$(pgrep -i nsppe | xargs -I% sh -c 'lsof -p % 2>>$OUTLOG| egrep -q "PIPE" && echo "%: suspicious pipe"')
echo "#############################################################" >> $OUTLOG
echo "" >> $OUTLOG

echo "##### 15 - Check files in /var/vpn/ with extension .php #####" | tee -a $OUTLOG
T15=$(find /var/vpn -regex '/var/vpn/vpn.*' -type f -iname '*.php' -exec ls -al {} \; 2>>$OUTLOG)
echo "#############################################################" >> $OUTLOG
echo "" >> $OUTLOG

echo "##### 16 - Check files in /var/netscaler/logon/LogonPoint with extension .php #####" | tee -a $OUTLOG
T16=$(find /var/netscaler/logon/LogonPoint -depth 1 -type f -iname '*.php' -exec ls -al {} \; 2>>$OUTLOG)
echo "#############################################################" >> $OUTLOG
echo "" >> $OUTLOG

echo "##### 17 - Run python script to get newest timestamps #####" | tee -a $OUTLOG
T17=$(python -c "import os, glob, time; newest_timestamp = max(os.stat(f).st_mtime for f in glob.glob('/var/nsinstall/*')); print('\n'.join(os.path.join(dirpath, f) for dir in ['/var/netscaler/logon/', '/var/python/', '/var/vpn/'] for dirpath, _, files in os.walk(dir) for f in files if os.stat(os.path.join(dirpath, f)).st_mtime > newest_timestamp))" >> $OUTLOG)
echo "#############################################################" >> $OUTLOG
echo "" >> $OUTLOG

echo "##### 18 - Check for '.sh' in /var/log/httperror.log* #####" | tee -a $OUTLOG
T18=$(zgrep '.sh' /var/log/httperror.log* >> $OUTLOG)
echo "#############################################################" >> $OUTLOG
echo "" >> $OUTLOG

echo "##### 19 - Check for '.php' in /var/log/httperror.log* #####" | tee -a $OUTLOG
T19=$(zgrep '.php' /var/log/httperror.log* >> $OUTLOG)
echo "#############################################################" >> $OUTLOG
echo "" >> $OUTLOG

echo "##### 20 - Check for '.pl' in /var/log/httperror.log #####" | tee -a $OUTLOG
T20=$(zgrep '.pl' /var/log/httperror.log* >> $OUTLOG)
echo "#############################################################" >> $OUTLOG
echo "" >> $OUTLOG

echo "##### 21 - Check /var/log/sh.log #####" | tee -a $OUTLOG
T21=$(zgrep -E 'database.php|/flash/nsconfig/keys/updated|/flash/nsconfig/keys|/ns_gui/vpn|LDAPTLS_REQCERT|ldapsearch|openssl|/nsconfig/ns.conf|del /etc/auth.conf|cp /usr/bin/bash|.F1.key|.F2.key|nobody' /var/log/sh.log* >> $OUTLOG)
echo "#############################################################" >> $OUTLOG
echo "" >> $OUTLOG

echo "##### 22 - Check /var/log/bash.log #####" | tee -a $OUTLOG
T22=$(zgrep -E 'database.php|/flash/nsconfig/keys/updated|/flash/nsconfig/keys|/ns_gui/vpn|LDAPTLS_REQCERT|ldapsearch|openssl|/nsconfig/ns.conf|del /etc/auth.conf|cp /usr/bin/bash|.F1.key|.F2.key|nobody' /var/log/bash.log* >> $OUTLOG)
echo "#############################################################" >> $OUTLOG
echo "" >> $OUTLOG

echo "##### 23 - Check for httpd processes running as 'nobody' #####" | tee -a $OUTLOG
T23=$(ps aux | grep nobody | grep -v '/bin/httpd' >> $OUTLOG)
echo "#############################################################" >> $OUTLOG
echo "" >> $OUTLOG

echo "##### 24 - Check /etc/crontab #####" | tee -a $OUTLOG
T24=$(grep '' /etc/crontab >> $OUTLOG)
echo "#############################################################" >> $OUTLOG
echo "" >> $OUTLOG

echo "##### 25 - Check crontab for user 'nobody' #####" | tee -a $OUTLOG
T25=$(crontab -l -u nobody >> $OUTLOG)
echo "#############################################################" >> $OUTLOG
echo "" >> $OUTLOG

echo "##### 26 - Check /var/log/httpaccess-vpn.log* for status code 200 but not 'CitrixReceiver'#####" | tee -a $OUTLOG
T26=$(zgrep -E -v 'CitrixReceiver' /var/log/httpaccess-vpn.log* | grep ' 200 ' >> $OUTLOG)
echo "#############################################################" >> $OUTLOG
echo "" >> $OUTLOG

echo "##### 27 - Check /var/log/httpaccess-vpn.log* for 'HeadlessChrome' #####" | tee -a $OUTLOG
T27=$(zgrep 'HeadlessChrome' /var/log/httpaccess-vpn.log* >> $OUTLOG)
echo "#############################################################" >> $OUTLOG
echo "" >> $OUTLOG

echo "##### 28 - Check files in /var/core/1 #####" | tee -a $OUTLOG
T28=$(ls -ll /var/core/1 >> $OUTLOG)
echo "#############################################################" >> $OUTLOG
echo "" >> $OUTLOG

echo "##### 29 - Check for running python processes #####" | tee -a $OUTLOG
T29=$(ps -aux | grep python >> $OUTLOG)
echo "#############################################################" >> $OUTLOG
echo "" >> $OUTLOG

echo "##### 30 - Check for running perl processes #####" | tee -a $OUTLOG
T30=$(ps -aux | grep perl >> $OUTLOG)
echo "#############################################################" >> $OUTLOG
echo "" >> $OUTLOG

echo "##### 31 - Top 10 running processes #####" | tee -a $OUTLOG
T31=$(top -n 10 >> $OUTLOG)
echo "#############################################################" >> $OUTLOG
echo "" >> $OUTLOG

echo "##### 32 - Check for illegal remote commands #####" | tee -a $OUTLOG
T32=$(grep -v '127.0.0' /var/log/*.log | grep 'nc -l|/etc/passwd|curl|python -c|.php' >> $OUTLOG)
echo "#############################################################" >> $OUTLOG
echo "" >> $OUTLOG

echo "##### 33 - Check file permissions in /var #####" | tee -a $OUTLOG
T33=$(find /var -perm -4000 -user root -not -path "/var/nslog/*" -newermt $UPDATE -exec ls -l {} \; 2>>$OUTLOG)
echo "#############################################################" >> $OUTLOG
echo "" >> $OUTLOG

echo "##### 34 - Check 'nsfsyncd' process #####" | tee -a $OUTLOG
T34=$(ps aux | grep nsfsyncd >> $OUTLOG)
echo "#############################################################" >> $OUTLOG
echo "" >> $OUTLOG

echo "##### 35 - Check /var/tmp for callhome_tmps #####" | tee -a $OUTLOG
T35=$(find /var/tmp -type f -iname 'callhome_tmps*' -exec ls -al {} \; 2>>$OUTLOG)
echo "#############################################################" >> $OUTLOG
echo "" >> $OUTLOG

echo "##### 36 - Find Root-owned SUID files #####" | tee -a $OUTLOG
T36=$(find /var \( -perm -4001 -or \( -perm -4010 -group nobody \) \) -user root -exec ls -al {} \; 2>>$OUTLOG)
echo "#############################################################" >> $OUTLOG
echo "" >> $OUTLOG

echo "##### 37 - Find backdoors in rc.netscaler #####" | tee -a $OUTLOG
T37=$(grep python /flash/nsconfig/rc.netscaler >> $OUTLOG)
echo "#############################################################" >> $OUTLOG
echo "" >> $OUTLOG

echo "" >> $OUTLOG
echo "Checking results..."
RESULT=
[ -z "$T01" ] || RESULT="$T01\n"
[ -z "$T02" ] || RESULT="$RESULT$T02\n"
[ -z "$T03" ] || RESULT="$RESULT$T03\n"
[ -z "$T04" ] || RESULT="$RESULT$T04\n"
[ -z "$T05" ] || RESULT="$RESULT$T05\n"
[ -z "$T06" ] || RESULT="$RESULT$T06\n"
[ -z "$T07" ] || RESULT="$RESULT$T07\n"
[ -z "$T08" ] || RESULT="$RESULT$T08\n"
[ -z "$T09" ] || RESULT="$RESULT$T09\n"
[ -z "$T10" ] || RESULT="$RESULT$T10\n"
[ -z "$T11" ] || RESULT="$RESULT$T11\n"
[ -z "$T12" ] || RESULT="$RESULT$T12\n"
[ -z "$T13" ] || RESULT="$RESULT$T13\n"
[ -z "$T14" ] || RESULT="$RESULT$T14\n"
[ -z "$T15" ] || RESULT="$RESULT$T15\n"
[ -z "$T16" ] || RESULT="$RESULT$T16\n"
[ -z "$T17" ] || RESULT="$RESULT$T17\n"
[ -z "$T18" ] || RESULT="$RESULT$T18\n"
[ -z "$T19" ] || RESULT="$RESULT$T19\n"
[ -z "$T20" ] || RESULT="$RESULT$T20\n"
[ -z "$T21" ] || RESULT="$RESULT$T21\n"
[ -z "$T22" ] || RESULT="$RESULT$T22\n"
[ -z "$T23" ] || RESULT="$RESULT$T23\n"
[ -z "$T24" ] || RESULT="$RESULT$T24\n"
[ -z "$T25" ] || RESULT="$RESULT$T25\n"
[ -z "$T26" ] || RESULT="$RESULT$T26\n"
[ -z "$T27" ] || RESULT="$RESULT$T27\n"
[ -z "$T28" ] || RESULT="$RESULT$T28\n"
[ -z "$T29" ] || RESULT="$RESULT$T29\n"
[ -z "$T30" ] || RESULT="$RESULT$T30\n"
[ -z "$T31" ] || RESULT="$RESULT$T31\n"
[ -z "$T32" ] || RESULT="$RESULT$T32\n"
[ -z "$T33" ] || RESULT="$RESULT$T33\n"
[ -z "$T34" ] || RESULT="$RESULT$T34\n"
[ -z "$T35" ] || RESULT="$RESULT$T35\n"
[ -z "$T36" ] || RESULT="$RESULT$T36\n"
[ -z "$T37" ] || RESULT="$RESULT$T37\n"
echo "Done."
echo ""
echo ""
echo "#############################################################"
# [ -z "$RESULT" ] || RESULT="$RESULT$(ps auxd)\n$(sysctl -a netscaler.version kern.boottime)"
[ -z "$RESULT" ] || echo -e "$RESULT\n!! Scan possibly identified one or more IOCs"
[ -n "$RESULT" ] || echo "Scan OK, don't forget to look through the log file"
echo "#############################################################"

Back to top