#!/bin/bash ### # https://github.com/sefinek24/UFW-AbuseIPDB-Reporter ## LOG_FILE="/var/log/ufw.log" ENCODED_API_KEY_FILE="./.abuseipdb_token" REPORTED_IPS_FILE="/tmp/ufw-abuseipdb-reporter.cache" REPORT_INTERVAL=43200 # 12h (in seconds) declare -A reported_ips log() { local level="$1" local message="$2" echo "[$level] $message" } # Check if the API key file exists and decode it if [[ -f "$ENCODED_API_KEY_FILE" ]]; then DECODED_API_KEY=$(openssl enc -d -base64 -in "$ENCODED_API_KEY_FILE") if [[ -z "$DECODED_API_KEY" ]]; then log "ERROR" "Failed to decode API key from $ENCODED_API_KEY_FILE" exit 1 fi else log "ERROR" "API key file not found at $ENCODED_API_KEY_FILE" exit 1 fi ABUSEIPDB_API_KEY="$DECODED_API_KEY" # Check if jq, curl, or wget packages are available if ! command -v jq &> /dev/null; then log "ERROR" "jq is not installed. Please install jq to run this script." exit 1 fi if ! command -v curl &> /dev/null && ! command -v wget &> /dev/null; then log "ERROR" "Neither curl nor wget is available. Please install one of them to continue." exit 1 fi load_reported_ips() { if [[ -f "$REPORTED_IPS_FILE" ]]; then while IFS= read -r line; do [[ -z "$line" ]] && continue IFS=' ' read -r ip report_time <<< "$line" if [[ -n "$ip" && -n "$report_time" ]]; then reported_ips["$ip"]=$report_time else log "WARN" "Invalid line format: '$line'" fi done < "$REPORTED_IPS_FILE" log "INFO" "Loaded ${#reported_ips[@]} IPs from $REPORTED_IPS_FILE" else log "INFO" "$REPORTED_IPS_FILE does not exist. No data to load!" fi } save_reported_ips() { : > "$REPORTED_IPS_FILE" for ip in "${!reported_ips[@]}"; do echo "$ip ${reported_ips[$ip]}" >> "$REPORTED_IPS_FILE" done } is_local_ip() { local ip="$1" [[ "$ip" =~ ^(10\.|172\.(1[6-9]|2[0-9]|3[01])\.|192\.168\.|127\.|fc|fd|fe80|::1) ]] } report_to_abuseipdb() { local ip="$1" categories="$2" proto="$3" spt="$4" dpt="$5" ttl="$6" len="$7" tos="$8" warsaw_time="$9" local comment="Blocked by UFW ($proto on port $dpt). Source port: $spt" [[ -n "$ttl" ]] && comment+=" TTL: $ttl" [[ -n "$len" ]] && comment+=" Packet length: $len" [[ -n "$tos" ]] && comment+=" TOS: $tos" comment+=" Timestamp: $warsaw_time [Europe/Warsaw] This report (for $ip) was generated by: https://github.com/sefinek24/UFW-AbuseIPDB-Reporter" # Please do not remove the URL to the repository of this script. I would be really grateful. 💙 local res if command -v curl >/dev/null 2>&1; then res=$(curl -s -X POST "https://api.abuseipdb.com/api/v2/report" \ --data-urlencode "ip=$ip" \ --data-urlencode "categories=$categories" \ --data-urlencode "comment=$comment" \ -H "Key: $ABUSEIPDB_API_KEY" \ -H "Accept: application/json") elif command -v wget >/dev/null 2>&1; then res=$(wget -qO- --post-data="ip=$ip&categories=$categories&comment=$comment" \ --header="Key: $ABUSEIPDB_API_KEY" \ --header="Accept: application/json" \ "https://api.abuseipdb.com/api/v2/report") else log "ERROR" "Neither curl nor wget is available to send the report." return 1 fi local abuse_confidence_score abuse_confidence_score=$(echo "$res" | jq -r '.data.abuseConfidenceScore') if [[ "$abuse_confidence_score" =~ ^[0-9]+$ ]]; then log "INFO" "Successfully reported IP $ip to AbuseIPDB (score $abuse_confidence_score)" return 0 else log "ERROR" "Failed to report IP $ip to AbuseIPDB: $res" return 1 fi } is_ip_reported_recently() { local ip="$1" local current_time current_time=$(date +%s) if [[ -v reported_ips["$ip"] ]]; then local report_time=${reported_ips["$ip"]} (( current_time - report_time < REPORT_INTERVAL )) && return 0 fi return 1 } mark_ip_as_reported() { local ip="$1" reported_ips["$ip"]=$(date +%s) } determine_categories() { local proto="$1" local dpt="$2" # See: https://www.abuseipdb.com/categories case "$proto" in "TCP") case "$dpt" in 22) echo "14,22,18" ;; # Port Scan | SSH | Brute-Force 80 | 443 | 8080) echo "14,21" ;; # Port Scan | Web App Attack 25) echo "14,11" ;; # Port Scan | Email Spam 21) echo "14,5,18" ;; # Port Scan | FTP Brute-Force | Brute-Force 53) echo "14,1,2" ;; # Port Scan | DNS Compromise | DNS Poisoning 23 | 3389) echo "14,15,18" ;; # Port Scan | Hacking | Brute-Force 3306) echo "14,16" ;; # Port Scan | SQL Injection 6666 | 6667 | 6668 | 6669) echo "14,8" ;; # Port Scan | Fraud VoIP 9999) echo "14,6" ;; # Port Scan | Ping of Death *) echo "14" ;; # Port Scan esac ;; "UDP") case "$dpt" in 53) echo "14,1,2" ;; # Port Scan | DNS Compromise | DNS Poisoning 123) echo "14,17" ;; # Port Scan | Spoofing *) echo "14" ;; # Port Scan esac ;; *) echo "14" ;; # Port Scan esac } process_log_line() { local line="$1" if [[ "$line" == *"[UFW BLOCK]"* ]]; then local timestamp src_ip proto spt dpt ttl len tos categories warsaw_time timestamp=$(echo "$line" | awk '{print $1, $2, $3}') [[ -z "$timestamp" ]] && timestamp=$(date '+%Y-%m-%d %H:%M:%S') src_ip=$(echo "$line" | grep -oP 'SRC=\K[^\s]+') if is_local_ip "$src_ip"; then log "INFO" "Ignoring local IP: $src_ip" return fi proto=$(echo "$line" | grep -oP 'PROTO=\K[^\s]+') spt=$(echo "$line" | grep -oP 'SPT=\K[^\s]+') dpt=$(echo "$line" | grep -oP 'DPT=\K[^\s]+') ttl=$(echo "$line" | grep -oP 'TTL=\K[^\s]+') len=$(echo "$line" | grep -oP 'LEN=\K[^\s]+') tos=$(echo "$line" | grep -oP 'TOS=\K[^\s]+') if is_ip_reported_recently "$src_ip"; then log "INFO" "IP $src_ip ($proto) was reported recently" return fi categories=$(determine_categories "$proto" "$dpt") warsaw_time=$(TZ="Europe/Warsaw" date -d "$timestamp" '+%Y-%m-%d %H:%M:%S') log "INFO" "Reporting IP $src_ip ($proto $dpt) with categories $categories..." if report_to_abuseipdb "$src_ip" "$categories" "$proto" "$spt" "$dpt" "$ttl" "$len" "$tos" "$warsaw_time"; then mark_ip_as_reported "$src_ip" save_reported_ips fi fi } load_reported_ips log "INFO" "Starting to monitor $LOG_FILE" tail -Fn0 "$LOG_FILE" | while read -r line; do process_log_line "$line" done