RDPGuard服务:Windows服务器的守护者

Windows服务器的日常运维中,经常会遇到远程桌面协议(RDP)的暴力破解攻击。这种攻击尝试通过不断尝试不同的用户名和密码组合来非法登录服务器。由于服务器资源有限,这些无休止的认证尝试会消耗大量的处理器资源。为了应对这种情况,决定创建一个Windows服务来自动处理这个问题。

服务工作原理

RDPGuard服务的基本结构如下图所示。蓝色部分主要是简单的Windows API,黄色部分展示了程序的简化部分。

服务订阅了Windows的"Security"事件日志,只要服务运行,就会收到每个新条目的通知。如果事件日志条目是一个带有有效源IP地址的"AuditFailure"条目,这些信息就会作为"RDPGuardHit"条目存储在数据库中(包含时间戳、IP地址和尝试登录的用户名)。使用C#进行订阅非常简单,只需打开一个事件日志并订阅其提供的"EntryWritten"事件:

_log = new EventLog("Security"); _log.EnableRaisingEvents = true; _log.EntryWritten += EventLog_EntryWritten;

提取所需数据有点棘手,因为"Message"属性包含一个本地化的字符串,格式不适合提取数据。但还有一个属性"ReplacementStrings",它包含用于创建本地化消息字符串的数据数组。对于这个服务感兴趣的"AuditFailure"条目,字段如下:

  • SubjectSecurityID
  • SubjectAccountName
  • SubjectAccountDomain
  • SubjectLogonID
  • AccountSecurityID
  • AccountAccountName
  • AccountAccountDomain
  • Status
  • FailureReason
  • SubStatus
  • LogonType
  • LogonProcess
  • AuthenticationPackage
  • SourceWorkstationName
  • TransitedServices
  • PackageName
  • KeyLength
  • CallerProcessID
  • CallerProcessName
  • SourceNetworkAddress
  • SourcePort

首先检查条目的字段数是否为21,然后感兴趣的字段位于索引19,需要检查其是否为格式良好的IP地址。

同时,一个单独的线程用于定期检查保存的"RDPGuardHit"条目。服务中的一些逻辑已经移动到数据提供者中,以提高性能,因为很多操作可以在SQL中更容易地完成。选择使用SQLite,因为对它很熟悉。线程的每个周期执行以下步骤:

  • 每个被阻止的IP都存储为"RDPGuardBlock",仅包含阻止的时间戳和被阻止的IP地址。
  • 如每个循环的前两个操作所示,所有收集的数据如果超过设置的禁止时间,则会被删除 - 攻击IP在某种程度上被"康复"了。
  • 尽管如此,数据库中也使用"RDPGuardLog"条目记录了日志。

SQLite数据提供者在这项服务中使用简单的SQL命令和一些帮助方法。它在服务启动时初始化,数据库文件放置在与服务可执行文件相同的目录下,文件名与服务可执行文件相同,后缀为".db3",所有必要的表都使用"CREATE TABLE IF NOT EXISTS"命令创建。

为了阻止识别出的攻击者的IP,这项服务使用了Windows防火墙API。要访问它,必须以管理员权限启动Visual Studio,否则"firewallapi.dll"的COM引用根本不会显示。为了在没有管理员权限的情况下也能使用代码,提取了Interop-DLLs并使用它们代替 - 尽管在没有管理员权限的情况下调试无法工作,因为需要这些权限来更改Windows防火墙。初始化Interop类需要一些Google搜索,但之后就非常简单了。

当服务启动时,辅助类"FirewallBlockIpRule"被初始化。它尝试使用设置中指定的名称打开防火墙规则,如果失败,则在该名称下创建一个新的入站阻止规则。从那时起,使用就非常简单了:通过设置"RemoteAddresses"属性,可以设置要阻止的IP,规则可以通过设置"Enabled"属性来启用或禁用。

使用服务

如果想更改源代码,请随意 - 尽量添加了尽可能多的内联注释,以便仍然可以称之为合理的。解决方案的64位项目的链接指向32位版本的所有代码文件。

  • RDPGuardBlock:位于文件"RDPGuardEntities.cs"中;这个实体用于模拟被阻止的IP,也可以由DataProvider管理。
  • RDPGuardHit:位于文件"RDPGuardEntities.cs"中;这个实体用于模拟识别出的失败登录尝试,也可以由DataProvider管理。
  • RDPGuardLog:位于文件"RDPGuardEntities.cs"中;这个实体用于模拟日志条目,也可以由DataProvider管理。
  • ABjSThread:一个抽象类,实现了一个通用线程的所有任务,该线程的任务是定期调用,每次执行之间有指定的暂停。它还实现了"Start"和"Stop"函数以及一个"StatusMessage"事件。
  • RDPGuardThread:这是服务的核心部分。它是ABjSThread的实现,其功能在上面解释过。
  • RDPGuardDao:这是DataProvider,使用SQLite作为后端。
  • FirewallBlockIpRule:这是一个辅助类,围绕NetFwTypeLib类提供对防火墙规则的访问。
  • GenericEventArgs<T>:这是一个辅助类,用于传递任何类型的对象与EventHandler。
  • Program:这是程序的主入口点。它确定服务是否以命令行(或调试)运行,并相应地切换行为。
  • Service1:当作为服务使用时,用于控制RDPGuardThread。
  • ServiceInstaller1:用于控制服务的安装,以设置名称、描述和运行帐户类型。

通常,使用命令行上的installutil.exe来安装.NETWindows服务。它可能位于以下文件夹中(取决于安装了哪些版本的.NET):

  • C:\Windows\Microsoft.NET\Framework64\v4.0.30319\InstallUtil.exe
  • C:\Windows\Microsoft.NET\Framework64\v2.0.50727\InstallUtil.exe
  • C:\Windows\Microsoft.NET\Framework\v4.0.30319\InstallUtil.exe
  • C:\Windows\Microsoft.NET\Framework\v2.0.50727\InstallUtil.exe

根据打算安装的服务版本(32位或64位),应该使用Framework文件夹用于32位或Framework64文件夹用于64位,然后使用最新版本。

转到installutil.exe的文件夹,然后运行以下命令,将%path_to_service%替换为服务的实际路径:

installutil.exe /i %path_to_service%\BjSTools.RDPGuard.exe

要卸载服务,只需使用相同的命令,将开关/i替换为/u。

服务有一个标准XML格式的配置文件。它包含以下选项:

  • WhiteList:string类型,一个逗号分隔的白名单IP地址列表,这些地址永远不会被阻止。
  • BlockSpanHours:int类型,IP地址被阻止的时间(小时),超过这个时间后阻止将被移除。
  • BlockHitCount:int类型,IP地址被阻止前的最小失败登录尝试次数。
  • LogCategoryFilter:int类型,一个二进制过滤器,用于设置哪些类别的日志消息被记录(见下文)。
  • FirewallRuleName:string类型,在Windows防火墙中设置阻止规则的名称。

LogCategoryFilter选项使用整数的位 - 如果位设置为1,则相应的日志类别被记录。可以简单地将需要记录的日志类别的位值相加,以获得过滤器:

位值 类别 描述
0 1 Debug 仅用于调试的消息
1 2 Information 状态或其他非干扰性消息
2 4 Warning 干扰性信息,但不影响程序的继续运行
3 8 Error 干扰性错误,但不会导致程序退出
4 16 Critical 干扰性错误,会导致程序退出

即使服务已经安装,也可以更改设置,但只有在服务启动时才会读取。

服务也可以作为命令行程序使用。尝试启动服务而不使用以下命令行选项中的任何一个将导致错误,因为服务试图作为服务启动,而这只有在Windows服务控制器执行时才可能。这些选项是可用的:

  • /?:显示帮助消息,描述命令行选项。
  • /L[=DateTime]:输出所有日志条目。如果指定了有效的DateTime值,则只加载从该时间开始的日志条目。使用Convert.ToDateTime(string)方法。
  • /C:将服务作为命令行工具启动。日志消息也写入命令行。

以下是命令行使用的示例:

  • 以命令行方式运行服务:
  • BjSTools.RDPGuard /C
  • 将完整日志写入文件log.txt:
  • BjSTools.RDPGuard /L > log.txt
  • 显示2015年及以后的日志:
  • BjSTools.RDPGuard /L=2015-01-01
  • 显示2015年4月1日中午12点及以后的日志:
  • BjSTools.RDPGuard /L=2015-04-01T12:00:00
沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485