65.9K
CodeProject 正在变化。 阅读更多。
Home

阻止 RDP 攻击的 Windows 服务

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.97/5 (33投票s)

2015年7月28日

CPOL

8分钟阅读

viewsIcon

43364

downloadIcon

1558

一个监听 Windows 事件日志、识别攻击者 IP 并将其阻止在 Windows 防火墙中的服务。

引言

我最近上线了一个新的 Windows 2008 服务器,像往常一样,你可以在 Windows EventLog 中看到远程桌面协议 (RDP) 被暴力破解的情况。而且这只是一个小型机器,所以无休止的授权尝试占用了服务器相当大的处理器能力。所以我开始手动阻止从 EventLog 条目中提取的 IP,但这当然并没有持续太久。所以,我决定创建一个 Windows 服务来完成这项任务。

上面显示的是 Windows EventLog Explorer 的一个示例,显示了审计失败条目 - 这些条目指示了暴力破解攻击。

服务如何工作

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

事件日志订阅

服务订阅 Windows EventLog “Security”,只要服务正在运行,就会收到每个新条目的通知。如果 EventLog 条目是 AudithFailure 条目并且包含一个有效的源 IP 地址,则该信息将作为 RDPGuardHit 条目存储在数据库中(包含时间戳、IP 地址和尝试登录的用户名 - 后者只是出于我的好奇)。使用 C# 进行订阅非常简单 - 你只需按名称打开一个 EventLog 并订阅提供的 EntryWritten 事件。

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

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

  1. SubjectSecurityID
  2. SubjectAccountName
  3. SubjectAccountDomain
  4. SubjectLogonID
  5. AccountSecurityID
  6. AccountAccountName
  7. AccountAccountDomain
  8. 状态
  9. FailureReason
  10. SubStatus
  11. LogonType
  12. LogonProcess
  13. AuthenticationPackage
  14. SourceWorkstationName
  15. TransitedServices
  16. PackageName
  17. KeyLength
  18. CallerProcessID
  19. CallerProcessName
  20. SourceNetworkAddress
  21. 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,否则 COM 引用 NetFwTypeLib(用于 firewallapi.dll)将根本不会出现。为了在没有管理员权限的情况下也能进行代码工作,我提取了 Interop-DLL 并使用了它们 - 当然,在没有管理员权限的情况下调试是不可能工作的,因为更改 Windows 防火墙需要这些权限。初始化 Interop 类需要一点搜索,但之后就相当直接了。

当服务启动时,会初始化助手类 FirewallBlockIpRule。它尝试按名称打开防火墙规则(使用设置中指定的名称),如果失败,则在该名称下创建一个新的入站阻止规则。从那时起,用法非常简单:通过设置 RemoteAddresses 属性,可以将要阻止的 IP 设置为逗号分隔的字符串,并且可以通过设置 Enabled 属性来启用或禁用该规则。

使用服务

源代码

如果您想修改源代码,请随时这样做 - 我已尽力添加了尽可能多的在线注释。解决方案中的 64 位项目链接到 32 位版本的所有代码文件。

这是项目类别的概述:

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

Install

通常,要安装 .NET Windows 服务,我通常会在命令行中使用 installutil.exe 。它很可能位于以下文件夹(取决于您安装的 .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 位),您应该使用 32 位服务的 Framework 文件夹或 64 位服务的 Framework64 文件夹,然后是最新版本。

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

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

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

配置

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

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

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

位值 类别 描述
0 1 Debug 消息仅用于调试目的
1 2 信息 状态或不干扰性的消息
2 4 警告 不影响正在运行程序的干扰性信息
3 8 Error(错误) 一个不会导致程序退出的干扰性错误
4 16 关键 一个导致程序退出的干扰性错误

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

命令行用法

该服务也可以用作命令行程序。尝试在没有任何以下命令行选项的情况下启动服务将导致错误,因为服务试图作为服务启动,而这只有在 Windows 服务控制器进行时才可能。这些选项可用:

选项 描述
/? 显示帮助消息,描述命令行选项。
/L[=DateTime] 输出所有日志条目。如果指定了 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 日中午及以后的日志
    BjSTools.RDPGuard /L=2015-04-01T12:00:00

当服务以命令行模式运行时,可以通过按 [CTRL]+[C] 来停止它 - 它将以一种受管理的方式停止服务。

关注点

当我开始编写这个服务时,我准备好应对一些棘手的互操作代码调用,然后却惊喜地发现 COM 引用实现得多么好。罕见:干得好,微软!

© . All rights reserved.