-
Notifications
You must be signed in to change notification settings - Fork 12
/
Copy pathioc-scanner-CVE-2019-19781.sh
executable file
·419 lines (373 loc) · 14.4 KB
/
ioc-scanner-CVE-2019-19781.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
#!/bin/bash
# Indicator of Compromise Scanner for CVE-2019-19781 (Citrix ADC)
# Copyright 2020 FireEye, Inc. and Citrix Systems, Inc.
#
# Usage:
#
# bash ioc-scanner-CVE-2019-19781.sh [-v|--verbose] [root path, optional, default: /]
#
# Must be run as root when running against a live device.
# Writes status to STDERR.
# Writes results to STDOUT.
# Non-zero status upon failure.
#
# ----------------------------------------------------
# Note, please keep the above in-sync with `build.sh`.
# unset variables are errors
set -o nounset;
# any failed commands are errors
set -o errexit;
# temp directory for intermediate stages of report
# (removed at end of script)
readonly tmpdir="/tmp/tmp_$(date +%s)";
mkdir "$tmpdir";
# initialize summary flags
found_evidence_compromise="No";
found_evidence_scanning="N/A - Script Executed in Default Mode";
found_evidence_failed_exploitation="N/A - Script Executed in Default Mode";
panic() {
echo "[erro]: $@" >&2;
exit 1;
}
info() {
echo "[info]: $@" >&2;
}
verbose=false;
debug() {
if "$verbose"; then
echo "[debu]: $@" >&2;
fi
}
repfile="$tmpdir/repbody.txt";
report() {
echo "$@" >> "$repfile";
}
report_encoded_file_name() {
echo "$1" | perl -lne 'print(pack("H*", $_))' >> "$repfile";
}
report_encoded_file_name_and_contents() {
report_encoded_file_name "$1";
echo "contents:" >> "$repfile";
echo "$1" | perl -0ne 'print(pack("H*", $_))' | tr -d '\240' | xargs -0 cat >> "$repfile";
}
# format a key/value pair ($1, $2) with column width $3
fmt_key_val() {
printf "%-$3s : %s\n" "$1" "$2"
}
# record a match - that a compromise was detected.
report_match() {
found_evidence_compromise="Yes"
report "";
report "**********************************************************************";
report "MATCH: $@";
report "Found evidence of potential compromise. ";
report "You should consider performing a forensic investigation of the system.";
report "**********************************************************************";
}
# record a match - that successful scanning was detected.
report_scanning() {
found_evidence_scanning="Yes"
report "";
report "**********************************************************************";
report "SCANNING: $@";
report "Found evidence of successful scanning. ";
report "The device was probably vulnerable during the period of scanning. ";
report "There is a strong likelihood of compromise. ";
report "**********************************************************************";
}
# record a match - that failed exploitation was detected.
report_failed_exploitation() {
found_evidence_failed_exploitation="Yes"
report "";
report "**********************************************************************";
report "FAILED EXPLOITATION: $@";
report "Found evidence of failed exploitation. ";
report "An actor attempted to exploit the device; however, it failed. ";
report "This may be due to any of many things, including: ";
report " - the device was patched correctly ";
report " - the exploit had a bug ";
report " - another actor (such as NOTROBIN) blocked it ";
report "**********************************************************************";
}
# parse args.
# if we find -h or --help, print usage and quit (status: 0).
# if we find -v or --verbose, enable the verbose flag.
# otherwise, capture `$arg_root_directory` as positional argument, if present.
# there should really only be one (optional) positional argument,
# but we don't check this right now.
arg_root_directory="";
while test $# -gt 0; do
case "$1" in
-h|--help)
echo "Indicator of Compromise Scanner for CVE-2019-19781 ";
echo "Copyright 2020 FireEye, Inc. and Citrix Systems, Inc. ";
echo " ";
echo "Usage: ";
echo " ";
echo " bash $0 [-v|--verbose] [root path, optional, default: /]";
echo " ";
echo "Must be run as root when running against a live device. ";
echo "Writes status to STDERR. ";
echo "Writes results to STDOUT. ";
echo "Non-zero status upon failure. ";
echo " ";
echo "Please see additional documentation here: ";
echo " ";
echo " https://github.com/fireeye/ioc-scanner-CVE-2019-19781 ";
echo " ";
exit 0;
;;
-v|--verbose)
verbose=true;
shift;
;;
*)
# this will overwrite existing variable, so it doesn't handle
# the case when multiple args are provided (incorrectly).
arg_root_directory="$1";
shift;
;;
esac
done
# root_directory is the directory from which to begin scanning.
# comes from first positional CLI argument, or default: "/".
# this supports:
# - running against mounted forensic images.
# - running against test cases.
if [ -n "$arg_root_directory" ]; then
readonly root_directory="$arg_root_directory";
else
readonly root_directory="/";
fi
# live_mode is true when the tool is running on and inspecting a live system.
# live_mode is false when the tool is inspecting a forensic image.
if [ "$root_directory" == "/" ]; then
readonly live_mode=true;
else
readonly live_mode=false;
fi
# return md5 hash for $1
echo_hash() {
# OpenBSD/NetScaler doesn't have md5sum, but it has md5.
# If it doesn't exist, assume `md5sum` is available (Linux).
if [ -f "/sbin/md5" ]; then
md5 -q "$1"
else
md5sum "$1" | cut -d" " -f1
fi
}
info "**********************************************************************"
info "* Indicator of Compromise Scanner for CVE-2019-19781 *"
info "* Copyright 2020 FireEye, Inc. and Citrix Systems, Inc. *"
info "* *"
info "* https://github.com/fireeye/ioc-scanner-CVE-2019-19781 *"
info "**********************************************************************"
info ""
readonly scripthash=$(echo_hash "$0")
report "this script: $0 $scripthash"
report "root_directory: $root_directory";
if [ ! -d "$root_directory" ]; then
panic "$root_directory does not exist";
fi
# when running in live mode, must be super user
# this is so we can access all files, list ports, enumerate processes, etc.
if $live_mode; then
if [[ $UID -ne 0 || $EUID -ne 0 ]]; then
panic "$0 must be run as root in live mode";
fi
fi
# collect some basic stats about the system
readonly startdate=$(date)
if $live_mode; then
info ""
info "Since you are running the tool in live mode,"
info " the tool will now emit metadata, such as timestamp and hostname."
info ""
report "whoami: $(whoami)"
report "uname: $(uname -a)"
report "hostname: $(hostname)"
report "date: $startdate"
fi
info ""
info "Next, the tool will emit first and last entries from select log files."
info "This will show the time ranges that the logs cover."
info "If the logs have rolled since scanning and exploitation began (~Jan 7),"
info " then the tool may not identify all compromise."
info "Likewise, if the logs have been modified by an attacker,"
info " then this tool may not recognize compromise."
info ""
readonly log_patterns=(
"/var/log/bash.log"
"/var/log/notice.log"
"/var/log/httpaccess.log"
"/var/log/httperror.log"
);
for logpattern in "${log_patterns[@]}"; do
if ! compgen -G "$root_directory$logpattern*" >/dev/null; then
continue
fi
# sorry for the indentation here...
# because we're looping over ls results, which may include paths with spaces,
# then we need be careful to split on newlines, not spaces.
IFSBAK="$IFS"
IFS='
'
# use `ls | sort` to find all available log files.
# order from old to new (-r).
for logfile in $(ls "$root_directory$logpattern"* | sort -r); do
report "log file: $logfile";
report " first entry: $(zgrep ":" "$logfile" | head -n 1)";
report " last entry: $(zgrep ":" "$logfile" | tail -n 1)";
report " metadata: $(ls -lah $logfile)";
report " md5: $(echo_hash $logfile)":
done
IFS="$IFSBAK"
done
# current_directory is the path to the directory containing this script.
# ref: https://stackoverflow.com/a/4774063/87207
readonly current_directory="$( cd "$(dirname "$0")" ; pwd -P )"
readonly version_file="$current_directory/version.sh";
if [ ! -f "$version_file" ]; then
panic "missing script: $version_file";
fi
source "$version_file";
# scanners is the list of scanners that we'll load as dependencies.
# register new scanners here. this loads the code and makes it accessible
# invoke the exported functions manually below.
declare -a scanners;
# FreeBSD/NetScaler bash doesn't support array declaration shortcut
# so we create the array by hand... I'm sorry.
scanners[0]="scanners/fs-paths.sh";
scanners[1]="scanners/ports.sh";
scanners[2]="scanners/processes.sh";
scanners[3]="scanners/crontab.sh";
scanners[4]="scanners/access-logs.sh";
scanners[5]="scanners/error-logs.sh";
scanners[6]="scanners/netscaler-content.sh";
scanners[7]="scanners/shell-history.sh";
scanners[8]="scanners/cron-history.sh";
scanners[9]="scanners/successful-scanning.sh";
scanners[10]="scanners/failed-exploitation.sh";
info ""
info "Next, the tool will load supporting scanning modules."
info "It will report the MD5 hashes of these files"
info " so that we can recover exact versions if you have bugs to report."
info ""
for scanner_name in "${scanners[@]}"; do
scanner_path="$current_directory/$scanner_name";
if [ ! -f "$scanner_path" ]; then
panic "missing script: $scanner_name";
fi
report "loading: $scanner_name ($(echo_hash "$scanner_path"))";
source "$scanner_path";
done
# manually invoke scanner routines, loaded from above.
# do shell history first, cause its most likely to be overwritten.
info "";
info "The tool will now scan shell history logs for post-exploitation.";
info "If it identifies evidence of compromise, it will emit like this:";
info "";
info "example: ****************************************************";
info "example: MATCH: description-of-evidence-here ";
info "example: Found evidence of potential compromise. ";
info "example: You should consider performing a ";
info "example: forensic investigation of the system. ";
info "example: ****************************************************";
info "";
info "here we go...";
info "";
scan_shell_history;
info "";
info "The tool will now look for unexpected cron history entries.";
info "";
scan_cron_history;
info "";
info "The tool will now look for known paths to malware files.";
info "";
scan_fs_known_paths;
info "";
info "The tool will now scan for unexpected listening ports.";
info "";
scan_ports;
info "";
info "The tool will now scan for unexpected processes.";
info "";
scan_processes;
info "";
info "The tool will now scan for unexpected crontab entries.";
info "";
scan_crontab;
info "";
info "The tool will now search web server logs for exploitation.";
info "";
scan_access_logs;
info "";
info "The tool will now search error logs for post-exploitation.";
info "";
scan_error_logs;
info "";
info "The tool will now scan NetScaler directories for unexpected content.";
info "";
scan_netscaler_content;
if "$verbose"; then
info "";
info "The tool will now search web server logs for successful scanning activity.";
info "";
found_evidence_scanning="No";
scan_successful_scanning;
fi
if "$verbose"; then
info "";
info "The tool will now search web server logs for failed exploitation attempts.";
info "";
found_evidence_failed_exploitation="No";
scan_failed_exploitation;
fi
# save the report text we've gathered to here
report "";
report "end of report.";
# the remaining information is placed at the top of the report
repfile="$tmpdir/rephead.txt";
# print a summary of the findings
report "";
report "**********************************************************************";
report "SUMMARY:"
colwidth="37";
report "$(fmt_key_val "Date" "$startdate" "$colwidth")";
# Hostname
if [ -f "$root_directory/nsconfig/ns.conf" ]; then
readonly hostname=$(grep 'set ns hostName' "$root_directory/nsconfig/ns.conf" | awk -F ' ' '{print $4}');
report "$(fmt_key_val "Hostname" "$hostname" "$colwidth")";
fi
# IP address
if [ -f "$root_directory/nsconfig/ns.conf" ]; then
readonly ipstr=$(grep 'ns config \-IPAddress' "$root_directory/nsconfig/ns.conf" | awk -F ' ' '{print $5}');
report "$(fmt_key_val "IP" "$ipstr" "$colwidth")";
fi
# NS version
if [ -f "$root_directory/flash/boot/loader.conf" ]; then
# grab the version string from kernel image filename
ns_verstr="$(grep "kernel=" "$root_directory/flash/boot/loader.conf" | sed 's/["/]//g' | awk -F '=' '{print $2}')";
# strip leading "ns-"
if [[ $ns_verstr == "ns-"* ]]; then
ns_verstr=${ns_verstr:3};
fi
report "$(fmt_key_val "NS version" "$ns_verstr" "$colwidth")";
fi
report "$(fmt_key_val "Scanner version" "$git_tag" "$colwidth")";
if "$verbose"; then
runmode="Verbose";
else
runmode="Default";
fi
report "$(fmt_key_val "Scanner run mode" "$runmode" "$colwidth")";
report "$(fmt_key_val "Evidence of compromise found" "$found_evidence_compromise" "$colwidth")";
report "$(fmt_key_val "Evidence of scanning found" "$found_evidence_scanning" "$colwidth")";
report "$(fmt_key_val "Evidence of failed exploitation found" "$found_evidence_failed_exploitation" "$colwidth")";
report "**********************************************************************";
report "";
cat "$tmpdir/rephead.txt";
cat "$tmpdir/repbody.txt";
info "done.";
rm -rf "$tmpdir";
exit 0;