
很多做运维或自己搭过Windows服务器的人应该都有过这种经历装好一台Windows Server开了远程桌面RDP结果没过几天一看安全日志密密麻麻全是4625登录失败事件源IP来自天南海北一分钟能刷几十条。这些IP就是全网扫描的爆破机器人不处理的话轻则占用系统资源、干扰日志排查重则真有弱口令被撞库成功服务器直接变肉鸡。我之前也被这个问题烦了很久后来写了一个“监控Windows远程登录失败事件并自动封锁恶意IP”的PowerShell脚本配合任务计划程序跑起来基本做到了无人值守自动封禁。这篇文章把这个脚本的设计思路、完整实现、参数计算、排坑经验全部拆开讲内容偏实操每一步都可以直接落到自己的机器上。如果你是刚接触Windows安全配置的新手也能按步骤跟下来如果你已经写过类似脚本重点看后面的细节调优和问题排查部分应该会有一些参考价值。1. 为什么要在Windows层面监控远程登录失败场景与威胁模型1.1 谁在攻击你的远程桌面爆破攻击的现实场景先说一个很现实的情况只要你的服务器有公网IP而且开放了3389端口你不需要做什么“值得被攻击”的事也会被扫描器盯上。扫描器不会挑目标它按网段顺序扫发现响应RDP协议的端口就挂到字典里开始试密码。这类攻击是7x24小时不间断的哪怕你密码强度再高攻击者也会一直换着字典试直到某一天你自己把密码改弱了或者某台机器真的用了弱口令服务器就沦陷了。我见过不止一台被攻陷的Windows机器事后看日志基本都是一个套路前几个小时还是普通的4625失败记录突然某一条4625后面紧跟着一条4624登录成功然后系统被创建了隐藏账户、加了计划任务、端口反弹一条龙拿走。所以监控失败登录不是为了“看着热闹”而是要在爆破阶段就把IP按住让攻击者根本没有机会进入下一阶段。1.2 事件日志里的“数字指纹”4625事件解析Windows安全日志里远程登录失败的核心事件ID是4625。这个事件记录了非常多的关键字段对我们写脚本有用的主要有几个Account Name尝试登录的用户名可以判断是针对性攻击还是广撒网爆破。Source Network Address发起登录请求的源IP这是封禁的关键依据。Logon Type登录类型远程桌面是10网络共享是3本地控制台是2。我们监控RDP失败重点看类型10。Process Name发起登录的进程正常情况下是svchost.exe或系统进程如果出现奇怪的进程名就要小心了。Failure Reason失败原因区分密码错误、账户不存在、账户锁定等情况。这里有一个特别容易踩的坑4625事件里如果账户不存在Account Name会显示不存在的用户名如果账户存在但密码错误会显示真实的用户名。这本身不影响我们按IP封禁但如果你后续想做用户维度的告警就必须把这两个情况分开统计否则会被大量随机用户名刷屏误以为有人在爆破真实账户。1.3 只监控4625就够了吗事件ID组合判断坦白说只盯4625能覆盖90%以上的爆破场景但作为一套完整的监控方案我建议把下面几个事件ID一起纳入判断事件ID含义监控价值4625登录失败爆破直接证据核心事件4624登录成功结合4625判断是否被攻破4776凭据验证失败NTLM认证失败覆盖更多登录场景4740账户被锁定爆破触发账户锁定的信号4771Kerberos预认证失败域环境下的失败认证非域场景较少用对于一台独立的Windows Server4625 4624组合就够了。当你看到同一IP在短时间内有大量4625且其中紧跟着一条4624说明爆破已经成功这时候光封IP来不及了还要马上检查账户、清理后门。我自己的脚本里单独维护了一个“爆破成功”检测逻辑发现这种情况直接触发高优先级告警而不是走普通的IP封禁流程。2. 脚本的整体设计思路先想清楚再动手2.1 两种常见的监控实现路线对比实现“监控失败登录并封禁IP”这个需求主流路线有两条各有适用场景。第一条是事件订阅触发Event Trigger。Windows支持在事件日志上挂订阅任务EventTrigger当符合条件的事件被写入日志时系统自动触发任务执行脚本。这种方式响应最快几乎是事件发生就执行但配置复杂度高订阅关系容易断而且如果日志量太大触发任务本身可能成为瓶颈。第二条是计划任务轮询Scheduled Task Polling。脚本每隔固定时间比如1分钟或5分钟扫描一次安全日志统计最近时间窗口内的失败次数达到阈值就执行封禁。这种方式实现简单、逻辑可控缺点是响应有延迟但对于爆破封锁场景1分钟级别的延迟完全可以接受——攻击者要试几千几万个密码组合不可能在1分钟内完成。对比下来我个人更推荐计划任务轮询。原因有三个一是实现难度低整个逻辑写在一个PowerShell脚本里就行不需要额外配置事件订阅二是便于调试脚本可以手动执行输出日志一眼就能看出问题三是策略调整方便想改变扫描频率、阈值改配置参数就行。事件订阅路线适合对实时性要求极高的场景比如检测登录成功后立刻触发阻断普通服务器不需要这个级别的响应速度。2.2 为什么我选择了“踩点记录增量扫描”的设计简单的轮询方案有个问题每次扫描都把最近几分钟内的所有4625事件统计一遍那么上一轮已经处理过的IP下一轮会被重复统计导致计数器虚高。比如一个IP在10分钟内发起了30次失败登录你把扫描间隔设为5分钟第一轮统计20次触发封禁第二轮又把20次统计一遍没被封禁的其他IP也跟着被重复计数阈值判断就失真了。所以我的脚本采用了一个很关键的设计记录每一轮扫描到的最后一条事件记录IDEventRecordId下一轮只扫描比这个ID更新的日志。这样每个事件只会被统计一次计数是真实的不会重复累加。这个思路其实跟增量同步差不多每次只处理新增的部分天然避免了重复消费。另外在封禁状态上也要做“幂等”处理。脚本每次封IP之前先查一下防火墙里有没有这条规则如果已存在就跳过封禁动作只更新计数信息。幂等设计的好处是即使任务被重复执行或者多台机器同时跑同一个脚本也不会产生重复的防火墙规则这个对后续人工清理特别重要。2.3 整体架构与任务执行流程整个系统的结构可以画成下面这个逻辑流不用画图软件文字描述也足够清晰任务计划程序每1分钟调用一次PowerShell脚本。脚本查询安全日志中Event ID 4625日志类型为Security过滤掉本地回环地址127.0.0.1和::1。只取LogonType 10的远程桌面登录事件提取源IP和用户名。只处理事件记录ID大于上次标记值的增量事件。按源IP聚合统计在滚动时间窗口内失败的次数达到阈值进入封禁流程。封禁前三重白名单检查内网网段、本机回环、自定义白名单列表。执行netsh advfirewall firewall add rule新增入站阻断规则。脚本把处理结果写入自定义日志文件供后续查看。这套流程看起来不复杂但每一步都对应着一个实际的坑后面我会在实现和排查部分展开讲。3. 从零搭建PowerShell脚本渐进式实现3.1 环境准备日志设置与脚本权限在写脚本之前有两件基础事情必须确认不然脚本跑起来会各种莫名其妙。第一件事是确保安全日志足够大。Windows安全日志默认上限可能只有20MB或128MB不同版本不同在高强度爆破下日志很快会被写满而默认策略是“覆盖最旧事件”这意味着你的脚本还没来得及扫描旧事件就被冲掉了直接导致漏统计。强烈建议把安全日志上限调高至少512MB甚至1GB。注意调整日志大小在WinR执行eventvwr.msc选择“Windows日志 安全”右侧“属性”设置日志最大大小并勾选“不覆盖事件手动清除日志”。不覆盖模式也意味着日志满了之后新事件写不进去所以日志大小要根据攻击强度合理设置。我个人习惯设为1GB按一台被重点爆破的机器一天几十万条失败日志来算大约能存7-15天够用了。第二件事是PowerShell执行策略。默认Windows机器的执行策略可能是Restricted脚本根本不让跑。在管理员PowerShell里执行Set-ExecutionPolicy RemoteSigned -Force执行策略改为RemoteSigned的意思是本地创建的脚本可以直接运行从网络下载的脚本需要有签名。对绝大多数场景来说这个策略够用且风险可控。3.2 核心脚本一捕获失败登录并记录追踪状态下面这个脚本是整个监控系统的主脚本负责扫描日志、提取IP、维护增量状态。代码不长但每一行背后都有讲究。# 监控远程登录失败事件并自动封锁恶意IP # 适用Windows Server 2016/2019/2022PowerShell 5.1 及以上 param( [int]$ScanWindowMinutes 5, # 统计时间窗口 [int]$FailThreshold 5, # 阈值窗口内失败次数达到该值则封禁 [string]$StateFile C:\Scripts\rdp_monitor_state.json, [string]$LogFile C:\Scripts\rdp_monitor.log ) function Write-Log { param([string]$Message) $time Get-Date -Format yyyy-MM-dd HH:mm:ss Add-Content -Path $LogFile -Value [$time] $Message } # 读取上次处理的事件记录ID $lastId if (Test-Path $StateFile) { (Get-Content $StateFile -Raw | ConvertFrom-Json).LastEventRecordId } else { 0 } # 查询安全日志中4625事件只取增量部分 $filter { LogName Security Id 4625 StartTime (Get-Date).AddMinutes(-$ScanWindowMinutes) } $events Get-WinEvent -FilterHashtable $filter -ErrorAction SilentlyContinue | Where-Object { $_.RecordId -gt $lastId -and $_.Properties[18].Value -eq 10 } # 统计每个源IP在窗口内的失败次数 $ipFailCount {} $ipUserMap {} foreach ($evt in $events) { # Properties数组索引对应不同字段这里用名字解析更稳 $ip $null $user $null foreach ($prop in $evt.Properties) { if ($prop.Value -match ^\d{1,3}(\.\d{1,3}){3}$) { $ip $prop.Value } if ($prop.Name -match Account Name) { $user $prop.Value } } if (-not $ip) { continue } if (-not $ipFailCount.ContainsKey($ip)) { $ipFailCount[$ip] 0 } $ipFailCount[$ip] if (-not $ipUserMap.ContainsKey($ip)) { $ipUserMap[$ip] () } if ($user -and $ipUserMap[$ip] -notcontains $user) { $ipUserMap[$ip] $user } } # 记录当前最大记录ID供下一轮扫描用 $maxId if ($events) { ($events | Measure-Object -Property RecordId -Maximum).Maximum } else { $lastId } { LastEventRecordId $maxId } | ConvertTo-Json | Set-Content -Path $StateFile Write-Log 扫描完成增量事件数: $($events.Count)最大记录ID: $maxId # 输出统计结果供下一步封禁脚本使用 $ipFailCount.GetEnumerator() | ForEach-Object { [PSCustomObject]{ IP $_.Key Count $_.Value AttemptedUsers ($ipUserMap[$_.Key] -join ,) } } | Export-Clixml -Path C:\Scripts\rdp_failed_summary.xml这里重点解释两个细节第一LogonType的判断方式。4625事件的Properties是一个数组不同位置的元素代表不同字段。直接按下标比如Properties[18]判断LogonType在Windows版本变化时容易出错。更稳妥的做法是遍历Properties通过字段名Logon Type来定位值。上面的代码里为了简洁没有完全展开实际用的时候建议用名字解析我在后面的完整版脚本里就是这么写的。第二源IP的提取。4625事件里源IP字段在Properties中的名字叫Source Network Address取值是字符串形式的IP地址。上面这段用了正则匹配的方式从所有属性值里找类似IP地址的字段这种写法简单粗暴但可靠因为IP地址的格式足够特殊不会跟其他字段混淆。当然如果追求严谨还是按属性名精确提取更规范。3.3 核心脚本二自动封锁恶意IP扫描脚本拿到统计结果后下一步就是封禁。这里有个设计上的选择可以把封禁逻辑写进同一个脚本也可以拆成独立脚本。我倾向于拆成两个因为职责分离便于单独调试和维护。下面是被调用的封禁脚本核心段# 封禁脚本 block-bad-ip.ps1 param( [string]$StateFile C:\Scripts\rdp_monitor_state.json, [string]$SummaryXml C:\Scripts\rdp_failed_summary.xml, [string]$WhitelistFile C:\Scripts\rdp_whitelist.txt ) $summary Import-Clixml -Path $SummaryXml if (-not $summary) { exit } # 白名单文件里每行一个IP或网段 $whitelist if (Test-Path $WhitelistFile) { Get-Content $WhitelistFile } else { () } # 遍历统计结果按阈值封禁 foreach ($item in $summary) { $ip $item.IP # 跳过回环与内网地址避免误封本机或内网设备 if ($ip -eq 127.0.0.1 -or $ip -like 10.* -or $ip -like 192.168.* -or $ip -like 172.16.* -or $ip -like 172.31.*) { continue } if ($ip -in $whitelist) { continue } # 阈值判断 if ($item.Count -ge 5) { # 先检查是否已有规则幂等处理 $existing Get-NetFirewallRule -DisplayName RDP_BLOCK_$ip -ErrorAction SilentlyContinue if (-not $existing) { New-NetFirewallRule -DisplayName RDP_BLOCK_$ip -Direction Inbound -Protocol TCP -LocalPort 3389 -RemoteAddress $ip -Action Block | Out-Null Write-Log 已封禁IP: $ip, 失败次数: $($item.Count), 尝试用户: $($item.AttemptedUsers) } else { Write-Log IP已在封禁列表: $ip, 当前失败次数: $($item.Count) } } }这个封禁方案用的是Windows Defender Firewall的入站规则。RemoteAddress参数支持单个IP地址也支持CIDR格式网段。如果你需要封的IP非常多直接给每个IP建单条规则会造成几千条规则堆积查起来麻烦但阻断性能没有明显问题因为防火墙规则是按顺序匹配的遍历几千条的成本对现代CPU来说可以忽略。如果想更精细也可以按网络前缀聚合比如把连续的同C段IP合并成一条规则但聚合逻辑会复杂很多普通场景没必要。3.4 注册计划任务让系统自动周期性执行脚本写完以后剩下最关键的一步是让系统自动、持续地运行它。这一步做不好脚本写得再漂亮也是白费。用任务计划程序注册一个每分钟执行一次的定时任务操作如下WinR 输入taskschd.msc打开任务计划程序。右侧操作栏点“创建任务”名称填RDP失败登录监控与封禁。“常规”选项卡选择“使用最高权限运行”否则写安全日志和防火墙规则可能权限不足。“触发器”选项卡新建触发器“重复任务间隔”设为1分钟“持续时间”选择“无限期”勾选“已启用”。“操作”选项卡新建操作程序填powershell.exe参数填-ExecutionPolicy Bypass -WindowStyle Hidden -File C:\Scripts\rdp_monitor.ps1。“条件”选项卡取消勾选“只有在计算机使用交流电源时才启动此任务”避免服务器临时切换到电池场景时任务不跑。“设置”选项卡勾选“如果任务运行失败按以下频率重启每次尝试重新运行”并设置重试次数同时勾选“如果任务运行时间超过以下时间停止任务”。注意任务计划程序里的“使用最高权限运行”是基于管理员凭据的。创建任务时输入的密码如果是普通管理员可能还是会遇到UAC截断问题。最稳妥的方式是用一个独立的服务账户并赋予“作为批处理作业登录”的权限或者直接用内置管理员账户创建任务。生产环境建议用托管服务账户gMSA之类的方案但小规模服务器直接用内置管理员也问题不大。注册好以后建议在计划任务里点“运行”手动触发一次确认脚本能正常执行并且日志有输出然后把触发器改成每分钟运行也可以反过来先手动跑排错完再挂任务。4. 封禁策略与参数调优这里藏着大量细节4.1 封禁阈值怎么定失败次数与时间窗口的计算阈值和时间窗口是两个紧密相关的参数不能拍脑袋定。设置太激进比如窗口2分钟内失败2次就封禁很可能会把正常用户误封比如别人在远程桌面里连了几次密码敲错两次就被封了设置太宽松比如窗口60分钟失败100次才封禁那攻击者可能已经试了几万个密码风险太高。我给的参考经验值5分钟内失败5次即$ScanWindowMinutes 5$FailThreshold 5。这个数值怎么推出来的先看正常用户行为一个人手动输错密码连续失败三四次就会警觉并且Windows默认也有账户锁定策略通常锁定阈值为5-10次所以正常用户大概率在5分钟内不会有5次以上的失败。再看攻击行为爆破工具跑字典时一个IP一分钟内通常能发起20-100次登录尝试5分钟内失败次数至少是几十次。所以5分钟5次的阈值可以做到攻击IP大概率命中正常用户几乎不会触发。如果你希望更灵敏可以降低到3分钟3次如果攻击非常猛烈可以提阈值到10次。核心原则是阈值必须显著大于正常用户的最差输入同时显著小于攻击者的最低强度。另外有一个很多人忽略的点账户锁定策略与脚本封禁策略的联动。如果Windows账户锁定阈值设得比较低比如3次锁定账户那么攻击者靠爆破大概率不会成功但因为锁定和解锁也需要管理员介入可能造成合法用户被锁。所以如果你用了自动化封禁脚本可以考虑把系统的账户锁定阈值调高一些比如10次让脚本封IP成为第一道防线账户锁定策略退居其次。这是一个策略协同的权衡不要盲目参照网上默认的“3次锁定”配置。4.2 白名单机制千万别把自己锁在门外自动封禁最难处理的风险是误封尤其是把自己封了。很多时候你在公司内网远程到服务器源IP是固定IP或者动态分配的办公网出口IP一旦脚本误判你可能就再也连不上了。更麻烦的是外网管理场景某人出差在酒店连服务器IP变了就直接被脚本封禁严重影响业务。所以在封禁逻辑里白名单必须是第一优先级。我的做法是用一个独立的文本文件rdp_whitelist.txt每行一个IP或网段脚本先读白名单再决定是否封禁。白名单不仅包含固定的管理员IP还要把所有内网网段都放进去10.0.0.0/8 192.168.0.0/16 172.16.0.0/12 118.xx.xx.xx # 公司固定出口IP注意脚本里New-NetFirewallRule的RemoteAddress参数既接受单个IP也接受CIDR网段但白名单匹配代码要单独处理CIDR格式不能用简单的字符串包含判断。上面的示例里我用的是-like 10.*这种前缀通配方式这只适合标准网段划分。更通用、更严谨的做法是解析IP与掩码做子网比较完整代码里可以用IPv4地址加前缀长度拆成二进制位做与运算网上有很多工具函数可以直接抄这里不展开。经验白名单文件里一定要把本机网卡所在网段写进去。有些云服务器有多块网卡公网IP是NAT映射到内网IP的脚本只看到内网IP如果不加白名单本机或者云平台健康检查的IP就可能被误封导致服务器被云平台判定为不健康。这个坑我踩过必须提醒。4.3 防火墙规则管理封禁后的生命周期问题被封禁的IP是永久封还是临时封永久封的好处是省心坏处是规则列表会越来越长而且某些IP是运营商动态分配的过一段时间可能被分配给正常用户持续封禁会伤及无辜。临时封的好处是规则库不会膨胀误封能自动解除坏处是攻击者换个IP就能再来。我的做法是默认永久封禁 定期人工复核白名单。理由有两个一是爆破IP的池子很大很多IP来自恶意网络永久封禁无伤大雅二是定期看一遍封锁清单把明显的误封IP加进白名单并手动删除规则成本很低。如果你不想人工介入也可以加一个“过期解封”逻辑给每条规则打上时间戳比如7天后自动删除这个实现也不复杂但脚本要从规则描述字段里解析创建时间代码会啰嗦一些。综合来看小型服务器人工复核更划算大规模场景才需要自动解封。5. 实际运行中的常见问题与排查手册跑了半年这个脚本之后我把遇到过的典型问题整理如下每一类都对应一个真实的坑。5.1 安全日志被写满导致漏统计之前提到安全日志默认大小有限在持续爆破下很容易被写满。如果你设置的是“覆盖最旧事件”那脚本还没跑到旧的失败记录就被新的覆盖了。尤其当脚本按“增量事件ID”方式扫描时如果日志覆盖发生记录的LastEventRecordId对应的那条事件已经不存在但新的日志ID比记录值大所以还能接着扫只是中间被覆盖的部分丢了计数会偏低。这种情况下你看到的封禁数量会明显少于实际攻击IP数量。解决思路前面说过把安全日志调大到1GB并且选择“不覆盖事件”。但这里有个新的问题日志写满后新事件无法写入审计能力反而断了。所以更完整的方案是定期导出安全日志归档并清空或者用第三方日志收集工具转发出去。对单机小场景1GB 每月手动归档一次就够。5.2 任务计划程序运行失败权限与执行策略我最初遇到过任务计划里脚本运行但没有任何效果的情况。排查后发现根本原因是PowerShell执行策略在 System 上下文里没有生效Set-ExecutionPolicy改的是当前用户级别的策略而计划任务以管理员账户运行却读取的是本机策略。最后的解决办法是在计划任务的启动参数里显式加-ExecutionPolicy Bypass这就绕过了策略限制。另一个问题是脚本写日志文件时如果路径不存在脚本会报错退出。解决方法是脚本开头把日志目录和状态文件目录都补建一遍不要依赖手动创建。另外计划任务默认的“运行用户”如果不是管理员组访问安全日志和防火墙规则就会报“拒绝访问”。创建任务时务必选择管理员账户并且勾选“使用最高权限运行”这个在3.4节提过但太多人栽在这里还是值得再说一次。5.3 误封和漏封IP提取与判断逻辑的问题有一次我发现某个内网网段的IP被批量封了查日志发现那个IP的失败次数很高问题就出在它不仅是RDP的源IP也是某台内网打印机的IP。很多设备在局域网里用SMB或RDP短连接尝试登录也会有失败记录而我的脚本只按类型10远程桌面过滤理论上不该把内网打印机计入后来仔细看发现打印机走的是另一个端口但同一IP上还有别的服务发起的4625记录类型也是10这就傻了。避免这种误判的方法是加端口维度。脚本可以只处理Source Network Address对应的连接端口是3389的事件但4625事件里“源端口”属性在Properties中是没有直接暴露的需要配合网络跟踪或更底层的日志。这个信息在大多数情况下可用性不高所以我采用了更实用的策略把所有内网网段统统加进白名单。不需要精确到端口只按网段排除简单粗暴但可靠。漏封的情况则相反某个攻击IP走的是IPv6::ffff:xxx.xxx.xxx.xxx这种映射格式脚本的正则只匹配纯IPv4导致漏掉。遇到这种情况可以把正则改成同时匹配IPv4和IPv4-mapped IPv6或者干脆统一转成字符串判断是否包含IPv4四段数字。实战中纯IPv6扫描器很少但映射格式确实有建议在源IP提取时做一次标准化处理。5.4 误封自己之后如何紧急恢复这是最紧急的场景我也踩过一次。当时把公司出口IP写进白名单里但脚本判断逻辑有bug白名单没生效结果整个公司出口被封连远程桌面都进不去。这种时候人有两种选择第一去云控制台用VNC/Web终端登录服务器删掉对应防火墙规则第二如果你用的是云主机且厂商提供了安全组在安全组层面临时放行从外部倒过来解封本机防火墙规则。在脚本设计上应对这种风险最优的办法是给脚本加一个“最大封禁数量”上限。比如单次运行最多封10个IP超过就不再封了这样即使白名单有bug也不会一瞬间封光整个网段。另一个办法是加“封禁通知”每次执行封禁时往运维邮箱发一封邮件知道封了什么IP出了问题也能快速定位。6. 扩展与优化从“能用”到“好用”基础版本的脚本已经能完成“监控-统计-封禁”的闭环但实际使用中还值得再做几个增强按投入产出比排序第一个增强是告警通知。被封IP是不需要人盯着的但“爆破成功”必须有人响应。我在脚本里加了4624登录成功的检测一旦发现同一IP在连续几次4625后出现4624立即发告警邮件。把邮件通知用SMTP协议发出去PowerShell自带的Send-MailMessage就能实现但需要注意新版PowerShell移除了这个cmdlet建议直接用System.Net.Mail命名空间。第二个增强是IP信誉联动。单纯按失败次数封禁是自学习策略还可以引入外部威胁情报库。比如把封禁的IP拿到公共威胁情报接口查询如果是已知的恶意扫描源就提高封禁优先级如果是正常的IDC出口可以考虑加长观察期。这个对小型服务器性价比不高但对防护要求较高的生产环境很有价值。第三个增强是规则聚合和清理。运行几个月后防火墙里可能积累了几万条封锁规则手动清理不现实。可以写一个定期任务统计每条规则对应的IP在近30天内还有没有新的失败事件没有就自动删除规则。这就是前面提的“临时解封”逻辑做成独立脚本按周运行保持规则库精简。第四个方向是集成SIEM或日志系统。如果公司已经有日志集中平台可以把4625事件实时转发过去用平台自带的规则引擎做封禁动作脚本就退化成纯采集组件。这种架构更规范但需要依赖已有平台不是所有环境都具备所以我自己的脚本仍然保持了独立运行的形态。写在最后脚本只是工具策略才是核心这套脚本跑了很长时间累计封禁了上千个IP也踩过各种坑。如果有人问我“这个脚本能保证服务器不被攻破吗”我的回答是不一定。它能挡住的是最无聊、最普遍的字典爆破挡不住的是有人拿着真实凭据直接登录也挡不住新出现的高水平定向攻击。但它确实把每天几千条失败日志这种噪音消化掉了让我在翻日志的时候能一眼看到真正值得关注的东西。如果你要自己部署这套方案我建议顺序是先开安全日志调大小再把手工版脚本跑通最后挂计划任务。不要一上来就自动化先在测试机上验证逻辑确认不会误封、白名单正确再拿到生产环境。脚本本身是死的策略参数和安全边界要靠你根据实际场景去调这个过程中的每次误封和漏封都是对脚本规则的校准。最后还是那句话安全问题没有一劳永逸的答案脚本可以减少人工投入但替代不了管理员对系统安全状态的理解和判断。