在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"条目,字段如下:
首先检查条目的字段数是否为21,然后感兴趣的字段位于索引19,需要检查其是否为格式良好的IP地址。
同时,一个单独的线程用于定期检查保存的"RDPGuardHit"条目。服务中的一些逻辑已经移动到数据提供者中,以提高性能,因为很多操作可以在SQL中更容易地完成。选择使用SQLite,因为对它很熟悉。线程的每个周期执行以下步骤:
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位版本的所有代码文件。
通常,使用命令行上的installutil.exe来安装.NETWindows服务。它可能位于以下文件夹中(取决于安装了哪些版本的.NET):
根据打算安装的服务版本(32位或64位),应该使用Framework文件夹用于32位或Framework64文件夹用于64位,然后使用最新版本。
转到installutil.exe的文件夹,然后运行以下命令,将%path_to_service%替换为服务的实际路径:
installutil.exe /i %path_to_service%\BjSTools.RDPGuard.exe
要卸载服务,只需使用相同的命令,将开关/i替换为/u。
服务有一个标准XML格式的配置文件。它包含以下选项:
LogCategoryFilter选项使用整数的位 - 如果位设置为1,则相应的日志类别被记录。可以简单地将需要记录的日志类别的位值相加,以获得过滤器:
位 | 位值 | 类别 | 描述 |
---|---|---|---|
0 | 1 | Debug | 仅用于调试的消息 |
1 | 2 | Information | 状态或其他非干扰性消息 |
2 | 4 | Warning | 干扰性信息,但不影响程序的继续运行 |
3 | 8 | Error | 干扰性错误,但不会导致程序退出 |
4 | 16 | Critical | 干扰性错误,会导致程序退出 |
即使服务已经安装,也可以更改设置,但只有在服务启动时才会读取。
服务也可以作为命令行程序使用。尝试启动服务而不使用以下命令行选项中的任何一个将导致错误,因为服务试图作为服务启动,而这只有在Windows服务控制器执行时才可能。这些选项是可用的:
以下是命令行使用的示例:
BjSTools.RDPGuard /C
BjSTools.RDPGuard /L > log.txt
BjSTools.RDPGuard /L=2015-01-01
BjSTools.RDPGuard /L=2015-04-01T12:00:00