Fail2ban网络安全防护
一、前言
Fail2ban是一款基于Python开发的开源网络安全防护软件,主要应用于UNIX/Linux系统,通过实时监控SSH、Apache、FTP等服务日志文件,自动识别并拦截恶意访问行为。
二、Fail2ban安装
# 安装 EPEL 仓库(CentOS/RHEL 7/8 需要)
yum install epel-release -y
# 安装 Fail2Ban 及相关包
yum install -y fail2ban fail2ban-systemd iptables-services iptables
# 启动并设置开机自启
systemctl start fail2ban
systemctl enable fail2ban
# 检查状态
systemctl status fail2ban
三、Fail2ban 防护脚本
#!/bin/bash
# ============================================
# Fail2Ban 一键安装配置脚本(永久封禁版)
# 版本:7.1 - 动态时间范围扫描 + 永久豁免列表
# ============================================
set -e
# 颜色定义(必须在最前面)
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
NC='\033[0m'
# 配置参数
SSH_PORT="22"
MAX_RETRY="3"
FIND_TIME="600"
BAN_TIME="-1" # 改为 -1 永久封禁
IGNORE_IP="127.0.0.1/8 ::1"
# 性能优化参数
PARALLEL_JOBS=2
SCAN_MONTHS="24" # 默认扫描24个月
# 日志文件
INSTALL_LOG="/var/log/fail2ban-install.log"
REPORT_DIR="/var/log/fail2ban-reports"
ATTACK_LOG="/var/log/fail2ban-attack-details.log"
FULL_SCAN_LOG="/var/log/fail2ban-full-scan.log"
mkdir -p ${REPORT_DIR}
# ============================================
# 定义所有打印函数
# ============================================
print_info() {
echo -e "${GREEN}[INFO]${NC} $1" | tee -a ${INSTALL_LOG}
}
print_error() {
echo -e "${RED}[ERROR]${NC} $1" | tee -a ${INSTALL_LOG}
}
print_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1" | tee -a ${INSTALL_LOG}
}
print_step() {
echo ""
echo -e "${BLUE}========================================${NC}"
echo -e "${CYAN}步骤 $1: $2${NC}"
echo -e "${BLUE}========================================${NC}"
}
# ============================================
# 验证函数是否可用
# ============================================
test_functions() {
print_info "测试打印函数"
print_warning "这是警告信息测试"
print_error "这是错误信息测试"
}
# ============================================
# 检查root权限
# ============================================
check_root() {
if [ "$EUID" -ne 0 ]; then
print_error "请使用root权限运行此脚本: sudo $0"
exit 1
fi
}
# ============================================
# 安装依赖
# ============================================
install_dependencies() {
print_step "1" "安装依赖包"
yum install epel-release -y >> ${INSTALL_LOG} 2>&1
yum install -y fail2ban fail2ban-systemd iptables-services iptables >> ${INSTALL_LOG} 2>&1
print_info "依赖包安装完成"
}
# ============================================
# 配置SSH端口
# ============================================
configure_ssh() {
print_step "2" "配置SSH端口为 ${SSH_PORT}"
cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak.$(date +%Y%m%d_%H%M%S) 2>/dev/null
if grep -q "^Port" /etc/ssh/sshd_config; then
sed -i "s/^Port.*/Port ${SSH_PORT}/" /etc/ssh/sshd_config
else
echo "Port ${SSH_PORT}" >> /etc/ssh/sshd_config
fi
sed -i '/^#Port/d' /etc/ssh/sshd_config
systemctl restart sshd
if systemctl is-active --quiet sshd; then
print_info "SSH服务运行正常,端口: ${SSH_PORT}"
netstat -tlnp 2>/dev/null | grep sshd || ss -tlnp 2>/dev/null | grep sshd
else
print_error "SSH服务启动失败"
exit 1
fi
# 防火墙配置
if command -v firewall-cmd &> /dev/null; then
firewall-cmd --permanent --add-port=${SSH_PORT}/tcp 2>/dev/null
firewall-cmd --permanent --remove-service=ssh 2>/dev/null
firewall-cmd --reload 2>/dev/null
print_info "firewalld已配置"
elif command -v iptables &> /dev/null; then
iptables -I INPUT -p tcp --dport ${SSH_PORT} -j ACCEPT 2>/dev/null
service iptables save 2>/dev/null
print_info "iptables已配置"
fi
}
# ============================================
# 配置Fail2Ban(永久封禁)
# ============================================
configure_fail2ban() {
print_step "3" "配置Fail2Ban(端口 ${SSH_PORT},永久封禁)"
systemctl stop fail2ban 2>/dev/null
rm -f /etc/fail2ban/jail.local
rm -f /var/run/fail2ban/fail2ban.sock
mkdir -p /var/lib/fail2ban /etc/fail2ban/action.d /var/log
# 创建基础jail配置(只配置SSH)
cat > /etc/fail2ban/jail.local << EOF
[DEFAULT]
bantime = -1
findtime = ${FIND_TIME}
maxretry = ${MAX_RETRY}
ignoreip = ${IGNORE_IP}
banaction = iptables-multiport
[sshd]
enabled = true
port = ${SSH_PORT}
filter = sshd
logpath = /var/log/secure
maxretry = ${MAX_RETRY}
bantime = -1
action = iptables-multiport[name=sshd, port=${SSH_PORT}, protocol=tcp]
EOF
touch /var/lib/fail2ban/permanent-bans.list
chmod 666 /var/lib/fail2ban/permanent-bans.list
# 创建永久豁免列表文件(手动解封的IP将记录在此)
touch /var/lib/fail2ban/unban-permanent.list
chmod 666 /var/lib/fail2ban/unban-permanent.list
touch /var/log/fail2ban.log
chmod 644 /var/log/fail2ban.log
touch ${ATTACK_LOG}
chmod 644 ${ATTACK_LOG}
touch ${FULL_SCAN_LOG}
chmod 644 ${FULL_SCAN_LOG}
print_info "Fail2Ban配置完成(永久封禁模式)"
print_info "永久豁免列表: /var/lib/fail2ban/unban-permanent.list"
}
# ============================================
# 获取需要扫描的日志文件列表(基于时间范围)
# ============================================
get_log_files_by_months() {
local months=$1
local log_path=$2
local log_files=()
# 当前日志文件
if [ -f "${log_path}" ]; then
log_files+=("${log_path}")
fi
# 计算起始日期
local start_date=$(date -d "-${months} months" +%s 2>/dev/null || echo "")
if [ -n "$start_date" ]; then
# 扫描轮转日志
for rotated in ${log_path}-* ${log_path}-*.gz ${log_path}.* ${log_path}.*.gz; do
if [ -f "$rotated" ]; then
# 提取日志文件中的日期
local file_date=""
if [[ "$rotated" =~ ([0-9]{8}) ]]; then
file_date="${BASH_REMATCH[1]}"
elif [[ "$rotated" =~ ([0-9]{6}) ]]; then
file_date="${BASH_REMATCH[1]}"
elif [[ "$rotated" =~ ([0-9]{4}-[0-9]{2}-[0-9]{2}) ]]; then
file_date=$(echo "${BASH_REMATCH[1]}" | tr -d '-')
fi
if [ -n "$file_date" ] && [ ${#file_date} -ge 6 ]; then
local file_timestamp=$(date -d "${file_date:0:4}-${file_date:4:2}-${file_date:6:2}" +%s 2>/dev/null)
if [ -n "$file_timestamp" ] && [ $file_timestamp -ge $start_date ]; then
log_files+=("$rotated")
fi
else
# 无法判断日期,包含所有轮转日志
log_files+=("$rotated")
fi
fi
done
else
# 无法计算日期,包含所有轮转日志
for rotated in ${log_path}-* ${log_path}-*.gz ${log_path}.* ${log_path}.*.gz; do
[ -f "$rotated" ] && log_files+=("$rotated")
done
fi
# 去重
printf '%s\n' "${log_files[@]}" | sort -u
}
# ============================================
# 优化的IP提取函数(使用grep -oP)
# ============================================
extract_ips_optimized() {
local log_file=$1
local temp_file="/tmp/ip_extract_$$.txt"
if [[ "$log_file" == *.gz ]]; then
zcat "$log_file" 2>/dev/null | grep -a "Failed password" | grep -oP 'from \K[0-9.]+' | sort | uniq -c | awk -v threshold=3 '$1 >= threshold {print $2 "|" $1}' > ${temp_file}
else
grep -a "Failed password" "$log_file" 2>/dev/null | grep -oP 'from \K[0-9.]+' | sort | uniq -c | awk -v threshold=3 '$1 >= threshold {print $2 "|" $1}' > ${temp_file}
fi
cat ${temp_file}
rm -f ${temp_file}
}
# ============================================
# 获取攻击详情(优化版)
# ============================================
get_attack_details_fast() {
local ip=$1
local log_file=$2
local username="unknown"
local password="unknown"
local attack_time=""
# 快速获取最后一条攻击记录
local last_line=""
if [[ "$log_file" == *.gz ]]; then
last_line=$(zcat "$log_file" 2>/dev/null | grep -a "Failed password.*from ${ip}" | tail -1)
else
last_line=$(grep -a "Failed password.*from ${ip}" "$log_file" 2>/dev/null | tail -1)
fi
if [ -n "$last_line" ]; then
# 提取时间
attack_time=$(echo "$last_line" | grep -oP '^[A-Za-z]{3}\s+\d{1,2}\s+\d{2}:\d{2}:\d{2}')
# 提取用户名
if echo "$last_line" | grep -q "for user"; then
username=$(echo "$last_line" | grep -oP 'for user \K[^ ]+' | head -1)
elif echo "$last_line" | grep -q "user"; then
username=$(echo "$last_line" | grep -oP 'user \K[^ ]+' | head -1)
elif echo "$last_line" | grep -q "invalid user"; then
username=$(echo "$last_line" | grep -oP 'invalid user \K[^ ]+' | head -1)
fi
# 提取密码
if echo "$last_line" | grep -q "password"; then
password=$(echo "$last_line" | grep -oP 'password \K[^ ]+' | head -1 | sed 's/ //g')
fi
fi
[ -z "$username" ] && username="unknown"
[ -z "$password" ] && password="unknown"
echo "${attack_time}|${username}|${password}"
}
# ============================================
# 创建优化的quick_ban脚本(支持永久豁免列表)
# ============================================
create_quick_ban_script() {
print_step "4" "创建优化的quick_ban脚本(支持永久豁免列表)"
cat > /usr/local/bin/quick_ban << 'EOF'
#!/bin/bash
BAN_LIST="/var/lib/fail2ban/permanent-bans.list"
UNBAN_PERMANENT="/var/lib/fail2ban/unban-permanent.list"
SECURE_LOG="/var/log/secure"
ATTACK_LOG="/var/log/fail2ban-attack-details.log"
FULL_SCAN_LOG="/var/log/fail2ban-full-scan.log"
SSH_PORT="22"
REPORT_DIR="/var/log/fail2ban-reports"
SCAN_MONTHS="${SCAN_MONTHS:-24}"
# 性能优化参数
export LC_ALL=C
export LANG=C
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
NC='\033[0m'
# ============================================
# 核心函数:豁免列表管理
# ============================================
# 检查IP是否在永久豁免列表中
is_ip_exempted() {
local ip=$1
[ -f "${UNBAN_PERMANENT}" ] && grep -q "^${ip}$" ${UNBAN_PERMANENT} 2>/dev/null
}
# 将IP添加到永久豁免列表
add_to_exempt_list() {
local ip=$1
local reason=$2
if ! is_ip_exempted "$ip"; then
echo "${ip}" >> ${UNBAN_PERMANENT}
echo "[$(date '+%Y-%m-%d %H:%M:%S')] EXEMPT_ADD|ip=${ip}|reason=${reason}" >> ${BAN_LIST}
echo -e "${GREEN} → IP ${ip} 已加入永久豁免列表${NC}"
fi
}
# 从永久豁免列表移除IP
remove_from_exempt_list() {
local ip=$1
if [ -f "${UNBAN_PERMANENT}" ]; then
sed -i "/^${ip}$/d" ${UNBAN_PERMANENT}
echo "[$(date '+%Y-%m-%d %H:%M:%S')] EXEMPT_REMOVE|ip=${ip}" >> ${BAN_LIST}
echo -e "${GREEN} → IP ${ip} 已从豁免列表移除${NC}"
fi
}
# 手动解封IP并加入豁免列表
manual_unban() {
local ip=$1
if [ -z "$ip" ]; then
echo -e "${RED}用法: quick_ban -u <IP地址>${NC}"
return 1
fi
echo -e "${YELLOW}正在解封并豁免 IP: ${ip}${NC}"
# 从fail2ban解封
fail2ban-client set sshd unbanip ${ip} 2>/dev/null && echo -e " ✓ fail2ban解封成功"
# 从iptables移除
iptables -D fail2ban-sshd -s ${ip} -j DROP 2>/dev/null && echo -e " ✓ iptables规则移除成功"
# 加入永久豁免列表
add_to_exempt_list "$ip" "manual_unban"
echo -e "${GREEN}✓ IP ${ip} 已解封并加入永久豁免列表,后续扫描将不再封禁${NC}"
}
# 列出永久豁免列表
list_exempted() {
echo -e "${BLUE}╔══════════════════════════════════════════════════════════════════════════════════════╗${NC}"
echo -e "${BLUE}║ 永久豁免IP列表(手动解封后不再封禁) ║${NC}"
echo -e "${BLUE}╚══════════════════════════════════════════════════════════════════════════════════════╝${NC}"
if [ -f "${UNBAN_PERMANENT}" ] && [ -s "${UNBAN_PERMANENT}" ]; then
echo ""
local count=0
cat ${UNBAN_PERMANENT} | while read ip; do
[ -n "$ip" ] && echo " • ${ip}" && count=$((count+1))
done
echo ""
echo -e "${GREEN}总计豁免IP数: $(wc -l < ${UNBAN_PERMANENT})${NC}"
else
echo -e "${YELLOW}暂无永久豁免IP${NC}"
fi
}
# ============================================
# 原有函数(增强豁免检查)
# ============================================
# 转换时间格式函数
convert_time_format() {
local time_str="$1"
local converted=""
if [[ "$time_str" =~ ^([A-Za-z]{3})\s+([0-9]{1,2})\s+([0-9]{2}:[0-9]{2}:[0-9]{2})$ ]]; then
local mon="${BASH_REMATCH[1]}"
local day="${BASH_REMATCH[2]}"
local time="${BASH_REMATCH[3]}"
local current_year=$(date +%Y)
local current_month=$(date +%m)
local month_num=""
case $mon in
Jan) month_num="01" ;;
Feb) month_num="02" ;;
Mar) month_num="03" ;;
Apr) month_num="04" ;;
May) month_num="05" ;;
Jun) month_num="06" ;;
Jul) month_num="07" ;;
Aug) month_num="08" ;;
Sep) month_num="09" ;;
Oct) month_num="10" ;;
Nov) month_num="11" ;;
Dec) month_num="12" ;;
esac
local year=$current_year
if [ $((10#$month_num)) -gt $((10#$current_month)) ]; then
year=$((current_year - 1))
fi
printf -v converted "%04d-%02d-%02d %s" "$year" "$((10#$month_num))" "$((10#$day))" "$time"
echo "$converted"
else
echo "$time_str"
fi
}
# 记录攻击详情
log_attack_detail() {
local ip=$1
local username=$2
local password=$3
local port=$4
local attack_time=$5
local service=$6
local formatted_time=$(convert_time_format "$attack_time")
echo "[$(date '+%Y-%m-%d %H:%M:%S')] ATTACK_DETAIL|service=${service}|ip=${ip}|username=${username}|password=${password}|port=${port}|attack_time=${formatted_time}" >> ${ATTACK_LOG}
}
# 增强版:检查IP是否已被封禁(包含豁免检查)
is_ip_banned() {
local ip=$1
# 优先检查豁免列表
if is_ip_exempted "$ip"; then
return 0
fi
# 检查fail2ban封禁列表
if fail2ban-client get sshd banip 2>/dev/null | grep -qw "${ip}"; then
return 0
fi
# 检查iptables规则
if iptables -L fail2ban-sshd -n 2>/dev/null | grep -q "${ip}"; then
return 0
fi
# 检查本地记录
if grep -q "ip=${ip}" ${BAN_LIST} 2>/dev/null; then
return 0
fi
return 1
}
# 修复iptables规则并确保生效
fix_iptables_rules() {
# 确保fail2ban-sshd链存在
if ! iptables -L fail2ban-sshd &>/dev/null; then
iptables -N fail2ban-sshd 2>/dev/null
fi
# 确保INPUT链有跳转到fail2ban-sshd的规则
if ! iptables -L INPUT -n | grep -q "fail2ban-sshd.*${SSH_PORT}"; then
iptables -I INPUT -p tcp --dport ${SSH_PORT} -j fail2ban-sshd 2>/dev/null
fi
# 确保规则生效
if ! iptables -L INPUT -n | grep -q "fail2ban-sshd"; then
iptables -A INPUT -p tcp --dport ${SSH_PORT} -j fail2ban-sshd 2>/dev/null
fi
# 保存iptables规则
service iptables save 2>/dev/null || iptables-save > /etc/sysconfig/iptables 2>/dev/null
}
# 自动修复 iptables 链
auto_fix_iptables() {
local need_fix=0
if ! iptables -L fail2ban-sshd &>/dev/null; then
echo -e "${YELLOW}[修复] 创建 fail2ban-sshd 链...${NC}"
iptables -N fail2ban-sshd 2>/dev/null
need_fix=1
fi
if ! iptables -L INPUT -n | grep -q "fail2ban-sshd.*${SSH_PORT}"; then
echo -e "${YELLOW}[修复] 添加 INPUT 链跳转规则 (端口 ${SSH_PORT})...${NC}"
iptables -I INPUT -p tcp --dport ${SSH_PORT} -j fail2ban-sshd 2>/dev/null
need_fix=1
fi
if [ $need_fix -eq 1 ]; then
service iptables save 2>/dev/null
echo -e "${GREEN}[修复] iptables 链修复完成${NC}"
fi
}
# 生成拦截报告
generate_report() {
local report_file="${REPORT_DIR}/fail2ban-report-$(date +%Y%m%d_%H%M%S).txt"
local banned_ips=$(fail2ban-client get sshd banip 2>/dev/null)
local total_bans=0
if [ -n "$banned_ips" ] && [ "$banned_ips" != "None" ]; then
total_bans=$(echo "$banned_ips" | wc -w)
fi
{
echo "╔════════════════════════════════════════════════════════════════════════════════════════════╗"
echo "║ Fail2Ban 永久封禁拦截报告 ║"
echo "╠════════════════════════════════════════════════════════════════════════════════════════════╣"
echo "║ 报告时间: $(date '+%Y-%m-%d %H:%M:%S')"
echo "║ SSH端口: ${SSH_PORT}"
echo "║ 封禁模式: 永久封禁 (bantime = -1)"
echo "║ 失败阈值: ${MAX_RETRY}次/${FIND_TIME}秒"
echo "║ 当前封禁IP数: ${total_bans}"
echo "╠════════════════════════════════════════════════════════════════════════════════════════════╣"
echo "║ 封禁IP详细列表(含攻击详情) ║"
echo "╠════════════════════════════════════════════════════════════════════════════════════════════╣"
if [ -n "$banned_ips" ] && [ "$banned_ips" != "None" ]; then
local count=1
echo "$banned_ips" | tr ' ' '\n' | while read ip; do
if [ -n "$ip" ]; then
local ban_date=$(grep "ip=${ip}" ${BAN_LIST} 2>/dev/null | tail -1 | grep -oP '\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}' || echo "永久")
local attack_info=$(get_attack_details_fast "$ip")
local last_time=$(echo "$attack_info" | cut -d'|' -f1)
local username=$(echo "$attack_info" | cut -d'|' -f2)
local password=$(echo "$attack_info" | cut -d'|' -f3)
echo "║ [$count] IP: ${ip}"
echo "║ 封禁时间: ${ban_date}"
echo "║ 最后攻击: ${last_time}"
echo "║ 用户名: ${username}"
echo "║ 密码: ${password}"
echo "║ ──────────────────────────────────────────────────────────────────────────"
count=$((count + 1))
fi
done
else
echo "║ 暂无封禁IP ║"
fi
echo "╠════════════════════════════════════════════════════════════════════════════════════════════╣"
echo "║ 永久豁免IP列表(手动解封后不再封禁) ║"
echo "╠════════════════════════════════════════════════════════════════════════════════════════════╣"
if [ -f "${UNBAN_PERMANENT}" ] && [ -s "${UNBAN_PERMANENT}" ]; then
local exempt_count=1
cat ${UNBAN_PERMANENT} | while read ip; do
[ -n "$ip" ] && echo "║ [$exempt_count] ${ip}"
exempt_count=$((exempt_count + 1))
done
else
echo "║ 暂无豁免IP ║"
fi
echo "╠════════════════════════════════════════════════════════════════════════════════════════════╣"
echo "║ iptables DROP规则状态 ║"
echo "╠════════════════════════════════════════════════════════════════════════════════════════════╣"
local drop_rules=$(iptables -L fail2ban-sshd -n 2>/dev/null | grep "DROP" | wc -l)
echo "║ DROP规则数量: ${drop_rules}"
if [ $drop_rules -gt 0 ]; then
echo "║ 详细规则:"
iptables -L fail2ban-sshd -n 2>/dev/null | grep "DROP" | while read line; do
echo "║ ${line}"
done
fi
echo "╚════════════════════════════════════════════════════════════════════════════════════════════╝"
} > "$report_file"
echo -e "${GREEN}[报告] 拦截报告已保存: ${report_file}${NC}"
echo ""
echo -e "${CYAN}════════════════════════════════════════════════════════════════════════════════════════${NC}"
echo -e "${GREEN}拦截报告摘要${NC}"
echo -e "${CYAN}════════════════════════════════════════════════════════════════════════════════════════${NC}"
echo -e "报告时间: $(date '+%Y-%m-%d %H:%M:%S')"
echo -e "当前封禁IP数: ${total_bans}"
echo -e "当前豁免IP数: $(wc -l < ${UNBAN_PERMANENT} 2>/dev/null || echo 0)"
echo -e "报告位置: ${report_file}"
echo -e "${CYAN}════════════════════════════════════════════════════════════════════════════════════════${NC}"
}
# 获取攻击详情(快速版)
get_attack_details_fast() {
local ip=$1
local username="unknown"
local password="unknown"
local attack_time=""
# 快速获取最后一条攻击记录
local last_line=$(grep -a "Failed password.*from ${ip}" ${SECURE_LOG} 2>/dev/null | tail -1)
if [ -z "$last_line" ]; then
for log in ${SECURE_LOG}-* ${SECURE_LOG}-*.gz; do
if [ -f "$log" ]; then
if [[ "$log" == *.gz ]]; then
last_line=$(zcat "$log" 2>/dev/null | grep -a "Failed password.*from ${ip}" | tail -1)
else
last_line=$(grep -a "Failed password.*from ${ip}" "$log" 2>/dev/null | tail -1)
fi
[ -n "$last_line" ] && break
fi
done
fi
if [ -n "$last_line" ]; then
attack_time=$(echo "$last_line" | grep -oP '^[A-Za-z]{3}\s+\d{1,2}\s+\d{2}:\d{2}:\d{2}')
attack_time=$(convert_time_format "$attack_time")
if echo "$last_line" | grep -q "for user"; then
username=$(echo "$last_line" | grep -oP 'for user \K[^ ]+' | head -1)
elif echo "$last_line" | grep -q "user"; then
username=$(echo "$last_line" | grep -oP 'user \K[^ ]+' | head -1)
fi
if echo "$last_line" | grep -q "password"; then
password=$(echo "$last_line" | grep -oP 'password \K[^ ]+' | head -1 | sed 's/ //g')
fi
fi
[ -z "$username" ] && username="unknown"
[ -z "$password" ] && password="unknown"
echo "${attack_time}|${username}|${password}"
}
# 优化的单文件扫描函数(核心:跳过豁免IP)
scan_single_log_optimized() {
local log_file=$1
local temp_ban_file="/tmp/ban_ips_$$.txt"
local processed_ips="/tmp/processed_ips_$$.txt"
# 使用grep + awk组合,极速提取
if [[ "$log_file" == *.gz ]]; then
zcat "$log_file" 2>/dev/null | grep -a "Failed password" | grep -oP 'from \K[0-9.]+' | sort | uniq -c | awk -v threshold=3 '$1 >= threshold {print $2 "|" $1}' > ${temp_ban_file}
else
grep -a "Failed password" "$log_file" 2>/dev/null | grep -oP 'from \K[0-9.]+' | sort | uniq -c | awk -v threshold=3 '$1 >= threshold {print $2 "|" $1}' > ${temp_ban_file}
fi
# 批量处理封禁
if [ -s "${temp_ban_file}" ]; then
while IFS='|' read -r ip count; do
# 检查是否已处理过
if grep -q "^${ip}$" ${processed_ips} 2>/dev/null; then
continue
fi
echo "${ip}" >> ${processed_ips}
# 【关键】检查是否在豁免列表中
if is_ip_exempted "$ip"; then
echo -e " ${YELLOW}→ 跳过豁免IP: ${ip} (手动解封后不再封禁)${NC}"
continue
fi
if ! is_ip_banned "$ip"; then
echo -e " ${GREEN}→ 封禁IP: ${ip} (攻击${count}次)${NC}"
# 获取攻击详情
local attack_info=$(get_attack_details_fast "$ip")
local attack_time=$(echo "$attack_info" | cut -d'|' -f1)
local username=$(echo "$attack_info" | cut -d'|' -f2)
local password=$(echo "$attack_info" | cut -d'|' -f3)
echo "[$(date '+%Y-%m-%d %H:%M:%S')] FULL_SCAN|service=ssh|ip=${ip}|attacks=${count}|last_attack=${attack_time}|username=${username}" >> ${FULL_SCAN_LOG}
log_attack_detail "$ip" "$username" "$password" "${SSH_PORT}" "$attack_time" "ssh"
if fail2ban-client set sshd banip ${ip} 2>/dev/null; then
echo "[$(date '+%Y-%m-%d %H:%M:%S')] PERMANENT_BAN|ip=${ip}|attacks=${count}|service=ssh|username=${username}" >> ${BAN_LIST}
else
echo -e " ${RED}→ 封禁失败: ${ip}${NC}"
fi
else
echo -e " ${YELLOW}→ 跳过已封禁IP: ${ip}${NC}"
fi
done < ${temp_ban_file}
fi
rm -f ${temp_ban_file} ${processed_ips}
}
# 获取需要扫描的日志文件列表(基于时间范围)
get_log_files_by_months() {
local months=$1
local log_files=()
# 当前日志文件
if [ -f "${SECURE_LOG}" ]; then
log_files+=("${SECURE_LOG}")
fi
# 计算起始日期
local start_date=$(date -d "-${months} months" +%s 2>/dev/null)
if [ -n "$start_date" ]; then
# 扫描轮转日志
for rotated in ${SECURE_LOG}-* ${SECURE_LOG}-*.gz; do
if [ -f "$rotated" ]; then
# 提取日志文件中的日期
local file_date=""
if [[ "$rotated" =~ ([0-9]{8}) ]]; then
file_date="${BASH_REMATCH[1]}"
elif [[ "$rotated" =~ ([0-9]{6}) ]]; then
file_date="${BASH_REMATCH[1]}"
fi
if [ -n "$file_date" ] && [ ${#file_date} -ge 6 ]; then
local year="${file_date:0:4}"
local month="${file_date:4:2}"
local day="${file_date:6:2}"
local file_timestamp=$(date -d "${year}-${month}-${day}" +%s 2>/dev/null)
if [ -n "$file_timestamp" ] && [ $file_timestamp -ge $start_date ]; then
log_files+=("$rotated")
fi
else
# 无法判断日期,包含所有轮转日志
log_files+=("$rotated")
fi
fi
done
else
# 无法计算日期,包含所有轮转日志
for rotated in ${SECURE_LOG}-* ${SECURE_LOG}-*.gz; do
[ -f "$rotated" ] && log_files+=("$rotated")
done
fi
# 去重并返回
printf '%s\n' "${log_files[@]}" | sort -u
}
# 优化的SSH扫描(支持动态时间范围)
scan_ssh_dynamic() {
local scan_months=${1:-24}
auto_fix_iptables
fix_iptables_rules
echo -e "${BLUE}╔══════════════════════════════════════════════════════════════════════════════════════╗${NC}"
echo -e "${BLUE}║ 动态时间范围SSH攻击扫描 - 永久封禁模式(极致优化版) ║${NC}"
echo -e "${BLUE}╚══════════════════════════════════════════════════════════════════════════════════════╝${NC}"
echo -e "${YELLOW}扫描时间: $(date '+%Y-%m-%d %H:%M:%S')${NC}"
echo -e "${YELLOW}扫描范围: 最近 ${scan_months} 个月${NC}"
echo -e "${YELLOW}封禁模式: 永久封禁${NC}"
echo -e "${YELLOW}豁免列表: 自动跳过手动解封的IP${NC}"
echo -e "${YELLOW}豁免文件: ${UNBAN_PERMANENT}${NC}"
echo ""
echo -e "${CYAN}[1/3] 智能收集日志文件(最近${scan_months}个月)...${NC}"
local LOG_FILES=($(get_log_files_by_months ${scan_months}))
echo -e "${GREEN}✓ 找到 ${#LOG_FILES[@]} 个日志文件${NC}"
for log in "${LOG_FILES[@]}"; do
echo -e " - $(basename $log)"
done
echo ""
echo -e "${CYAN}[2/3] 极速扫描并实时封禁(自动跳过豁免IP)...${NC}"
echo ""
local file_count=0
local total_files=${#LOG_FILES[@]}
for log_file in "${LOG_FILES[@]}"; do
file_count=$((file_count + 1))
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${CYAN}[${file_count}/${total_files}] 正在处理: $(basename $log_file)${NC}"
scan_single_log_optimized "$log_file"
local current_bans=$(grep -c "PERMANENT_BAN" ${BAN_LIST} 2>/dev/null || echo 0)
local current_exempt=$(wc -l < ${UNBAN_PERMANENT} 2>/dev/null || echo 0)
echo -e "${GREEN} 当前已封禁IP总数: ${current_bans} | 豁免IP数: ${current_exempt}${NC}"
echo ""
done
echo -e "${CYAN}[3/3] 生成扫描报告...${NC}"
echo ""
echo -e "${GREEN}══════════════════════════════════════════════════════════════════════════════════════${NC}"
echo -e "${GREEN}动态扫描完成统计:${NC}"
echo -e " 扫描时间范围: 最近 ${scan_months} 个月"
echo -e " 扫描日志文件数: ${#LOG_FILES[@]}"
echo -e " 当前总封禁IP数: $(grep -c "PERMANENT_BAN" ${BAN_LIST} 2>/dev/null || echo 0)"
echo -e " 当前豁免IP数: $(wc -l < ${UNBAN_PERMANENT} 2>/dev/null || echo 0)"
echo -e " 处理方式: grep+awk极速处理 + 自动跳过豁免IP"
echo -e "${GREEN}══════════════════════════════════════════════════════════════════════════════════════${NC}"
# 确保iptables规则生效
fix_iptables_rules
generate_report
}
# 兼容旧版scan_2years函数
scan_2years() {
scan_ssh_dynamic 24
}
# 优化的全面扫描函数
full_scan_optimized() {
auto_fix_iptables
fix_iptables_rules
echo -e "${BLUE}╔══════════════════════════════════════════════════════════════════════════════════════╗${NC}"
echo -e "${BLUE}║ 优化全面扫描所有服务 - 永久封禁模式(极致优化版) ║${NC}"
echo -e "${BLUE}╚══════════════════════════════════════════════════════════════════════════════════════╝${NC}"
echo -e "${YELLOW}扫描时间: $(date '+%Y-%m-%d %H:%M:%S')${NC}"
echo -e "${YELLOW}封禁模式: 永久封禁${NC}"
echo -e "${YELLOW}豁免列表: 自动跳过手动解封的IP${NC}"
echo ""
echo -e "${CYAN}[1/2] 扫描secure日志(SSH攻击)...${NC}"
scan_ssh_dynamic ${SCAN_MONTHS}
echo -e "${CYAN}[2/2] 生成全面扫描报告...${NC}"
generate_full_report
}
# 生成全面扫描报告
generate_full_report() {
local report_file="${REPORT_DIR}/full-scan-report-$(date +%Y%m%d_%H%M%S).txt"
{
echo "╔════════════════════════════════════════════════════════════════════════════════════════════╗"
echo "║ Fail2Ban 优化全面扫描永久封禁报告 ║"
echo "╠════════════════════════════════════════════════════════════════════════════════════════════╣"
echo "║ 报告时间: $(date '+%Y-%m-%d %H:%M:%S')"
echo "║ 封禁模式: 永久封禁 (bantime = -1)"
echo "║ 扫描范围: SSH服务"
echo "║ 扫描方式: grep+awk极速扫描 + 自动跳过豁免IP"
echo "╠════════════════════════════════════════════════════════════════════════════════════════════╣"
echo "║ 封禁IP统计:"
echo "╠════════════════════════════════════════════════════════════════════════════════════════════╣"
if [ -f "${BAN_LIST}" ]; then
local total_bans=$(grep -c "PERMANENT_BAN" ${BAN_LIST} 2>/dev/null || echo 0)
echo "║ SSH服务封禁数: ${total_bans}"
fi
if [ -f "${UNBAN_PERMANENT}" ]; then
local exempt_count=$(wc -l < ${UNBAN_PERMANENT} 2>/dev/null || echo 0)
echo "║ 永久豁免IP数: ${exempt_count}"
fi
echo "╠════════════════════════════════════════════════════════════════════════════════════════════╣"
echo "║ iptables 全局封禁统计:"
echo "╠════════════════════════════════════════════════════════════════════════════════════════════╣"
local total_drop=$(iptables-save 2>/dev/null | grep "^-A fail2ban" | grep "DROP" | wc -l)
echo "║ fail2ban链DROP规则总数: ${total_drop}"
echo "╚════════════════════════════════════════════════════════════════════════════════════════════╝"
} > "$report_file"
echo -e "${GREEN}[报告] 全面扫描报告已保存: ${report_file}${NC}"
}
# 查看全面扫描日志
view_full_scan_log() {
if [ -f "${FULL_SCAN_LOG}" ]; then
echo -e "${BLUE}╔══════════════════════════════════════════════════════════════════════════════════════╗${NC}"
echo -e "${BLUE}║ 全面扫描日志(最近50条) ║${NC}"
echo -e "${BLUE}╚══════════════════════════════════════════════════════════════════════════════════════╝${NC}"
tail -50 ${FULL_SCAN_LOG} | while read line; do
echo "$line" | sed 's/|/ │ /g'
done
else
echo -e "${YELLOW}全面扫描日志文件不存在${NC}"
fi
}
# 列出所有封禁IP
list_all_banned() {
echo -e "${BLUE}╔══════════════════════════════════════════════════════════════════════════════════════╗${NC}"
echo -e "${BLUE}║ 所有服务永久封禁IP列表 ║${NC}"
echo -e "${BLUE}╚══════════════════════════════════════════════════════════════════════════════════════╝${NC}"
echo -e "${YELLOW}封禁模式: 永久封禁${NC}"
echo -e "${YELLOW}查询时间: $(date '+%Y-%m-%d %H:%M:%S')${NC}"
echo ""
local banned=$(fail2ban-client get sshd banip 2>/dev/null)
if [ -n "$banned" ] && [ "$banned" != "None" ]; then
local ban_count=$(echo "$banned" | wc -w)
echo -e "${CYAN}当前封禁IP列表 (${ban_count}个):${NC}"
echo "────────────────────────────────────────────────────────"
echo "$banned" | tr ' ' '\n' | while read ip; do
[ -n "$ip" ] && echo " • $ip"
done
echo ""
echo -e "${GREEN}总计封禁IP数: ${ban_count}${NC}"
else
echo -e "${GREEN}暂无任何封禁IP${NC}"
fi
echo ""
echo -e "${YELLOW}iptables DROP 规则:${NC}"
iptables -L fail2ban-sshd -n 2>/dev/null | grep "DROP" | while read line; do
echo " $line"
done
}
# 列出封禁IP(带详情)
list_banned() {
echo -e "${BLUE}╔══════════════════════════════════════════════════════════════════════════════════════╗${NC}"
echo -e "${BLUE}║ 当前永久封禁IP列表(含攻击详情) ║${NC}"
echo -e "${BLUE}╚══════════════════════════════════════════════════════════════════════════════════════╝${NC}"
echo -e "${YELLOW}封禁模式: 永久封禁${NC}"
echo -e "${YELLOW}查询时间: $(date '+%Y-%m-%d %H:%M:%S')${NC}"
echo ""
BANNED_IPS=$(fail2ban-client get sshd banip 2>/dev/null)
if [ -n "$BANNED_IPS" ] && [ "$BANNED_IPS" != "None" ]; then
echo "┌────┬──────────────────┬────────────────────┬────────────────────────┬──────────────┬───────────────┐"
printf "│%-4s│%-18s│%-20s│%-24s│%-14s│%-15s│\n" "序号" "IP地址" "封禁时间" "最后攻击时间" "用户名" "密码"
echo "├────┼──────────────────┼────────────────────┼────────────────────────┼──────────────┼───────────────┤"
local count=1
echo "$BANNED_IPS" | tr ' ' '\n' | while read ip; do
if [ -n "$ip" ]; then
local ban_date=$(grep "ip=${ip}" ${BAN_LIST} 2>/dev/null | tail -1 | grep -oP '\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}' || echo "永久")
local attack_info=$(get_attack_details_fast "$ip")
local last_time=$(echo "$attack_info" | cut -d'|' -f1)
local username=$(echo "$attack_info" | cut -d'|' -f2)
local password=$(echo "$attack_info" | cut -d'|' -f3)
printf "│%-4s│%-18s│%-20s│%-24s│%-14s│%-15s│\n" "$count" "$ip" "$ban_date" "$last_time" "${username:0:12}" "${password:0:13}"
count=$((count + 1))
fi
done
echo "└────┴──────────────────┴────────────────────┴────────────────────────┴──────────────┴───────────────┘"
else
echo -e "${GREEN}暂无封禁IP${NC}"
fi
echo ""
echo -e "${YELLOW}iptables DROP 规则:${NC}"
iptables -L fail2ban-sshd -n 2>/dev/null | grep "DROP" | while read line; do
echo " $line"
done
}
# 查看攻击日志
view_attack_log() {
if [ -f "${ATTACK_LOG}" ]; then
echo -e "${BLUE}╔══════════════════════════════════════════════════════════════════════════════════════╗${NC}"
echo -e "${BLUE}║ 攻击详情日志(最近50条) ║${NC}"
echo -e "${BLUE}╚══════════════════════════════════════════════════════════════════════════════════════╝${NC}"
tail -50 ${ATTACK_LOG} | while read line; do
echo "$line" | sed 's/|/ │ /g'
done
else
echo -e "${YELLOW}攻击详情日志文件不存在${NC}"
fi
}
# 动态月份扫描函数
scan_custom_months() {
local months=$1
if [ -z "$months" ] || [ "$months" -lt 1 ]; then
echo -e "${RED}错误: 请输入有效的月份数(至少1个月)${NC}"
echo "用法: quick_ban -m <月份数>"
return 1
fi
scan_ssh_dynamic $months
}
# ============================================
# 命令行参数处理
# ============================================
case "$1" in
-s) scan_2years ;;
-m) shift; scan_custom_months $1 ;;
-l) list_banned ;;
-r) generate_report ;;
-a) view_attack_log ;;
-f) full_scan_optimized ;;
-F) list_all_banned ;;
-L) view_full_scan_log ;;
-u) shift; manual_unban $1 ;;
-e) list_exempted ;;
-d) shift; remove_from_exempt_list $1 ;;
*)
echo "用法: quick_ban [选项]"
echo ""
echo "扫描选项:"
echo " -s 扫描最近2年攻击日志"
echo " -m <月份> 扫描最近N个月攻击日志"
echo " -f 优化全面扫描所有服务"
echo ""
echo "查看选项:"
echo " -l 查看封禁列表(含攻击详情)"
echo " -F 列出所有封禁IP"
echo " -r 生成拦截报告"
echo " -a 查看攻击日志"
echo " -L 查看全面扫描日志"
echo " -e 查看永久豁免列表"
echo ""
echo "管理选项:"
echo " -u <IP> 手动解封IP并加入永久豁免列表"
echo " -d <IP> 从豁免列表中移除IP"
;;
esac
EOF
chmod +x /usr/local/bin/quick_ban
print_info "quick_ban脚本创建完成(优化版 - 支持永久豁免列表)"
}
# ============================================
# 启动服务
# ============================================
start_services() {
print_step "5" "启动服务并验证(永久封禁模式)"
# 重置失败状态
systemctl reset-failed fail2ban.service 2>/dev/null
rm -f /var/run/fail2ban/fail2ban.sock
systemctl start fail2ban
systemctl enable fail2ban
sleep 3
if systemctl is-active --quiet fail2ban; then
print_info "Fail2Ban服务启动成功(永久封禁模式)"
echo ""
echo -e "${YELLOW}验证iptables规则:${NC}"
# 创建iptables链
if ! iptables -L fail2ban-sshd &>/dev/null; then
echo -e "${YELLOW}[修复] 主动创建 iptables 链...${NC}"
iptables -N fail2ban-sshd 2>/dev/null
iptables -I INPUT -p tcp --dport ${SSH_PORT} -j fail2ban-sshd 2>/dev/null
iptables -I INPUT -p tcp --dport 22 -j fail2ban-sshd 2>/dev/null
service iptables save 2>/dev/null
echo -e "${GREEN}[修复] iptables 链创建成功${NC}"
fi
# 测试永久封禁功能
echo ""
echo -e "${YELLOW}测试永久封禁功能:${NC}"
TEST_IP="192.168.255.254"
# 检查测试IP是否已存在
if ! iptables -L fail2ban-sshd -n 2>/dev/null | grep -q "${TEST_IP}"; then
if fail2ban-client set sshd banip ${TEST_IP} 2>/dev/null; then
sleep 2
if iptables -L fail2ban-sshd -n 2>/dev/null | grep -q "${TEST_IP}"; then
echo -e "${GREEN}[测试] 永久封禁功能正常${NC}"
fail2ban-client set sshd unbanip ${TEST_IP} 2>/dev/null
iptables -D fail2ban-sshd -s ${TEST_IP} -j DROP 2>/dev/null
else
echo -e "${YELLOW}[测试] 封禁规则可能未生效,尝试手动修复...${NC}"
iptables -A fail2ban-sshd -s ${TEST_IP} -j DROP 2>/dev/null
if iptables -L fail2ban-sshd -n 2>/dev/null | grep -q "${TEST_IP}"; then
echo -e "${GREEN}[测试] 手动修复成功${NC}"
iptables -D fail2ban-sshd -s ${TEST_IP} -j DROP 2>/dev/null
fi
fi
else
echo -e "${YELLOW}[测试] 封禁测试失败${NC}"
fi
else
echo -e "${YELLOW}[测试] 测试IP已存在,跳过测试${NC}"
fi
# 确保iptables规则持久化
service iptables save 2>/dev/null || iptables-save > /etc/sysconfig/iptables 2>/dev/null
else
echo -e "${RED}[ERROR]${NC} Fail2Ban服务启动失败"
journalctl -u fail2ban -n 30 --no-pager
exit 1
fi
}
# ============================================
# 配置定时任务
# ============================================
configure_crontab() {
print_step "6" "配置定时任务"
(
crontab -l 2>/dev/null | grep -v "quick_ban"
echo "# Fail2Ban - 每小时执行一次永久封禁扫描(最近2年)"
echo "0 * * * * /usr/local/bin/quick_ban -s >> /var/log/fail2ban-hourly.log 2>&1"
echo "# Fail2Ban - 每天凌晨2点执行优化全面扫描"
echo "0 2 * * * /usr/local/bin/quick_ban -f >> /var/log/fail2ban-full-scan-daily.log 2>&1"
echo "# Fail2Ban - 每30分钟修复iptables规则"
echo "*/30 * * * * /usr/sbin/iptables -L fail2ban-sshd &>/dev/null || (iptables -N fail2ban-sshd && iptables -I INPUT -p tcp --dport ${SSH_PORT} -j fail2ban-sshd) 2>/dev/null"
) | crontab -
systemctl restart crond
print_info "定时任务配置完成(每小时执行SSH封禁扫描,每天凌晨2点执行优化全面扫描)"
}
# ============================================
# 显示总结
# ============================================
show_summary() {
echo ""
echo -e "${GREEN}╔════════════════════════════════════════════════╗${NC}"
echo -e "${GREEN}║ 安装完成! ║${NC}"
echo -e "${GREEN}╚════════════════════════════════════════════════╝${NC}"
echo -e "\n${YELLOW}配置信息:${NC}"
echo " SSH端口: ${SSH_PORT}"
echo " 封禁模式: ${GREEN}永久封禁${NC} (bantime = -1)"
echo " 失败阈值: ${MAX_RETRY}次/${FIND_TIME}秒"
echo " 扫描范围: ${GREEN}动态时间范围(支持自定义月份)${NC}"
echo " 优化模式: ${GREEN}grep+awk极速处理 + 永久豁免列表${NC}"
echo -e "\n${GREEN}常用命令:${NC}"
echo " quick_ban -s # 扫描最近2年攻击日志"
echo " ${CYAN}quick_ban -m 12 # 扫描最近12个月攻击日志${NC}"
echo " ${CYAN}quick_ban -m 6 # 扫描最近6个月攻击日志${NC}"
echo " ${CYAN}quick_ban -m 3 # 扫描最近3个月攻击日志${NC}"
echo " quick_ban -l # 查看永久封禁列表(含攻击详情)"
echo " quick_ban -r # 生成拦截报告"
echo " quick_ban -a # 查看攻击详情日志"
echo " ${CYAN}quick_ban -f # 优化全面扫描所有服务${NC}"
echo " ${CYAN}quick_ban -F # 列出所有服务的封禁IP${NC}"
echo " ${CYAN}quick_ban -L # 查看全面扫描日志${NC}"
echo -e "\n${GREEN}豁免列表管理(新增):${NC}"
echo " ${CYAN}quick_ban -u 1.2.3.4 # 手动解封IP并加入永久豁免列表${NC}"
echo " ${CYAN}quick_ban -e # 查看永久豁免列表${NC}"
echo " ${CYAN}quick_ban -d 1.2.3.4 # 从豁免列表移除IP${NC}"
echo -e "\n${YELLOW}日志文件:${NC}"
echo " 攻击详情日志: ${ATTACK_LOG}"
echo " 全面扫描日志: ${FULL_SCAN_LOG}"
echo " 报告目录: ${REPORT_DIR}"
echo " 豁免列表: /var/lib/fail2ban/unban-permanent.list"
echo -e "\n${YELLOW}性能提升说明:${NC}"
echo " ✓ 使用grep+awk替代bash循环(20-100倍速度提升)"
echo " ✓ 智能时间范围过滤,只扫描指定月份的日志"
echo " ✓ 防止重复封禁同一IP"
echo " ✓ 永久豁免列表,手动解封后不再封禁"
echo " ✓ iptables规则自动修复和持久化"
echo -e "\n${YELLOW}永久封禁管理:${NC}"
echo " 手动封禁: fail2ban-client set sshd banip IP地址"
echo " 手动解封: quick_ban -u IP地址 # 会自动加入豁免列表"
echo " 直接解封: fail2ban-client set sshd unbanip IP地址"
echo " 查看封禁: iptables -L fail2ban-sshd -n"
echo -e "\n${RED}⚠ 重要提醒:${NC}"
echo " SSH端口已修改为 ${SSH_PORT}"
echo " 测试连接: ssh -p ${SSH_PORT} root@服务器IP"
echo " ${YELLOW}封禁模式为永久封禁,建议使用 quick_ban -u IP 解封${NC}"
echo ""
}
# ============================================
# 主程序
# ============================================
main() {
clear
echo -e "${BLUE}"
echo "╔════════════════════════════════════════════════╗"
echo "║ Fail2Ban SSH安全防护一键安装脚本 v7.1 ║"
echo "║ SSH端口: 22 | 永久封禁 | 永久豁免列表 ║"
echo "║ 优化: grep+awk极速处理 + 智能跳过豁免IP ║"
echo "╚════════════════════════════════════════════════╝"
echo -e "${NC}"
check_root
test_functions
install_dependencies
configure_ssh
configure_fail2ban
create_quick_ban_script
start_services
configure_crontab
show_summary
echo -e "${YELLOW}请选择扫描范围:${NC}"
echo " 1) 最近24个月(默认)"
echo " 2) 最近12个月"
echo " 3) 最近6个月"
echo " 4) 最近3个月"
echo " 5) 自定义月份"
echo " 6) 跳过扫描"
echo ""
read -p "请选择 [1-6]: " scan_choice
case $scan_choice in
2) SCAN_MONTHS=12; /usr/local/bin/quick_ban -m 12 ;;
3) SCAN_MONTHS=6; /usr/local/bin/quick_ban -m 6 ;;
4) SCAN_MONTHS=3; /usr/local/bin/quick_ban -m 3 ;;
5) read -p "请输入月份数: " custom_months
if [[ $custom_months =~ ^[0-9]+$ ]] && [ $custom_months -ge 1 ]; then
SCAN_MONTHS=$custom_months
/usr/local/bin/quick_ban -m $custom_months
else
echo -e "${YELLOW}输入无效,跳过扫描${NC}"
fi
;;
6) echo -e "${YELLOW}跳过初始扫描${NC}" ;;
*) SCAN_MONTHS=24; /usr/local/bin/quick_ban -m 24 ;;
esac
echo -e "\n${YELLOW}是否执行全面扫描(所有服务)?[y/N]${NC}"
read -r answer
if [[ $answer =~ ^[Yy]$ ]]; then
/usr/local/bin/quick_ban -f
fi
}
# 运行
main "$@"
四、安装过程记录
豁免列表管理(新增):
quick_ban -u 1.2.3.4 # 手动解封IP并加入永久豁免列表
quick_ban -e # 查看永久豁免列表
quick_ban -d 1.2.3.4 # 从豁免列表移除IP
日志文件:
攻击详情日志: /var/log/fail2ban-attack-details.log
全面扫描日志: /var/log/fail2ban-full-scan.log
报告目录: /var/log/fail2ban-reports
豁免列表: /var/lib/fail2ban/unban-permanent.list
性能提升说明:
✓ 使用grep+awk替代bash循环(20-100倍速度提升)
✓ 智能时间范围过滤,只扫描指定月份的日志
✓ 防止重复封禁同一IP
✓ 永久豁免列表,手动解封后不再封禁
✓ iptables规则自动修复和持久化
永久封禁管理:
手动封禁: fail2ban-client set sshd banip IP地址
手动解封: quick_ban -u IP地址 # 会自动加入豁免列表
直接解封: fail2ban-client set sshd unbanip IP地址
查看封禁: iptables -L fail2ban-sshd -n
测试连接: ssh -p 22 root@服务器IP
浙公网安备 33010602011771号