Windows 版 fail2ban:真正的替代方案

Windows 上没有 fail2ban。本文讲清楚自己用 PowerShell 写的版本长什么样、它究竟会在哪里出问题,以及各种替代方案实际能提供什么。

阅读约 8 分钟

这个问题为什么反复出现

在 Linux 上,fail2ban 是应对密码猜解的条件反射式答案:它跟踪日志、用正则匹配失败行,并为越过阈值的地址插入一条防火墙规则。它很小,它是标准配置,过去十五年写的每一份加固指南里都有它。

转向 Windows 的管理员去找同一个工具,却什么也找不到。fail2ban 是 Python 加 iptables,在 Windows 上没有任何有意义的运行方式——既没有可跟踪的 syslog,也没有可写入的 iptables。Windows 有的是安全事件日志和 Windows 防火墙:同样的两种原料,只是没有连起来。

所以真正的问题并不是「怎么在 Windows 上装 fail2ban」,而是「什么东西把事件 4625 和防火墙规则连起来,其中有多少需要我自己造」。

自己动手的版本,以及它的代价

自己动手的答案是一个 PowerShell 计划任务。骨架确实很短——下面这段读取最近十分钟的失败记录,按来源分组,并封禁一切超过阈值的地址:

powershell
$Threshold = 10
$RuleName  = 'Block-BruteForce'

$offenders = Get-WinEvent -FilterHashtable @{
    LogName='Security'; Id=4625; StartTime=(Get-Date).AddMinutes(-10)
  } -ErrorAction SilentlyContinue |
  ForEach-Object { ([xml]$_.ToXml()).Event.EventData.Data |
    Where-Object Name -eq 'IpAddress' | Select-Object -ExpandProperty '#text' } |
  Where-Object { $_ -and $_ -ne '-' } |
  Group-Object | Where-Object Count -ge $Threshold | Select-Object -ExpandProperty Name

$rule = Get-NetFirewallRule -DisplayName $RuleName -ErrorAction SilentlyContinue
if (-not $rule) {
  New-NetFirewallRule -DisplayName $RuleName -Direction Inbound -Action Block `
    -RemoteAddress $offenders | Out-Null
} else {
  $existing = ($rule | Get-NetFirewallAddressFilter).RemoteAddress
  $rule | Set-NetFirewallRule -RemoteAddress @($existing + $offenders | Select-Object -Unique)
}

脚本会从哪里开始漏水

这三十行今晚就能封住真实的攻击者。它与「可以放着跑一年」之间的差距,是由一堆不起眼的问题组成的,而每一个都只在生产环境中才被发现:

  • 您会把自己封掉。上面的代码里没有白名单。只要您自己办公室的地址某个下午出了状况——最经典的是某个过期的已保存凭据在循环重试——您就被挡在一台只能通过 RDP 访问的服务器之外了。
  • 只封单个地址不够。僵尸网络会在一个网段里轮换。封掉 203.0.113.47 只为您争取一小时,然后 .48 就上场了。封掉 /24 才能终结这一轮,但正确地计算与合并 CIDR 网段,比上面这段代码难得多。
  • 轮询窗口会丢事件。每十分钟跑一次,意味着要么漏掉正好落在一个窗口里的爆发,要么在两个窗口交界处重复计数。高负载下安全日志轮转得比您的间隔还快,事件在您读到之前就消失了。
  • 没有人会告诉您它停了。计划任务是静默失效的——重启之后、运行账户改密码之后、PowerShell 策略更新之后。您会在几个月后才发现,保护从三月起就没再运行过。
  • RDP 端口未必是 3389。如果有人挪过它,脚本仍然会封禁来源,但没有任何东西会核实规则应该覆盖哪些端口,而 FTP 和 MS SQL 则完全没有被覆盖。
  • 封禁挺不过重建。状态存在于一台机器的防火墙里。从镜像恢复之后,您学到的所有攻击者都被遗忘了。

Windows 自带了什么

在这个话题下通常会被推荐两个原生功能,有必要说清楚每一个到底做了什么。

账户锁定策略会在 N 次失败后禁用账户。面对暴露在公网的 RDP,这更像是一个隐患而非防御:账户名由攻击者选择,所以任何人都能从任何地方随时锁死您的 Administrator 账户。它对内部账户是合理的控制措施,在边界上则很糟糕。

网络级身份验证要求客户端在会话建立前完成认证。它确实降低了每次尝试的成本,也消除了一类认证前漏洞,应该开启。但它不会减少尝试次数,因为机器人照样会去认证——认证得很糟,而且永不停歇。

这两者都没有把事件日志和防火墙连起来。而这个连接恰恰是 fail2ban 真正提供的东西,Windows 并不自带。

把各种方案摆在一起

按投入与产出排列:

  • PowerShell 脚本——免费、完全可控,大约一天写完,外加长期的维护义务。适合您经常查看的单台服务器。它的失效方式是沉默。
  • 前置 VPN 或 RDP 网关——最强的答案,也是运维成本最高的。如果每个用户都能连上 VPN,就用它,本文到此为止。但大多数机器群里总有外包人员和手机,这件事永远差那么一点做不成。
  • 在防火墙上做 IP 白名单——当合法地址集合小而稳定时非常好用,但只要有人在酒店办公就立刻不可用。
  • 托管代理——一个安装程序、一份在封禁任何东西之前就已填好的白名单、子网封禁、自动识别的端口,以及一个能显示它是否仍在运行的控制台。RDP Protector 属于这一类。

托管代理比脚本多出什么

有两样东西是脚本无论写得多好都不可能具备的。

第一是运维底座:安装时就把您的地址加入白名单、用一条合并规则代替数千条、从注册表和监听套接字识别端口、封禁能挺过重启,以及一个能回答「它到底还活着吗」的面板——而这恰恰是真正会咬人的那种失效。

第二则根本无法独自构建。每一台受保护的服务器都会为共享信誉库贡献数据,因此昨天攻击过别人的地址,在到达您这里时就已经被封禁。单台服务器上的脚本,只能从针对这台服务器的攻击中学习。

判断依然在代理本地做出,所以保护并不依赖云端可达:连接中断时,本地策略照常运行。

FAQ

我真的能在 Windows 上运行 fail2ban 吗?
无法以有用的方式运行。fail2ban 依赖 Python 加 iptables,也依赖跟踪 syslog 风格的文本日志;Windows 既没有 iptables 也没有 syslog,而认证失败记录在二进制的安全事件日志里。在 WSL 下运行它保护的是 WSL 环境,而不是 Windows 主机的 RDP。您需要的是基于 Get-WinEvent 和 Windows 防火墙构建的工具。
对单台服务器来说,PowerShell 脚本够用吗?
对于您每天维护并会查看的单台服务器,够用——上面那三十行确实能封住真实的攻击者。请预留一天时间加上白名单、子网处理和存活检查,并且接受它的失效方式是沉默:计划任务会在重启和改密码之后停止运行,而且不会通知任何人。
为什么一条防火墙规则好过每个被封 IP 一条规则?
Windows 防火墙按顺序求值规则,处理少量携带大地址列表的规则,远比处理数千条小规则轻松。按地址建规则是这类脚本最常见的死法:头两周一切正常,随后规则求值开始耗费数分钟,管理控制台也变得无法使用。
在防火墙上封禁能代替强密码吗?
不能——它消除的是量,而不是要求。强密码和 NLA 拦住的是那些穿透进来的尝试;防火墙封禁则让猜解根本到不了 Windows,从而保护您的 CPU、审计日志,以及您在日志中找到真实事件的能力。两者都要,而不是二选一。

跳过写脚本这一步

白名单、子网封禁、端口识别与共享信誉,一分钟装好。一台服务器永久免费,无需信用卡。