一句话精华:一个Python脚本解决FreeSwitch暴力攻击,让SIP服务器从"肉鸡"变"堡垒"
凌晨两点,运维小张被手机铃声吵醒。客户的FreeSwitch服务器又被攻击了——服务器CPU飙到100%,上千个失败的SIP注册请求疯狂涌入,话费账单上出现了几十个陌生的国际长途。这已经是本月第三次了。
"能不能写个脚本,自动把这些攻击者封禁掉?"客户的质问让小张彻夜难眠。如果你也遇到过类似的问题,或者正在运维FreeSwitch服务器,那么这篇文章将为你揭秘:如何用一个Python脚本,构建自动化的SIP攻击防护系统。
读完本文你将收获:
- ✅ 理解VoIP系统常见的4种攻击方式
- ✅ 掌握Fail2Ban防护机制的工作原理
- ✅ 学会用Python自动化部署安全策略
- ✅ 获得一套生产环境可用的防护脚本
🌟 基础篇:FreeSwitch的安全困境
什么是FreeSwitch?
FreeSwitch是一个开源的电信平台,可以理解为"软件版的电话交换机"。它能处理SIP协议的语音、视频通话,广泛应用于呼叫中心、企业电话系统、视频会议等场景。但正因为它需要对外开放端口(通常是5060、5080等),就成了黑客的重点攻击目标。
黑客是怎么攻击的?
想象你家门口的门铃,正常人按一次就够了。但黑客会带一个机器人,每秒按1000次门铃,还用各种假名字尝试开锁。在VoIP世界里,这些攻击主要有四种形式:
1. SIP暴力注册攻击 🔐
黑客用自动化工具疯狂尝试用户名和密码组合,企图破解合法账号。就像小偷拿着一串钥匙挨个试你家的锁。
2. SIP扫描攻击 🔍
攻击者大量发送"探测性请求",扫描哪些用户存在、哪些端口开放。这就像小偷先踩点,看哪户人家有钱。
3. SIP洪水攻击 🌊
短时间内发送海量SIP请求,耗尽服务器资源。相当于用DDoS把你家门铃堵死。
4. SIP暴力破解 💣
针对已知用户名,暴力破解密码。最直接也最暴力的方式。
为什么需要自动化防护?
手动封禁IP?太慢了。等你发现攻击、找到IP、敲完iptables命令,黑客已经尝试了几千次登录。我们需要的是:
- ⚡ 毫秒级响应:发现攻击立即封禁
- 🤖 无人值守:不需要运维24小时盯着
- 🧠 智能识别:区分正常请求和恶意攻击
- 🛡️ 多层防护:结合日志分析、防火墙规则、服务监控
这就是Fail2Ban + Python自动化脚本的价值所在。
最小可运行示例
在深入复杂脚本之前,先看一个最简单的防护逻辑:
import re
import os
# 读取FreeSwitch日志
with open('/var/log/freeswitch/freeswitch.log', 'r') as f:
for line in f:
# 检测失败的注册尝试
if "Can't find user" in line:
# 提取攻击者IP
match = re.search(r'from ([\d.]+)', line)
if match:
attack_ip = match.group(1)
# 封禁IP
os.system(f"iptables -I INPUT -s {attack_ip} -j DROP")
print(f"已封禁攻击IP: {attack_ip}")
这个10行代码已经能实现基础防护了!但生产环境需要更多考虑:误封、配置管理、服务重启、日志轮转……这就是我们要深入分析的753行脚本解决的问题。
🔍 深入篇:自动化防护系统的工作原理
整体架构设计
这套防护系统采用"三层防御"架构:
graph TD
A[攻击者发起SIP请求] --> B[FreeSwitch处理请求]
B --> C[记录失败日志]
C --> D[Fail2Ban监控日志]
D --> E{匹配攻击特征?}
E -->|是| F[触发防御规则]
E -->|否| G[继续监控]
F --> H[添加iptables规则]
H --> I[封禁攻击IP]
I --> J[记录封禁日志]
J --> K[定时解封或永久封禁]
💡 图解说明:
- 第一层:FreeSwitch日志记录(数据源)
- 第二层:Fail2Ban模式匹配(智能大脑)
- 第三层:iptables防火墙(执行层)
核心类设计:FreeSwitchFail2BanSetup
脚本采用面向对象设计,核心类FreeSwitchFail2BanSetup封装了所有部署逻辑:
class FreeSwitchFail2BanSetup:
def __init__(self):
self.freeswitch_conf_path = "/usr/local/freeswitch/conf"
self.freeswitch_log_path = "/var/log/freeswitch"
self.fail2ban_jail_path = "/etc/fail2ban/jail.d"
self.fail2ban_filter_path = "/etc/fail2ban/filter.d"
self.install_type = None
这个类就像一个"防护管家",知道所有配置文件的位置,能处理从检查系统到部署配置的全流程。
安装流程时序图
sequenceDiagram
participant User as 用户
participant Script as Python脚本
participant System as 系统
participant Fail2Ban as Fail2Ban服务
User->>Script: 执行脚本
Script->>System: 检查系统要求
System-->>Script: 返回检查结果
Script->>System: 检查现有安装
System-->>Script: 返回安装状态
alt 需要安装fail2ban
Script->>System: apt install fail2ban
System-->>Script: 安装完成
end
Script->>System: 读取FreeSwitch配置
System-->>Script: 返回端口、日志路径
Script->>System: 创建Filter规则
Script->>System: 创建Jail配置
Script->>Fail2Ban: 重启服务
Fail2Ban-->>Script: 服务启动成功
Script->>System: 封禁已知攻击IP
Script->>User: 显示防护状态
关键技术细节解析
1. 智能日志路径查找
FreeSwitch的日志路径可能因安装方式而异,脚本实现了智能查找机制,尝试多个可能的路径,如果都没找到,还会检查系统日志,最后创建默认路径。这保证了在各种安装环境下都能正常工作。
2. 正则表达式攻击特征匹配
Fail2Ban的核心是正则表达式匹配,脚本为4种攻击创建了专门的Filter:
# SIP暴力注册攻击特征
failregex = ^\\s*.*Can't find user \\[.*@.*\\] from <HOST>.*
^\\s*.*WARNING sofia_reg\\.c:.*Can't find user.*from <HOST>.*
^.*SIP auth failure.* from <HOST>
^.*Registration failed.* from <HOST>
这些正则表达式匹配日志中的攻击模式,<HOST>是Fail2Ban的特殊标记,会自动提取IP地址。
3. 动态配置生成
根据FreeSwitch的实际配置(端口、日志路径),动态生成Jail配置:
def create_freeswitch_jail(self, config):
jail_config = f'''
[freeswitch-sip-registration]
enabled = true
filter = freeswitch-sip-registration
port = {",".join(config["sip_ports"] + config["tls_ports"])}
logpath = {main_log_file}
maxretry = 3
findtime = 300
bantime = 7200
'''
参数含义:
maxretry = 3:300秒内失败3次就封禁findtime = 300:检测时间窗口5分钟bantime = 7200:封禁2小时
4. 带超时的命令执行
防止服务重启卡死,所有关键命令都加了超时控制。这在fail2ban服务重启时特别重要——有时服务会卡住几十秒。
4种防护规则对比
| 防护类型 | 触发条件 | 封禁时长 | 适用场景 | 严格程度 |
|---|---|---|---|---|
| SIP注册攻击 | 5分钟内失败3次 | 2小时 | 暴力破解注册 | ⭐⭐⭐⭐ |
| SIP扫描攻击 | 1分钟内失败2次 | 1小时 | 端口扫描探测 | ⭐⭐⭐⭐⭐ |
| SIP洪水攻击 | 1分钟内失败10次 | 1小时 | DDoS攻击 | ⭐⭐⭐ |
| SIP暴力破解 | 5分钟内失败5次 | 2小时 | 密码爆破 | ⭐⭐⭐⭐ |
💡 策略说明:扫描攻击最严格(2次就封),因为正常用户不会频繁触发"用户不存在"错误。
⚡ 实战篇:真实场景应用
场景1:全新服务器部署
某创业公司刚搭建好FreeSwitch服务器,需要快速部署防护:
# 1. 下载脚本
wget https://example.com/freeswitch_fail2ban_setup.py
# 2. 赋予执行权限
chmod +x freeswitch_fail2ban_setup.py
# 3. root权限运行
sudo python3 freeswitch_fail2ban_setup.py
脚本自动完成:
- ✅ 检测系统环境(Debian/Ubuntu)
- ✅ 安装fail2ban软件包
- ✅ 读取FreeSwitch配置(端口、日志)
- ✅ 创建4种攻击防护规则
- ✅ 备份原有配置
- ✅ 重启服务并验证
实际效果:部署后1小时内,自动封禁了17个扫描IP,服务器CPU使用率从75%降到15%。
场景2:现有系统升级防护
某呼叫中心已部署fail2ban,但只有SSH防护,需要增加SIP防护:
脚本智能识别安装状态:
⚠️ 检测到fail2ban已安装,但未配置FreeSwitch规则
✅ 将仅配置FreeSwitch防护规则
只创建缺失的SIP防护配置,不影响现有SSH等规则。
场景3:紧急处理攻击事件
凌晨3点,FreeSwitch被攻击,管理员紧急执行脚本:
sudo python3 freeswitch_fail2ban_setup.py
脚本除了部署防护规则,还会立即封禁已知攻击IP:
def ban_known_attackers(self):
known_attackers = [
"xx.xx.xx.xx",
"xx.xx.xx.xx",
]
for attack_ip in known_attackers:
os.system(f"iptables -I INPUT -s {attack_ip} -j DROP")
这是一个有趣的设计——脚本内置了"黑名单",可以立即阻断正在进行的攻击。
最佳实践总结
✅ DO(推荐做法)
1. 定期检查封禁日志
# 查看今天封禁的IP
grep "Ban" /var/log/fail2ban.log | grep $(date +%Y-%m-%d)
2. 监控误封情况
# 解封被误封的IP
fail2ban-client set freeswitch-sip-registration unbanip 1.2.3.4
3. 持久化iptables规则
# 保存规则,重启后生效
iptables-save > /etc/iptables/rules.v4
4. 配置日志轮转防止磁盘满
# /etc/logrotate.d/freeswitch
/var/log/freeswitch/*.log {
daily
rotate 7
compress
delaycompress
}
❌ DON'T(避免这样做)
1. 不要盲目降低封禁阈值
maxretry = 1 # ❌ 太严格,容易误封正常用户
2. 不要永久封禁IP
bantime = -1 # ❌ IP可能被重新分配给其他用户
3. 不要忽略服务监控 定期检查fail2ban服务状态:
systemctl status fail2ban
fail2ban-client ping
常见陷阱与避坑指南
陷阱1:日志路径不匹配
现象:防护规则不生效,fail2ban状态显示0个匹配。
原因:配置的logpath与实际日志路径不一致。
解决:
# 检查实际日志路径
ls -la /var/log/freeswitch/
# 手动修改jail配置
vim /etc/fail2ban/jail.d/freeswitch.conf
陷阱2:正则表达式过于宽松
现象:大量正常用户被误封。
原因:failregex匹配了正常的连接失败。
解决:使用fail2ban测试工具验证:
fail2ban-regex /var/log/freeswitch/freeswitch.log \
/etc/fail2ban/filter.d/freeswitch-sip-registration.conf
陷阱3:服务重启卡死
现象:执行脚本时卡在"重启fail2ban服务"步骤。
原因:fail2ban服务偶尔会因socket文件问题卡住。
解决:脚本已内置修复逻辑,自动删除旧socket文件并强制重启。
💡 总结与进阶
知识脉络回顾
让我们串联一下整个防护体系的关键点:
- 攻击识别:通过正则表达式匹配日志中的4种攻击特征
- 自动响应:Fail2Ban检测到攻击后自动添加iptables规则
- 智能配置:Python脚本自动读取FreeSwitch配置并生成防护规则
- 容错处理:处理各种异常情况(日志路径、服务重启、APT源问题)
- 运维友好:一键部署、自动备份、状态监控
记忆口诀
"一日志,二匹配,三封禁,四监控"
- 一日志:确保FreeSwitch正确记录攻击日志
- 二匹配:Fail2Ban用正则表达式匹配攻击特征
- 三封禁:iptables自动封禁攻击IP
- 四监控:持续监控防护状态和误封情况
延伸学习方向
如果你对VoIP安全感兴趣,接下来可以学习:
- SIP协议深度解析:理解INVITE、REGISTER、ACK等消息流程
- FreeSWITCH模块开发:用C/Lua开发自定义防护模块
- iptables进阶:conntrack连接跟踪、ipset批量封禁
- 威胁情报集成:对接威胁情报平台,提前封禁恶意IP
- 蜜罐技术:部署SIP蜜罐,主动诱捕攻击者
推荐工具:
sngrep:SIP协议抓包分析神器sipvicious:SIP安全扫描工具(用于测试防护效果)fail2ban-dashboard:Web可视化监控面板
脚本核心功能清单
这个753行的Python脚本主要实现了以下功能:
- ✅ 智能环境检测:自动识别系统、权限、FreeSwitch安装
- ✅ 灵活安装模式:支持全新安装、仅配置、重新安装三种模式
- ✅ 自动依赖处理:智能修复APT源问题、自动安装fail2ban
- ✅ 动态配置生成:根据实际环境生成最优配置
- ✅ 4层攻击防护:注册、扫描、洪水、暴力破解全覆盖
- ✅ 健壮错误处理:超时控制、服务修复、配置验证
- ✅ 运维管理功能:状态查看、手动封禁、一键卸载
- ✅ 紧急响应能力:内置黑名单立即封禁已知攻击者
📖 附录:完整脚本代码
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
FreeSwitch Fail2Ban 一键安装配置脚本
用于防护FreeSwitch暴力注册和呼叫轰炸攻击
支持Debian系统
"""
import os
import sys
import subprocess
import json
import re
import time
from pathlib import Path
class FreeSwitchFail2BanSetup:
def __init__(self):
self.freeswitch_conf_path = "/usr/local/freeswitch/conf"
self.freeswitch_log_path = "/var/log/freeswitch"
self.fail2ban_jail_path = "/etc/fail2ban/jail.d"
self.fail2ban_filter_path = "/etc/fail2ban/filter.d"
self.config_backup_path = "/tmp/freeswitch_fail2ban_backup"
self.install_type = None # 安装类型:full_install, configure_only, reinstall
def run_command(self, command, check=True, capture_output=True):
"""执行系统命令"""
try:
result = subprocess.run(command, shell=True, check=check,
capture_output=capture_output, text=True)
return result.stdout.strip() if capture_output else ""
except subprocess.CalledProcessError as e:
print(f"❌ 命令执行失败: {command}")
print(f"错误信息: {e.stderr}")
return None
def check_existing_installation(self):
"""检查是否已安装fail2ban"""
print("🔍 检查现有安装...")
# 检查fail2ban是否已安装
fail2ban_installed = self.run_command("which fail2ban-client", check=False) is not None
# 检查FreeSwitch配置是否已存在
freeswitch_config_exists = os.path.exists(os.path.join(self.fail2ban_jail_path, "freeswitch.conf"))
if fail2ban_installed and freeswitch_config_exists:
print("⚠️ 检测到FreeSwitch fail2ban配置已存在")
choice = input("是否要重新安装并覆盖现有配置?[y/N]: ").lower().strip()
if choice != 'y' and choice != 'yes':
print("🚫 用户取消安装")
return False, "existing_installation"
print("🔄 将重新安装并覆盖现有配置...")
return True, "reinstall"
if fail2ban_installed and not freeswitch_config_exists:
print("✅ fail2ban已安装,但未配置FreeSwitch规则")
return True, "configure_only"
if not fail2ban_installed:
print("✅ fail2ban未安装,将进行完整安装")
return True, "full_install"
return True, "unknown"
def check_system_requirements(self):
"""检查系统要求"""
print("🔍 检查系统要求...")
# 检查是否为Debian系统
if not os.path.exists("/etc/debian_version"):
print("❌ 此脚本仅支持Debian系统")
return False
# 检查root权限
if os.geteuid() != 0:
print("❌ 请使用root权限运行此脚本")
return False
# 检查FreeSwitch安装
if not os.path.exists(self.freeswitch_conf_path):
print(f"❌ 未找到FreeSwitch配置目录: {self.freeswitch_conf_path}")
return False
# 检查现有安装状态
install_ok, install_type = self.check_existing_installation()
if not install_ok:
return False
self.install_type = install_type
print("✅ 系统要求检查通过")
return True
def find_freeswitch_logs(self):
"""智能查找FreeSwitch日志文件"""
possible_log_paths = [
"/var/log/freeswitch/freeswitch.log",
"/var/log/freeswitch.log",
"/usr/local/freeswitch/log/freeswitch.log",
"/opt/freeswitch/log/freeswitch.log",
"/var/log/freeswitch/freeswitch.log.1",
"/var/log/freeswitch.log.1"
]
found_logs = []
for log_path in possible_log_paths:
if os.path.exists(log_path):
found_logs.append(log_path)
# 如果都没找到,尝试查找系统日志中的FreeSwitch条目
if not found_logs:
# 检查系统日志
system_logs = ["/var/log/syslog", "/var/log/messages"]
for sys_log in system_logs:
if os.path.exists(sys_log):
# 检查是否有FreeSwitch日志条目
if self.run_command(f"grep -q 'freeswitch' {sys_log}", check=False):
found_logs.append(sys_log)
break
# 如果仍然没找到,创建一个默认的日志路径
if not found_logs:
print("⚠️ 未找到FreeSwitch日志文件,将创建默认配置")
default_log = "/var/log/freeswitch/freeswitch.log"
# 创建日志目录
os.makedirs(os.path.dirname(default_log), exist_ok=True)
# 创建一个空的日志文件
Path(default_log).touch()
found_logs.append(default_log)
return found_logs
def get_freeswitch_config(self):
"""读取FreeSwitch配置信息"""
print("🔍 读取FreeSwitch配置...")
config = {
"sip_ports": [],
"tls_ports": [],
"log_files": []
}
# 读取vars.xml获取端口信息
vars_xml = os.path.join(self.freeswitch_conf_path, "vars.xml")
if os.path.exists(vars_xml):
content = self.read_file(vars_xml)
if content:
# 查找SIP端口
sip_ports = re.findall(r'<X-PRE-PROCESS\s+cmd="set"\s+data="sip_port=(\d+)"', content)
config["sip_ports"] = list(set(sip_ports))
# 查找TLS端口
tls_ports = re.findall(r'<X-PRE-PROCESS\s+cmd="set"\s+data="tls_sip_port=(\d+)"', content)
config["tls_ports"] = list(set(tls_ports))
# 设置默认端口
if not config["sip_ports"]:
config["sip_ports"] = ["5060", "5080"]
if not config["tls_ports"]:
config["tls_ports"] = ["5061", "5081"]
# 智能查找日志文件
config["log_files"] = self.find_freeswitch_logs()
print(f"✅ 发现SIP端口: {', '.join(config['sip_ports'])}")
print(f"✅ 发现TLS端口: {', '.join(config['tls_ports'])}")
print(f"✅ 发现日志文件: {', '.join(config['log_files'])}")
return config
def read_file(self, filepath):
"""读取文件内容"""
try:
with open(filepath, 'r', encoding='utf-8', errors='ignore') as f:
return f.read()
except Exception as e:
print(f"❌ 读取文件失败 {filepath}: {e}")
return None
def write_file(self, filepath, content):
"""写入文件内容"""
try:
os.makedirs(os.path.dirname(filepath), exist_ok=True)
with open(filepath, 'w', encoding='utf-8') as f:
f.write(content)
return True
except Exception as e:
print(f"❌ 写入文件失败 {filepath}: {e}")
return False
def fix_apt_sources(self):
"""修复APT源问题"""
print("🔧 修复APT源问题...")
# 临时禁用有问题的MySQL源
mysql_sources = [
"/etc/apt/sources.list.d/mysql.list",
"/etc/apt/sources.list.d/mysql-apt-config.list"
]
for source_file in mysql_sources:
if os.path.exists(source_file):
backup_file = f"{source_file}.backup"
self.run_command(f"mv {source_file} {backup_file}", check=False)
print(f"✅ 已备份并禁用: {source_file}")
# 尝试更新,忽略GPG错误
print("⚠️ 尝试更新包列表(忽略GPG警告)...")
result = self.run_command("apt update --allow-unauthenticated 2>/dev/null || apt update --allow-insecure-repositories 2>/dev/null || apt update", check=False)
if result is None:
print("⚠️ APT更新失败,尝试使用缓存...")
# 如果更新失败,尝试直接安装
return True
print("✅ APT源问题已处理")
return True
def install_fail2ban(self):
"""安装fail2ban"""
# 根据安装类型决定操作
if hasattr(self, 'install_type') and self.install_type in ['configure_only', 'reinstall']:
print("📦 fail2ban已安装,跳过安装步骤...")
return True
print("📦 安装fail2ban...")
# 修复APT源问题
if not self.fix_apt_sources():
print("⚠️ APT源修复失败,尝试继续安装...")
# 尝试安装fail2ban(多种方法)
install_commands = [
"apt install -y fail2ban",
"apt-get install -y fail2ban --force-yes",
"DEBIAN_FRONTEND=noninteractive apt install -y fail2ban -o Dpkg::Options::=\"--force-confdef\" -o Dpkg::Options::=\"--force-confold\""
]
for cmd in install_commands:
print(f"尝试安装命令: {cmd}")
if self.run_command(cmd, check=False):
print("✅ fail2ban安装成功")
break
else:
print("❌ fail2ban安装失败")
return False
# 启动并启用fail2ban服务
self.run_command("systemctl start fail2ban", check=False)
self.run_command("systemctl enable fail2ban", check=False)
# 验证安装
if self.run_command("which fail2ban-client", check=False):
print("✅ fail2ban安装完成")
return True
else:
print("❌ fail2ban安装验证失败")
return False
def create_freeswitch_filters(self):
"""创建FreeSwitch过滤器规则"""
print("🛡️ 创建FreeSwitch过滤器规则...")
# SIP暴力注册攻击过滤器(增强版本,覆盖更多攻击模式)
sip_registration_filter = '''# Fail2Ban filter for FreeSwitch SIP registration attacks - Enhanced
[Definition]
failregex = ^\\s*.*Can't find user \\[.*@.*\\] from <HOST>.*
^\\s*.*WARNING sofia_reg\\.c:.*Can't find user.*from <HOST>.*
^.*SIP auth failure.* from <HOST>
^.*Registration failed.* from <HOST>
^.*Failed authentication.* from <HOST>
^.*Authentication failed.* from <HOST>
^.*Invalid password.* from <HOST>
^.*Failed to authenticate.* from <HOST>
^.*Bad credentials.* from <HOST>
ignoreregex =
'''
# SIP扫描攻击过滤器(新增,专门处理扫描攻击)
sip_scan_filter = '''# Fail2Ban filter for FreeSwitch SIP scanning attacks
[Definition]
failregex = ^\\s*.*Can't find user .* from <HOST>
^\\s*.*New Channel sofia/internal/.*@<HOST>.*CALL_REJECTED
^\\s*.*WARNING sofia_reg\\.c:.*Can't find user.*from <HOST>.*
^\\s*.*sofia/internal/.*@<HOST>.*Can't find user
ignoreregex =
'''
# SIP呼叫轰炸攻击过滤器(简化版本)
sip_flood_filter = '''# Fail2Ban filter for FreeSwitch SIP flood attacks
[Definition]
failregex = ^.*SIP message length exceeded.* from <HOST>
^.*Invalid SIP packet.* from <HOST>
^.*SIP parsing failed.* from <HOST>
^.*Malformed SIP message.* from <HOST>
^.*Rate limit exceeded.* <HOST>
ignoreregex =
'''
# SIP暴力破解密码过滤器(简化版本)
sip_bruteforce_filter = '''# Fail2Ban filter for FreeSwitch SIP brute force attacks
[Definition]
failregex = ^.*Invalid password.* from <HOST>
^.*Failed to authenticate.* from <HOST>
^.*Bad credentials.* from <HOST>
ignoreregex =
'''
# 写入过滤器文件(包含新增的SIP扫描过滤器)
filters = {
"freeswitch-sip-registration.conf": sip_registration_filter,
"freeswitch-sip-scan.conf": sip_scan_filter, # 新增
"freeswitch-sip-flood.conf": sip_flood_filter,
"freeswitch-sip-bruteforce.conf": sip_bruteforce_filter
}
for filename, content in filters.items():
filepath = os.path.join(self.fail2ban_filter_path, filename)
# 如果是重新安装,先删除旧文件
if hasattr(self, 'install_type') and self.install_type == 'reinstall':
if os.path.exists(filepath):
os.remove(filepath)
print(f" 删除旧文件: {filename}")
if self.write_file(filepath, content):
print(f"✅ 创建过滤器: {filename}")
else:
print(f"❌ 创建过滤器失败: {filename}")
return False
return True
def create_freeswitch_jail(self, config):
"""创建FreeSwitch jail配置"""
print("🔒 创建FreeSWitch jail配置...")
# 使用主日志文件,避免重复logpath选项
main_log_file = config["log_files"][0] if config["log_files"] else "/var/log/freeswitch.log"
jail_config = f'''# FreeSwitch Fail2Ban Configuration
# Generated by FreeSwitch Fail2Ban Setup Script
# Enhanced with SIP scanning attack protection
[DEFAULT]
bantime = 3600
findtime = 600
maxretry = 5
backend = auto
banaction = iptables-allports
protocol = all
# SIP Registration Attack Protection
[freeswitch-sip-registration]
enabled = true
filter = freeswitch-sip-registration
port = {",".join(config["sip_ports"] + config["tls_ports"])}
logpath = {main_log_file}
maxretry = 3
findtime = 300
bantime = 7200
# SIP Scanning Attack Protection (NEW)
[freeswitch-sip-scan]
enabled = true
filter = freeswitch-sip-scan
port = {",".join(config["sip_ports"] + config["tls_ports"])}
logpath = {main_log_file}
maxretry = 2
findtime = 60
bantime = 3600
# SIP Flood Attack Protection
[freeswitch-sip-flood]
enabled = true
filter = freeswitch-sip-flood
port = {",".join(config["sip_ports"] + config["tls_ports"])}
logpath = {main_log_file}
maxretry = 10
findtime = 60
bantime = 3600
# SIP Brute Force Attack Protection
[freeswitch-sip-bruteforce]
enabled = true
filter = freeswitch-sip-bruteforce
port = {",".join(config["sip_ports"] + config["tls_ports"])}
logpath = {main_log_file}
maxretry = 5
findtime = 300
bantime = 7200
'''
jail_file = os.path.join(self.fail2ban_jail_path, "freeswitch.conf")
if self.write_file(jail_file, jail_config):
print("✅ 创建jail配置完成")
return True
else:
print("❌ 创建jail配置失败")
return False
def backup_config(self):
"""备份现有配置"""
print("💾 备份现有配置...")
os.makedirs(self.config_backup_path, exist_ok=True)
# 备份fail2ban配置
if os.path.exists("/etc/fail2ban"):
backup_cmd = f"cp -r /etc/fail2ban {self.config_backup_path}/fail2ban_backup_$(date +%Y%m%d_%H%M%S)"
self.run_command(backup_cmd, check=False)
print("✅ 配置备份完成")
def run_command_with_timeout(self, command, timeout=30):
"""执行命令并设置超时"""
try:
print(f" 执行命令: {command[:50]}...")
result = subprocess.run(command, shell=True, timeout=timeout,
capture_output=True, text=True)
return result.returncode == 0, result.stdout.strip(), result.stderr.strip()
except subprocess.TimeoutExpired:
print(f" ⏰ 命令执行超时 ({timeout}秒): {command[:50]}...")
return False, "", "Command timeout"
except Exception as e:
print(f" ❌ 命令执行异常: {e}")
return False, "", str(e)
def restart_fail2ban(self):
"""重启fail2ban服务"""
print("🔄 重启fail2ban服务...")
print(" ⏳ 这可能需要几秒钟时间...")
# 检查配置语法(带超时)
print(" 🔍 检查配置语法...")
success, stdout, stderr = self.run_command_with_timeout("fail2ban-client -t", timeout=15)
if success:
print("✅ 配置语法检查通过")
else:
print("⚠️ 配置语法检查失败,尝试重启服务...")
# 先停止服务
print(" 🛑 停止现有服务...")
stop_commands = [
"systemctl stop fail2ban",
"service fail2ban stop",
"/etc/init.d/fail2ban stop"
]
for cmd in stop_commands:
success, _, _ = self.run_command_with_timeout(cmd, timeout=10)
if success:
print(" ✅ 服务已停止")
break
# 等待服务完全停止
print(" ⏳ 等待服务停止...")
time.sleep(3)
# 启动服务
print(" 🚀 启动fail2ban服务...")
start_commands = [
"systemctl start fail2ban",
"service fail2ban start",
"/etc/init.d/fail2ban start"
]
service_started = False
for cmd in start_commands:
print(f" 尝试: {cmd}")
success, stdout, stderr = self.run_command_with_timeout(cmd, timeout=15)
if success:
print("✅ fail2ban服务启动成功")
service_started = True
break
else:
print(f" 失败: {stderr[:100]}")
if not service_started:
print("❌ fail2ban服务启动失败")
return False
# 等待服务完全启动
print(" ⏳ 等待服务完全启动...")
time.sleep(5)
# 验证服务状态
print(" 🔍 验证服务状态...")
for i in range(3): # 尝试3次
success, stdout, stderr = self.run_command_with_timeout("fail2ban-client ping", timeout=10)
if success:
print("✅ 服务通信正常")
return True
else:
print(f" 第{i+1}次验证失败,等待后重试...")
time.sleep(2)
print("⚠️ 服务启动成功但通信验证失败")
print(" 💡 这通常不是问题,服务可能需要更多时间启动")
return True # 仍然认为重启成功
def fix_fail2ban_service(self):
"""修复fail2ban服务问题"""
print("🔧 修复fail2ban服务问题...")
# 检查socket文件问题
socket_path = "/var/run/fail2ban/fail2ban.sock"
if os.path.exists(socket_path):
print(" 删除旧的socket文件...")
try:
os.remove(socket_path)
print(" ✅ 已删除旧socket文件")
except Exception as e:
print(f" ⚠️ 删除socket文件失败: {e}")
# 强制重启服务
print(" 强制重启fail2ban服务...")
restart_commands = [
"systemctl restart fail2ban",
"systemctl reset-failed fail2ban",
"systemctl daemon-reload",
"systemctl restart fail2ban"
]
for cmd in restart_commands:
success, _, _ = self.run_command_with_timeout(cmd, timeout=20)
if not success and "daemon-reload" not in cmd:
print(f" ⚠️ 命令执行失败: {cmd}")
elif success:
print(f" ✅ 执行成功: {cmd}")
# 等待服务启动
print(" 等待服务启动...")
for i in range(10): # 最多等待10次,每次2秒
time.sleep(2)
success, _, _ = self.run_command_with_timeout("fail2ban-client ping", timeout=5)
if success:
print(" ✅ fail2ban服务通信恢复")
return True
print(f" 第{i+1}次尝试连接...")
# 如果还是无法通信,尝试最后的修复方法
print(" 尝试最后的修复方法...")
fix_commands = [
"pkill -f fail2ban-server",
"sleep 2",
"systemctl start fail2ban",
"sleep 3"
]
for cmd in fix_commands:
self.run_command(cmd, check=False)
# 最终验证
success, _, _ = self.run_command_with_timeout("fail2ban-client ping", timeout=5)
if success:
print(" ✅ 服务修复成功")
return True
else:
print(" ❌ 服务修复失败,但配置已安装")
print(" 💡 请手动检查: systemctl status fail2ban")
return False
def show_status(self):
"""显示防护状态"""
print("📊 查看防护状态...")
# 先尝试修复服务通信问题
success, _, _ = self.run_command_with_timeout("fail2ban-client ping", timeout=5)
if not success:
print("⚠️ fail2ban服务通信异常,尝试修复...")
self.fix_fail2ban_service()
status = self.run_command("fail2ban-client status", check=False)
if status:
print("fail2ban 总体状态:")
print(status)
else:
print("⚠️ 无法获取fail2ban状态信息")
# 查看各个jail状态(包含新增的扫描防护)
jails = ["freeswitch-sip-registration", "freeswitch-sip-scan", "freeswitch-sip-flood", "freeswitch-sip-bruteforce"]
for jail in jails:
jail_status = self.run_command(f"fail2ban-client status {jail}", check=False)
if jail_status:
print(f"\n{jail} 状态:")
print(jail_status)
else:
print(f"\n{jail}: 无法获取状态信息")
# 显示系统服务状态
print(f"\n系统服务状态:")
service_status = self.run_command("systemctl status fail2ban --no-pager", check=False)
if service_status:
print(service_status[:500]) # 限制长度
def uninstall(self):
"""卸载fail2ban配置"""
print("🗑️ 卸载FreeSwitch fail2ban配置...")
# 停止fail2ban服务
self.run_command("systemctl stop fail2ban", check=False)
# 删除配置文件(包含新增的SIP扫描过滤器)
files_to_remove = [
os.path.join(self.fail2ban_jail_path, "freeswitch.conf"),
os.path.join(self.fail2ban_filter_path, "freeswitch-sip-registration.conf"),
os.path.join(self.fail2ban_filter_path, "freeswitch-sip-scan.conf"), # 新增
os.path.join(self.fail2ban_filter_path, "freeswitch-sip-flood.conf"),
os.path.join(self.fail2ban_filter_path, "freeswitch-sip-bruteforce.conf")
]
for file_path in files_to_remove:
if os.path.exists(file_path):
os.remove(file_path)
print(f"✅ 删除配置文件: {os.path.basename(file_path)}")
# 重启fail2ban
self.run_command("systemctl start fail2ban", check=False)
print("✅ FreeSwitch fail2ban配置已卸载")
def install(self):
"""完整安装流程"""
print("🚀 开始FreeSwitch fail2ban防护安装...")
print("=" * 50)
# 检查系统要求
if not self.check_system_requirements():
return False
# 备份配置
self.backup_config()
# 获取FreeSwitch配置
config = self.get_freeswitch_config()
if not config or not config["log_files"]:
print("❌ 无法获取FreeSwitch配置信息")
return False
# 安装fail2ban
if not self.install_fail2ban():
return False
# 创建过滤器
if not self.create_freeswitch_filters():
return False
# 创建jail配置
if not self.create_freeswitch_jail(config):
return False
# 重启服务
if not self.restart_fail2ban():
return False
# 显示状态
self.show_status()
# 手动封禁已知的攻击IP
self.ban_known_attackers()
print("\n" + "=" * 50)
print("🎉 FreeSwitch fail2ban防护安装完成!")
print("📁 配置备份位置:", self.config_backup_path)
print("🛡️ 防护功能:")
print(" ✅ SIP注册攻击防护")
print(" ✅ SIP扫描攻击防护(新增)")
print(" ✅ SIP洪水攻击防护")
print(" ✅ SIP暴力破解防护")
print(" ✅ 已知攻击IP已被封禁: 91.208.92.186, 108.181.5.53")
print("\n📋 管理命令:")
print(" 查看状态: fail2ban-client status")
print(" 解封IP: fail2ban-client set [jail名称] unbanip [IP地址]")
print(" 查看日志: tail -f /var/log/fail2ban.log")
print(" 手动封禁: iptables -I INPUT -s [IP] -j DROP")
print("🔄 卸载防护: python3 freeswitch_fail2ban_setup.py --uninstall")
return True
def main():
if len(sys.argv) > 1 and sys.argv[1] == "--uninstall":
# 卸载模式
setup = FreeSwitchFail2BanSetup()
if setup.check_system_requirements():
setup.uninstall()
else:
print("❌ 无法执行卸载操作,请检查系统要求")
else:
# 安装模式
setup = FreeSwitchFail2BanSetup()
if setup.install():
print("\n✅ 安装成功!FreeSwitch现在受到fail2ban保护。")
else:
print("\n❌ 安装失败!请检查错误信息并重试。")
sys.exit(1)
if __name__ == "__main__":
main()