由于工作原因,偶尔需要在家里远程连接到公司计算机上处理一些工作事宜(犹记得2021年上海lockdown期间,远程办公了近2个月,就是以这种方式进行的😂)。因为公司的电脑是在一个局域网内的,并没有对外暴露IP地址,所以一般的做法就是通过第三方软件比如TeamView、向日葵这类软件来提供支持,但这些第三方软件经常会爆发一些安全事件,且把电脑交给第三方软件其实也存在安全隐患。

不使用第三方软件,通用的做法就是在路由器上将外网IP地址和特定的端口转发到特定内网IP地址的机器和端口上。这样通过外网IP地址和端口,就可以使用远程桌面的方式访问到内网的计算机,但在外网暴露特定端口是有风险的,尤其是对于远程桌面3369这样常用的端口,因为任何人都可以通过一些端口扫描工具进行连接,进而通过暴力的方式尝试登录(正规的做法是通过VPN的方式,首先登录一台跳板机或者堡垒机,然后再访问目标机器)。之前在记一次.NET程序内存暴涨分析这篇文章中就遇到过RDP攻击,今天又遇到了一个远程连接攻击的例子。

本文首先回顾了一次RDP攻击,并从Windows日志上查看RDP攻击留下来的信息,接下来通过新建防火墙入站规则,阻止特定的疑似攻击的IP地址对本机3389端口的访问。最后通过程序的方式自动监控这种RDP失败的登录,并在登录失败超过一定次数后,自动新建防火墙入站规则,并将疑似IP地址添加到规则中,从而防止来自这类IP地址的RDP攻击的进一步尝试。

缘由


为了防止登录密码被暴力破解,所以我留了一手,设置了登录失败一定次数之后,账户会锁定一段时间。这样,即使周末有人通过暴力破解也能拖到工作日来处理。设置锁定策略的步骤如下:

  • 在命令行中运行gpedit.msc
  • 在弹出的组策略里面,打开[计算机配置] - [Windows设置] - [安全设置] - [帐户策略] - [帐户锁定策略] 项,设置如下。

这个策略表示,如果5次登录失败,则锁定账户10分钟,然后等待5分钟之后重置登录失败计数。即5次错误后锁定15分钟。相当于一天最多的错误密码尝试次数为((24*60))/15)*5=480次。把登录密码长度设置长一点,这样用户名和密码这两项,要全部在480*2=960次之内尝试成功,概率是很小的,足以应付周末被攻击的情况。

果不其然,在某个周末我在家里想远程连接到公司电脑处理事情,结果远程连接失败弹出以下提示:

看到这个界面,就表示有人在尝试远程登录,因为用户名或密码错误超过次数后,电脑把账户锁定了,这种情况,只有等工作日上班解决。

蛛丝马迹


到公司后,第一件事就是把网线拔掉,然后重启计算机。然后查看Window相关日志信息,发现确实有很多RDP连接请求。比如在:"应用程序和服务日志"->"Microsoft"->"Windows"->"TerminalServices-RemoteConnectionManager"->"Operational"下面有很多“RDP-TCP”的连接尝试。

在“Windows日志”->"安全"下面,过滤所有表示为Logon的事件ID为4625的日志,注意,4625这个事件表示登录失败,它包含远程桌面登录失败和本地登录(127.0.0.1)失败:

上面这个日志里面记录了尝试登录但失败的日志信息,包括源网络地址,登录的时候用的用户名等信息。

由于此时人已经在电脑前面,所以我又重新连上网络,发现这种尝试的连接仍然存在,通过netstat也能看到,有些IP地址还非常顽固,会不断地尝试:

C:\Users\xxxxxx>netstat -ano | findstr "3389"
  TCP    0.0.0.0:3389           0.0.0.0:0              LISTENING       15884
  TCP    192.168.6.200:3389     80.234.104.149:51703   ESTABLISHED     15884
  TCP    192.168.6.200:3389     80.234.104.149:52890   ESTABLISHED     15884
  TCP    192.168.6.200:3389     80.234.104.149:58331   ESTABLISHED     15884
  TCP    [::]:3389              [::]:0                 LISTENING       15884
  UDP    0.0.0.0:3389           *:*                                    15884
  UDP    [::]:3389              *:*                                    15884

比如上面这个 80.234.104.149 ,这个IP地址会一直尝试连接,而且还是通过机器上不同的端口发起的连接请求:

当把远程桌面关掉之后,这一切又都回归平静。

解决方案


最简单的方法就是将netstat中查询到的那些可疑的IP地址添加到防火墙中,阻止这些IP地址到本机的连接。具体操作为“Windows Defender防火墙”->“高级设置”->“入站规则”下面新建规则:

▲ 规则类型选择端口

▲ RDP是通过TCP连接的,端口类型可以选择所有端口,也可以选择特定端口,这里选择特定的3389端口

▲ 这里选择阻止连接

▲ 给起个名字和描述

现在,这个规则因为没有添加任何需要阻止的IP地址,所以默认全部禁止访问3389端口。需要右键单击添加一个IP地址:

▲在“作用域”中,远程IP地址里,添加之前异常的IP地址

再次强调一点,因为创建规则是禁止访问端口,如果远程IP地址里面没有内容,则默认禁止所有的IP访问3389端口,这就不符合我们的要求了。所以这里面一定要添加一个IP地址,哪怕是虚拟的也可以。

更进一步


上面这个方法还是略显被动。如果能通过程序自动判断异常的登录,然后将这些异常的IP地址自动加入到防火墙里面就好了。幸运的是,Windows提供了操作防火墙设置的相关API,可以很方便的创建规则。现在就剩下如何判断异常的IP地址了,一种思路是根据上面的日志分析,只需要判断ID为4625的事件,统计这个事件出现的频次,达到一定阈值,就把这个事件里面的IP地址加入到防火墙里面。

恰好Github上BruteForceBuster个项目就是这样实现的。它的逻辑也非常简单,主要就是分两步:

首先监听4625的事件:

public static void EventLogSubscription()
{
    try
    {
        string eventQueryString = @"<QueryList>
  <Query Id=""0"" Path=""Security"">
    <Select Path=""Security"">*[System[(EventID=4625)]]</Select>
  </Query>
</QueryList>";

        EventLogQuery eventQuery = new EventLogQuery("Security", PathType.LogName, eventQueryString);

        using EventLogWatcher watcher = new EventLogWatcher(eventQuery);
        watcher.EventRecordWritten += Watcher_EventRecordWritten;
        watcher.Enabled = true;
        Console.ReadLine();
    }
    catch (EventLogReadingException e)
    {
        Console.WriteLine(e.Message);
    }
    Console.ReadLine();
}

这样,当有新的ID为4625事件发生时,就会回调Watcher_EventRecordWritten方法:

private static void Watcher_EventRecordWritten(object sender, EventRecordWrittenEventArgs e)
{
    if (e.EventRecord != null)
    {
        string accountName = e.EventRecord.Properties[5].Value.ToString();
        string ip = e.EventRecord.Properties[19].Value.ToString();
        string port = e.EventRecord.Properties[20].Value.ToString();
        string type = port == "0" ? "RDP" : $"SMB source port : {port}";
        IPDict.TryGetValue(ip, out int count);
        IPDict[ip] = ++count;

        Console.WriteLine($"Account Name : {accountName}, Source Network Address : {ip}, Count : {count}, Time : {DateTime.Now}, Type : {type}");

        if (count >= BlockThreshold)
            BlockIP(ip);
    }
}

在这个方法里,会提取IP地址,端口号,登录的用户名等一系列信息,这里要做的就是统计这个IP地址的出错频率,如果大于设定的阈值BlockThreshold,则执行第二步,将该IP地址添加到防火墙的规则中,禁止该IP地址的连接。BlockIP方法如下:

public static void BlockIP(params string[] remoteAddresses)
{
    string ruleName = "Block3389";

    Type typeFWPolicy2 = Type.GetTypeFromProgID("HNetCfg.FwPolicy2");
    Type typeFWRule = Type.GetTypeFromProgID("HNetCfg.FwRule");
    try
    {

        INetFwPolicy2 fwPolicy2 = (INetFwPolicy2)Activator.CreateInstance(typeFWPolicy2);
        var rules = fwPolicy2.Rules.Cast<INetFwRule>();
        var rule = rules.FirstOrDefault(x => x.Name == ruleName);
        string newAddresses = string.Join(',', remoteAddresses);
        if (rule is null)
        {
            INetFwRule newRule = (INetFwRule)Activator.CreateInstance(typeFWRule);
            newRule.Name = ruleName;
            newRule.Description = "Block inbound traffic over TCP port 3389";
            newRule.Protocol = (int)NET_FW_IP_PROTOCOL_.NET_FW_IP_PROTOCOL_TCP;
            newRule.LocalPorts = "3389,445,135,139";
            newRule.RemoteAddresses = newAddresses;
            newRule.Direction = NET_FW_RULE_DIRECTION_.NET_FW_RULE_DIR_IN;
            newRule.Enabled = true;
            newRule.Grouping = "@firewallapi.dll,-23255";
            newRule.Profiles = fwPolicy2.CurrentProfileTypes;
            newRule.Action = NET_FW_ACTION_.NET_FW_ACTION_BLOCK;
            fwPolicy2.Rules.Add(newRule);
        }
        else
        {
            rule.RemoteAddresses += $",{newAddresses}";
        }
        Console.ForegroundColor = ConsoleColor.Red;
        Console.WriteLine($"Blocked! IP : {newAddresses}");
        Console.ResetColor();
    }
    catch (COMException ex)
    {
        Console.WriteLine(ex);
    }
}

这段代码首先查找有没有名为“Block3389"的规则,如果没有,则新建该规则,该规则的Action是Block,端口是"3389,445,135,139",RemoteAddress就是日志里面提取的地址,需要注意的是因为Action是Block,所以这里的创建规则的时候,地址必须要填,否则就是禁止所有的远程IP地址连接了。如果之前新建有”Block3389“规则,则直接将IP地址添加到RemoteAddresses后面。

运行之后可以看到,非常完美的解决了这些恶意访问本机3389端口的IP地址。

如上图,可以看到“81.17.20.98”这个IP地址尝试RDP登录了3次,均失败了,第1次和第3次用的用户名是“ADMINISTRATOR”,第2次用的实“ADMIN”。程序里设置的是如果登录失败3次,这将这个IP地址添加到防火墙规则中。这里也可以给个提醒,就是我们最好禁用"ADMINISTRATOR"这样的管理员账户。

上面这个规则比较简单粗暴,只是按照出错的次数来限制,这里可以做的更复杂一些,比如使用类似熔断器之类的策略,限定允许错误的频次,设置屏蔽的时长等等,这里不展开,因为单纯的使用累计出错的次数已经满足要求。

在防火墙设置中,可以看到程序创建了一个名为“Block3389”的进站规则:

这个规则与我们之前手动创建的规则是一样的。

在“作用域”的远程IP地址中可以看到程序监测到的“81.17.20.98”这个异常的IP地址。

 

参考: