缘起

前阵子家里一台 黑群晖 NAS 因为端口暴露在公网,被勒索病毒敲了一回,收拾完残局之后我就一直在补家里的网络安全课。其中一个问题始终没解决:路由器每天到底被多少人敲门,我完全看不见。哪些是我自己在外面的正常登录,哪些是扫描和爆破,心里没数。

家里的路由器一直用的是OpenWrt。

OpenWrt 自带的日志其实能看一部分:SSH 和 LuCI 的登录成败会写进系统日志(logread | grep -i dropbear),防火墙区域打开"启用日志"后,被拒绝的入站包也会记录。但光看日志是被动的,我想要的是三件套:已知的坏 IP 直接拦在门外、反复登录失败的自动拉黑、所有拦截都有据可查。找了一圈,官方软件源里的 banIP 正好就是干这个的,这篇就是我把它装到家里主路由上的全过程,包括三个差点让我翻车的坑。

黑群晖被勒索病毒加密。好在没有重要资料,没有什么损失,只是家里一些监控视频而已。

我的环境

  • x86 小主机虚拟化(PVE),OpenWrt 系固件 24.10.6(ImmortalWrt,与 OpenWrt 24.10 同源)做全家主路由
  • 电信宽带 PPPoE 拨号,有公网 IPv4,也下发 IPv6
  • 家里还有一条电信 IPTV 专线,接口和宽带同在一个防火墙区域——记住这一点,第三个坑就出在这
  • 内网一台 Debian NAS,对外只留了一个非标准端口的 SSH(仅密钥登录)
  • 内网一台黑群晖,暴露了一个公网端口在外面,被侵
  • 路由器上还跑着 daed 做透明代理分流

banIP 是什么

banIP 是 OpenWrt 官方源里的老牌插件(带 LuCI 界面),干三件事:

  1. 订阅威胁情报名单:全球已知的扫描器、僵尸网络、爆破源 IP 段,下载后生成 nftables 集合,在防火墙层直接拦截;
  2. 日志监控自动封禁:盯着系统日志,谁对 SSH(dropbear)、LuCI 反复登录失败,就把谁自动加进本地黑名单——相当于路由器版的 fail2ban;
  3. 全程留痕:拦截事件写进系统日志,LuCI 里有处理日志和报表页,哪个 IP、命中哪份名单、拦了多少次,一目了然。

它工作的三条链也值得先搞清楚,后面配错方向时能自己排:

  • WAN-Input:打路由器本身的流量
  • WAN-Forward:打内网设备的流量(端口转发走这条)
  • LAN-Forward:内网往外发的流量

安装

opkg update
opkg install luci-app-banip

装完在 LuCI 的"服务"菜单里就能看到 banIP。先别急着点启用,按下面配置完再开,不然要踩坑一。

配置要点(逐页)

banIP 的设置有七个标签页,大部分保持默认就行,真正要动的就几处:

源选择(最重要):在"黑名单源"里勾选这四份起步:

  • cinsscore、debl、turris:入站方向,都是已知攻击者/扫描器 IP
  • doh:出站方向,阻止内网设备绕开本地 DNS 直连公共加密 DNS

上面四份合计三万六千多条。不要贪多,名单不是越多越好,体积和误伤概率都跟着涨。country(按国家封)、asn 这类整段封的也别碰,家庭网络用不上。本地源设置里确认"自动白名单""自动黑名单"开着,"自动允许上行线路"选"子网"——它会把自家上行地址自动放行,防误伤。

日志设置:

  • 日志数(触发封禁次数)从默认 1 改成 3。默认失败 1 次就拉黑太敏感了,自己输错一次路由器密码都能把自己关门外;3 次挡爆破足够
  • 勾上"记录传入数据"。被名单拦下的入站包写不写日志就看它,我要的"谁在扫我"的记录就来自这里
  • 日志限制从 100 调到 300。日志监控只扫最近 N 条日志,家里日志滚得快,100 条容易把失败记录冲掉
  • 日志项保持预置的「dropbear 登录失败」「LuCI 登录失败」两条

常规设置:启动触发接口选 wan——PPPoE 用户必设,名单要在拨上号之后才下载得下来,不设可能开机抢跑、拉不到名单空转。触发延时保持默认。

其余各页:高级设置、表/链设置、源/集合设置基本保持默认(封禁策略记得是"丢弃 drop",静默丢弃不给扫描者任何回应);电子邮件设置先跳过(要另外装 msmtp,以后再说)。

还有一个防自锁动作:把自家内网网段(我的是 10.0.0.0/24)和公司固定出口 IP 加进 banIP 的本地白名单,免得日志联动封禁误伤自己人。

踩坑实录

坑一:启用有两道开关,只开一道直接 error

我配完所有页面,勾上"已启用",保存应用——状态页显示 error,元素数量 0,日志里翻来覆去只有一句话:开始十几条是 banIP is disabled,最后变成:

banIP service autostart is disabled

原来 banIP 有两道开关:设置页里勾的"已启用"只是它自己配置里的开关,OpenWrt 系统服务层面的自启还没打开,系统发现这个服务没在启动项里登记,每次都拒绝拉起它。补上就好了:

/etc/init.d/banip enable
/etc/init.d/banip restart

等价于 LuCI 里"系统 → 启动项"把 banip 设为启用。这一课很朴素:OpenWrt 上装完服务,先确认启动项。

坑二:名单下载全部失败,凶手是坏掉的 curl

开关补上后状态变 active 了,但元素数量只有 2(那是自动放行的自家上行地址),四份名单一份都没进来。看日志,七个下载任务(四份源的 v4/v6,cinsscore 只有 v4)在同一秒内全部失败,返回码整齐划一 rc: 4;换 reload 再试,还是一样。

全部同一秒同一个码倒下,说明压根没真正出门——不是源站挂了(那会拖几秒到几分钟、各有先后),是下载工具在调用层就死了。banIP 只记了个返回码,把工具的原话吞了,那就手动复现它干的事,让 curl 自己说人话:

curl -v -o /dev/null https://cinsscore.com/list/ci-badguys.txt

真相大白:

Error relocating /usr/bin/curl: curl_multi_notify_enable: symbol not found
Error relocating /usr/bin/curl: curl_multi_get_offt: symbol not found

curl 本人根本启动不了——它要用的新函数在系统里的 libcurl 库里不存在,curl 二进制比 libcurl 库新,版本错位了,动态链接器在程序第一行代码之前就把它掐死。banIP 的自动检测只看"curl 这个文件在不在",在,所以状态里写着 fetch: curl 看着健康,实际一干活就全灭。

这病是怎么得的?典型的单个包 opkg upgrade 的后遗症:某个时刻 curl 被单独升到了软件源的新版,配套的 libcurl 没跟着走。修复就是把两口子对齐:

opkg update
opkg install --force-reinstall libcurl4 curl
curl -V   # 能正常打印版本就是好了
/etc/init.d/banip reload

这次 reload 跑了 23 秒(之前那趟是 0 秒,一看就不对),36,000 多个元素全部就位。

总教训:OpenWrt 上绝对不要对单个包跑 opkg upgrade。系统升级走 sysupgrade 整包刷,单个包升级迟早给你埋这种二进制和库对不上版的雷,而且平时根本看不出来,等到某个依赖它的服务半夜罢工才爆。

坑三:上线几分钟,IPTV 被掐了

名单刚跑起来不到十分钟,电视没信号了。

这个坑的根在我的网络结构:banIP 的自动检测是按防火墙区域划界的,我的 IPTV 接口和 WAN 划在同一个 wan 区域里,于是它把 IPTV 接口也当成 WAN 在管——证据就在状态页里:wan-if 列表中赫然混着 IPTV,被自动放行的"上行线路"居然是我 IPTV 接口的静态地址(10.108.48.65),而不是宽带真正的公网地址。它那些链和预路由防护(丢弃无效连接跟踪包、UDP 泛洪阈值之类)和组播电视流天生不对付,流被当异常流量掐了,电视自然黑屏。

我家的设备关系先列清楚,这个坑看懂了就不难(坑就坑在 banIP 不懂你的拓扑,它只认区域):

设备归属
eth0、eth1LAN 网桥的成员
eth2IPTV 接口的设备
eth3宽带物理口(PPPoE 的底层通道)
pppoe-WAN拨号隧道设备,真实 IP 流量走这里

修法是关掉自动检测,手动指定:LuCI → banIP → 常规设置,取消勾选"自动检测",填:

  • IPv4 接口:wan
  • IPv6 接口:WAN_6
  • 设备:pppoe-WAN

这里有个很容易填错的点,特意强调:设备不要填物理口 eth3。banIP 是在 IP 层做匹配的,eth3 上跑的是 PPPoE 封装帧(还没拆封的信封),名单挂在它身上等于白挂,不会报错、会装模作样地 active,但什么都拦不到——比报错更坑。也不要填 eth2,那正是要摘出去的 IPTV。

保存重启后验收,三项全对才算完工:

  1. status 的 active_devices 只剩 wan: pppoe-WAN / wan-if: WAN, WAN_6,IPTV 和 eth2 消失
  2. active_uplink 变回宽带真正的公网地址(14.216.134.x)和 WAN_6 的 IPv6 地址——白名单放行的终于是正主
  3. 电视恢复播放;过几分钟看日志,新的拦截记录全是 IN=pppoe-WAN,这是名单在真 WAN 上干活的实证

插曲:日志里冲着 NAS 的流量,先认领再紧张

拦截日志里有一组很扎眼:多个名单上的 IP 在往我 NAS 的 UDP 50001、50040 端口发小包,被 WAN-Forward 拦下。第一反应是"NAS 被人盯上了?",查了一条命令就破案了:

ss -lunp | grep -E '50001|50040'

两个端口上各蹲着一个 qbittorrent-nox——是自家下载软件的 BT 流量。有些 BT 节点本身就在灰名单上,被拦掉无伤大雅,peer 池子里多得是。留个经验:转发链的日志先认领服务再下结论,家里跑着什么自己先有数,免得虚惊。

上线效果

banIP 正式上岗两分钟内,日志就开始出战果了(我的公网 IP 已打码):

banIP/inbound/drop/debl.v4: IN=pppoe-WAN OUT= SRC=35.210.98.50 DST=14.216.134.x PROTO=TCP DPT=21 SYN
banIP/inbound/drop/cinsscore.v4: IN=pppoe-WAN OUT= SRC=130.193.77.236 DST=14.216.134.x PROTO=TCP DPT=16428 SYN
banIP/inbound/drop/turris.v4: IN=pppoe-WAN OUT=br-lan SRC=115.51.39.171 DST=10.0.0.3 PROTO=UDP DPT=50040

读法:OUT 为空是打路由器本机的(21 端口 FTP 试探、随机高端口 SYN 撒网扫描),OUT=br-lan 是冲内网设备去的。两分钟近二十条,全是 drop——这就是公网 IP 的日常:不是"被针对了",是互联网的背景噪音一直都在,只是以前我看不见。现在它每一条都被记下来、拦在门外,我偶尔翻翻 logread | grep banIP 就心里有数。想看总账就跑:

/etc/init.d/banip report    # 各集合拦了多少,一目了然
/etc/init.d/banip search <某个IP>   # 查某个 IP 在不在名单里,服务连不上先查它

第二条是排障口诀:启用 banIP 后某个网站/服务突然连不上,先 search 目标 IP,查到了就是名单误伤,加进本地白名单即可,这是这类情报拦截唯一的常见副作用。

顺手给 Debian 也加道锁:Fail2ban

路由器管大门,主机还得管自己的门锁。我 NAS 对外开着 SSH(非标准端口、仅密钥登录),爆破者连密码都试不了,但无效连接和密钥探测的噪音不断,装 Fail2ban 按行为封禁,正好和 banIP 按名单拦截互补:名单拦已知坏人,行为封现行犯。

Debian 官方源里有 Fail2ban(Debian 13 是 1.1.0),但不在默认安装清单里,要自己装,一并带上读 systemd 日志要用的组件:

apt update
apt install -y fail2ban python3-systemd

配置不要改自带的 jail.conf(升级会被覆盖),新建 /etc/fail2ban/jail.local:

[DEFAULT]
ignoreip = 127.0.0.1/8 ::1 10.0.0.0/24 100.64.0.0/10
bantime = 1h
findtime = 10m
maxretry = 5
banaction = nftables-multiport
[sshd]
enabled = true
port = <你自己的SSH端口>
backend = systemd
mode = aggressive

几个要点,都是对着家庭 NAS 场景选的:

  • port 必须填 sshd 的真实端口,填默认 22 就封了个寂寞
  • backend = systemd 直接读系统日志,不依赖 auth.log 文件在不在;Debian 13 默认就是这个路子,Debian 12 建议手动写死
  • mode = aggressive:纯密钥登录没有"密码错误"这种日志行,普通模式抓不到连接探测;aggressive 连协商失败、预认证断开都算数
  • ignoreip 把本机、内网、Tailscale 网段(100.64.0.0/10)都放行,防自锁
  • nftables-multiport 的封禁范围是"这个 IP → 本机这个端口"这一条路,罚当其罪;想要"一次触发全端口封"可以换 nftables-allports,我家暴露面就一个 SSH,没必要

重启后 fail2ban-client status sshd 能看到 jail 在列就成了。

日常维护

  • 更新名单用 reload,不用 restart:restart 只恢复已缓存的名单,只有 reload 才真正重新下载。可以挂个计划任务(/etc/crontabs/root)每周自动拉一次:15 4 * * 1 /etc/init.d/banip reload
  • 误伤处理:先 search 确认,再加白名单
  • 电视、盒子这类走专线/专接口的业务,上线任何防火墙类插件后都要第一时间验一遍——这次 IPTV 的教训就是,插件按它理解的拓扑干活,你的拓扑比它以为的复杂时,先遭殃的总是这类"第二条线"

总结

这一晚的收获按重要性排:

  1. 看得见,才谈得上安全。banIP 的名单拦截是锦上添花,真正让我安心的是日志——谁在敲门、敲哪个门、拦没拦住,从此有据可查。
  2. 插件不懂你的拓扑。自动检测在简单网络里是省事,在宽带+IPTV+IPv6 多线并存的环境里必然认错亲。手动指定接口多花两分钟,省掉一晚上的电视黑屏。
  3. 不要 opkg upgrade 单个包,这句话值得贴在路由器旁边。这次 curl 和 libcurl 的版本错位潜伏了不知道多久,直到 banIP 来调用它才爆炸——你系统里可能也埋着这种雷,某个服务突然罢工时,先想想是不是这类问题。
  4. 防御要分层:路由器上 banIP 按名单守大门,主机上 Fail2ban 按行为守门锁,基础防火墙默认拒绝打底。任何一层都不是银弹,叠起来才是普通家庭能长期维持的安全水位。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注