diff --git a/.gitignore b/.gitignore index 2f63682..f69817b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ -*.log -.env.local +*.env.local +*.env.secret +.env.real diff --git a/install.sh b/install.sh index 5eb7440..c8e8e66 100755 --- a/install.sh +++ b/install.sh @@ -3,6 +3,7 @@ set -Eeuo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" ENV_FILE="${ENV_FILE:-$SCRIPT_DIR/.env}" +SRC_DIR="$SCRIPT_DIR/src" log(){ printf '[malware-watch] %s\n' "$*"; } die(){ printf '[malware-watch] ERROR: %s\n' "$*" >&2; exit 1; } @@ -13,12 +14,14 @@ fi [[ -r "$ENV_FILE" ]] || die "Missing $ENV_FILE" set -a -# shellcheck disable=SC1090 . "$ENV_FILE" set +a : "${DISCORD_WEBHOOK:?DISCORD_WEBHOOK is required in .env}" : "${ABUSECH_AUTH_KEY:?ABUSECH_AUTH_KEY is required in .env}" +[[ "$DISCORD_WEBHOOK" != "디스코드웹훅URL" ]] || die "Replace DISCORD_WEBHOOK placeholder in .env" +[[ "$ABUSECH_AUTH_KEY" != "abuse.ch api 키" ]] || die "Replace ABUSECH_AUTH_KEY placeholder in .env" + CPU_THRESHOLD="${CPU_THRESHOLD:-150}" ALERT_SCORE="${ALERT_SCORE:-60}" CRITICAL_SCORE="${CRITICAL_SCORE:-100}" @@ -36,6 +39,10 @@ esac command -v systemctl >/dev/null || die "systemd is required" command -v apt-get >/dev/null || die "This installer currently supports Debian/Ubuntu (apt)" +for f in malware-watch.py malware-quarantine.py malware-feed-update.py clamav-watch-scan.sh block-malware-iocs; do + [[ -f "$SRC_DIR/$f" ]] || die "Missing $SRC_DIR/$f" +done + log "Installing base dependencies" apt-get update -y DEBIAN_FRONTEND=noninteractive apt-get install -y \ @@ -67,397 +74,9 @@ umask 077 } > /etc/malware-watch/config chmod 600 /etc/malware-watch/config -install_payload(){ - local dest="$1" mode="$2" - base64 -d > "$dest" - chmod "$mode" "$dest" - chown root:root "$dest" -} - -install_payload /usr/local/sbin/malware-watch.py 700 <<'B64_MALWARE_WATCH' -IyEvdXNyL2Jpbi9lbnYgcHl0aG9uMwppbXBvcnQgb3MscmUsanNvbix0aW1lLGhhc2hsaWIsc29j -a2V0LHN1YnByb2Nlc3Msc2hsZXgKZnJvbSBwYXRobGliIGltcG9ydCBQYXRoCkNGRz0nL2V0Yy9t -YWx3YXJlLXdhdGNoL2NvbmZpZyc7IEJBU0U9UGF0aCgnL3Zhci9saWIvbWFsd2FyZS13YXRjaCcp -OyBGRUVEUz1CQVNFLydmZWVkcyc7IFNUQVRFPUJBU0UvJ3N0YXRlLmpzb24nOyBFVklEPVBhdGgo -Jy9yb290L3NlY3VyaXR5LWV2aWRlbmNlL21hbHdhcmUtd2F0Y2gnKQpLTk9XTj0oJ3htcmlnJywn -eGFybScsJ2tpbnNpbmcnLCdrZGV2dG1wZnNpJywnd2F0Y2hib2cnLCdtaW5lcmQnLCdjcHVtaW5l -cicsJ21vbmVyb29jZWFuJywnY3J5cHRvbWluZXInKQpURU1QPSgnL3RtcC8nLCcvdmFyL3RtcC8n -LCcvZGV2L3NobS8nKQoKZGVmIGNmZygpOgogZD17fQogZm9yIGxpbmUgaW4gb3BlbihDRkcsZXJy -b3JzPSdpZ25vcmUnKToKICBpZiAnPScgaW4gbGluZSBhbmQgbm90IGxpbmUubHN0cmlwKCkuc3Rh -cnRzd2l0aCgnIycpOgogICBrLHY9bGluZS5yc3RyaXAoJ1xuJykuc3BsaXQoJz0nLDEpOyBkW2td -PXYKIHJldHVybiBkCgpkZWYgbG9hZGoocCxkZWZhdWx0KToKIHRyeTogcmV0dXJuIGpzb24ubG9h -ZChvcGVuKHApKQogZXhjZXB0IEV4Y2VwdGlvbjogcmV0dXJuIGRlZmF1bHQKCmRlZiBzYXZlaihw -LG9iaik6CiB0PXN0cihwKSsnLnRtcCc7IGpzb24uZHVtcChvYmosb3Blbih0LCd3Jyksc2VwYXJh -dG9ycz0oJywnLCc6JykpOyBvcy5jaG1vZCh0LDBvNjAwKTsgb3MucmVwbGFjZSh0LHApCgpkZWYg -c2hhMjU2KHBhdGgpOgogaD1oYXNobGliLnNoYTI1NigpCiB3aXRoIG9wZW4ocGF0aCwncmInLGJ1 -ZmZlcmluZz0wKSBhcyBmOgogIHdoaWxlIFRydWU6CiAgIGI9Zi5yZWFkKDEwMjQqMTAyNCkKICAg -aWYgbm90IGI6IGJyZWFrCiAgIGgudXBkYXRlKGIpCiByZXR1cm4gaC5oZXhkaWdlc3QoKQoKZGVm -IGFsZXJ0KHdlYmhvb2ssdGV4dCk6CiBwYXlsb2FkPWpzb24uZHVtcHMoeydjb250ZW50Jzp0ZXh0 -fSxlbnN1cmVfYXNjaWk9RmFsc2UpCiByPXN1YnByb2Nlc3MucnVuKFsnY3VybCcsJy1zUycsJy1v -JywnL2Rldi9udWxsJywnLXcnLCcle2h0dHBfY29kZX0nLCctQScsJ01vemlsbGEvNS4wIChjb21w -YXRpYmxlOyBtYWx3YXJlLXdhdGNoLzIuMCknLCctSCcsJ0NvbnRlbnQtVHlwZTogYXBwbGljYXRp -b24vanNvbicsJy0tZGF0YScscGF5bG9hZCx3ZWJob29rXSx0ZXh0PVRydWUsY2FwdHVyZV9vdXRw -dXQ9VHJ1ZSx0aW1lb3V0PTIwKQogcmV0dXJuIHIuc3Rkb3V0LnN0cmlwKCkgaW4gKCcyMDAnLCcy -MDQnKQoKZGVmIGV2aWRlbmNlKHBpZCxpbmZvKToKIHRzPXRpbWUuc3RyZnRpbWUoJyVZJW0lZC0l -SCVNJVMnKTsgZD1FVklEL2Yne3RzfS1waWR7cGlkfSc7IGQubWtkaXIocGFyZW50cz1UcnVlLGV4 -aXN0X29rPVRydWUpCiAoZC8nc3VtbWFyeS5qc29uJykud3JpdGVfdGV4dChqc29uLmR1bXBzKGlu -Zm8sZW5zdXJlX2FzY2lpPUZhbHNlLGluZGVudD0yKSkKIGZvciBzcmMsbmFtZSBpbiBbKGYnL3By -b2Mve3BpZH0vc3RhdHVzJywnc3RhdHVzJyksKGYnL3Byb2Mve3BpZH0vY2dyb3VwJywnY2dyb3Vw -JyksKGYnL3Byb2Mve3BpZH0vbWFwcycsJ21hcHMnKV06CiAgdHJ5OiAoZC9uYW1lKS53cml0ZV90 -ZXh0KG9wZW4oc3JjLGVycm9ycz0ncmVwbGFjZScpLnJlYWQoKSkKICBleGNlcHQgRXhjZXB0aW9u -OiBwYXNzCiB0cnk6IChkLyduZXR3b3JrLnR4dCcpLndyaXRlX3RleHQoc3VicHJvY2Vzcy5jaGVj -a19vdXRwdXQoWydzcycsJy10bnAnXSx0ZXh0PVRydWUsc3RkZXJyPXN1YnByb2Nlc3MuREVWTlVM -TCkpCiBleGNlcHQgRXhjZXB0aW9uOiBwYXNzCiBvcy5jaG1vZChkLDBvNzAwKQogcmV0dXJuIHN0 -cihkKQoKYz1jZmcoKTsgdGhyZXNob2xkPWZsb2F0KGMuZ2V0KCdDUFVfVEhSRVNIT0xEJywnMTUw -JykpOyBtaW5zY29yZT1pbnQoYy5nZXQoJ0FMRVJUX1NDT1JFJywnNjAnKSk7IGNyaXRpY2FsPWlu -dChjLmdldCgnQ1JJVElDQUxfU0NPUkUnLCcxMDAnKSk7IGNvb2xkb3duPWludChjLmdldCgnQ09P -TERPV05fU0VDT05EUycsJzE4MDAnKSk7IHdlYmhvb2s9Yy5nZXQoJ0RJU0NPUkRfV0VCSE9PSycs -JycpCnRmPWxvYWRqKEZFRURTLyd0aHJlYXRmb3guanNvbicse30pLmdldCgnaW9jcycse30pOyBi -YWQ9c2V0KHRmLmtleXMoKSk7IG1iPWxvYWRqKEZFRURTLydtYWx3YXJlYmF6YWFyLmpzb24nLHt9 -KS5nZXQoJ2hhc2hlcycse30pCnN0PWxvYWRqKFNUQVRFLHsnYWxlcnRzJzp7fSwnaGFzaGNhY2hl -Jzp7fX0pOyBub3c9dGltZS50aW1lKCk7IGhvc3Q9c29ja2V0LmdldGhvc3RuYW1lKCk7IGZpbmRp -bmdzPXt9CgojIHByb2Nlc3Mgc25hcHNob3QKb3V0PXN1YnByb2Nlc3MuY2hlY2tfb3V0cHV0KFsn -cHMnLCctZW8nLCdwaWQ9LHBjcHU9LHVzZXI9LGNvbW09LGFyZ3M9J10sdGV4dD1UcnVlLGVycm9y -cz0ncmVwbGFjZScpCmZvciBsaW5lIGluIG91dC5zcGxpdGxpbmVzKCk6CiBtPXJlLm1hdGNoKHIn -XHMqKFxkKylccysoWzAtOS5dKylccysoXFMrKVxzKyhcUyspXHMrKC4qKScsbGluZSkKIGlmIG5v -dCBtOiBjb250aW51ZQogcGlkPWludChtLmdyb3VwKDEpKTsgY3B1PWZsb2F0KG0uZ3JvdXAoMikp -OyB1c2VyLGNvbW0sYXJncz1tLmdyb3VwKDMpLG0uZ3JvdXAoNCksbS5ncm91cCg1KQogaWYgcGlk -PT1vcy5nZXRwaWQoKTogY29udGludWUKIGV4ZT0nJzsgZGVsZXRlZD1GYWxzZQogdHJ5OgogIGV4 -ZT1vcy5yZWFkbGluayhmJy9wcm9jL3twaWR9L2V4ZScpOyBkZWxldGVkPWV4ZS5lbmRzd2l0aCgn -IChkZWxldGVkKScpCiBleGNlcHQgRXhjZXB0aW9uOiBwYXNzCiBsb3c9KGNvbW0rJyAnK2FyZ3Mr -JyAnK2V4ZSkubG93ZXIoKTsgc2NvcmU9MDsgcmVhc29ucz1bXQogaWYgYW55KHggaW4gbG93IGZv -ciB4IGluIEtOT1dOKTogc2NvcmUrPTYwOyByZWFzb25zLmFwcGVuZCgna25vd24gbWluZXIvbWFs -d2FyZSBuYW1lJykKIGlmIGV4ZS5zdGFydHN3aXRoKFRFTVApOiBzY29yZSs9NDA7IHJlYXNvbnMu -YXBwZW5kKCdleGVjdXRhYmxlIGZyb20gdGVtcCBkaXJlY3RvcnknKQogaWYgZGVsZXRlZDogc2Nv -cmUrPTUwOyByZWFzb25zLmFwcGVuZCgnZGVsZXRlZCBleGVjdXRhYmxlIHN0aWxsIHJ1bm5pbmcn -KQogaWYgY3B1Pj10aHJlc2hvbGQ6IHNjb3JlKz0yMDsgcmVhc29ucy5hcHBlbmQoZidoaWdoIENQ -VSB7Y3B1Oi4xZn0lJykKIGg9Jyc7IGNsZWFuZXhlPWV4ZS5yZXBsYWNlKCcgKGRlbGV0ZWQpJywn -JykKIGlmIGNsZWFuZXhlIGFuZCBvcy5wYXRoLmlzZmlsZShjbGVhbmV4ZSk6CiAgdHJ5OgogICBz -PW9zLnN0YXQoY2xlYW5leGUpOyBjaz1mJ3tjbGVhbmV4ZX18e3Muc3Rfc2l6ZX18e2ludChzLnN0 -X210aW1lKX0nOyBoYz1zdFsnaGFzaGNhY2hlJ10uZ2V0KGNrKQogICBpZiBoYzogaD1oYwogICBl -bGlmIHMuc3Rfc2l6ZSA8PSAyNjg0MzU0NTY6CiAgICBoPXNoYTI1NihjbGVhbmV4ZSk7IHN0Wydo -YXNoY2FjaGUnXVtja109aAogICBpZiBoIGluIG1iOiBzY29yZSs9MTAwOyByZWFzb25zLmFwcGVu -ZCgnTWFsd2FyZUJhemFhciBTSEEyNTYgbWF0Y2gnKQogIGV4Y2VwdCBFeGNlcHRpb246IHBhc3MK -IGlmIHNjb3JlOiBmaW5kaW5nc1twaWRdPXsncGlkJzpwaWQsJ2NwdSc6Y3B1LCd1c2VyJzp1c2Vy -LCdjb21tJzpjb21tLCdhcmdzJzphcmdzWzo1MDBdLCdleGUnOmV4ZSwnc2hhMjU2JzpoLCdzY29y -ZSc6c2NvcmUsJ3JlYXNvbnMnOnJlYXNvbnMsJ2lvY19oaXRzJzpbXX0KCiMgb3V0Ym91bmQgZXN0 -YWJsaXNoZWQgVENQIHZzIFRocmVhdEZveCBJT0MgdmFsdWVzCnRyeToKIHNzPXN1YnByb2Nlc3Mu -Y2hlY2tfb3V0cHV0KFsnc3MnLCctSHRucCcsJ3N0YXRlJywnZXN0YWJsaXNoZWQnXSx0ZXh0PVRy -dWUsZXJyb3JzPSdyZXBsYWNlJyxzdGRlcnI9c3VicHJvY2Vzcy5ERVZOVUxMKQogZm9yIGxpbmUg -aW4gc3Muc3BsaXRsaW5lcygpOgogIHBhcnRzPWxpbmUuc3BsaXQoKQogIGlmIGxlbihwYXJ0cyk8 -NTogY29udGludWUKICBwZWVyPXBhcnRzWzRdOyBwbT1yZS5zZWFyY2gocidwaWQ9KFxkKyknLGxp -bmUpCiAgaWYgbm90IHBtOiBjb250aW51ZQogIHBpZD1pbnQocG0uZ3JvdXAoMSkpOyBwZWVyX2lw -PXBlZXIucnNwbGl0KCc6JywxKVswXS5zdHJpcCgnW10nKTsgaGl0PU5vbmUKICBpZiBwZWVyIGlu -IGJhZDogaGl0PXBlZXIKICBlbGlmIHBlZXJfaXAgaW4gYmFkOiBoaXQ9cGVlcl9pcAogIGlmIGhp -dDoKICAgZj1maW5kaW5ncy5zZXRkZWZhdWx0KHBpZCx7J3BpZCc6cGlkLCdjcHUnOjAsJ3VzZXIn -Oic/JywnY29tbSc6Jz8nLCdhcmdzJzonJywnZXhlJzonJywnc2hhMjU2JzonJywnc2NvcmUnOjAs -J3JlYXNvbnMnOltdLCdpb2NfaGl0cyc6W119KQogICBmWydzY29yZSddKz04MDsgZlsncmVhc29u -cyddLmFwcGVuZCgnVGhyZWF0Rm94IG5ldHdvcmsgSU9DICcraGl0KTsgZi5zZXRkZWZhdWx0KCdp -b2NfaGl0cycsW10pLmFwcGVuZChoaXQpCmV4Y2VwdCBFeGNlcHRpb246IHBhc3MKCnNlbnQ9MApx -dWFyYW50aW5lZD0wCmZvciBwaWQsZiBpbiBmaW5kaW5ncy5pdGVtcygpOgogaWYgZlsnc2NvcmUn -XSA8IG1pbnNjb3JlOiBjb250aW51ZQogbWJfbWF0Y2g9J01hbHdhcmVCYXphYXIgU0hBMjU2IG1h -dGNoJyBpbiBmWydyZWFzb25zJ10KIG1pbmVyX21hdGNoPSdrbm93biBtaW5lci9tYWx3YXJlIG5h -bWUnIGluIGZbJ3JlYXNvbnMnXQogYmVoYXZpb3JfbWF0Y2g9YW55KHguc3RhcnRzd2l0aCgnZXhl -Y3V0YWJsZSBmcm9tIHRlbXAgZGlyZWN0b3J5Jykgb3IgeC5zdGFydHN3aXRoKCdkZWxldGVkIGV4 -ZWN1dGFibGUnKSBvciB4LnN0YXJ0c3dpdGgoJ2hpZ2ggQ1BVICcpIGZvciB4IGluIGZbJ3JlYXNv -bnMnXSkKIHRmX2hpdHM9Zi5nZXQoJ2lvY19oaXRzJyxbXSkKIGNvbmZpcm1lZD1tYl9tYXRjaCBv -ciAobWluZXJfbWF0Y2ggYW5kIGJvb2wodGZfaGl0cykgYW5kIGJlaGF2aW9yX21hdGNoKQogcmVz -cG9uc2U9e30KIGV2PScnCiBpZiBjb25maXJtZWQ6CiAgY21kPVsnL3Vzci9sb2NhbC9zYmluL21h -bHdhcmUtcXVhcmFudGluZS5weScsJy0tcGlkJyxzdHIocGlkKSwnLS1yZWFzb24nLCc7ICcuam9p -bihmWydyZWFzb25zJ10pXQogIGNsZWFuZXhlPWYuZ2V0KCdleGUnLCcnKS5yZXBsYWNlKCcgKGRl -bGV0ZWQpJywnJykKICBpZiBjbGVhbmV4ZTogY21kICs9IFsnLS1maWxlJyxjbGVhbmV4ZV0KICBm -b3IgaGl0IGluIHRmX2hpdHM6IGNtZCArPSBbJy0taW9jJyxoaXRdCiAgdHJ5OgogICByPXN1YnBy -b2Nlc3MucnVuKGNtZCx0ZXh0PVRydWUsY2FwdHVyZV9vdXRwdXQ9VHJ1ZSx0aW1lb3V0PTQ1KQog -ICBpZiByLnN0ZG91dC5zdHJpcCgpOiByZXNwb25zZT1qc29uLmxvYWRzKHIuc3Rkb3V0LnN0cmlw -KCkuc3BsaXRsaW5lcygpWy0xXSkKICAgZXY9cmVzcG9uc2UuZ2V0KCdldmlkZW5jZScsJycpCiAg -IGlmIHJlc3BvbnNlLmdldCgnb2snKTogcXVhcmFudGluZWQrPTEKICBleGNlcHQgRXhjZXB0aW9u -IGFzIGU6CiAgIHJlc3BvbnNlPXsnb2snOkZhbHNlLCdlcnJvcic6c3RyKGUpfQogZWxpZiBmWydz -Y29yZSddPj1jcml0aWNhbDoKICB0cnk6IGV2PWV2aWRlbmNlKHBpZCxmKQogIGV4Y2VwdCBFeGNl -cHRpb246IHBhc3MKIGtleT1oYXNobGliLnNoYTI1Nigoc3RyKHBpZCkrJ3wnK2YuZ2V0KCdleGUn -LCcnKSsnfCcrJywnLmpvaW4oc29ydGVkKGZbJ3JlYXNvbnMnXSkpKS5lbmNvZGUoKSkuaGV4ZGln -ZXN0KCkKIGlmIG5vdy1mbG9hdChzdFsnYWxlcnRzJ10uZ2V0KGtleSwwKSkgPCBjb29sZG93bjog -Y29udGludWUKIGxldmVsPSdDT05GSVJNRUQvQVVUTy1RVUFSQU5USU5FRCcgaWYgY29uZmlybWVk -IGFuZCByZXNwb25zZS5nZXQoJ29rJykgZWxzZSAoJ0NSSVRJQ0FMJyBpZiBmWydzY29yZSddPj1j -cml0aWNhbCBlbHNlICdISUdIJykKIG1zZz0oZifwn5qoICoqTWFsd2FyZSBXYXRjaCB7bGV2ZWx9 -KipcblNlcnZlcjogYHtob3N0fWAgIFNjb3JlOiAqKntmWyJzY29yZSJdfSoqXG5QSUQ6IGB7cGlk -fWAgIENQVTogYHtmWyJjcHUiXX0lYCAgVXNlcjogYHtmWyJ1c2VyIl19YFxuUHJvY2VzczogYHtm -WyJjb21tIl19YFxuRXhlOiBge2ZbImV4ZSJdIG9yICJ1bmtub3duIn1gXG5SZWFzb25zOiAnKycs -ICcuam9pbihmWydyZWFzb25zJ10pKQogaWYgZi5nZXQoJ3NoYTI1NicpOiBtc2crPWYnXG5TSEEy -NTY6IGB7Zlsic2hhMjU2Il19YCcKIGlmIGNvbmZpcm1lZDoKICBhY3RzPXJlc3BvbnNlLmdldCgn -YWN0aW9ucycsW10pIGlmIGlzaW5zdGFuY2UocmVzcG9uc2UsZGljdCkgZWxzZSBbXQogIGlmIGFj -dHM6IG1zZys9J1xuQWN0aW9uczogJysnLCAnLmpvaW4oYWN0c1s6OF0pCiAgaWYgcmVzcG9uc2Uu -Z2V0KCdjb250YWluZXInKTogbXNnKz1mJ1xuQ29udGFpbmVyIGlzb2xhdGVkOiBge3Jlc3BvbnNl -WyJjb250YWluZXIiXX1gJwogIGlmIG5vdCByZXNwb25zZS5nZXQoJ29rJyk6IG1zZys9ZidcbkF1 -dG8tcmVzcG9uc2UgRVJST1I6IGB7cmVzcG9uc2UuZ2V0KCJlcnJvciIsInVua25vd24iKX1gJwog -aWYgZXY6IG1zZys9ZidcbkV2aWRlbmNlOiBge2V2fWAnCiBpZiB3ZWJob29rIGFuZCBhbGVydCh3 -ZWJob29rLG1zZ1s6MTkwMF0pOiBzdFsnYWxlcnRzJ11ba2V5XT1ub3c7IHNlbnQrPTEKCiMgcHJ1 -bmUgc3RhdGUKc3RbJ2FsZXJ0cyddPXtrOnYgZm9yIGssdiBpbiBzdFsnYWxlcnRzJ10uaXRlbXMo -KSBpZiBub3ctZmxvYXQodik8NjA0ODAwfQppZiBsZW4oc3RbJ2hhc2hjYWNoZSddKT41MDAwOiBz -dFsnaGFzaGNhY2hlJ109ZGljdChsaXN0KHN0WydoYXNoY2FjaGUnXS5pdGVtcygpKVstMzAwMDpd -KQpzYXZlaihTVEFURSxzdCkKcHJpbnQoanNvbi5kdW1wcyh7J2ZpbmRpbmdzJzpsZW4oZmluZGlu -Z3MpLCdhbGVydHNfc2VudCc6c2VudCwncXVhcmFudGluZWQnOnF1YXJhbnRpbmVkLCd0aHJlYXRm -b3hfaW9jcyc6bGVuKGJhZCksJ21hbHdhcmViYXphYXJfaGFzaGVzJzpsZW4obWIpfSxzZXBhcmF0 -b3JzPSgnLCcsJzonKSkpCg== -B64_MALWARE_WATCH - -install_payload /usr/local/sbin/malware-quarantine.py 700 <<'B64_MALWARE_QUARANTINE' -IyEvdXNyL2Jpbi9lbnYgcHl0aG9uMwppbXBvcnQgYXJncGFyc2UsIGhhc2hsaWIsIGlwYWRkcmVz -cywganNvbiwgb3MsIHJlLCBzaHV0aWwsIHNpZ25hbCwgc3VicHJvY2VzcywgdGltZQpmcm9tIHBh -dGhsaWIgaW1wb3J0IFBhdGgKCkJBU0U9UGF0aCgnL3Zhci9saWIvbWFsd2FyZS13YXRjaCcpCkVW -SUQ9UGF0aCgnL3Jvb3Qvc2VjdXJpdHktZXZpZGVuY2UvbWFsd2FyZS13YXRjaCcpClFVQVI9UGF0 -aCgnL3Jvb3Qvc2VjdXJpdHktZXZpZGVuY2UvcXVhcmFudGluZScpCkJMT0NLRUQ9UGF0aCgnL2V0 -Yy9tYWx3YXJlLXdhdGNoL2Jsb2NrZWQtaW9jcy50eHQnKQoKCmRlZiBydW4oY21kLCB0aW1lb3V0 -PTIwKToKICAgIHRyeToKICAgICAgICByPXN1YnByb2Nlc3MucnVuKGNtZCx0ZXh0PVRydWUsY2Fw -dHVyZV9vdXRwdXQ9VHJ1ZSx0aW1lb3V0PXRpbWVvdXQpCiAgICAgICAgcmV0dXJuIHIucmV0dXJu -Y29kZSxyLnN0ZG91dCxyLnN0ZGVycgogICAgZXhjZXB0IEV4Y2VwdGlvbiBhcyBlOgogICAgICAg -IHJldHVybiAyNTUsJycsc3RyKGUpCgpkZWYgc2hhMjU2KHBhdGgpOgogICAgaD1oYXNobGliLnNo -YTI1NigpCiAgICB3aXRoIG9wZW4ocGF0aCwncmInLGJ1ZmZlcmluZz0wKSBhcyBmOgogICAgICAg -IGZvciBiIGluIGl0ZXIobGFtYmRhOmYucmVhZCgxMDI0KjEwMjQpLGInJyk6IGgudXBkYXRlKGIp -CiAgICByZXR1cm4gaC5oZXhkaWdlc3QoKQoKZGVmIGNvbnRhaW5lcl9pZChwaWQpOgogICAgdHJ5 -OiBkYXRhPVBhdGgoZicvcHJvYy97cGlkfS9jZ3JvdXAnKS5yZWFkX3RleHQoZXJyb3JzPSdpZ25v -cmUnKQogICAgZXhjZXB0IEV4Y2VwdGlvbjogcmV0dXJuICcnCiAgICBwYXRzPVtyJy9kb2NrZXIv -KFswLTlhLWZdezEyLDY0fSkoPzokfC8pJyxyJ2RvY2tlci0oWzAtOWEtZl17MTIsNjR9KVwuc2Nv -cGUnLHInL2RvY2tlci8oWzAtOWEtZl17MTIsNjR9KSQnXQogICAgZm9yIHAgaW4gcGF0czoKICAg -ICAgICBtPXJlLnNlYXJjaChwLGRhdGEscmUuTSkKICAgICAgICBpZiBtOiByZXR1cm4gbS5ncm91 -cCgxKQogICAgcmV0dXJuICcnCgpkZWYgY2FwdHVyZV9wcm9jZXNzKHBpZCxkKToKICAgIGZvciBz -cmMsbmFtZSBpbiBbKGYnL3Byb2Mve3BpZH0vc3RhdHVzJywnc3RhdHVzJyksKGYnL3Byb2Mve3Bp -ZH0vY2dyb3VwJywnY2dyb3VwJyksKGYnL3Byb2Mve3BpZH0vbWFwcycsJ21hcHMnKSwoZicvcHJv -Yy97cGlkfS9jbWRsaW5lJywnY21kbGluZScpXToKICAgICAgICB0cnk6CiAgICAgICAgICAgIHJh -dz1QYXRoKHNyYykucmVhZF9ieXRlcygpCiAgICAgICAgICAgIChkL25hbWUpLndyaXRlX2J5dGVz -KHJhdykKICAgICAgICBleGNlcHQgRXhjZXB0aW9uOiBwYXNzCgpkZWYgY2FwdHVyZV9nbG9iYWwo -ZCk6CiAgICBmb3IgY21kLG5hbWUgaW4gWyhbJ3BzJywnLWVmd3cnXSwncHMudHh0JyksKFsnc3Mn -LCctdG5wJ10sJ25ldHdvcmsudHh0JyldOgogICAgICAgIHJjLG91dCxlcnI9cnVuKGNtZCkKICAg -ICAgICAoZC9uYW1lKS53cml0ZV90ZXh0KG91dCtlcnIsZXJyb3JzPSdyZXBsYWNlJykKCmRlZiBi -bG9ja19pb2MoaW9jLGFjdGlvbnMsZHJ5KToKICAgIHJhdz0oaW9jIG9yICcnKS5zdHJpcCgpCiAg -ICBpZiBub3QgcmF3OiByZXR1cm4KICAgIGhvc3Q9cmF3CiAgICBpZiByYXcuc3RhcnRzd2l0aCgn -WycpIGFuZCAnXTonIGluIHJhdzogaG9zdD1yYXdbMTpyYXcuaW5kZXgoJ10nKV0KICAgIGVsaWYg -cmF3LmNvdW50KCc6Jyk9PTEgYW5kIHJhdy5yc3BsaXQoJzonLDEpWzFdLmlzZGlnaXQoKTogaG9z -dD1yYXcucnNwbGl0KCc6JywxKVswXQogICAgdHJ5OiBpcD1zdHIoaXBhZGRyZXNzLmlwX2FkZHJl -c3MoaG9zdCkpCiAgICBleGNlcHQgRXhjZXB0aW9uOiByZXR1cm4KICAgIGFjdGlvbnMuYXBwZW5k -KCdibG9ja2VkX2lvYzonK2lwKQogICAgaWYgZHJ5OiByZXR1cm4KICAgIEJMT0NLRUQucGFyZW50 -Lm1rZGlyKHBhcmVudHM9VHJ1ZSxleGlzdF9vaz1UcnVlKQogICAgdmFscz1bXQogICAgaWYgQkxP -Q0tFRC5leGlzdHMoKTogdmFscz1beC5zdHJpcCgpIGZvciB4IGluIEJMT0NLRUQucmVhZF90ZXh0 -KCkuc3BsaXRsaW5lcygpIGlmIHguc3RyaXAoKV0KICAgIGlmIGlwIG5vdCBpbiB2YWxzOgogICAg -ICAgIHdpdGggb3BlbihCTE9DS0VELCdhJykgYXMgZjogZi53cml0ZShpcCsnXG4nKQogICAgICAg -IG9zLmNobW9kKEJMT0NLRUQsMG82MDApCiAgICBydW4oWycvdXNyL2xvY2FsL3NiaW4vYmxvY2st -bWFsd2FyZS1pb2NzJ10pCgpkZWYgbmV1dHJhbGl6ZV9wZXJzaXN0ZW5jZShleGUsZXZkaXIsYWN0 -aW9ucyxkcnkpOgogICAgaWYgbm90IGV4ZSBvciBub3QgZXhlLnN0YXJ0c3dpdGgoJy8nKTogcmV0 -dXJuCiAgICBjYW5kaWRhdGVzPVtdCiAgICByb290cz1bUGF0aCgnL2V0Yy9jcm9udGFiJyksUGF0 -aCgnL2V0Yy9jcm9uLmQnKSxQYXRoKCcvdmFyL3Nwb29sL2Nyb24vY3JvbnRhYnMnKSxQYXRoKCcv -ZXRjL3N5c3RlbWQvc3lzdGVtJyksUGF0aCgnL2V0Yy9yYy5sb2NhbCcpXQogICAgZm9yIHJvb3Qg -aW4gcm9vdHM6CiAgICAgICAgaWYgcm9vdC5pc19maWxlKCk6IGNhbmRpZGF0ZXMuYXBwZW5kKHJv -b3QpCiAgICAgICAgZWxpZiByb290LmlzX2RpcigpOgogICAgICAgICAgICB0cnk6CiAgICAgICAg -ICAgICAgICBjYW5kaWRhdGVzICs9IFtwIGZvciBwIGluIHJvb3Qucmdsb2IoJyonKSBpZiBwLmlz -X2ZpbGUoKSBhbmQgcC5zdGF0KCkuc3Rfc2l6ZSA8PSAyKjEwMjQqMTAyNF0KICAgICAgICAgICAg -ZXhjZXB0IEV4Y2VwdGlvbjogcGFzcwogICAgcGRpcj1ldmRpci8ncGVyc2lzdGVuY2UnOyBwZGly -Lm1rZGlyKGV4aXN0X29rPVRydWUpCiAgICBmb3IgcCBpbiBjYW5kaWRhdGVzOgogICAgICAgIHRy -eToKICAgICAgICAgICAgdHh0PXAucmVhZF90ZXh0KGVycm9ycz0naWdub3JlJykKICAgICAgICAg -ICAgaWYgZXhlIG5vdCBpbiB0eHQ6IGNvbnRpbnVlCiAgICAgICAgICAgIHNhZmU9cC5uYW1lLnJl -cGxhY2UoJy8nLCdfJykKICAgICAgICAgICAgc2h1dGlsLmNvcHkyKHAscGRpci8oc2FmZSsnLmJl -Zm9yZScpKQogICAgICAgICAgICBpZiAnL3N5c3RlbWQvc3lzdGVtLycgaW4gc3RyKHApIGFuZCBw -LnN1ZmZpeCBpbiAoJy5zZXJ2aWNlJywnLnRpbWVyJywnLnNvY2tldCcsJy5wYXRoJyk6CiAgICAg -ICAgICAgICAgICB1bml0PXAubmFtZQogICAgICAgICAgICAgICAgYWN0aW9ucy5hcHBlbmQoJ2Rp -c2FibGVkX3BlcnNpc3RlbmNlX3VuaXQ6Jyt1bml0KQogICAgICAgICAgICAgICAgaWYgbm90IGRy -eToKICAgICAgICAgICAgICAgICAgICBydW4oWydzeXN0ZW1jdGwnLCdkaXNhYmxlJywnLS1ub3cn -LHVuaXRdKQogICAgICAgICAgICAgICAgICAgIHE9cC53aXRoX25hbWUocC5uYW1lKycubWFsd2Fy -ZS13YXRjaC1kaXNhYmxlZCcpCiAgICAgICAgICAgICAgICAgICAgdHJ5OiBvcy5yZXBsYWNlKHAs -cSkKICAgICAgICAgICAgICAgICAgICBleGNlcHQgRXhjZXB0aW9uOiBwYXNzCiAgICAgICAgICAg -ICAgICAgICAgcnVuKFsnc3lzdGVtY3RsJywnZGFlbW9uLXJlbG9hZCddKQogICAgICAgICAgICBl -bHNlOgogICAgICAgICAgICAgICAgYWN0aW9ucy5hcHBlbmQoJ2Rpc2FibGVkX3BlcnNpc3RlbmNl -X2ZpbGU6JytzdHIocCkpCiAgICAgICAgICAgICAgICBpZiBub3QgZHJ5OgogICAgICAgICAgICAg -ICAgICAgIGxpbmVzPVtdCiAgICAgICAgICAgICAgICAgICAgZm9yIGxpbmUgaW4gdHh0LnNwbGl0 -bGluZXMoVHJ1ZSk6CiAgICAgICAgICAgICAgICAgICAgICAgIGlmIGV4ZSBpbiBsaW5lIGFuZCBu -b3QgbGluZS5sc3RyaXAoKS5zdGFydHN3aXRoKCcjJyk6CiAgICAgICAgICAgICAgICAgICAgICAg -ICAgICBsaW5lcy5hcHBlbmQoJyMgbWFsd2FyZS13YXRjaCBkaXNhYmxlZDogJytsaW5lKQogICAg -ICAgICAgICAgICAgICAgICAgICBlbHNlOiBsaW5lcy5hcHBlbmQobGluZSkKICAgICAgICAgICAg -ICAgICAgICBwLndyaXRlX3RleHQoJycuam9pbihsaW5lcykpCiAgICAgICAgZXhjZXB0IEV4Y2Vw -dGlvbjogcGFzcwoKZGVmIHF1YXJhbnRpbmVfaG9zdF9maWxlKHBhdGgsZXZkaXIsYWN0aW9ucyxk -cnkpOgogICAgcD1QYXRoKHBhdGgpCiAgICBpZiBub3QgcC5pc19maWxlKCk6IHJldHVybiAnJwog -ICAgdHJ5OiBoPXNoYTI1NihwKQogICAgZXhjZXB0IEV4Y2VwdGlvbjogaD0ndW5rbm93bicKICAg -IGFjdGlvbnMuYXBwZW5kKCdxdWFyYW50aW5lX2ZpbGU6JytzdHIocCkpCiAgICBpZiBkcnk6IHJl -dHVybiBzdHIocCkKICAgIFFVQVIubWtkaXIocGFyZW50cz1UcnVlLGV4aXN0X29rPVRydWUpOyBv -cy5jaG1vZChRVUFSLDBvNzAwKQogICAgYmFja3VwPWV2ZGlyLygnc2FtcGxlLScrcC5uYW1lKQog -ICAgdHJ5OiBzaHV0aWwuY29weTIocCxiYWNrdXApOyBvcy5jaG1vZChiYWNrdXAsMG80MDApCiAg -ICBleGNlcHQgRXhjZXB0aW9uOiBwYXNzCiAgICBkZXN0PVFVQVIvKHRpbWUuc3RyZnRpbWUoJyVZ -JW0lZC0lSCVNJVMnKSsnLScraFs6MTZdKyctJytwLm5hbWUpCiAgICB0cnk6CiAgICAgICAgc2h1 -dGlsLm1vdmUoc3RyKHApLHN0cihkZXN0KSk7IG9zLmNobW9kKGRlc3QsMG8wMDApOyByZXR1cm4g -c3RyKGRlc3QpCiAgICBleGNlcHQgRXhjZXB0aW9uOiByZXR1cm4gJycKCmRlZiBtYXRjaGluZ19w -aWRzKHBhdGgpOgogICAgb3V0PVtdCiAgICB0cnk6IHRhcmdldD1vcy5wYXRoLnJlYWxwYXRoKHBh -dGgpCiAgICBleGNlcHQgRXhjZXB0aW9uOiByZXR1cm4gb3V0CiAgICBmb3IgZW50IGluIFBhdGgo -Jy9wcm9jJykuaXRlcmRpcigpOgogICAgICAgIGlmIG5vdCBlbnQubmFtZS5pc2RpZ2l0KCk6IGNv -bnRpbnVlCiAgICAgICAgdHJ5OgogICAgICAgICAgICBleD1vcy5yZWFkbGluayhlbnQvJ2V4ZScp -LnJlcGxhY2UoJyAoZGVsZXRlZCknLCcnKQogICAgICAgICAgICBpZiBvcy5wYXRoLnJlYWxwYXRo -KGV4KT09dGFyZ2V0OiBvdXQuYXBwZW5kKGludChlbnQubmFtZSkpCiAgICAgICAgZXhjZXB0IEV4 -Y2VwdGlvbjogcGFzcwogICAgcmV0dXJuIG91dAoKZGVmIHN0b3BfcGlkKHBpZCxhY3Rpb25zLGRy -eSk6CiAgICBpZiBwaWQ8PTEgb3IgcGlkPT1vcy5nZXRwaWQoKTogcmV0dXJuCiAgICBhY3Rpb25z -LmFwcGVuZCgnc3RvcHBlZF9waWQ6JytzdHIocGlkKSkKICAgIGlmIGRyeTogcmV0dXJuCiAgICB0 -cnk6IG9zLmtpbGwocGlkLHNpZ25hbC5TSUdTVE9QKQogICAgZXhjZXB0IEV4Y2VwdGlvbjogcmV0 -dXJuCiAgICB0cnk6IG9zLmtpbGwocGlkLHNpZ25hbC5TSUdDT05UKTsgb3Mua2lsbChwaWQsc2ln -bmFsLlNJR1RFUk0pCiAgICBleGNlcHQgRXhjZXB0aW9uOiBwYXNzCiAgICBmb3IgXyBpbiByYW5n -ZSgzMCk6CiAgICAgICAgaWYgbm90IFBhdGgoZicvcHJvYy97cGlkfScpLmV4aXN0cygpOiByZXR1 -cm4KICAgICAgICB0aW1lLnNsZWVwKDAuMSkKICAgIHRyeTogb3Mua2lsbChwaWQsc2lnbmFsLlNJ -R0tJTEwpCiAgICBleGNlcHQgRXhjZXB0aW9uOiBwYXNzCgpkZWYgaGFuZGxlX2NvbnRhaW5lcihj -aWQscGlkLGV4ZSxldmRpcixhY3Rpb25zLGRyeSk6CiAgICBhY3Rpb25zLmFwcGVuZCgnaXNvbGF0 -ZWRfY29udGFpbmVyOicrY2lkWzoxMl0pCiAgICBpZiBkcnk6IHJldHVybgogICAgZm9yIGNtZCxu -YW1lIGluIFsoWydkb2NrZXInLCdpbnNwZWN0JyxjaWRdLCdkb2NrZXItaW5zcGVjdC5qc29uJyks -KFsnZG9ja2VyJywnbG9ncycsJy0tdGFpbCcsJzUwMCcsY2lkXSwnZG9ja2VyLWxvZ3MudHh0Jyks -KFsnZG9ja2VyJywnZGlmZicsY2lkXSwnZG9ja2VyLWRpZmYudHh0JyldOgogICAgICAgIHJjLG91 -dCxlcnI9cnVuKGNtZCwzMCk7IChldmRpci9uYW1lKS53cml0ZV90ZXh0KG91dCtlcnIsZXJyb3Jz -PSdyZXBsYWNlJykKICAgIGlmIGV4ZSBhbmQgZXhlLnN0YXJ0c3dpdGgoJy8nKToKICAgICAgICBk -ZXN0PWV2ZGlyLygnY29udGFpbmVyLXNhbXBsZS0nK1BhdGgoZXhlKS5uYW1lKQogICAgICAgIHJ1 -bihbJ2RvY2tlcicsJ2NwJyxjaWQrJzonK2V4ZSxzdHIoZGVzdCldLDMwKQogICAgICAgIGlmIGRl -c3QuZXhpc3RzKCk6CiAgICAgICAgICAgIHRyeTogb3MuY2htb2QoZGVzdCwwbzQwMCkKICAgICAg -ICAgICAgZXhjZXB0IEV4Y2VwdGlvbjogcGFzcwogICAgcnVuKFsnZG9ja2VyJywndXBkYXRlJywn -LS1yZXN0YXJ0PW5vJyxjaWRdLDIwKQogICAgcnVuKFsnZG9ja2VyJywnc3RvcCcsJy10JywnMTAn -LGNpZF0sMjApCgpkZWYgbWFpbigpOgogICAgYXA9YXJncGFyc2UuQXJndW1lbnRQYXJzZXIoKQog -ICAgYXAuYWRkX2FyZ3VtZW50KCctLXBpZCcsdHlwZT1pbnQsZGVmYXVsdD0wKQogICAgYXAuYWRk -X2FyZ3VtZW50KCctLWZpbGUnLGRlZmF1bHQ9JycpCiAgICBhcC5hZGRfYXJndW1lbnQoJy0tcmVh -c29uJyxkZWZhdWx0PSdjb25maXJtZWQgbWFsd2FyZScpCiAgICBhcC5hZGRfYXJndW1lbnQoJy0t -aW9jJyxhY3Rpb249J2FwcGVuZCcsZGVmYXVsdD1bXSkKICAgIGFwLmFkZF9hcmd1bWVudCgnLS1k -cnktcnVuJyxhY3Rpb249J3N0b3JlX3RydWUnKQogICAgYT1hcC5wYXJzZV9hcmdzKCkKICAgIEVW -SUQubWtkaXIocGFyZW50cz1UcnVlLGV4aXN0X29rPVRydWUpOyBvcy5jaG1vZChFVklELDBvNzAw -KQogICAgZXZkaXI9RVZJRC8odGltZS5zdHJmdGltZSgnJVklbSVkLSVIJU0lUycpKyctcXVhcmFu -dGluZScrKGYnLXBpZHthLnBpZH0nIGlmIGEucGlkIGVsc2UgJycpKQogICAgZXZkaXIubWtkaXIo -bW9kZT0wbzcwMCkKICAgIGFjdGlvbnM9W10KICAgIGV4ZT1hLmZpbGUKICAgIGlmIGEucGlkIGFu -ZCBub3QgZXhlOgogICAgICAgIHRyeTogZXhlPW9zLnJlYWRsaW5rKGYnL3Byb2Mve2EucGlkfS9l -eGUnKS5yZXBsYWNlKCcgKGRlbGV0ZWQpJywnJykKICAgICAgICBleGNlcHQgRXhjZXB0aW9uOiBl -eGU9JycKICAgIGluZm89eyd0aW1lc3RhbXAnOnRpbWUudGltZSgpLCdyZWFzb24nOmEucmVhc29u -LCdwaWQnOmEucGlkLCdmaWxlJzpleGUsJ2lvY3MnOmEuaW9jLCdkcnlfcnVuJzphLmRyeV9ydW59 -CiAgICAoZXZkaXIvJ3JlcXVlc3QuanNvbicpLndyaXRlX3RleHQoanNvbi5kdW1wcyhpbmZvLGlu -ZGVudD0yKSk7IG9zLmNobW9kKGV2ZGlyLydyZXF1ZXN0Lmpzb24nLDBvNjAwKQogICAgY2FwdHVy -ZV9nbG9iYWwoZXZkaXIpCiAgICBpZiBhLnBpZDogY2FwdHVyZV9wcm9jZXNzKGEucGlkLGV2ZGly -KQogICAgY2lkPWNvbnRhaW5lcl9pZChhLnBpZCkgaWYgYS5waWQgZWxzZSAnJwogICAgaWYgYS5w -aWQgYW5kIG5vdCBhLmRyeV9ydW46CiAgICAgICAgdHJ5OiBvcy5raWxsKGEucGlkLHNpZ25hbC5T -SUdTVE9QKTsgYWN0aW9ucy5hcHBlbmQoJ2Zyb3plbl9waWQ6JytzdHIoYS5waWQpKQogICAgICAg -IGV4Y2VwdCBFeGNlcHRpb246IHBhc3MKICAgIGZvciBpb2MgaW4gYS5pb2M6IGJsb2NrX2lvYyhp -b2MsYWN0aW9ucyxhLmRyeV9ydW4pCiAgICBpZiBjaWQ6CiAgICAgICAgaGFuZGxlX2NvbnRhaW5l -cihjaWQsYS5waWQsZXhlLGV2ZGlyLGFjdGlvbnMsYS5kcnlfcnVuKQogICAgZWxzZToKICAgICAg -ICBwaWRzPXNldCgoW2EucGlkXSBpZiBhLnBpZCBlbHNlIFtdKSArIChtYXRjaGluZ19waWRzKGV4 -ZSkgaWYgZXhlIGVsc2UgW10pKQogICAgICAgIG5ldXRyYWxpemVfcGVyc2lzdGVuY2UoZXhlLGV2 -ZGlyLGFjdGlvbnMsYS5kcnlfcnVuKQogICAgICAgIGZvciBwIGluIHNvcnRlZCh4IGZvciB4IGlu -IHBpZHMgaWYgeCk6IHN0b3BfcGlkKHAsYWN0aW9ucyxhLmRyeV9ydW4pCiAgICAgICAgaWYgZXhl -OiBxdWFyYW50aW5lX2hvc3RfZmlsZShleGUsZXZkaXIsYWN0aW9ucyxhLmRyeV9ydW4pCiAgICBy -ZXN1bHQ9eydvayc6VHJ1ZSwnZXZpZGVuY2UnOnN0cihldmRpciksJ2NvbnRhaW5lcic6Y2lkWzox -Ml0gaWYgY2lkIGVsc2UgJycsICdhY3Rpb25zJzphY3Rpb25zLCdkcnlfcnVuJzphLmRyeV9ydW59 -CiAgICAoZXZkaXIvJ3Jlc3VsdC5qc29uJykud3JpdGVfdGV4dChqc29uLmR1bXBzKHJlc3VsdCxp -bmRlbnQ9MikpOyBvcy5jaG1vZChldmRpci8ncmVzdWx0Lmpzb24nLDBvNjAwKQogICAgcHJpbnQo -anNvbi5kdW1wcyhyZXN1bHQsc2VwYXJhdG9ycz0oJywnLCc6JykpKQoKaWYgX19uYW1lX189PSdf -X21haW5fXyc6IG1haW4oKQo= -B64_MALWARE_QUARANTINE - -install_payload /usr/local/sbin/malware-feed-update.py 700 <<'B64_MALWARE_FEED_UPDATE' -IyEvdXNyL2Jpbi9lbnYgcHl0aG9uMwppbXBvcnQganNvbiwgb3MsIHN5cywgdGltZSwgdGVtcGZp -bGUsIHVybGxpYi5yZXF1ZXN0LCB1cmxsaWIucGFyc2UKQkFTRT0nL3Zhci9saWIvbWFsd2FyZS13 -YXRjaCcKRkVFRFM9QkFTRSsnL2ZlZWRzJwpvcy5tYWtlZGlycyhGRUVEUywgZXhpc3Rfb2s9VHJ1 -ZSkKa2V5PW9zLmVudmlyb24uZ2V0KCdBQlVTRUNIX0FVVEhfS0VZJywnJykuc3RyaXAoKQppZiBu -b3Qga2V5OgogICAgcHJpbnQoJ0FCVVNFQ0hfQVVUSF9LRVkgbWlzc2luZycsIGZpbGU9c3lzLnN0 -ZGVycikKICAgIHN5cy5leGl0KDIpCgpkZWYgZmV0Y2godXJsLCBwYXlsb2FkLCBpc19qc29uKToK -ICAgIGlmIGlzX2pzb246CiAgICAgICAgZGF0YT1qc29uLmR1bXBzKHBheWxvYWQpLmVuY29kZSgp -CiAgICAgICAgY3R5cGU9J2FwcGxpY2F0aW9uL2pzb24nCiAgICBlbHNlOgogICAgICAgIGRhdGE9 -dXJsbGliLnBhcnNlLnVybGVuY29kZShwYXlsb2FkKS5lbmNvZGUoKQogICAgICAgIGN0eXBlPSdh -cHBsaWNhdGlvbi94LXd3dy1mb3JtLXVybGVuY29kZWQnCiAgICByZXE9dXJsbGliLnJlcXVlc3Qu -UmVxdWVzdCh1cmwsZGF0YT1kYXRhLGhlYWRlcnM9eydBdXRoLUtleSc6a2V5LCdDb250ZW50LVR5 -cGUnOmN0eXBlLCdVc2VyLUFnZW50JzonbWFsd2FyZS13YXRjaC8yJ30sbWV0aG9kPSdQT1NUJykK -ICAgIHdpdGggdXJsbGliLnJlcXVlc3QudXJsb3BlbihyZXEsdGltZW91dD00NSkgYXMgcjoKICAg -ICAgICByZXR1cm4ganNvbi5sb2FkKHIpCgpkZWYgc2F2ZShwYXRoLG9iaik6CiAgICBmZCx0bXA9 -dGVtcGZpbGUubWtzdGVtcChwcmVmaXg9Jy50bXAtJyxkaXI9RkVFRFMpCiAgICB3aXRoIG9zLmZk -b3BlbihmZCwndycpIGFzIGY6CiAgICAgICAganNvbi5kdW1wKG9iaixmLHNlcGFyYXRvcnM9KCcs -JywnOicpKQogICAgb3MuY2htb2QodG1wLDBvNjAwKQogICAgb3MucmVwbGFjZSh0bXAscGF0aCkK -CnRmPWZldGNoKCdodHRwczovL3RocmVhdGZveC1hcGkuYWJ1c2UuY2gvYXBpL3YxLycseydxdWVy -eSc6J2dldF9pb2NzJywnZGF5cyc6N30sVHJ1ZSkKaWYgdGYuZ2V0KCdxdWVyeV9zdGF0dXMnKSE9 -J29rJzoKICAgIHJhaXNlIFJ1bnRpbWVFcnJvcignVGhyZWF0Rm94IHN0YXR1cz0nK3N0cih0Zi5n -ZXQoJ3F1ZXJ5X3N0YXR1cycpKSkKaW9jcz17fQpmb3IgeCBpbiB0Zi5nZXQoJ2RhdGEnKSBvciBb -XToKICAgIHZhbD0oeC5nZXQoJ2lvYycpIG9yICcnKS5zdHJpcCgpCiAgICBpZiB2YWw6CiAgICAg -ICAgaW9jc1t2YWxdPXsnaW9jX3R5cGUnOnguZ2V0KCdpb2NfdHlwZScpLCd0aHJlYXRfdHlwZSc6 -eC5nZXQoJ3RocmVhdF90eXBlJyksJ21hbHdhcmUnOnguZ2V0KCdtYWx3YXJlX3ByaW50YWJsZScp -IG9yIHguZ2V0KCdtYWx3YXJlJyksJ2ZpcnN0X3NlZW4nOnguZ2V0KCdmaXJzdF9zZWVuJyl9CnNh -dmUoRkVFRFMrJy90aHJlYXRmb3guanNvbicseyd1cGRhdGVkJzppbnQodGltZS50aW1lKCkpLCdp -b2NzJzppb2NzfSkKCm1iPWZldGNoKCdodHRwczovL21iLWFwaS5hYnVzZS5jaC9hcGkvdjEvJyx7 -J3F1ZXJ5JzoncmVjZW50X2RldGVjdGlvbnMnLCdob3Vycyc6JzE2OCd9LEZhbHNlKQppZiBtYi5n -ZXQoJ3F1ZXJ5X3N0YXR1cycpIG5vdCBpbiAoJ29rJywnbm9fcmVzdWx0cycpOgogICAgcmFpc2Ug -UnVudGltZUVycm9yKCdNYWx3YXJlQmF6YWFyIHN0YXR1cz0nK3N0cihtYi5nZXQoJ3F1ZXJ5X3N0 -YXR1cycpKSkKaGFzaGVzPXt9CmZvciB4IGluIG1iLmdldCgnZGF0YScpIG9yIFtdOgogICAgaD0o -eC5nZXQoJ3NoYTI1Nl9oYXNoJykgb3IgJycpLmxvd2VyKCkKICAgIGlmIGxlbihoKT09NjQ6CiAg -ICAgICAgaGFzaGVzW2hdPXsnc2lnbmF0dXJlJzp4LmdldCgnc2lnbmF0dXJlJyksJ2ZpbGVfbmFt -ZSc6eC5nZXQoJ2ZpbGVfbmFtZScpLCdmaXJzdF9zZWVuJzp4LmdldCgnZmlyc3Rfc2VlbicpfQpz -YXZlKEZFRURTKycvbWFsd2FyZWJhemFhci5qc29uJyx7J3VwZGF0ZWQnOmludCh0aW1lLnRpbWUo -KSksJ2hhc2hlcyc6aGFzaGVzfSkKcHJpbnQoanNvbi5kdW1wcyh7J3RocmVhdGZveF9pb2NzJzps -ZW4oaW9jcyksJ21hbHdhcmViYXphYXJfaGFzaGVzJzpsZW4oaGFzaGVzKX0sc2VwYXJhdG9ycz0o -JywnLCc6JykpKQo= -B64_MALWARE_FEED_UPDATE - -install_payload /usr/local/sbin/clamav-watch-scan.sh 700 <<'B64_CLAMAV_WATCH_SCAN' -IyEvYmluL2Jhc2gKc2V0IC11Ck1PREU9IiR7MTotcXVpY2t9IgpDRkc9L2V0Yy9tYWx3YXJlLXdh -dGNoL2NvbmZpZwpEQkRJUj0vdmFyL2xpYi9jbGFtYXYKRVZJRD0vcm9vdC9zZWN1cml0eS1ldmlk -ZW5jZS9tYWx3YXJlLXdhdGNoClNUQVRFPS92YXIvbGliL21hbHdhcmUtd2F0Y2gvY2xhbWF2LWFs -ZXJ0LnN0YXRlCkxPQ0s9L3J1bi9jbGFtYXYtd2F0Y2gubG9jawpDTEFNPS91c3IvbG9jYWwvYmlu -L2NsYW1zY2FuCgpleGVjIDk+IiRMT0NLIgpmbG9jayAtbiA5IHx8IHsgZWNobyAiQ0xBTUFWX1NL -SVAgcmVhc29uPXNjYW5fYWxyZWFkeV9ydW5uaW5nIG1vZGU9JE1PREUiOyBleGl0IDA7IH0KCmlm -ICEgZmluZCAiJERCRElSIiAtbWF4ZGVwdGggMSAtdHlwZSBmIFwoIC1uYW1lICcqLmN2ZCcgLW8g -LW5hbWUgJyouY2xkJyBcKSAtcHJpbnQgLXF1aXQgMj4vZGV2L251bGwgfCBncmVwIC1xIC47IHRo -ZW4KICBlY2hvICJDTEFNQVZfU0tJUCByZWFzb249ZGF0YWJhc2Vfbm90X3JlYWR5IG1vZGU9JE1P -REUiCiAgZXhpdCAwCmZpCgppZiBbICEgLXIgIiRDRkciIF07IHRoZW4KICBlY2hvICJDTEFNQVZf -RVJST1IgY29uZmlnX3VucmVhZGFibGUiCiAgZXhpdCAyCmZpCnNldCAtYQouICIkQ0ZHIgpzZXQg -K2EKCmlmIFsgIiRNT0RFIiA9ICJkZWVwIiBdOyB0aGVuCiAgVEFSR0VUUz0oL2V0YyAvdXNyL2xv -Y2FsIC9vcHQgL3Jvb3QgL2hvbWUgL2FwcCkKZWxzZQogIFRBUkdFVFM9KC90bXAgL3Zhci90bXAg -L2Rldi9zaG0pCmZpCgpFWElTVElORz0oKQpmb3IgcCBpbiAiJHtUQVJHRVRTW0BdfSI7IGRvIFsg -LWUgIiRwIiBdICYmIEVYSVNUSU5HKz0oIiRwIik7IGRvbmUKWyAkeyNFWElTVElOR1tAXX0gLWd0 -IDAgXSB8fCBleGl0IDAKClRNUD0kKG1rdGVtcCAvdG1wL2NsYW1hdi13YXRjaC5YWFhYWFgpCnRy -YXAgJ3JtIC1mICIkVE1QIicgRVhJVAoKQVJHUz0oLS1kYXRhYmFzZT0iJERCRElSIiAtLXJlY3Vy -c2l2ZT15ZXMgLS1jcm9zcy1mcz1ubyAtLWluZmVjdGVkIC0tbm8tc3VtbWFyeSAtLW1heC1maWxl -c2l6ZT0xMDBNIC0tbWF4LXNjYW5zaXplPTIwME0gLS1tYXgtcmVjdXJzaW9uPTMwIC0tZXhjbHVk -ZS1kaXI9J14vcm9vdC9zZWN1cml0eS1ldmlkZW5jZSgvfCQpJykKaWYgWyAiJE1PREUiID0gImRl -ZXAiIF07IHRoZW4KICBBUkdTKz0oLS1leGNsdWRlLWRpcj0nKF58Lykobm9kZV9tb2R1bGVzfFwu -Z2l0fFwuY2FjaGV8Y2FjaGV8Q2FjaGV8X19weWNhY2hlX198dmVudnxcLnZlbnYpKC98JCknKQpm -aQoKc2V0ICtlCm5pY2UgLW4gMTUgaW9uaWNlIC1jIDMgIiRDTEFNIiAiJHtBUkdTW0BdfSIgIiR7 -RVhJU1RJTkdbQF19IiA+IiRUTVAiIDI+JjEKUkM9JD8Kc2V0IC1lCkZPVU5EPSQoZ3JlcCAnIEZP -VU5EJCcgIiRUTVAiIDI+L2Rldi9udWxsIHx8IHRydWUpCgppZiBbIC1uICIkRk9VTkQiIF07IHRo -ZW4KICBta2RpciAtcCAiJEVWSUQiCiAgY2htb2QgNzAwICIkRVZJRCIKICBUUz0kKGRhdGUgKyVZ -JW0lZC0lSCVNJVMpCiAgT1VUPSIkRVZJRC8ke1RTfS1jbGFtYXYtJHtNT0RFfS5sb2ciCiAgY3Ag -IiRUTVAiICIkT1VUIgogIGNobW9kIDYwMCAiJE9VVCIKICBRTE9HPSIkT1VULnF1YXJhbnRpbmUu -anNvbmwiCiAgOiA+ICIkUUxPRyIKICB3aGlsZSBJRlM9IHJlYWQgLXIgSElUOyBkbwogICAgRklM -RT0ke0hJVCUlOiAqfQogICAgU0lHPSR7SElUIyo6IH0KICAgIFsgLWYgIiRGSUxFIiBdIHx8IGNv -bnRpbnVlCiAgICAvdXNyL2xvY2FsL3NiaW4vbWFsd2FyZS1xdWFyYW50aW5lLnB5IC0tZmlsZSAi -JEZJTEUiIC0tcmVhc29uICJDbGFtQVYgY29uZmlybWVkOiAkU0lHIiA+PiAiJFFMT0ciIDI+JjEg -fHwgdHJ1ZQogIGRvbmUgPDxFT0ZfRk9VTkQKJEZPVU5ECkVPRl9GT1VORAogIGNobW9kIDYwMCAi -JFFMT0ciCiAgRlA9JChwcmludGYgJyVzXG4nICIkRk9VTkQiIHwgc2hhMjU2c3VtIHwgYXdrICd7 -cHJpbnQgJDF9JykKICBMQVNURlA9Jyc7IExBU1RUUz0wCiAgaWYgWyAtciAiJFNUQVRFIiBdOyB0 -aGVuIHJlYWQgLXIgTEFTVEZQIExBU1RUUyA8ICIkU1RBVEUiIHx8IHRydWU7IGZpCiAgTk9XPSQo -ZGF0ZSArJXMpCiAgaWYgWyAiJEZQIiAhPSAiJExBU1RGUCIgXSB8fCBbICQoKE5PVy1MQVNUVFMp -KSAtZ2UgODY0MDAgXTsgdGhlbgogICAgU0hPUlQ9JChwcmludGYgJyVzXG4nICIkRk9VTkQiIHwg -aGVhZCAtMTAgfCBoZWFkIC1jIDEzMDApCiAgICBNU0c9IvCfmqggKipDbGFtQVYgTUFMV0FSRSBE -RVRFQ1RFRCoqClNlcnZlcjogXGAkKGhvc3RuYW1lKVxgClNjYW46IFxgJE1PREVcYAoKXGBcYFxg -CiRTSE9SVApcYFxgXGAKRXZpZGVuY2U6IFxgJE9VVFxgXG5RdWFyYW50aW5lIGxvZzogXGAkUUxP -R1xgIgogICAgUEFZTE9BRD0kKHByaW50ZiAnJXMnICIkTVNHIiB8IHB5dGhvbjMgLWMgJ2ltcG9y -dCBqc29uLHN5czsgcHJpbnQoanNvbi5kdW1wcyh7ImNvbnRlbnQiOnN5cy5zdGRpbi5yZWFkKCl9 -LCBlbnN1cmVfYXNjaWk9RmFsc2UpKScpCiAgICBDT0RFPSQoY3VybCAtc1MgLW8gL2Rldi9udWxs -IC13ICcle2h0dHBfY29kZX0nIC1BICdNb3ppbGxhLzUuMCAoY29tcGF0aWJsZTsgbWFsd2FyZS13 -YXRjaC8yLjApJyAtSCAnQ29udGVudC1UeXBlOiBhcHBsaWNhdGlvbi9qc29uJyAtLWRhdGEgIiRQ -QVlMT0FEIiAiJHtESVNDT1JEX1dFQkhPT0s6LX0iIHx8IHRydWUpCiAgICBpZiBbICIkQ09ERSIg -PSAiMjAwIiBdIHx8IFsgIiRDT0RFIiA9ICIyMDQiIF07IHRoZW4KICAgICAgcHJpbnRmICclcyAl -c1xuJyAiJEZQIiAiJE5PVyIgPiAiJFNUQVRFIgogICAgICBjaG1vZCA2MDAgIiRTVEFURSIKICAg -IGZpCiAgICBlY2hvICJDTEFNQVZfQUxFUlQgbW9kZT0kTU9ERSBodHRwPSRDT0RFIGV2aWRlbmNl -PSRPVVQiCiAgZWxzZQogICAgZWNobyAiQ0xBTUFWX0RFVEVDVEVEX0RVUExJQ0FURSBtb2RlPSRN -T0RFIGV2aWRlbmNlPSRPVVQiCiAgZmkKICBleGl0IDEKZmkKCmlmIFsgIiRSQyIgLWd0IDEgXTsg -dGhlbgogIGVjaG8gIkNMQU1BVl9TQ0FOX0VSUk9SIG1vZGU9JE1PREUgcmM9JFJDIgogIHRhaWwg -LTIwICIkVE1QIgogIGV4aXQgIiRSQyIKZmkKCmVjaG8gIkNMQU1BVl9PSyBtb2RlPSRNT0RFIgpl -eGl0IDAK -B64_CLAMAV_WATCH_SCAN - -install_payload /usr/local/sbin/block-malware-iocs 700 <<'B64_BLOCK_MALWARE_IOCS' -IyEvYmluL3NoCnNldCAtZXUKRj0vZXRjL21hbHdhcmUtd2F0Y2gvYmxvY2tlZC1pb2NzLnR4dApb -IC1yICIkRiIgXSB8fCBleGl0IDAKd2hpbGUgSUZTPSByZWFkIC1yIGlwOyBkbwogIFsgLW4gIiRp -cCIgXSB8fCBjb250aW51ZQogIGlwdGFibGVzIC1DIE9VVFBVVCAtZCAiJGlwIiAtaiBSRUpFQ1Qg -Mj4vZGV2L251bGwgfHwgaXB0YWJsZXMgLUkgT1VUUFVUIDEgLWQgIiRpcCIgLWogUkVKRUNUCiAg -aWYgaXB0YWJsZXMgLW5MIERPQ0tFUi1VU0VSID4vZGV2L251bGwgMj4mMTsgdGhlbgogICAgaXB0 -YWJsZXMgLUMgRE9DS0VSLVVTRVIgLWQgIiRpcCIgLWogUkVKRUNUIDI+L2Rldi9udWxsIHx8IGlw -dGFibGVzIC1JIERPQ0tFUi1VU0VSIDEgLWQgIiRpcCIgLWogUkVKRUNUCiAgZmkKZG9uZSA8ICIk -RiIK -B64_BLOCK_MALWARE_IOCS - - -python3 -m py_compile /usr/local/sbin/malware-watch.py /usr/local/sbin/malware-quarantine.py /usr/local/sbin/malware-feed-update.py -bash -n /usr/local/sbin/clamav-watch-scan.sh +for f in malware-watch.py malware-quarantine.py malware-feed-update.py clamav-watch-scan.sh block-malware-iocs; do + install -o root -g root -m 700 "$SRC_DIR/$f" "/usr/local/sbin/$f" +done if [[ "$INSTALL_CLAMAV" == "1" ]]; then arch="$(uname -m)" @@ -482,8 +101,6 @@ if [[ "$INSTALL_CLAMAV" == "1" ]]; then [[ -x /usr/local/bin/clamscan ]] || die "Official ClamAV clamscan not found after installation" [[ -f /usr/local/etc/freshclam.conf.sample ]] || die "freshclam.conf.sample not found" - # Official ClamAV packages can be present without a pre-created service account. - # Ensure the account exists before assigning ownership to the signature DB directory. if ! getent group clamav >/dev/null 2>&1; then groupadd --system clamav fi @@ -491,6 +108,7 @@ if [[ "$INSTALL_CLAMAV" == "1" ]]; then useradd --system --gid clamav --home-dir /var/lib/clamav --shell /usr/sbin/nologin clamav fi install -d -o clamav -g clamav -m 755 /var/lib/clamav + cp /usr/local/etc/freshclam.conf.sample /usr/local/etc/freshclam.conf sed -i 's/^Example/#Example/' /usr/local/etc/freshclam.conf cat >> /usr/local/etc/freshclam.conf <<'EOF_FRESH' @@ -501,7 +119,7 @@ EOF_FRESH chmod 644 /usr/local/etc/freshclam.conf fi -cat > /etc/systemd/system/malware-watch.service <<'EOF' +cat > /etc/systemd/system/malware-watch.service <<'EOF_UNIT' [Unit] Description=Malware Watch realtime detector After=network-online.target docker.service @@ -514,9 +132,9 @@ User=root Nice=10 IOSchedulingClass=best-effort IOSchedulingPriority=7 -EOF +EOF_UNIT -cat > /etc/systemd/system/malware-watch.timer <<'EOF' +cat > /etc/systemd/system/malware-watch.timer <<'EOF_UNIT' [Unit] Description=Run Malware Watch every minute @@ -528,9 +146,9 @@ Persistent=true [Install] WantedBy=timers.target -EOF +EOF_UNIT -cat > /etc/systemd/system/malware-feed-update.service <<'EOF' +cat > /etc/systemd/system/malware-feed-update.service <<'EOF_UNIT' [Unit] Description=Malware Watch abuse.ch feed updater After=network-online.target @@ -541,9 +159,9 @@ Type=oneshot EnvironmentFile=/etc/malware-watch/config ExecStart=/usr/local/sbin/malware-feed-update.py User=root -EOF +EOF_UNIT -cat > /etc/systemd/system/malware-feed-update.timer <<'EOF' +cat > /etc/systemd/system/malware-feed-update.timer <<'EOF_UNIT' [Unit] Description=Update malware threat feeds every 6 hours @@ -555,9 +173,9 @@ Persistent=true [Install] WantedBy=timers.target -EOF +EOF_UNIT -cat > /etc/systemd/system/block-malware-iocs.service <<'EOF' +cat > /etc/systemd/system/block-malware-iocs.service <<'EOF_UNIT' [Unit] Description=Restore malware IOC firewall blocks After=network-online.target docker.service @@ -570,10 +188,10 @@ RemainAfterExit=yes [Install] WantedBy=multi-user.target -EOF +EOF_UNIT if [[ "$INSTALL_CLAMAV" == "1" ]]; then -cat > /etc/systemd/system/clamav-freshclam-local.service < /etc/systemd/system/clamav-freshclam-local.service < /etc/systemd/system/clamav-freshclam-local.timer <<'EOF' +cat > /etc/systemd/system/clamav-freshclam-local.timer <<'EOF_UNIT' [Unit] Description=Update ClamAV signatures every 2 hours @@ -601,9 +219,9 @@ Persistent=true [Install] WantedBy=timers.target -EOF +EOF_UNIT -cat > /etc/systemd/system/clamav-risk-scan.service <<'EOF' +cat > /etc/systemd/system/clamav-risk-scan.service <<'EOF_UNIT' [Unit] Description=ClamAV high-risk path scan After=local-fs.target @@ -615,9 +233,9 @@ User=root Nice=15 IOSchedulingClass=idle SuccessExitStatus=1 -EOF +EOF_UNIT -cat > /etc/systemd/system/clamav-risk-scan.timer <<'EOF' +cat > /etc/systemd/system/clamav-risk-scan.timer <<'EOF_UNIT' [Unit] Description=Hourly ClamAV scan of high-risk temporary paths @@ -629,9 +247,9 @@ Persistent=true [Install] WantedBy=timers.target -EOF +EOF_UNIT -cat > /etc/systemd/system/clamav-deep-scan.service <<'EOF' +cat > /etc/systemd/system/clamav-deep-scan.service <<'EOF_UNIT' [Unit] Description=ClamAV daily low-priority server scan After=local-fs.target @@ -643,9 +261,9 @@ User=root Nice=19 IOSchedulingClass=idle SuccessExitStatus=1 -EOF +EOF_UNIT -cat > /etc/systemd/system/clamav-deep-scan.timer <<'EOF' +cat > /etc/systemd/system/clamav-deep-scan.timer <<'EOF_UNIT' [Unit] Description=Daily ClamAV low-priority server scan @@ -656,7 +274,7 @@ Persistent=true [Install] WantedBy=timers.target -EOF +EOF_UNIT fi systemctl daemon-reload @@ -680,7 +298,7 @@ systemctl start malware-watch.service || true if [[ "$SEND_INSTALL_TEST" == "1" ]]; then payload="$(python3 - <<'PYMSG' import json,socket -print(json.dumps({'content':f'✅ malware-watch install OK | server: {socket.gethostname()}'},ensure_ascii=False)) +print(json.dumps({'content':f'malware-watch install OK | server: {socket.gethostname()}'},ensure_ascii=False)) PYMSG )" curl -sS -o /dev/null -A 'Mozilla/5.0 (compatible; malware-watch/2.0)' \ @@ -689,4 +307,6 @@ fi log "Installation complete" /usr/local/sbin/malware-watch.py || true -systemctl list-timers --all --no-pager | grep -E 'malware-watch|malware-feed-update|clamav-(freshclam-local|risk-scan|deep-scan)' || true +if [[ -x "$SCRIPT_DIR/verify.sh" ]]; then + "$SCRIPT_DIR/verify.sh" || true +fi diff --git a/src/__pycache__/malware-feed-update.cpython-38.pyc b/src/__pycache__/malware-feed-update.cpython-38.pyc new file mode 100644 index 0000000..434f414 Binary files /dev/null and b/src/__pycache__/malware-feed-update.cpython-38.pyc differ diff --git a/src/__pycache__/malware-quarantine.cpython-38.pyc b/src/__pycache__/malware-quarantine.cpython-38.pyc new file mode 100644 index 0000000..283755a Binary files /dev/null and b/src/__pycache__/malware-quarantine.cpython-38.pyc differ diff --git a/src/__pycache__/malware-watch.cpython-38.pyc b/src/__pycache__/malware-watch.cpython-38.pyc new file mode 100644 index 0000000..e87a0e4 Binary files /dev/null and b/src/__pycache__/malware-watch.cpython-38.pyc differ diff --git a/src/block-malware-iocs b/src/block-malware-iocs new file mode 100755 index 0000000..356106f --- /dev/null +++ b/src/block-malware-iocs @@ -0,0 +1,11 @@ +#!/bin/sh +set -eu +F=/etc/malware-watch/blocked-iocs.txt +[ -r "$F" ] || exit 0 +while IFS= read -r ip; do + [ -n "$ip" ] || continue + iptables -C OUTPUT -d "$ip" -j REJECT 2>/dev/null || iptables -I OUTPUT 1 -d "$ip" -j REJECT + if iptables -nL DOCKER-USER >/dev/null 2>&1; then + iptables -C DOCKER-USER -d "$ip" -j REJECT 2>/dev/null || iptables -I DOCKER-USER 1 -d "$ip" -j REJECT + fi +done < "$F" diff --git a/src/clamav-watch-scan.sh b/src/clamav-watch-scan.sh new file mode 100755 index 0000000..2d70f9c --- /dev/null +++ b/src/clamav-watch-scan.sh @@ -0,0 +1,103 @@ +#!/bin/bash +set -u +MODE="${1:-quick}" +CFG=/etc/malware-watch/config +DBDIR=/var/lib/clamav +EVID=/root/security-evidence/malware-watch +STATE=/var/lib/malware-watch/clamav-alert.state +LOCK=/run/clamav-watch.lock +CLAM=/usr/local/bin/clamscan + +exec 9>"$LOCK" +flock -n 9 || { echo "CLAMAV_SKIP reason=scan_already_running mode=$MODE"; exit 0; } + +if ! find "$DBDIR" -maxdepth 1 -type f \( -name '*.cvd' -o -name '*.cld' \) -print -quit 2>/dev/null | grep -q .; then + echo "CLAMAV_SKIP reason=database_not_ready mode=$MODE" + exit 0 +fi + +if [ ! -r "$CFG" ]; then + echo "CLAMAV_ERROR config_unreadable" + exit 2 +fi +set -a +. "$CFG" +set +a + +if [ "$MODE" = "deep" ]; then + TARGETS=(/etc /usr/local /opt /root /home /app) +else + TARGETS=(/tmp /var/tmp /dev/shm) +fi + +EXISTING=() +for p in "${TARGETS[@]}"; do [ -e "$p" ] && EXISTING+=("$p"); done +[ ${#EXISTING[@]} -gt 0 ] || exit 0 + +TMP=$(mktemp /tmp/clamav-watch.XXXXXX) +trap 'rm -f "$TMP"' EXIT + +ARGS=(--database="$DBDIR" --recursive=yes --cross-fs=no --infected --no-summary --max-filesize=100M --max-scansize=200M --max-recursion=30 --exclude-dir='^/root/security-evidence(/|$)') +if [ "$MODE" = "deep" ]; then + ARGS+=(--exclude-dir='(^|/)(node_modules|\.git|\.cache|cache|Cache|__pycache__|venv|\.venv)(/|$)') +fi + +set +e +nice -n 15 ionice -c 3 "$CLAM" "${ARGS[@]}" "${EXISTING[@]}" >"$TMP" 2>&1 +RC=$? +set -e +FOUND=$(grep ' FOUND$' "$TMP" 2>/dev/null || true) + +if [ -n "$FOUND" ]; then + mkdir -p "$EVID" + chmod 700 "$EVID" + TS=$(date +%Y%m%d-%H%M%S) + OUT="$EVID/${TS}-clamav-${MODE}.log" + cp "$TMP" "$OUT" + chmod 600 "$OUT" + QLOG="$OUT.quarantine.jsonl" + : > "$QLOG" + while IFS= read -r HIT; do + FILE=${HIT%%: *} + SIG=${HIT#*: } + [ -f "$FILE" ] || continue + /usr/local/sbin/malware-quarantine.py --file "$FILE" --reason "ClamAV confirmed: $SIG" >> "$QLOG" 2>&1 || true + done < "$STATE" + chmod 600 "$STATE" + fi + echo "CLAMAV_ALERT mode=$MODE http=$CODE evidence=$OUT" + else + echo "CLAMAV_DETECTED_DUPLICATE mode=$MODE evidence=$OUT" + fi + exit 1 +fi + +if [ "$RC" -gt 1 ]; then + echo "CLAMAV_SCAN_ERROR mode=$MODE rc=$RC" + tail -20 "$TMP" + exit "$RC" +fi + +echo "CLAMAV_OK mode=$MODE" +exit 0 diff --git a/src/malware-feed-update.py b/src/malware-feed-update.py new file mode 100755 index 0000000..953a98a --- /dev/null +++ b/src/malware-feed-update.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 +import json, os, sys, time, tempfile, urllib.request, urllib.parse +BASE='/var/lib/malware-watch' +FEEDS=BASE+'/feeds' +os.makedirs(FEEDS, exist_ok=True) +key=os.environ.get('ABUSECH_AUTH_KEY','').strip() +if not key: + print('ABUSECH_AUTH_KEY missing', file=sys.stderr) + sys.exit(2) + +def fetch(url, payload, is_json): + if is_json: + data=json.dumps(payload).encode() + ctype='application/json' + else: + data=urllib.parse.urlencode(payload).encode() + ctype='application/x-www-form-urlencoded' + req=urllib.request.Request(url,data=data,headers={'Auth-Key':key,'Content-Type':ctype,'User-Agent':'malware-watch/2'},method='POST') + with urllib.request.urlopen(req,timeout=45) as r: + return json.load(r) + +def save(path,obj): + fd,tmp=tempfile.mkstemp(prefix='.tmp-',dir=FEEDS) + with os.fdopen(fd,'w') as f: + json.dump(obj,f,separators=(',',':')) + os.chmod(tmp,0o600) + os.replace(tmp,path) + +tf=fetch('https://threatfox-api.abuse.ch/api/v1/',{'query':'get_iocs','days':7},True) +if tf.get('query_status')!='ok': + raise RuntimeError('ThreatFox status='+str(tf.get('query_status'))) +iocs={} +for x in tf.get('data') or []: + val=(x.get('ioc') or '').strip() + if val: + iocs[val]={'ioc_type':x.get('ioc_type'),'threat_type':x.get('threat_type'),'malware':x.get('malware_printable') or x.get('malware'),'first_seen':x.get('first_seen')} +save(FEEDS+'/threatfox.json',{'updated':int(time.time()),'iocs':iocs}) + +mb=fetch('https://mb-api.abuse.ch/api/v1/',{'query':'recent_detections','hours':'168'},False) +if mb.get('query_status') not in ('ok','no_results'): + raise RuntimeError('MalwareBazaar status='+str(mb.get('query_status'))) +hashes={} +for x in mb.get('data') or []: + h=(x.get('sha256_hash') or '').lower() + if len(h)==64: + hashes[h]={'signature':x.get('signature'),'file_name':x.get('file_name'),'first_seen':x.get('first_seen')} +save(FEEDS+'/malwarebazaar.json',{'updated':int(time.time()),'hashes':hashes}) +print(json.dumps({'threatfox_iocs':len(iocs),'malwarebazaar_hashes':len(hashes)},separators=(',',':'))) diff --git a/src/malware-quarantine.py b/src/malware-quarantine.py new file mode 100755 index 0000000..86fb385 --- /dev/null +++ b/src/malware-quarantine.py @@ -0,0 +1,192 @@ +#!/usr/bin/env python3 +import argparse, hashlib, ipaddress, json, os, re, shutil, signal, subprocess, time +from pathlib import Path + +BASE=Path('/var/lib/malware-watch') +EVID=Path('/root/security-evidence/malware-watch') +QUAR=Path('/root/security-evidence/quarantine') +BLOCKED=Path('/etc/malware-watch/blocked-iocs.txt') + + +def run(cmd, timeout=20): + try: + r=subprocess.run(cmd,text=True,capture_output=True,timeout=timeout) + return r.returncode,r.stdout,r.stderr + except Exception as e: + return 255,'',str(e) + +def sha256(path): + h=hashlib.sha256() + with open(path,'rb',buffering=0) as f: + for b in iter(lambda:f.read(1024*1024),b''): h.update(b) + return h.hexdigest() + +def container_id(pid): + try: data=Path(f'/proc/{pid}/cgroup').read_text(errors='ignore') + except Exception: return '' + pats=[r'/docker/([0-9a-f]{12,64})(?:$|/)',r'docker-([0-9a-f]{12,64})\.scope',r'/docker/([0-9a-f]{12,64})$'] + for p in pats: + m=re.search(p,data,re.M) + if m: return m.group(1) + return '' + +def capture_process(pid,d): + for src,name in [(f'/proc/{pid}/status','status'),(f'/proc/{pid}/cgroup','cgroup'),(f'/proc/{pid}/maps','maps'),(f'/proc/{pid}/cmdline','cmdline')]: + try: + raw=Path(src).read_bytes() + (d/name).write_bytes(raw) + except Exception: pass + +def capture_global(d): + for cmd,name in [(['ps','-efww'],'ps.txt'),(['ss','-tnp'],'network.txt')]: + rc,out,err=run(cmd) + (d/name).write_text(out+err,errors='replace') + +def block_ioc(ioc,actions,dry): + raw=(ioc or '').strip() + if not raw: return + host=raw + if raw.startswith('[') and ']:' in raw: host=raw[1:raw.index(']')] + elif raw.count(':')==1 and raw.rsplit(':',1)[1].isdigit(): host=raw.rsplit(':',1)[0] + try: ip=str(ipaddress.ip_address(host)) + except Exception: return + actions.append('blocked_ioc:'+ip) + if dry: return + BLOCKED.parent.mkdir(parents=True,exist_ok=True) + vals=[] + if BLOCKED.exists(): vals=[x.strip() for x in BLOCKED.read_text().splitlines() if x.strip()] + if ip not in vals: + with open(BLOCKED,'a') as f: f.write(ip+'\n') + os.chmod(BLOCKED,0o600) + run(['/usr/local/sbin/block-malware-iocs']) + +def neutralize_persistence(exe,evdir,actions,dry): + if not exe or not exe.startswith('/'): return + candidates=[] + roots=[Path('/etc/crontab'),Path('/etc/cron.d'),Path('/var/spool/cron/crontabs'),Path('/etc/systemd/system'),Path('/etc/rc.local')] + for root in roots: + if root.is_file(): candidates.append(root) + elif root.is_dir(): + try: + candidates += [p for p in root.rglob('*') if p.is_file() and p.stat().st_size <= 2*1024*1024] + except Exception: pass + pdir=evdir/'persistence'; pdir.mkdir(exist_ok=True) + for p in candidates: + try: + txt=p.read_text(errors='ignore') + if exe not in txt: continue + safe=p.name.replace('/','_') + shutil.copy2(p,pdir/(safe+'.before')) + if '/systemd/system/' in str(p) and p.suffix in ('.service','.timer','.socket','.path'): + unit=p.name + actions.append('disabled_persistence_unit:'+unit) + if not dry: + run(['systemctl','disable','--now',unit]) + q=p.with_name(p.name+'.malware-watch-disabled') + try: os.replace(p,q) + except Exception: pass + run(['systemctl','daemon-reload']) + else: + actions.append('disabled_persistence_file:'+str(p)) + if not dry: + lines=[] + for line in txt.splitlines(True): + if exe in line and not line.lstrip().startswith('#'): + lines.append('# malware-watch disabled: '+line) + else: lines.append(line) + p.write_text(''.join(lines)) + except Exception: pass + +def quarantine_host_file(path,evdir,actions,dry): + p=Path(path) + if not p.is_file(): return '' + try: h=sha256(p) + except Exception: h='unknown' + actions.append('quarantine_file:'+str(p)) + if dry: return str(p) + QUAR.mkdir(parents=True,exist_ok=True); os.chmod(QUAR,0o700) + backup=evdir/('sample-'+p.name) + try: shutil.copy2(p,backup); os.chmod(backup,0o400) + except Exception: pass + dest=QUAR/(time.strftime('%Y%m%d-%H%M%S')+'-'+h[:16]+'-'+p.name) + try: + shutil.move(str(p),str(dest)); os.chmod(dest,0o000); return str(dest) + except Exception: return '' + +def matching_pids(path): + out=[] + try: target=os.path.realpath(path) + except Exception: return out + for ent in Path('/proc').iterdir(): + if not ent.name.isdigit(): continue + try: + ex=os.readlink(ent/'exe').replace(' (deleted)','') + if os.path.realpath(ex)==target: out.append(int(ent.name)) + except Exception: pass + return out + +def stop_pid(pid,actions,dry): + if pid<=1 or pid==os.getpid(): return + actions.append('stopped_pid:'+str(pid)) + if dry: return + try: os.kill(pid,signal.SIGSTOP) + except Exception: return + try: os.kill(pid,signal.SIGCONT); os.kill(pid,signal.SIGTERM) + except Exception: pass + for _ in range(30): + if not Path(f'/proc/{pid}').exists(): return + time.sleep(0.1) + try: os.kill(pid,signal.SIGKILL) + except Exception: pass + +def handle_container(cid,pid,exe,evdir,actions,dry): + actions.append('isolated_container:'+cid[:12]) + if dry: return + for cmd,name in [(['docker','inspect',cid],'docker-inspect.json'),(['docker','logs','--tail','500',cid],'docker-logs.txt'),(['docker','diff',cid],'docker-diff.txt')]: + rc,out,err=run(cmd,30); (evdir/name).write_text(out+err,errors='replace') + if exe and exe.startswith('/'): + dest=evdir/('container-sample-'+Path(exe).name) + run(['docker','cp',cid+':'+exe,str(dest)],30) + if dest.exists(): + try: os.chmod(dest,0o400) + except Exception: pass + run(['docker','update','--restart=no',cid],20) + run(['docker','stop','-t','10',cid],20) + +def main(): + ap=argparse.ArgumentParser() + ap.add_argument('--pid',type=int,default=0) + ap.add_argument('--file',default='') + ap.add_argument('--reason',default='confirmed malware') + ap.add_argument('--ioc',action='append',default=[]) + ap.add_argument('--dry-run',action='store_true') + a=ap.parse_args() + EVID.mkdir(parents=True,exist_ok=True); os.chmod(EVID,0o700) + evdir=EVID/(time.strftime('%Y%m%d-%H%M%S')+'-quarantine'+(f'-pid{a.pid}' if a.pid else '')) + evdir.mkdir(mode=0o700) + actions=[] + exe=a.file + if a.pid and not exe: + try: exe=os.readlink(f'/proc/{a.pid}/exe').replace(' (deleted)','') + except Exception: exe='' + info={'timestamp':time.time(),'reason':a.reason,'pid':a.pid,'file':exe,'iocs':a.ioc,'dry_run':a.dry_run} + (evdir/'request.json').write_text(json.dumps(info,indent=2)); os.chmod(evdir/'request.json',0o600) + capture_global(evdir) + if a.pid: capture_process(a.pid,evdir) + cid=container_id(a.pid) if a.pid else '' + if a.pid and not a.dry_run: + try: os.kill(a.pid,signal.SIGSTOP); actions.append('frozen_pid:'+str(a.pid)) + except Exception: pass + for ioc in a.ioc: block_ioc(ioc,actions,a.dry_run) + if cid: + handle_container(cid,a.pid,exe,evdir,actions,a.dry_run) + else: + pids=set(([a.pid] if a.pid else []) + (matching_pids(exe) if exe else [])) + neutralize_persistence(exe,evdir,actions,a.dry_run) + for p in sorted(x for x in pids if x): stop_pid(p,actions,a.dry_run) + if exe: quarantine_host_file(exe,evdir,actions,a.dry_run) + result={'ok':True,'evidence':str(evdir),'container':cid[:12] if cid else '', 'actions':actions,'dry_run':a.dry_run} + (evdir/'result.json').write_text(json.dumps(result,indent=2)); os.chmod(evdir/'result.json',0o600) + print(json.dumps(result,separators=(',',':'))) + +if __name__=='__main__': main() diff --git a/src/malware-watch.py b/src/malware-watch.py new file mode 100755 index 0000000..6a66630 --- /dev/null +++ b/src/malware-watch.py @@ -0,0 +1,137 @@ +#!/usr/bin/env python3 +import os,re,json,time,hashlib,socket,subprocess,shlex +from pathlib import Path +CFG='/etc/malware-watch/config'; BASE=Path('/var/lib/malware-watch'); FEEDS=BASE/'feeds'; STATE=BASE/'state.json'; EVID=Path('/root/security-evidence/malware-watch') +KNOWN=('xmrig','xarm','kinsing','kdevtmpfsi','watchbog','minerd','cpuminer','moneroocean','cryptominer') +TEMP=('/tmp/','/var/tmp/','/dev/shm/') + +def cfg(): + d={} + for line in open(CFG,errors='ignore'): + if '=' in line and not line.lstrip().startswith('#'): + k,v=line.rstrip('\n').split('=',1); d[k]=v + return d + +def loadj(p,default): + try: return json.load(open(p)) + except Exception: return default + +def savej(p,obj): + t=str(p)+'.tmp'; json.dump(obj,open(t,'w'),separators=(',',':')); os.chmod(t,0o600); os.replace(t,p) + +def sha256(path): + h=hashlib.sha256() + with open(path,'rb',buffering=0) as f: + while True: + b=f.read(1024*1024) + if not b: break + h.update(b) + return h.hexdigest() + +def alert(webhook,text): + payload=json.dumps({'content':text},ensure_ascii=False) + r=subprocess.run(['curl','-sS','-o','/dev/null','-w','%{http_code}','-A','Mozilla/5.0 (compatible; malware-watch/2.0)','-H','Content-Type: application/json','--data',payload,webhook],text=True,capture_output=True,timeout=20) + return r.stdout.strip() in ('200','204') + +def evidence(pid,info): + ts=time.strftime('%Y%m%d-%H%M%S'); d=EVID/f'{ts}-pid{pid}'; d.mkdir(parents=True,exist_ok=True) + (d/'summary.json').write_text(json.dumps(info,ensure_ascii=False,indent=2)) + for src,name in [(f'/proc/{pid}/status','status'),(f'/proc/{pid}/cgroup','cgroup'),(f'/proc/{pid}/maps','maps')]: + try: (d/name).write_text(open(src,errors='replace').read()) + except Exception: pass + try: (d/'network.txt').write_text(subprocess.check_output(['ss','-tnp'],text=True,stderr=subprocess.DEVNULL)) + except Exception: pass + os.chmod(d,0o700) + return str(d) + +c=cfg(); threshold=float(c.get('CPU_THRESHOLD','150')); minscore=int(c.get('ALERT_SCORE','60')); critical=int(c.get('CRITICAL_SCORE','100')); cooldown=int(c.get('COOLDOWN_SECONDS','1800')); webhook=c.get('DISCORD_WEBHOOK','') +tf=loadj(FEEDS/'threatfox.json',{}).get('iocs',{}); bad=set(tf.keys()); mb=loadj(FEEDS/'malwarebazaar.json',{}).get('hashes',{}) +st=loadj(STATE,{'alerts':{},'hashcache':{}}); now=time.time(); host=socket.gethostname(); findings={} + +# process snapshot +out=subprocess.check_output(['ps','-eo','pid=,pcpu=,user=,comm=,args='],text=True,errors='replace') +for line in out.splitlines(): + m=re.match(r'\s*(\d+)\s+([0-9.]+)\s+(\S+)\s+(\S+)\s+(.*)',line) + if not m: continue + pid=int(m.group(1)); cpu=float(m.group(2)); user,comm,args=m.group(3),m.group(4),m.group(5) + if pid==os.getpid(): continue + exe=''; deleted=False + try: + exe=os.readlink(f'/proc/{pid}/exe'); deleted=exe.endswith(' (deleted)') + except Exception: pass + low=(comm+' '+args+' '+exe).lower(); score=0; reasons=[] + if any(x in low for x in KNOWN): score+=60; reasons.append('known miner/malware name') + if exe.startswith(TEMP): score+=40; reasons.append('executable from temp directory') + if deleted: score+=50; reasons.append('deleted executable still running') + if cpu>=threshold: score+=20; reasons.append(f'high CPU {cpu:.1f}%') + h=''; cleanexe=exe.replace(' (deleted)','') + if cleanexe and os.path.isfile(cleanexe): + try: + s=os.stat(cleanexe); ck=f'{cleanexe}|{s.st_size}|{int(s.st_mtime)}'; hc=st['hashcache'].get(ck) + if hc: h=hc + elif s.st_size <= 268435456: + h=sha256(cleanexe); st['hashcache'][ck]=h + if h in mb: score+=100; reasons.append('MalwareBazaar SHA256 match') + except Exception: pass + if score: findings[pid]={'pid':pid,'cpu':cpu,'user':user,'comm':comm,'args':args[:500],'exe':exe,'sha256':h,'score':score,'reasons':reasons,'ioc_hits':[]} + +# outbound established TCP vs ThreatFox IOC values +try: + ss=subprocess.check_output(['ss','-Htnp','state','established'],text=True,errors='replace',stderr=subprocess.DEVNULL) + for line in ss.splitlines(): + parts=line.split() + if len(parts)<5: continue + peer=parts[4]; pm=re.search(r'pid=(\d+)',line) + if not pm: continue + pid=int(pm.group(1)); peer_ip=peer.rsplit(':',1)[0].strip('[]'); hit=None + if peer in bad: hit=peer + elif peer_ip in bad: hit=peer_ip + if hit: + f=findings.setdefault(pid,{'pid':pid,'cpu':0,'user':'?','comm':'?','args':'','exe':'','sha256':'','score':0,'reasons':[],'ioc_hits':[]}) + f['score']+=80; f['reasons'].append('ThreatFox network IOC '+hit); f.setdefault('ioc_hits',[]).append(hit) +except Exception: pass + +sent=0 +quarantined=0 +for pid,f in findings.items(): + if f['score'] < minscore: continue + mb_match='MalwareBazaar SHA256 match' in f['reasons'] + miner_match='known miner/malware name' in f['reasons'] + behavior_match=any(x.startswith('executable from temp directory') or x.startswith('deleted executable') or x.startswith('high CPU ') for x in f['reasons']) + tf_hits=f.get('ioc_hits',[]) + confirmed=mb_match or (miner_match and bool(tf_hits) and behavior_match) + response={} + ev='' + if confirmed: + cmd=['/usr/local/sbin/malware-quarantine.py','--pid',str(pid),'--reason','; '.join(f['reasons'])] + cleanexe=f.get('exe','').replace(' (deleted)','') + if cleanexe: cmd += ['--file',cleanexe] + for hit in tf_hits: cmd += ['--ioc',hit] + try: + r=subprocess.run(cmd,text=True,capture_output=True,timeout=45) + if r.stdout.strip(): response=json.loads(r.stdout.strip().splitlines()[-1]) + ev=response.get('evidence','') + if response.get('ok'): quarantined+=1 + except Exception as e: + response={'ok':False,'error':str(e)} + elif f['score']>=critical: + try: ev=evidence(pid,f) + except Exception: pass + key=hashlib.sha256((str(pid)+'|'+f.get('exe','')+'|'+','.join(sorted(f['reasons']))).encode()).hexdigest() + if now-float(st['alerts'].get(key,0)) < cooldown: continue + level='CONFIRMED/AUTO-QUARANTINED' if confirmed and response.get('ok') else ('CRITICAL' if f['score']>=critical else 'HIGH') + msg=(f'🚨 **Malware Watch {level}**\nServer: `{host}` Score: **{f["score"]}**\nPID: `{pid}` CPU: `{f["cpu"]}%` User: `{f["user"]}`\nProcess: `{f["comm"]}`\nExe: `{f["exe"] or "unknown"}`\nReasons: '+', '.join(f['reasons'])) + if f.get('sha256'): msg+=f'\nSHA256: `{f["sha256"]}`' + if confirmed: + acts=response.get('actions',[]) if isinstance(response,dict) else [] + if acts: msg+='\nActions: '+', '.join(acts[:8]) + if response.get('container'): msg+=f'\nContainer isolated: `{response["container"]}`' + if not response.get('ok'): msg+=f'\nAuto-response ERROR: `{response.get("error","unknown")}`' + if ev: msg+=f'\nEvidence: `{ev}`' + if webhook and alert(webhook,msg[:1900]): st['alerts'][key]=now; sent+=1 + +# prune state +st['alerts']={k:v for k,v in st['alerts'].items() if now-float(v)<604800} +if len(st['hashcache'])>5000: st['hashcache']=dict(list(st['hashcache'].items())[-3000:]) +savej(STATE,st) +print(json.dumps({'findings':len(findings),'alerts_sent':sent,'quarantined':quarantined,'threatfox_iocs':len(bad),'malwarebazaar_hashes':len(mb)},separators=(',',':')))