31306a520f
lib/threat-intelligence.sh: - Add --max-time 10 to AbuseIPDB API curl call (line 47) tools/update-attack-signatures.sh: - Add --timeout=60 to ET Open rules download wget (line 68) tools/toolkit-qa-check.sh: - Improve NET-TIMEOUT detection to exclude false positives: * Skip comment lines * Skip echo/string statements * Skip variable assignments with pipes * Only flag actual network calls without timeouts This reduces false positive NET-TIMEOUT detections from 10 to 2. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
371 lines
13 KiB
Bash
371 lines
13 KiB
Bash
#!/bin/bash
|
|
#
|
|
# Attack Signature Auto-Updater
|
|
# Downloads latest ET Open rules and extracts HTTP-level attack patterns
|
|
#
|
|
# Usage: bash update-attack-signatures.sh
|
|
# Can be run manually or via cron (weekly recommended)
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
LIB_DIR="$SCRIPT_DIR/../lib"
|
|
BACKUP_DIR="$SCRIPT_DIR/../backups/signatures"
|
|
TEMP_DIR="/tmp/et-rules-update-$$"
|
|
|
|
# ET Open ruleset URL
|
|
ET_RULES_URL="https://rules.emergingthreats.net/open/suricata-7.0.0/emerging.rules.tar.gz"
|
|
|
|
# Colors
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
BLUE='\033[0;34m'
|
|
NC='\033[0m'
|
|
|
|
# Log function
|
|
log_info() {
|
|
echo -e "${BLUE}[INFO]${NC} $1"
|
|
}
|
|
|
|
log_success() {
|
|
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
|
}
|
|
|
|
log_warn() {
|
|
echo -e "${YELLOW}[WARN]${NC} $1"
|
|
}
|
|
|
|
log_error() {
|
|
echo -e "${RED}[ERROR]${NC} $1"
|
|
}
|
|
|
|
# Cleanup on exit
|
|
cleanup() {
|
|
[ -d "$TEMP_DIR" ] && rm -rf "$TEMP_DIR"
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
# Create directories
|
|
mkdir -p "$BACKUP_DIR" "$TEMP_DIR"
|
|
|
|
echo "========================================"
|
|
echo "Attack Signature Auto-Updater"
|
|
echo "Source: Emerging Threats Open Ruleset"
|
|
echo "========================================"
|
|
echo ""
|
|
|
|
# Step 1: Backup current signatures
|
|
log_info "Backing up current signatures..."
|
|
if [ -f "$LIB_DIR/attack-signatures.sh" ]; then
|
|
backup_file="$BACKUP_DIR/attack-signatures-$(date +%Y%m%d_%H%M%S).sh"
|
|
cp "$LIB_DIR/attack-signatures.sh" "$backup_file"
|
|
log_success "Backed up to: $backup_file"
|
|
else
|
|
log_warn "No existing signatures found (first run)"
|
|
fi
|
|
|
|
# Step 2: Download ET Open rules
|
|
log_info "Downloading ET Open ruleset..."
|
|
if wget -q --timeout=60 "$ET_RULES_URL" -O "$TEMP_DIR/rules.tar.gz"; then
|
|
log_success "Downloaded $(du -h "$TEMP_DIR/rules.tar.gz" | cut -f1)"
|
|
else
|
|
log_error "Failed to download ET Open rules"
|
|
exit 1
|
|
fi
|
|
|
|
# Step 3: Extract rules
|
|
log_info "Extracting rules..."
|
|
tar -xzf "$TEMP_DIR/rules.tar.gz" -C "$TEMP_DIR" 2>/dev/null
|
|
if [ $? -eq 0 ]; then
|
|
rule_count=$(find "$TEMP_DIR/rules" -name "*.rules" -type f 2>/dev/null | wc -l)
|
|
log_success "Extracted $rule_count rule files"
|
|
else
|
|
log_error "Failed to extract rules"
|
|
exit 1
|
|
fi
|
|
|
|
# Step 4: Parse rules and generate new signature file
|
|
log_info "Parsing rules and generating signatures..."
|
|
|
|
cat > "$TEMP_DIR/attack-signatures-new.sh" << 'HEADER_EOF'
|
|
#!/bin/bash
|
|
#
|
|
# Attack Signature Database
|
|
# Auto-generated from Emerging Threats Open Ruleset (BSD License)
|
|
# Source: https://rules.emergingthreats.net/
|
|
# Generated: $(date)
|
|
#
|
|
# Copyright (c) 2003-2025, Emerging Threats
|
|
# All rights reserved.
|
|
# Redistribution and use permitted under BSD license terms.
|
|
|
|
# Initialize associative arrays for attack patterns
|
|
declare -A ATTACK_SQLI # SQL Injection patterns
|
|
declare -A ATTACK_XSS # Cross-Site Scripting
|
|
declare -A ATTACK_CMD # Command Injection
|
|
declare -A ATTACK_TRAVERSAL # Path Traversal
|
|
declare -A ATTACK_INCLUSION # File Inclusion (LFI/RFI)
|
|
declare -A ATTACK_WEBSHELL # Webshell detection
|
|
declare -A ATTACK_CVE # Known CVE exploits
|
|
declare -A ATTACK_UPLOAD # File upload attacks
|
|
|
|
HEADER_EOF
|
|
|
|
# Function to extract patterns from Suricata rules
|
|
parse_et_rules() {
|
|
local rules_dir="$1"
|
|
local output_file="$2"
|
|
|
|
echo "" >> "$output_file"
|
|
echo "# ============================================================================" >> "$output_file"
|
|
echo "# SQL INJECTION PATTERNS (auto-extracted from emerging-sql.rules)" >> "$output_file"
|
|
echo "# ============================================================================" >> "$output_file"
|
|
echo "" >> "$output_file"
|
|
|
|
# Extract SQL injection patterns
|
|
if [ -f "$rules_dir/emerging-sql.rules" ]; then
|
|
local count=0
|
|
while IFS= read -r line; do
|
|
# Skip comments and empty lines
|
|
[[ "$line" =~ ^#.*$ ]] && continue
|
|
[[ -z "$line" ]] && continue
|
|
|
|
# Extract content patterns from rules
|
|
if [[ "$line" =~ content:\"([^\"]+)\" ]]; then
|
|
local pattern="${BASH_REMATCH[1]}"
|
|
|
|
# Clean up Suricata-specific syntax
|
|
pattern=$(echo "$pattern" | sed 's/|20|/ /g') # Replace |20| with space
|
|
pattern=$(echo "$pattern" | sed 's/|0d 0a|/\\n/g') # Replace CRLF
|
|
pattern=$(echo "$pattern" | sed 's/|[0-9a-f][0-9a-f]|//g') # Remove hex
|
|
|
|
# Skip binary patterns (contain pipes)
|
|
[[ "$pattern" =~ \| ]] && continue
|
|
|
|
# Skip too short patterns
|
|
[ "${#pattern}" -lt 3 ] && continue
|
|
|
|
# Determine severity from rule priority
|
|
local severity=80
|
|
if [[ "$line" =~ priority:1 ]]; then
|
|
severity=90
|
|
elif [[ "$line" =~ priority:2 ]]; then
|
|
severity=85
|
|
elif [[ "$line" =~ priority:3 ]]; then
|
|
severity=75
|
|
fi
|
|
|
|
# Extract description from msg field
|
|
local description="SQL Injection"
|
|
if [[ "$line" =~ msg:\"([^\"]+)\" ]]; then
|
|
description="${BASH_REMATCH[1]}"
|
|
fi
|
|
|
|
# Generate pattern name
|
|
local pattern_name="sqli_$(printf '%03d' $count)"
|
|
|
|
# Output pattern
|
|
echo "ATTACK_SQLI[\"$pattern_name\"]=\"$pattern|$severity|$description\"" >> "$output_file"
|
|
|
|
count=$((count + 1))
|
|
[ "$count" -ge 20 ] && break # Limit to 20 patterns per category
|
|
fi
|
|
done < "$rules_dir/emerging-sql.rules"
|
|
|
|
log_info " Extracted $count SQL injection patterns"
|
|
fi
|
|
|
|
echo "" >> "$output_file"
|
|
echo "# ============================================================================" >> "$output_file"
|
|
echo "# XSS PATTERNS (auto-extracted from emerging-web_server.rules)" >> "$output_file"
|
|
echo "# ============================================================================" >> "$output_file"
|
|
echo "" >> "$output_file"
|
|
|
|
# Extract XSS patterns
|
|
if [ -f "$rules_dir/emerging-web_server.rules" ]; then
|
|
local count=0
|
|
while IFS= read -r line; do
|
|
[[ "$line" =~ ^#.*$ ]] && continue
|
|
[[ -z "$line" ]] && continue
|
|
|
|
# Only process lines with XSS-related content
|
|
if [[ "$line" =~ (script|xss|javascript|onerror|onload) ]] && [[ "$line" =~ content:\"([^\"]+)\" ]]; then
|
|
local pattern="${BASH_REMATCH[1]}"
|
|
|
|
# Clean up
|
|
pattern=$(echo "$pattern" | sed 's/|20|/ /g')
|
|
pattern=$(echo "$pattern" | sed 's/|[0-9a-f][0-9a-f]|//g')
|
|
|
|
[[ "$pattern" =~ \| ]] && continue
|
|
[ "${#pattern}" -lt 3 ] && continue
|
|
|
|
local severity=75
|
|
[[ "$line" =~ priority:1 ]] && severity=85
|
|
[[ "$line" =~ priority:2 ]] && severity=80
|
|
|
|
local description="Cross-Site Scripting"
|
|
if [[ "$line" =~ msg:\"([^\"]+)\" ]]; then
|
|
description="${BASH_REMATCH[1]}"
|
|
fi
|
|
|
|
local pattern_name="xss_$(printf '%03d' $count)"
|
|
echo "ATTACK_XSS[\"$pattern_name\"]=\"$pattern|$severity|$description\"" >> "$output_file"
|
|
|
|
count=$((count + 1))
|
|
[ "$count" -ge 20 ] && break
|
|
fi
|
|
done < "$rules_dir/emerging-web_server.rules"
|
|
|
|
log_info " Extracted $count XSS patterns"
|
|
fi
|
|
|
|
# Add fallback patterns if extraction yielded few results
|
|
echo "" >> "$output_file"
|
|
echo "# Fallback patterns (always included)" >> "$output_file"
|
|
cat >> "$output_file" << 'FALLBACK_EOF'
|
|
|
|
# Critical fallback patterns (in case extraction misses common attacks)
|
|
ATTACK_SQLI["union_select"]="${ATTACK_SQLI["union_select"]:-union.*select|union.*all.*select|90|UNION SELECT injection}"
|
|
ATTACK_XSS["script_tag"]="${ATTACK_XSS["script_tag"]:-<script|</script>|80|Script tag injection}"
|
|
ATTACK_CMD["unix_cmd"]="${ATTACK_CMD["unix_cmd"]:-;cat |;ls |;wget |;curl |90|Unix command chaining}"
|
|
ATTACK_TRAVERSAL["dotdot"]="${ATTACK_TRAVERSAL["dotdot"]:-\\.\\./|\\.\\.|%2e%2e|80|Directory traversal}"
|
|
ATTACK_WEBSHELL["known_shells"]="${ATTACK_WEBSHELL["known_shells"]:-c99\\.php|r57\\.php|b374k|wso\\.php|95|Known webshell}"
|
|
ATTACK_CVE["log4shell"]="${ATTACK_CVE["log4shell"]:-jndi:ldap://|jndi:rmi://|95|CVE-2021-44228 Log4Shell}"
|
|
|
|
FALLBACK_EOF
|
|
}
|
|
|
|
# Parse the rules
|
|
parse_et_rules "$TEMP_DIR/rules" "$TEMP_DIR/attack-signatures-new.sh"
|
|
|
|
# Add helper functions (always included)
|
|
cat >> "$TEMP_DIR/attack-signatures-new.sh" << 'HELPER_EOF'
|
|
|
|
# ============================================================================
|
|
# HELPER FUNCTIONS
|
|
# ============================================================================
|
|
|
|
# Check if request matches attack pattern in specific category
|
|
check_attack_pattern() {
|
|
local request="$1"
|
|
local category="$2"
|
|
|
|
local -n patterns="$category"
|
|
|
|
for pattern_name in "${!patterns[@]}"; do
|
|
local pattern_data="${patterns[$pattern_name]}"
|
|
|
|
local regex="${pattern_data%%|*}"
|
|
local temp="${pattern_data#*|}"
|
|
local severity="${temp%%|*}"
|
|
local description="${temp#*|}"
|
|
|
|
if echo "$request" | grep -iEq "$regex"; then
|
|
echo "$severity|$pattern_name|$description"
|
|
return 0
|
|
fi
|
|
done
|
|
|
|
return 1
|
|
}
|
|
|
|
# Get all matching patterns across all categories
|
|
detect_all_attacks() {
|
|
local request="$1"
|
|
local matches=()
|
|
local max_severity=0
|
|
|
|
local categories=("ATTACK_SQLI" "ATTACK_XSS" "ATTACK_CMD" "ATTACK_TRAVERSAL"
|
|
"ATTACK_INCLUSION" "ATTACK_WEBSHELL" "ATTACK_CVE" "ATTACK_UPLOAD")
|
|
|
|
for category in "${categories[@]}"; do
|
|
local result=$(check_attack_pattern "$request" "$category")
|
|
if [ -n "$result" ]; then
|
|
local severity="${result%%|*}"
|
|
local temp="${result#*|}"
|
|
local pattern_name="${temp%%|*}"
|
|
local description="${temp#*|}"
|
|
|
|
matches+=("$severity|${category#ATTACK_}|$pattern_name|$description")
|
|
|
|
[ "$severity" -gt "$max_severity" ] && max_severity="$severity"
|
|
fi
|
|
done
|
|
|
|
if [ ${#matches[@]} -gt 0 ]; then
|
|
echo "$max_severity|${#matches[@]}|${matches[*]}"
|
|
return 0
|
|
fi
|
|
|
|
return 1
|
|
}
|
|
|
|
# Get attack category name (human-readable)
|
|
get_category_name() {
|
|
local category="$1"
|
|
|
|
case "$category" in
|
|
SQLI) echo "SQL Injection" ;;
|
|
XSS) echo "Cross-Site Scripting" ;;
|
|
CMD) echo "Command Injection" ;;
|
|
TRAVERSAL) echo "Path Traversal" ;;
|
|
INCLUSION) echo "File Inclusion" ;;
|
|
WEBSHELL) echo "Webshell" ;;
|
|
CVE) echo "CVE Exploit" ;;
|
|
UPLOAD) echo "Malicious Upload" ;;
|
|
*) echo "$category" ;;
|
|
esac
|
|
}
|
|
HELPER_EOF
|
|
|
|
# Step 5: Validate new signature file
|
|
log_info "Validating new signature file..."
|
|
if bash -n "$TEMP_DIR/attack-signatures-new.sh" 2>/dev/null; then
|
|
log_success "Syntax check passed"
|
|
else
|
|
log_error "Syntax check failed - keeping old signatures"
|
|
exit 1
|
|
fi
|
|
|
|
# Step 6: Test that patterns work
|
|
log_info "Testing pattern detection..."
|
|
test_result=$(bash -c "source $TEMP_DIR/attack-signatures-new.sh && detect_all_attacks \"union select\" 2>/dev/null")
|
|
if [ -n "$test_result" ]; then
|
|
log_success "Pattern detection working"
|
|
else
|
|
log_warn "Pattern detection test failed - but file is valid, installing anyway"
|
|
fi
|
|
|
|
# Step 7: Install new signature file
|
|
log_info "Installing new signatures..."
|
|
cp "$TEMP_DIR/attack-signatures-new.sh" "$LIB_DIR/attack-signatures.sh"
|
|
chmod 644 "$LIB_DIR/attack-signatures.sh"
|
|
log_success "Installed to: $LIB_DIR/attack-signatures.sh"
|
|
|
|
# Step 8: Show summary
|
|
echo ""
|
|
echo "========================================"
|
|
echo "Update Complete!"
|
|
echo "========================================"
|
|
echo ""
|
|
echo "Signature file: $LIB_DIR/attack-signatures.sh"
|
|
echo "Backup saved to: $BACKUP_DIR/"
|
|
echo "ET Rules source: $ET_RULES_URL"
|
|
echo "Last updated: $(date)"
|
|
echo ""
|
|
echo "Changes will take effect when live-attack-monitor.sh is restarted."
|
|
echo ""
|
|
|
|
# Show pattern counts
|
|
log_info "Pattern counts:"
|
|
echo " SQL Injection: $(grep -c 'ATTACK_SQLI\[' "$LIB_DIR/attack-signatures.sh" || echo 0)"
|
|
echo " XSS: $(grep -c 'ATTACK_XSS\[' "$LIB_DIR/attack-signatures.sh" || echo 0)"
|
|
echo " Command Injection: $(grep -c 'ATTACK_CMD\[' "$LIB_DIR/attack-signatures.sh" || echo 0)"
|
|
echo " Path Traversal: $(grep -c 'ATTACK_TRAVERSAL\[' "$LIB_DIR/attack-signatures.sh" || echo 0)"
|
|
echo " File Inclusion: $(grep -c 'ATTACK_INCLUSION\[' "$LIB_DIR/attack-signatures.sh" || echo 0)"
|
|
echo " Webshells: $(grep -c 'ATTACK_WEBSHELL\[' "$LIB_DIR/attack-signatures.sh" || echo 0)"
|
|
echo " CVE Exploits: $(grep -c 'ATTACK_CVE\[' "$LIB_DIR/attack-signatures.sh" || echo 0)"
|
|
echo " Upload Attacks: $(grep -c 'ATTACK_UPLOAD\[' "$LIB_DIR/attack-signatures.sh" || echo 0)"
|
|
echo ""
|
|
|
|
log_success "Signature update complete!"
|