This is the nineteenth part of the Availability Anywhere series. For your convenience you can find other parts in the table of contents in Part 1 – Connecting to SSH tunnel automatically in Windows
If you expose RDP or OpenSSH to the wide Internet, you’ll most likely get automated attacks. There is a way to block these attacks with firewall, but I didn’t find a nice solution to do so, so I created my own.
The idea is as follows: we periodically scan event log to get failed authentication attempts. We extract the IP address, and then ban it if it happened too often.
Event for RDP is in Security
with ID 4625
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
- <Event xmlns="http://schemas.microsoft.com/win/2004/08/events/event"> - <System> <Provider Name="Microsoft-Windows-Security-Auditing" Guid="{54849625-5478-4994-A5BA-3E3B0328C30D}" /> <EventID>4625</EventID> <Version>0</Version> <Level>0</Level> <Task>12544</Task> <Opcode>0</Opcode> <Keywords>0x8010000000000000</Keywords> <TimeCreated SystemTime="2023-07-15T08:12:14.780596300Z" /> <EventRecordID>27785286</EventRecordID> <Correlation /> <Execution ProcessID="992" ThreadID="41708" /> <Channel>Security</Channel> <Computer>COMPUTER</Computer> <Security /> </System> - <EventData> <Data Name="SubjectUserSid">S-1-0-0</Data> <Data Name="SubjectUserName">-</Data> <Data Name="SubjectDomainName">-</Data> <Data Name="SubjectLogonId">0x0</Data> <Data Name="TargetUserSid">S-1-0-0</Data> <Data Name="TargetUserName">administrator</Data> <Data Name="TargetDomainName">domainname</Data> <Data Name="Status">0xc000006d</Data> <Data Name="FailureReason">%%2313</Data> <Data Name="SubStatus">0xc000006a</Data> <Data Name="LogonType">3</Data> <Data Name="LogonProcessName">NtLmSsp</Data> <Data Name="AuthenticationPackageName">NTLM</Data> <Data Name="WorkstationName">LOCALPCNAME</Data> <Data Name="TransmittedServices">-</Data> <Data Name="LmPackageName">-</Data> <Data Name="KeyLength">0</Data> <Data Name="ProcessId">0x0</Data> <Data Name="ProcessName">-</Data> <Data Name="IpAddress">ADDRESS</Data> <Data Name="IpPort">PORT</Data> </EventData> </Event> |
For OpenSSH we extract these two events: Applications and Services -> OpenSSH -> Admin
for IDs 1
and 2
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
- <Event xmlns="http://schemas.microsoft.com/win/2004/08/events/event"> - <System> <Provider Name="OpenSSH" Guid="{C4B57D35-0636-4BC3-A262-370F249F9802}" /> <EventID>1</EventID> <Version>0</Version> <Level>1</Level> <Task>0</Task> <Opcode>0</Opcode> <Keywords>0x8000000000000000</Keywords> <TimeCreated SystemTime="2023-07-14T17:28:51.261109200Z" /> <EventRecordID>46166</EventRecordID> <Correlation /> <Execution ProcessID="32716" ThreadID="32356" /> <Channel>OpenSSH/Admin</Channel> <Computer>COMPUTER</Computer> <Security UserID="S-1-5-18" /> </System> - <EventData> <Data Name="process">sshd</Data> <Data Name="payload">fatal: Timeout before authentication for ADDRESS port PORT</Data> </EventData> </Event> |
and
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
- <Event xmlns="http://schemas.microsoft.com/win/2004/08/events/event"> - <System> <Provider Name="OpenSSH" Guid="{C4B57D35-0636-4BC3-A262-370F249F9802}" /> <EventID>2</EventID> <Version>0</Version> <Level>2</Level> <Task>0</Task> <Opcode>0</Opcode> <Keywords>0x8000000000000000</Keywords> <TimeCreated SystemTime="2023-07-13T07:17:18.615649100Z" /> <EventRecordID>45523</EventRecordID> <Correlation /> <Execution ProcessID="12388" ThreadID="24004" /> <Channel>OpenSSH/Admin</Channel> <Computer>COMPUTER</Computer> <Security UserID="S-1-5-18" /> </System> - <EventData> <Data Name="process">sshd</Data> <Data Name="payload">error: maximum authentication attempts exceeded for invalid user root from ADDRESS port PORT ssh2 [preauth]</Data> </EventData> </Event> |
We ban them with the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
while($True){ write-host (Get-Date) $timeFrame = (Get-Date).AddMinutes(-15) $attackThreshold = 20 $maxEvents = 10000 $logsWithPatterns = @( @("Security", 4625, @("Source Network Address")), @("OpenSSH/Admin", 2, @("maximum authentication attempts")), @("OpenSSH/Admin", 1, @("Timeout before authentication")) ) $newIpAddressesToBan = $logsWithPatterns | %{ $log = $_[0] $id = $_[1] $patterns = $_[2] write-host $log, " ", $id, " ", $patterns Get-WinEvent $log -maxevents $maxEvents | ? {$_.id -eq $id } | ?{$_.TimeCreated -ge $timeFrame } | %{ $_.message} | select-string $patterns | %{ [regex]::match($_,'(\d{1,4}[.]\d{1,4}[.]\d{1,4}[.]\d{1,4})') } | ?{ $_.Success} | %{$_.Groups[0].Value} | group-object | ?{$_.Count -ge $attackThreshold} | %{$_.Name} } $newIpAddressesToBan = @(($newIpAddressesToBan | %{ $_.Substring(0, $_.lastIndexOf(".")) + ".0/255.255.255.0"}) | sort | get-unique) $existingBannedIpAddresses = @((get-netfirewallrule -displayname "Rule for banning" | Get-NetFirewallAddressFilter).RemoteAddress | sort | get-unique) $allIpAddresses = (($newIpAddressesToBan + $existingBannedIpAddresses) | sort | get-unique) netsh advfirewall firewall set rule name="Rule for banning" new remoteip=([string]::Join(",", $allIpAddresses)) sleep -s 60 } |
I get at most 10000 events from the last 15 minutes, and I look for at least 20 attempts from a given IP. I extract IPv4 addresses with a regexp. I also replace them with a network subnet with mask of 24 bits.
Just run this script in the background with Administrator privileges and it will automatically add new IP addresses to the firewall. You need to create the rule Rule for banning
manually beforehand as you need. If you need some other patterns or event logs, just add them to the collection at the beginning.