# Copyright 2020 FireEye, Inc. and Citrix Systems, Inc.

# FreeBSD/NetScaler bash doesn't support array declaration shortcut
# so we create the array by hand... I'm sorry.
declare -a ns_content_blacklist;
ns_content_blacklist[0]="template.new";
# don't match "execute" which is found in /netscaler/portal/templates/homepage.html
ns_content_blacklist[1]="exec[^u]";
ns_content_blacklist[2]="\`";
# /netscaler/portal/scripts/subscription.pl contains the line:
# eval(handle_subscriptions());
ns_content_blacklist[3]="eval([^h][^a][^n]";
ns_content_blacklist[4]="bash";
ns_content_blacklist[5]="ns.conf";
ns_content_blacklist[6]="curl";
ns_content_blacklist[7]="wget";
# seen in webshells
ns_content_blacklist[8]="Encode::decode(chr(";
ns_content_blacklist[9]="}exit";
# ref: https://github.com/projectzeroindia/CVE-2019-19781/blob/master/CVE-2019-19781.sh#L22
ns_content_blacklist[10]="pwnpzi1337";
ns_content_blacklist[11]="test1337";
# ref: https://github.com/trustedsec/cve-2019-19781/blob/master/citrixmash.py
ns_content_blacklist[12]="chr(";
# FireEye has seen actors copying the /flash/nsconfig/ns.conf file to a template so that it can be fetched remotely.
# Look for the header associated with the ns.conf file, like: `#NS12.1 Build 51.19`
ns_content_blacklist[13]="^\#NS";
# IP of host serving EternalBlue scanner/exploiter
ns_content_blacklist[14]="45.120.53.214";
ns_content_blacklist[15]="eternalblue.replay";
ns_content_blacklist[16]="scan.py";
ns_content_blacklist[17]="x64.dll";
ns_content_blacklist[18]="x86.dll";
ns_content_blacklist[19]="xp_eternalblue.replay";
# match filename `ld.sh` without matching `build.sh`
ns_content_blacklist[20]="[^i]ld.sh";
ns_content_blacklist[21]="piz.Lan";
# match filename `de.py` without matching `upgrade.py`
# see #9
ns_content_blacklist[22]="[^a]de.py";
ns_content_blacklist[23]=".new.zip";
ns_content_blacklist[24]="/tmp/rAgn";
# other activity
ns_content_blacklist[25]="198.44.227.126";
ns_content_blacklist[26]="/tmp/l.sh";
ns_content_blacklist[27]="Digest::MD5";
ns_content_blacklist[28]="Could not execute command";
# from subsequent NOTROBIN and/or APT41 blog posts from FEYE
ns_content_blacklist[29]="/tmp/bsd";
ns_content_blacklist[30]="/tmp/un";
ns_content_blacklist[31]="66.42.98.220";
ns_content_blacklist[32]="/var/nstmp/.nscache/prev.sh";
ns_content_blacklist[33]="/var/nstmp/.nscache/httpd-nscache_clean";
ns_content_blacklist[34]="/vpn/themes/imgs/tiny.php";
ns_content_blacklist[35]="/vpn/themes/imgs/debug.php";
ns_content_blacklist[36]="/vpn/themes/imgs/conn.php";

declare -a ns_exploit_dirs;
ns_exploit_dirs[0]="/netscaler/portal/templates/";
ns_exploit_dirs[1]="/netscaler/portal/scripts/";
# ref: https://github.com/x1sec/CVE-2019-19781/blob/master/CVE-2019-19781-DFIR.md
#
# note: this directory is synced between devices in an HA pair setup.
# this means that content dropped here by attackers onto a compromised device may
#  be copied to the standby device.
# the standby device was not directly exploited, but does have attacker content.
# this should at least be cleaned, and possibly investigated.
ns_exploit_dirs[2]="/var/vpn/bookmark/";
ns_exploit_dirs[3]="/var/tmp/netscaler/portal/templates/";
ns_exploit_dirs[4]="/var/vpn/themes/";
ns_exploit_dirs[5]="/var/vpn/theme/";

# custom output formatting for matches.
#   if the filename ends with .xml print the whole file,
#   otherwise print the match as found
report_filename_or_cat_file_if_xml() {
    local readonly fname="$1";
    if [[ $fname = *2e786d6c ]]; then
        # encoded name ends with .xml
        report_encoded_file_name_and_contents "$fname";
    else
        report_encoded_file_name "$fname";
    fi
}

report_ns_matches() {
    entries="$1";
    for entry in $entries; do
        report_filename_or_cat_file_if_xml "$entry";
    done
}

# Search the files in the given path for blacklisted terms.
# The blacklist is found above, at `ns_content_blacklist`.
#
# args:
#   path - path to search, relative to root.
scan_ns_directory_content() {
    local readonly path="$root_directory/$1";

    if [ ! -d "$path" ]; then
        debug "didn't find directory: $path";
        return
    fi

    local found=false;
    for re in "${ns_content_blacklist[@]}"; do
        local findcmd="find -L $path -type f";
        if [[ "$1" == "/var/vpn/bookmark/" ]]; then
            # in bookmark directory, scan only non-xml files and xml files modified after 2019-12-15
            findcmd="$findcmd -not -name "*.xml" -or -newermt "2019-12-15"";
        fi
        local entries=$($findcmd -exec grep -lI --null "$re" {} + | perl -0ne 'chomp; print(unpack("H*", $_), "\n")');
        if [ -n "$entries" ]; then
            found=true;
            report_match "blacklisted content '$re'";
            report "matches for '$re':";
            report_ns_matches "$entries";
        fi
    done

    if [ "$found" != true ]; then
        debug "did not find blacklisted or suspicious content in $path";
    fi
}

# Search the files in the given path for permissions like 'rw-r--r--' (644).
# It seems that files created during exploitation default to this mask.
#
# args:
#   path - path to search, relative to root.
scan_ns_directory_perms() {
    local readonly path="$root_directory/$1";

    if [ ! -d "$path" ]; then
        debug "didn't find directory: $path";
        return
    fi

    # /var/vpn/bookmarks/ contains legit entries with prefix `bm_prefix_`
    # on NetScaler versions after 13.0.47.24 (patch)
    local entries=$(find "$path" -perm 644 -not -name "bm_prefix_*" -print0 | perl -0ne 'chomp; print(unpack("H*", $_), "\n")');
    if [ -n "$entries" ]; then
        report_match "incorrect file permissions";
        report "files with permissions 644:";
        report_ns_matches "$entries";
        report "Please review the above paths for any unexpected files.";
        report "Exploits commonly write to files with these permissions;"
        report "however, customization of a Citrix NetScaler environment may cause false positives in the above list."
        report "For example, '/var/vpn/bookmark/[legitimate-username].xml' may be valid in your environment."
    else
        debug "did not find incorrect permissions in $path";
    fi
}

# Find files created in the templates directory that have been created since Jan 1, 2020.
# This seems to detect lots of activity; however, we're not sure how this interacts with patches and customizations.
# Disabled for now, with preference towards the permission scanner.
scan_ns_new_templates() {
    if [ ! -d "$root_directory/netscaler/portal/templates" ]; then
        debug "didn't find templates directory";
        return;
    fi

    local readonly entries=$(find "$root_directory/netscaler/portal/templates/" -type f -newerct "2020-01-01" -print);
    if [ -n "$entries" ]; then
        report_match "recently created template";
        report "recently created templates:";
        report "$entries";
    else
        debug "did not find new files in /netscaler/portal/templates/.";
    fi
}


# Find files created in the scripts directory that have been created since Jan 1, 2020.
# This seems to detect lots of activity; however, we're not sure how this interacts with patches and customizations.
# Disabled for now, with preference towards the permission scanner.
scan_ns_new_scripts() {
    if [ ! -d "$root_directory/netscaler/portal/scripts" ]; then
        debug "didn't find scripts directory";
        return;
    fi

    local readonly entries=$(find "$root_directory/netscaler/portal/scripts/" -type f -newerct "2020-01-01" -print);
    if [ -n "$entries" ]; then
        report_match "recently created script";
        report "recently created scripts:";
        report "$entries";
    else
        debug "did not find new files in /netscaler/portal/scripts/.";
    fi
}

scan_netscaler_content() {
    for path in "${ns_exploit_dirs[@]}"; do
        scan_ns_directory_content "$path";
        scan_ns_directory_perms "$path";
    done

    # disabled, since we're not sure of the interaction with system updates and/or customizations.
    #scan_ns_new_templates;
    #scan_ns_new_scripts;
}
