#!/bin/sh if ! (set -o pipefail 2>/dev/null); then # dash does not support pipefail set -efx else set -efx -o pipefail fi # bash does not expand alias by default for non-interactive script if [ -n "$BASH_VERSION" ]; then shopt -s expand_aliases fi alias curl="curl -L" alias rm="rm -rf" ## Use GNU grep, busybox grep is not as performant DISTRO="" if [ -f "/etc/os-release" ]; then . "/etc/os-release" DISTRO="$ID" fi check_grep() { if [ -z "$(grep --help | grep 'GNU')" ]; then if [ -x "/usr/bin/grep" ]; then alias grep="/usr/bin/grep" check_grep else if [ "$DISTRO" = "alpine" ]; then echo "Please install GNU grep 'apk add grep'" else echo "GNU grep not found" fi exit 1 fi fi } check_grep ## Detect Musl C library LIBC="$(ldd /bin/ls | grep 'musl' || [ $? = 1 ])" if [ -z "$LIBC" ]; then rm "/tmp/musl.log" # Not Musl CSVQUOTE="../utils/csvquote-bin-glibc" else # Musl CSVQUOTE="../utils/csvquote-bin-musl" fi ## Fallback to busybox's dos2unix if installed if ! command -v dos2unix &> /dev/null then if command -v busybox &> /dev/null then alias dos2unix="busybox dos2unix" else echo "dos2unix not found" exit 1 fi fi if command -v unzip &> /dev/null then alias unzip="unzip -p" elif command -v busybox &> /dev/null then alias unzip="busybox unzip -p" elif command -v bsdunzip &> /dev/null then alias unzip="bsdunzip -p" else echo "unzip not found" exit 1 fi ## Create a temporary working folder mkdir -p "tmp/" cd "tmp/" USER_AGENT="phishtank/malware-filter" if [ -n "$GITLAB_USER_LOGIN" ]; then USER_AGENT="phishtank/$GITLAB_USER_LOGIN" elif [ -n "$GITHUB_REPOSITORY_OWNER" ]; then USER_AGENT="phishtank/$GITHUB_REPOSITORY_OWNER" fi ## Prepare datasets if [ -n "$PHISHTANK_API" ]; then curl --user-agent "$USER_AGENT" \ "https://data.phishtank.com/data/$PHISHTANK_API/online-valid.csv.bz2" -o "phishtank.bz2" else curl --user-agent "$USER_AGENT" \ "https://data.phishtank.com/data/online-valid.csv.bz2" -o "phishtank.bz2" fi curl "https://openphish.com/feed.txt" -o "openphish-raw.txt" curl "https://lists.ipthreat.net/file/ipthreat-lists/phishing/phishing-threat-0.txt.gz" -o "ipthreat.gz" curl "https://s3-us-west-1.amazonaws.com/umbrella-static/top-1m.csv.zip" -o "top-1m-umbrella.zip" curl "https://tranco-list.eu/download/daily/top-1m.csv.zip" -o "top-1m-tranco.zip" ## Cloudflare Radar if [ -n "$CF_API" ]; then mkdir -p "cf/" # Get the latest domain ranking buckets curl -X GET "https://api.cloudflare.com/client/v4/radar/datasets?limit=5&offset=0&datasetType=RANKING_BUCKET&format=json" \ -H "Authorization: Bearer $CF_API" -o "cf/datasets.json" # Get the top 1m bucket's dataset ID DATASET_ID=$(jq ".result.datasets[] | select(.meta.top==1000000) | .id" "cf/datasets.json") # Get the dataset download url curl --request POST \ --url "https://api.cloudflare.com/client/v4/radar/datasets/download" \ --header "Content-Type: application/json" \ --header "Authorization: Bearer $CF_API" \ --data "{ \"datasetId\": $DATASET_ID }" \ -o "cf/dataset-url.json" DATASET_URL=$(jq ".result.dataset.url" "cf/dataset-url.json" | sed 's/"//g') curl "$DATASET_URL" -o "cf/top-1m-radar.csv" ## Parse the Radar 1 Million cat "cf/top-1m-radar.csv" | \ dos2unix | \ tr "[:upper:]" "[:lower:]" | \ grep -F "." | \ sed "s/^www\.//" | \ sort -u > "top-1m-radar.txt" fi ## Parse URLs if [ -n "$(file 'phishtank.bz2' | grep 'bzip2 compressed data')" ]; then bunzip2 -kc "phishtank.bz2" | \ tr "[:upper:]" "[:lower:]" | \ ## Workaround for column with double quotes "./$CSVQUOTE" | \ cut -f 2 -d "," | \ "./$CSVQUOTE" -u | \ sed 's/"//g' | \ node "../src/clean_url.js" | \ sort -u > "phishtank.txt" else # cloudflare may impose captcha echo "phishtank.bz2 is not a bzip2, skipping it..." touch "phishtank.txt" fi cat "openphish-raw.txt" | \ dos2unix | \ tr "[:upper:]" "[:lower:]" | \ node "../src/clean_url.js" | \ sort -u > "openphish.txt" gzip -dc "ipthreat.gz" | \ # remove comment sed "/^#/d" | \ sed "s/ # .*//" | \ tr "[:upper:]" "[:lower:]" | \ node "../src/clean_url.js" | \ sort -u > "ipthreat.txt" ## Combine all sources cat "openphish.txt" "ipthreat.txt" "phishtank.txt" | \ # remove blank lines sed "/^$/d" | \ sort -u > "phishing.txt" ## Parse domain and IP address only cat "phishing.txt" | \ node "../src/clean_url.js" hostname | \ sort -u > "phishing-domains.txt" cp "../src/exclude.txt" "." ## Parse the Umbrella 1 Million unzip "top-1m-umbrella.zip" | \ dos2unix | \ tr "[:upper:]" "[:lower:]" | \ # Parse domains only cut -f 2 -d "," | \ grep -F "." | \ # Remove www. sed "s/^www\.//" | \ sort -u > "top-1m-umbrella.txt" ## Parse the Tranco 1 Million if [ -n "$(file 'top-1m-tranco.zip' | grep 'Zip archive data')" ]; then unzip "top-1m-tranco.zip" | \ dos2unix | \ tr "[:upper:]" "[:lower:]" | \ # Parse domains only cut -f 2 -d "," | \ grep -F "." | \ # Remove www. sed "s/^www\.//" | \ sort -u > "top-1m-tranco.txt" else # cloudflare may impose captcha echo "top-1m-tranco.zip is not a zip, skipping it..." touch "top-1m-tranco.txt" fi # Merge Umbrella, Tranco, Radar and self-maintained top domains cat "top-1m-umbrella.txt" "top-1m-tranco.txt" "exclude.txt" | \ sort -u > "top-1m-well-known.txt" if [ -n "$CF_API" ] && [ -f "top-1m-radar.txt" ]; then cat "top-1m-radar.txt" >> "top-1m-well-known.txt" # sort in-place sort "top-1m-well-known.txt" -u -o "top-1m-well-known.txt" fi ## Parse popular domains cat "phishing-domains.txt" | \ # grep match whole line grep -Fx -f "top-1m-well-known.txt" > "phishing-top-domains.txt" ## Exclude popular domains cat "phishing-domains.txt" | \ grep -F -vf "phishing-top-domains.txt" > "phishing-notop-domains.txt" cat "phishing-top-domains.txt" | \ # "example.com" -> "^example\.com" sed -e "s/^/^/" -e "s/\./\\\./g" > "phishing-top-domains-grep.txt" cat "phishing.txt" | \ # exact match hostname grep -f "phishing-top-domains-grep.txt" | \ # exclude URL of top domains without path #43 grep -Fx -vf "phishing-top-domains.txt" > "phishing-url-top-domains-temp.txt" cat "phishing-url-top-domains-temp.txt" | \ # url with path grep -F "/" > "phishing-url-top-domains-raw.txt" cat "phishing-url-top-domains-temp.txt" | \ # url without path grep -F -v "/" >> "phishing-notop-domains.txt" ## Merge malware domains and URLs CURRENT_TIME=$(date -u +"%Y-%m-%dT%H:%M:%SZ") FIRST_LINE="! Title: Phishing URL Blocklist" SECOND_LINE="! Updated: $CURRENT_TIME" THIRD_LINE="! Expires: 1 day (update frequency)" FOURTH_LINE="! Homepage: https://gitlab.com/malware-filter/phishing-filter" FIFTH_LINE="! License: https://gitlab.com/malware-filter/phishing-filter#license" SIXTH_LINE="! Sources: openphish.com, ipthreat.net, phishtank.org" COMMENT_UBO="$FIRST_LINE\n$SECOND_LINE\n$THIRD_LINE\n$FOURTH_LINE\n$FIFTH_LINE\n$SIXTH_LINE" mkdir -p "../public/" cat "phishing-url-top-domains-raw.txt" | \ sed "s/^/||/" | \ sed 's/$/^$all/' > "phishing-url-top-domains.txt" cat "phishing-notop-domains.txt" "phishing-url-top-domains.txt" | \ sort | \ sed "1i $COMMENT_UBO" > "../public/phishing-filter.txt" # Adguard Home cat "phishing-notop-domains.txt" | \ sed "s/^/||/" | \ sed "s/$/^/" > "phishing-domains-adguard-home.txt" cat "phishing-domains-adguard-home.txt" | \ sort | \ sed "1i $COMMENT_UBO" | \ sed "1s/Blocklist/Blocklist (AdGuard Home)/" > "../public/phishing-filter-agh.txt" # Adguard browser extension cat "phishing-notop-domains.txt" | \ sed "s/^/||/" | \ sed 's/$/^$all/' > "phishing-domains-adguard.txt" cat "phishing-domains-adguard.txt" "phishing-url-top-domains.txt" | \ sort | \ sed "1i $COMMENT_UBO" | \ sed "1s/Blocklist/Blocklist (AdGuard)/" > "../public/phishing-filter-ag.txt" # Vivaldi cat "phishing-notop-domains.txt" | \ sed "s/^/||/" | \ sed 's/$/^$document/' > "phishing-domains-vivaldi.txt" cat "phishing-domains-vivaldi.txt" "phishing-url-top-domains.txt" | \ sed 's/\$all$/$document/' | \ sort | \ sed "1i $COMMENT_UBO" | \ sed "1s/Blocklist/Blocklist (Vivaldi)/" > "../public/phishing-filter-vivaldi.txt" ## Domains-only blocklist # awk + head is a workaround for sed prepend COMMENT=$(printf "$COMMENT_UBO" | sed "s/^!/#/" | sed "1s/URL/Domains/" | awk '{printf "%s\\n", $0}' | head -c -2) cat "phishing-notop-domains.txt" | \ sort | \ sed "1i $COMMENT" > "../public/phishing-filter-domains.txt" cat "phishing-notop-domains.txt" | \ # exclude IPv4 grep -vE "^([0-9]{1,3}[\.]){3}[0-9]{1,3}$" | \ # exclude IPv6 grep -vE "^\[" > "phishing-notop-hosts.txt" ## Hosts file blocklist cat "phishing-notop-hosts.txt" | \ sed "s/^/0.0.0.0 /" | \ # Re-insert comment sed "1i $COMMENT" | \ sed "1s/Domains/Hosts/" > "../public/phishing-filter-hosts.txt" ## Dnsmasq-compatible blocklist cat "phishing-notop-hosts.txt" | \ sed "s/^/address=\//" | \ sed "s/$/\/0.0.0.0/" | \ sed "1i $COMMENT" | \ sed "1s/Blocklist/dnsmasq Blocklist/" > "../public/phishing-filter-dnsmasq.conf" ## BIND-compatible blocklist cat "phishing-notop-hosts.txt" | \ sed 's/^/zone "/' | \ sed 's/$/" { type master; notify no; file "null.zone.file"; };/' | \ sed "1i $COMMENT" | \ sed "1s/Blocklist/BIND Blocklist/" > "../public/phishing-filter-bind.conf" ## DNS Response Policy Zone (RPZ) CURRENT_UNIX_TIME="$(date +%s)" RPZ_SYNTAX="\n\$TTL 30\n@ IN SOA localhost. root.localhost. $CURRENT_UNIX_TIME 86400 3600 604800 30\n NS localhost.\n" cat "phishing-notop-hosts.txt" | \ sed "s/$/ CNAME ./" | \ sed '1 i\'"$RPZ_SYNTAX"'' | \ sed "1i $COMMENT" | \ sed "s/^#/;/" | \ sed "1s/Blocklist/RPZ Blocklist/" > "../public/phishing-filter-rpz.conf" ## Unbound-compatible blocklist cat "phishing-notop-hosts.txt" | \ sed 's/^/local-zone: "/' | \ sed 's/$/" always_nxdomain/' | \ sed "1i $COMMENT" | \ sed "1s/Blocklist/Unbound Blocklist/" > "../public/phishing-filter-unbound.conf" ## dnscrypt-proxy blocklists # name-based cat "phishing-notop-hosts.txt" | \ sed "1i $COMMENT" | \ sed "1s/Domains/Names/" > "../public/phishing-filter-dnscrypt-blocked-names.txt" # IPv4/6 if grep -Eq "^(([0-9]{1,3}[\.]){3}[0-9]{1,3}$|\[)" "phishing-notop-domains.txt"; then cat "phishing-notop-domains.txt" | \ sort | \ grep -E "^(([0-9]{1,3}[\.]){3}[0-9]{1,3}$|\[)" | \ sed -r "s/\[|\]//g" | \ sed "1i $COMMENT" | \ sed "1s/Domains/IPs/" > "../public/phishing-filter-dnscrypt-blocked-ips.txt" else echo | \ sed "1i $COMMENT" | \ sed "1s/Domains/IPs/" > "../public/phishing-filter-dnscrypt-blocked-ips.txt" fi ## Wildcard subdomain cat "phishing-notop-hosts.txt" | \ sed "s/^/*./" | \ sed "1i $COMMENT" | \ sed "1s/Domains/Wildcard Asterisk/" > "../public/phishing-filter-wildcard.txt" ## Snort & Suricata rulesets rm "../public/phishing-filter-snort2.rules" \ "../public/phishing-filter-snort3.rules" \ "../public/phishing-filter-suricata.rules" \ "../public/phishing-filter-splunk.csv" export CURRENT_TIME node "../src/ids.js" sed -i "1i $COMMENT" "../public/phishing-filter-snort2.rules" sed -i "1s/Domains Blocklist/URL Snort2 Ruleset/" "../public/phishing-filter-snort2.rules" sed -i "1i $COMMENT" "../public/phishing-filter-snort3.rules" sed -i "1s/Domains Blocklist/URL Snort3 Ruleset/" "../public/phishing-filter-snort3.rules" sed -i "1i $COMMENT" "../public/phishing-filter-suricata.rules" sed -i "1s/Domains Blocklist/URL Suricata Ruleset/" "../public/phishing-filter-suricata.rules" sed -i -e "1i $COMMENT" -e '1i "host","path","message","updated"' "../public/phishing-filter-splunk.csv" sed -i "1s/Domains Blocklist/URL Splunk Lookup/" "../public/phishing-filter-splunk.csv" ## IE blocklist COMMENT_IE="msFilterList\n$COMMENT\n: Expires=1\n#" cat "phishing-notop-hosts.txt" | \ sed "s/^/-d /" | \ sed "1i $COMMENT_IE" | \ sed "2s/Domains Blocklist/Hosts Blocklist (IE)/" > "../public/phishing-filter.tpl" ## Clean up artifacts rm "phishtank.bz2" "top-1m-umbrella.zip" "top-1m-umbrella.txt" "top-1m-tranco.txt" "openphish-raw.txt" "cf/" "top-1m-radar.txt" cd ../