背景
博客服务器(阿里云 ECS,CentOS 6.3)运行着一个旧的防攻击脚本,通过分析 Apache access_log 识别恶意请求,逐条 iptables -A 封禁 IP。随着黑名单增长,iptables 逐条匹配的性能问题日益突出。
听说 iptables 黑名单到 1 万条性能就明显不行,而 ipset 支撑到 10 万条都没问题。决定迁移到 ipset 方案。
iptables vs ipset
| iptables | ipset | |
|---|---|---|
| 匹配方式 | 逐条遍历规则 | 哈希查找 O(1) |
| 1万条IP | 10000条规则 | 1条规则 + 1个集合 |
| 性能 | 规则多时线性下降 | 几乎无影响 |
| 动态更新 | 操作整个链 | 直接操作集合 |
| 存储 | 内核链表 | 内核哈希表 |
一句话:ipset 是 iptables 的 IP 集合加速器,规则多时用 ipset。
环境
- 系统:CentOS 6.3 (Final)
- 内核:2.6.32
- iptables:v1.4.7
- ipset:v6.11(已预装)
- Apache:httpd,access_log 在 /var/log/httpd/access_log
- cron:每天凌晨 3 点自动重启
踩坑记录(6个坑,全部填平)
坑一:iptables –matchset 语法不兼容
CentOS 6 的 iptables v1.4.7 不支持 --matchset,需要用旧语法 --set:
# CentOS 7+ (新语法)
iptables -A INPUT -m set --matchset blacklist src -j DROP
# CentOS 6 (旧语法)
iptables -A INPUT -m set --set blacklist src -j DROP
坑二:ipset save 不支持 -f 参数
ipset v6.11 不支持 -f 参数指定输出文件,需要手动重定向:
# 错误(v6.11 不支持)
ipset save blacklist -f /etc/sysconfig/ipset
# 正确
ipset save blacklist > /etc/sysconfig/ipset
坑三:ipset save 默认输出到屏幕
ipset v6.11 的 save 命令默认输出到 stdout,不会自动写文件。脚本中必须用重定向 > 写入文件,否则重启后集合丢失。
坑四:旧脚本未做 iptables 持久化
原来的防攻击脚本每次 iptables -A 后没有执行 iptables-save,服务器每天凌晨 3 点自动重启,所有封禁规则全部丢失。迁移到 ipset 后修复了这个问题。
坑五:两个脚本同时跑
部署新脚本时忘了删除旧脚本的 cron 任务,导致两个脚本同时运行。旧脚本用 iptables,新脚本用 ipset,互相干扰。清理后正常。
坑六:cron 环境无 PATH 导致持久化文件被清空
这是最隐蔽的一个坑。cron 运行环境极简,PATH 变量不包含 /usr/sbin 和 /sbin,导致 ipset 和 iptables-save 命令找不到。而 shell 重定向 > 会先清空目标文件再执行命令,命令找不到输出为空,文件就被清空了。
表现:手动运行脚本一切正常(交互 shell 有完整 PATH),但 cron 每次执行后 /etc/sysconfig/ipset 和 /etc/sysconfig/iptables 都变成 0 字节。
修复:在脚本开头显式设置 PATH:
#!/bin/bash
PATH=/usr/sbin:/sbin:/usr/bin:/bin:/usr/local/sbin
export PATH
坑七:iptables -C 不支持 -m set 导致规则重复添加
脚本用 iptables -C 检测规则是否已存在(幂等添加),但 CentOS 6 的 iptables v1.4.7 的 -C 命令不支持 -m set --set 参数,报错 option -C requires an argument,检测总是失败,每轮 cron 都会重复添加一条规则。
表现:iptables INPUT 链中出现 5 条相同的 blacklist DROP 规则。
修复:改用 iptables -S + grep 检测:
# 旧方式(CentOS 6 不支持)
if ! iptables -C INPUT -m set --set "$SET_NAME" src -j DROP 2>/dev/null; then
# 新方式(兼容 CentOS 6)
if ! iptables -S INPUT 2>/dev/null | grep -q "match-set $SET_NAME"; then
最终方案
防攻击脚本(block-bots-ipset.sh)
核心逻辑:
- 脚本开头设置 PATH(解决 cron 环境问题)
- 首次运行创建 ipset 集合(hash:ip, maxelem 100000)+ iptables 引用规则
- 增量读取 access_log(基于文件偏移量,只读新增部分)
- 用 awk 匹配 11 类攻击特征
- 新攻击 IP 通过
ipset add加入集合 - 有新增则
ipset save > /etc/sysconfig/ipset+iptables-save > /etc/sysconfig/iptables持久化 - iptables 引用规则用
iptables -S | grep幂等检测,避免重复添加
11 条攻击检测规则
| 规则 | 检测内容 | 特征示例 |
|---|---|---|
| 1 | 5xx 服务器错误 | 注入触发崩溃 |
| 2 | SQL 注入 | UNION, SELECT, SLEEP(), DROP, INSERT |
| 3 | XSS 攻击 | %3Cscript, javascript:, onerror= |
| 4 | 路径穿越 | ../, %2e%2e, etc/passwd |
| 5 | 空 UA 扫描器 | User-Agent 为 “-“ |
| 6 | 敏感文件扫描 | .env, .git, .sql, .bak, wp-config, xmlrpc.php |
| 7 | HEAD 探测 | HEAD 方法扫描 |
| 8 | 恶意爬虫 | MJ12bot, YisouSpider, DotBot |
| 9 | 攻击工具 | sqlmap, nikto, nmap, acunetix, nessus, wpscan |
| 10 | RFI/LFI | php://input, php://filter, data:// |
| 11 | 命令注入 | ;wget, ;curl, eval(, system(, exec( |
持久化方案
| 组件 | 持久化方式 |
|---|---|
| ipset 集合 | ipset save > /etc/sysconfig/ipset + rc.local 开机 ipset restore |
| iptables 规则 | iptables-save > /etc/sysconfig/iptables + iptables 服务开机自启 |
| 脚本定时执行 | cron 每 5 分钟一次 |
| 封禁日志 | /var/log/block-bots.log,超过 10MB 自动轮转 |
迁移过程
- 检查环境:确认 ipset v6.11 已安装,CentOS 6.3
- 发现 iptables 为空:之前封的 IP 因重启全丢,旧脚本未持久化
- 部署新脚本:上传 block-bots-ipset.sh,首次运行创建集合 + iptables 引用
- 修复语法兼容:–matchset -> –set,-f -> 重定向
- 导入历史黑名单:从 /var/log/block-bots.log 提取 855 个 IP 导入 ipset
- 配置 cron:每 5 分钟执行,删除旧脚本 cron
- 配置持久化:ipset save + iptables-save + rc.local
- 修复 cron PATH 问题:脚本开头加 PATH 和 export
- 修复规则重复添加:iptables -C 改为 iptables -S + grep
- 全面检查:12 项检查脚本全部通过
最终效果
- 855 个攻击 IP 已封禁
- iptables 只需 1 条规则匹配整个集合
- 每 5 分钟自动检测新攻击并封禁
- 每天凌晨重启后自动恢复所有规则
- 封禁在 iptables 层生效,攻击 IP 进不了 Apache,不消耗服务器资源
经验总结
- CentOS 6 语法差异多:iptables 用
--set不用--matchset,ipset save 不支持-f,都要手动重定向 - 持久化要做全套:ipset save + iptables-save + rc.local + iptables 服务开机自启,缺一不可
- 部署新脚本要清理旧任务:两个脚本同时跑会互相干扰
- ipset save 的输出要重定向:v6.11 默认输出到屏幕,不写文件,重启后集合丢失
- cron 环境极简,必须手动设 PATH:否则
>重定向先清空文件,命令又找不到,文件就变空了。这个坑最隐蔽,手动测试一切正常,只有 cron 执行时才出问题 - iptables -C 不支持 -m set:CentOS 6 的幂等检测要改用
iptables -S | grep,否则每轮 cron 都重复添加规则 - 检查脚本很重要:写了 12 项检查脚本,一次性发现所有遗漏
附录:检查脚本
写了 12 项检查脚本(check-ipset-status.sh),覆盖:
- ipset 版本
- 集合状态与 IP 总数
- iptables 引用规则
- iptables 持久化文件
- iptables 服务开机自启
- ipset 持久化文件
- rc.local 开机恢复
- cron 定时任务
- 脚本文件与语法检查
- 封禁日志状态
- 实时拦截统计
- 抽样验证已封禁 IP