#!/bin/bash DATE="$(date +%d/%b/%Y:%H:%M -d '1 minute ago')" MY_IP=($(redis-cli --raw SMEMBERS my_ip)) ATTACK_THRESHOLD="50" NGINX_ACCESS="/tmp/access.log" ACCESS="/tmp/minute.log" HTTP_LIMIT="100" RATE_LIMITED_HTTP="30" grep $DATE $NGINX_ACCESS >$ACCESS #Firewall Port Configuration # declare -A portConfig portConfig["https"]="443" portConfig["http"]="80" portConfig["cups"]="631" portConfig["WireGuard"]="57692" portConfig["AdGuard-1"]="3000" portConfig["AdGuard-2"]="8082" portConfig["AdGuard-3"]="853" portConfig["Uptime"]="4001" portConfig["DNS-1"]="53" portConfig["DNS-2"]="67" portConfig["DNS-3"]="68" portConfig["CUPS-1"]="631" portConfig["CUPS-2"]="5353" #portConfig["Bitcoin-1"]="8333" #portConfig["Bitcoin-2"]="8332" #portConfig["Bitcoin-3"]="8333" #portConfig["Bitcoin-4"]="4050" #portConfig["KDE-Connect"]="1714-1764" #portConfig["Lightning-1"]="10009" #portConfig["Lightning-1"]="9735" #portConfig["Lightning-2"]="8080" #portConfig["Lightning-3"]="28334" #portConfig["Lightning-4"]="28333" #portConfig["Lightning-5"]="19998" #portConfig["Lightning-6"]="29000" portConfig["SyncThing-1"]="22000" portConfig["SyncThing-2"]="8384" portConfig["SyncThing-3"]="21027" #portConfig["NFS-1"]="2049" #portConfig["NFS-2"]="111" portConfig["Jellyfin-1"]="8096" portConfig["Jellyfin-1"]="7359" portConfig["SSH"]="22" MACHINES=(127.0.0.1) VIRT_BRIDGE="virbr0" #### NFT CONFIG #### # NFT='/usr/bin/nft' NFT_CACHE='/tmp/nft.cache' TMP_BLOCK=($(redis-cli --raw SMEMBERS tmp_block)) #Redis DB # SAVED_BOTS=($(redis-cli --raw SMEMBERS bots)) CRAWLER_DB=($(redis-cli --raw SMEMBERS crawlers)) SAFE_TRAFFIC=($(redis-cli --raw SMEMBERS safe_traffic)) RULE_SET='/opt/firewall/nft.rules' MENU_TOP="=============================FireWall=================================" MENU_BOTTOM="=====================================================================" nft list table filter >$NFT_CACHE ipBlockParser() { if [[ "$1" == *":"* ]]; then $NFT insert rule ip6 filter input position 0 ip6 saddr $1 drop else $NFT insert rule ip filter input position 0 ip saddr "$1" drop fi } portOpenParser() { if [[ "$1" == "443" || "$1" == "80" ]]; then echo "Skipping $1" else $NFT add rule ip filter input position 0 tcp dport $1 accept $NFT add rule ip filter input position 0 udp dport $1 accept $NFT add rule ip6 filter input position 0 tcp dport $1 accept $NFT add rule ip6 filter input position 0 udp dport $1 accept fi } ipDeleteParser() { if [[ "$1" == *":"* ]]; then $NFT delete rule ip6 filter input handle $HANDLE &>/dev/null else $NFT delete rule ip filter input handle $HANDLE &>/dev/null fi redis-cli SREM tmp_block $i redis-cli SREM bots $i } blockCountry() { DATA=($(redis-cli SMEMBERS country_ip)) for i in "${DATA[@]}"; do echo "Blocking $i" ipBlockParser $i done } wireguard-networking() { $NFT add table nat $NFT add chain nat postrouting $NFT add rule nat postrouting oif wg0 iif enp11s0 $NFT add rule nat postrouting oif enp11s0 iif wg0 $NFT add rule nat postrouting masquerade $NFT add rule filter forward iifname wg0 oif enp11s0 accept $NFT add rule filter forward iifname enp11s0 oif wg0 accept $NFT add rule ip filter input ip saddr 192.168.5.0/24 accept } attacker-protection() { watch module-nostr bot-search } bot-search() { CRAWLERS=($(grep $DATE $ACCESS | grep -vi $MY_IP | grep -Evi 'Guro|spank|report|rape|block' | grep -Ff <(printf '%s\n' "${CRAWLER_DB[@]}") | grep -Fivf <(printf '%s\n' "${SAFE_TRAFFIC[@]}") | cut -d "-" -f1 | sort -u)) echo echo "Processing Web Crawler list into NFT....." echo for i in "${CRAWLERS[@]}"; do CHECK=$(cat $NFT_CACHE | grep $i) if [ "$CHECK" = "" ]; then ipBlockParser $i redis-cli SADD bots $i else echo echo "Skipping Duplicate IP $i" echo fi done } basic-security() { $NFT flush ruleset $NFT -f /opt/firewall/ipv4-filter.nft $NFT -f /opt/firewall/ipv6-filter.nft $NFT add rule filter input icmp type echo-request drop $NFT rule filter output accept $NFT rule filter forward accept $NFT insert rule filter input ct state established accept $NFT insert rule filter input iif lo accept for i in "${!portConfig[@]}"; do echo "Enabling Port for: $i" portOpenParser "${portConfig[$i]}" done #$NFT add rule filter input drop #$NFT add rule ip6 filter input drop } virtualization() { ip link del virbr0 killall dnsmasq /usr/bin/systemctl restart libvirtd /usr/bin/virsh net-start default /usr/bin/systemctl restart pleroma $NFT insert rule filter input iif $VIRT_BRIDGE accept } trust() { for i in "${MACHINES[@]}"; do $NFT add rule filter input ip saddr $i accept done } import-saved() { for i in "${SAVED_BOTS[@]}"; do ipBlockParser $i done } start() { basic-security if [[ $HOSTNAME == *"nas"* ]]; then sysctl -w net.ipv4.conf.all.forwarding=1 import-saved blockCountry wireguard-networking docker restart uptime-kuma #Docker $NFT insert rule filter input iif docker0 accept #HTTP Rate Limit rateLimit $HTTP_LIMIT else virtualization fi } research() { STATS=($(redis-cli --raw SMEMBERS tmp_block | sort -u)) for i in "${STATS[@]}"; do echo $MENU_TOP echo " [Researching $i] " echo DATA=$(grep $i $NGINX_LOG | grep -Fivf <(printf '%s\n' "${SAFE_TRAFFIC[@]}")) echo "$DATA" echo echo $MENU_BOTTOM echo read -p 'Press Enter to Continue ' -e done } automaticStatus() { status sleep 30 automaticStatus } status() { clear sleep 2 RATE=$(cat $NFT_CACHE | grep 443 | cut -d ' ' -f14) DATE="$(date +%d/%b/%Y:%H:%M -d '1 minute ago')" STATS=$(grep $DATE $ACCESS | grep -vi $MY_IP | wc -l) GET=$(grep $DATE $ACCESS | grep -vi $MY_IP | grep GET | wc -l) POST=$(grep $DATE $ACCESS | grep -vi $MY_IP | grep POST | wc -l) PUT=$(grep $DATE $ACCESS | grep -vi $MY_IP | grep -i PUT | wc -l) NOT_FOUND=$(grep $DATE $ACCESS | grep -vi $MY_IP | grep 404 | wc -l) GATEWAY=$(grep $DATE $ACCESS | grep -vi $MY_IP | grep 502 | wc -l) SUCCESS=$(grep $DATE $ACCESS | grep -vi $MY_IP | grep 200 | wc -l) CRAWL=$(grep $DATE $ACCESS | grep -vi $MY_IP | grep -Ff <(printf '%s\n' "${CRAWLER_DB[@]}") | wc -l) echo $MENU_TOP echo "Attack Threshold: $ATTACK_THRESHOLD" echo "Firewall Rules: $($NFT list table filter | wc -l)" echo "Rate Limit: $RATE" echo echo "Traffic Last Minute: $STATS" echo " GET: $GET" echo " PUT: $PUT" echo " POST: $POST" echo " Crawlers: $CRAWL" echo echo "Query Stats:: " echo " 200: $SUCCESS" echo " 404: $NOT_FOUND" echo " 502: $GATEWAY" echo #echo "Dropped Traffic: $($NFT list table filter | grep -Ei 'log counter packets' | cut -d ' ' -f6)" echo echo "Rate-limited IP's:" echo redis-cli --raw SMEMBERS tmp_block | sort -u echo $MENU_BOTTOM } stop() { #forgive #$NFT -s list ruleset | tee $RULE_SET $NFT flush ruleset $NFT -f /usr/share/nftables/ipv4-filter.nft $NFT add rule filter input icmp type echo-request accept $NFT rule filter input accept $NFT rule filter output accept $NFT rule filter forward accept $NFT insert rule filter input ct state established accept $NFT insert rule filter input iif lo accept $NFT -f /opt/firewall/ipv6-filter.nft # $NFT add rule ip6 filter input icmpv6 type nd-neighbor-solicit accept # $NFT add rule ip6 filter input icmpv6 type nd-router-advert accept message "Stopping Firewall" } rateLimit() { HANDLE=($(nft -n -a list ruleset | grep "ct state 0x8 tcp dport" | grep -E '80|443' | grep handle | cut -d '#' -f2 | cut -d ' ' -f3)) for i in "${HANDLE[@]}"; do if [[ "$i" == *":"* ]]; then $NFT delete rule ip6 filter input handle $i &>/dev/null else $NFT delete rule filter input handle $i &>/dev/null fi done echo "Setting Rate Limit to : $1" echo $NFT add rule ip6 filter input ct state new tcp dport 443 update @http_ratelimit { ip6 saddr limit rate $1/second } accept $NFT add rule ip6 filter input ct state new tcp dport 80 update @http_ratelimit { ip6 saddr limit rate $1/second } accept $NFT add rule ip filter input ct state new tcp dport 443 update @http_ratelimit { ip saddr limit rate $1/second } accept $NFT add rule ip filter input ct state new tcp dport 80 update @http_ratelimit { ip saddr limit rate $1/second } accept } forgive() { IP=($(redis-cli --raw SMEMBERS tmp_block | sort -u)) echo $IP for i in "${IP[@]}"; do HANDLE=$(nft -n -a list ruleset | grep $i | grep handle | cut -d '#' -f2 | cut -d ' ' -f3) echo "Removing: $i Handle: $HANDLE" ipDeleteParser $HANDLE done echo "Clearing old $TMP_BLOCK" redis-cli DEL tmp_blocked rateLimit $HTTP_LIMIT } module-go() { ATTACK="module-go DDOS Attack " GO_SPAM=$(grep $2 $ACCESS | grep -E "Go-http-client" | wc -l) if [[ "$GO_SPAM" -gt 10 ]]; then ipBlockParser "$1" redis-cli SADD tmp_block $1 message "$ATTACK $1" fi } module-akkoma-accounts() { ATTACK="module-akkoma-accounts DDOS Attack " SEARCH_SPAM=$(grep $2 $ACCESS | grep "api/v1/accounts" | grep $1 | wc -l) CHECK=$(cat $NFT_CACHE | sort -u | grep $1) if [[ "$SEARCH_SPAM" -gt 30 ]]; then echo "$IP $CHECK $COUNT" if [ "$CHECK" = "" ]; then ipBlockParser "$1" redis-cli SADD tmp_block $1 message "$ATTACK $1" echo "$ATTACK $1" else echo "$ATTACK Ignoring Duplicate IP: $1" fi fi } module-akkoma-search() { ATTACK="module-akkoma-search DDOS Attack " SEARCH_SPAM=$(grep $2 $ACCESS | grep "api/v2/search" | grep $1 | wc -l) CHECK=$(cat $NFT_CACHE | sort -u | grep $1) if [[ "$SEARCH_SPAM" -gt 10 ]]; then echo "$IP $CHECK $COUNT" if [ "$CHECK" = "" ]; then ipBlockParser "$1" redis-cli SADD tmp_block $1 message "$ATTACK $1" echo "$ATTACK $1" else echo "$ATTACK Ignoring Duplicate IP: $1" fi fi } module-akkoma-instance() { ATTACK="module-akkoma-instance DDOS Attack " SEARCH_SPAM=$(grep $2 $ACCESS | grep -E "api/v1/instance" | grep $1 | wc -l) CHECK=$(cat $NFT_CACHE | sort -u | grep $1) if [[ "$SEARCH_SPAM" -gt 30 ]]; then echo "$IP $CHECK $COUNT" if [ "$CHECK" = "" ]; then ipBlockParser "$1" redis-cli SADD tmp_block $1 message "$ATTACK $1" echo "$ATTACK $1" else echo "$ATTACK Ignoring Duplicate IP: $1" fi fi } module-akkoma-timelines() { ATTACK="module-akkoma-timelines DDOS Attack " SEARCH_SPAM=$(grep $2 $ACCESS | grep -E "timelines/public|timelines/home" | grep $1 | wc -l) CHECK=$(cat $NFT_CACHE | sort -u | grep $1) if [[ "$SEARCH_SPAM" -gt 30 ]]; then echo "$IP $CHECK $COUNT" if [ "$CHECK" = "" ]; then ipBlockParser "$1" redis-cli SADD tmp_block $1 message "$ATTACK $1" echo "$ATTACK $1" else echo "$ATTACK Ignoring Duplicate IP: $1" fi fi } module-get-spam() { ATTACK="module-akkoma DDOS Attack " GET_SPAM=$(grep $2 $ACCESS | grep -E "GET / HTTP" | wc -l) if [[ "$GET_SPAM" -gt 20 ]]; then ipBlockParser "$1" redis-cli SADD tmp_block $1 message "$ATTACK $1" fi } module-php() { ATTACK="module-php DDOS Attack " PHP_SPAM=$(grep $2 $ACCESS | grep -E ".php|cgi-bin|wp-content|wp-admin|wp-includes" | wc -l) if [[ "$PHP_SPAM" -gt 1 ]]; then ipBlockParser "$1" message "$ATTACK $1" redis-cli SADD tmp_block $1 fi } module-lightning() { ATTACK="module-lightning DDOS Attack " LN_SPAM=$(grep $2 $ACCESS | grep "lnurlp/verita84" | wc -l) if [[ "$LN_SPAM" -gt 5 ]]; then ipBlockParser "$1" message "$ATTACK $1" redis-cli SADD tmp_block $1 fi } watch() { echo "Scanning $DATE" echo IP=($(grep $DATE $ACCESS | grep -Fivf <(printf '%s\n' "${SAFE_TRAFFIC[@]}") | grep -Fivf <(printf '%s\n' "${CRAWLER_DB[@]}") | grep -Fivf <(printf '%s\n' "${SAVED_BOTS[@]}") | grep -vi $MY_IP | grep -vi '127.0.0.1' | cut -d ' ' -f1 | sort -u)) for i in "${IP[@]}"; do module-akkoma-instance "$i" "$DATE" module-akkoma-timelines "$i" "$DATE" module-akkoma-accounts "$i" "$DATE" module-akkoma-search "$i" "$DATE" module-lightning "$i" "$DATE" module-php "$i" "$DATE" module-go "$i" "$DATE" module-get-spam "$i" "$DATE" COUNT=$(grep $DATE $ACCESS | grep $i | grep -Fivf <(printf '%s\n' "${SAFE_TRAFFIC[@]}") | grep -Fivf <(printf '%s\n' "${SAVED_BOTS[@]}") | wc -l) CHECK=$(cat $NFT_CACHE | sort -u | grep $i) if [[ "$COUNT" -gt $ATTACK_THRESHOLD ]]; then if [ "$CHECK" = "" ]; then echo "Danger! Blocking IP: $i Count: $COUNT" logger "Blocking IP: $i with a count of: $COUNT" redis-cli SADD tmp_block $i ipBlockParser $i message "Blocking IP: $i with a count of: $COUNT" else echo echo "Skipping Duplicate IP" echo fi else echo echo "$i count: $COUNT below Threshhold: $ATTACK_THRESHOLD" echo fi done BLOCK_CHECK=$(redis-cli --raw SMEMBERS tmp_block) if [[ "$BLOCK_CHECK" == *"empty"* || "$BLOCK_CHECK" == "" ]]; then rateLimit $HTTP_LIMIT else rateLimit $RATE_LIMITED_HTTP fi } message() { echo "$1" | /root/go/bin/algia dm-post -u 33c74427f3b2b73d5e38f3e6c991c122a55d204072356f71da49a0e209fb6940 --stdin } module-nostr() { IP=($(grep $DATE $ACCESS | grep "/block=" | cut -d '=' -f2 | cut -d ' ' -f1 | sed 's/"//')) for i in "${IP[@]}"; do echo $i if [[ "$i" == *"npub"* ]]; then bash /opt/strfry-policies/block.sh $i else echo "No Npubs to block" fi done } test-bots() { for i in "${SAVED_BOTS[@]}"; do DATA=$(grep $i $ACCESS | grep -Fivf <(printf '%s\n' "${CRAWLER_DB[@]}")) if [ "$DATA" = "" ]; then echo "No Data. Probably OK" else echo $DATA echo read -p 'Press Enter to Continue ' -e fi done } research-ip() { echo "Enter an IP Address to search" read -p 'IP Address: ' -e IP cat $ACCESS | grep $IP echo read -p 'Press Enter to Continue ' -e } menu() { clear echo echo $MENU_TOP echo "1. Start" echo "2. Stop" echo "3. Reseearch" echo "4. Forgive" echo "5. Status" echo "6. Live Traffic" echo "7. Test Bot Search Rules" echo "8. Research IP" echo "9. View Current Rule Set" echo "0. Quit" echo $MENU_BOTTOM echo read -p 'Choice: ' CHOICE echo if [ "$CHOICE" = "1" ]; then echo echo "Starting Firewall" start read -p 'Press Enter to Continue ' -e- elif [ "$CHOICE" = "2" ]; then echo echo "Stopping Firewall" stop read -p 'Press Enter to Continue ' -e elif [ "$CHOICE" = "3" ]; then research read -p 'Press Enter to Continue ' -e elif [ "$CHOICE" = "4" ]; then forgive elif [ "$CHOICE" = "55" ]; then automaticStatus elif [ "$CHOICE" = "5" ]; then status read -p 'Press Enter to Continue ' -e elif [ "$CHOICE" = "6" ]; then tail -f $ACCESS | grep -Fivf <(printf '%s\n' "${SAFE_TRAFFIC[@]}") | grep -Fivf <(printf '%s\n' "${CRAWLER_DB[@]}") read -p 'Press Enter to Continue ' -e elif [ "$CHOICE" = "7" ]; then test-bots read -p 'Press Enter to Continue ' -e elif [ "$CHOICE" = "8" ]; then research-ip read -p 'Press Enter to Continue ' -e elif [ "$CHOICE" = "9" ]; then nft -s list ruleset | less elif [ "$CHOICE" = "0" ]; then exit fi echo menu } importDB() { DATA=($(cat safe.txt)) for i in "${DATA[@]}"; do redis-cli SADD safe_traffic $i; done DATA=($(cat bots.txt)) for i in "${DATA[@]}"; do redis-cli SADD bots $i; done DATA=($(cat crawlers.txt)) for i in "${DATA[@]}"; do redis-cli SADD crawlers $i; done redis-cli SADD my_ip $(curl ifconfig.me) redis-cli SADD country https://www.ipdeny.com/ipblocks/data/countries/il.zone \ https://www.ipdeny.com/ipblocks/data/countries/cn.zone COUNTRY=($(redis-cli SMEMBERS country)) for i in "${COUNTRY[@]}"; do echo echo "Blocking $i" DB=($(curl $i)) for j in "${DB[@]}"; do redis-cli SADD country_ip $j done done } exportDB() { rm -f crawlers.txt rm -f bots.txt rm -f safe.txt for i in "${CRAWLER_DB[@]}"; do echo $i >>crawlers.txt; done for i in "${SAVED_BOTS[@]}"; do echo $i >>bots.txt; done for i in "${SAFE_TRAFFIC[@]}"; do echo $i >>safe.txt; done } if [ "$1" = "start" ]; then start elif [ "$1" = "import-db" ]; then importDB elif [ "$1" = "export-db" ]; then exportDB elif [ "$1" = "virt" ]; then virtualization elif [ "$1" = "bot-search" ]; then bot-search elif [ "$1" = "attacker-protection" ]; then attacker-protection elif [ "$1" = "status" ]; then automaticStatus elif [ "$1" = "forgive" ]; then forgive elif [ "$1" = "watch" ]; then watch elif [ "$1" = "research" ]; then research elif [ "$1" = "stop" ]; then stop elif [ "$1" = "message" ]; then message $2 elif [ "$1" = "test" ]; then test-bots elif [ "$1" = "import" ]; then import-saved else menu fi