{"id":3570,"date":"2021-12-31T15:28:27","date_gmt":"2021-12-31T20:28:27","guid":{"rendered":"https:\/\/osric.com\/chris\/accidental-developer\/?p=3570"},"modified":"2021-12-31T15:28:27","modified_gmt":"2021-12-31T20:28:27","slug":"ipban-fail2ban-for-windows","status":"publish","type":"post","link":"https:\/\/osric.com\/chris\/accidental-developer\/2021\/12\/ipban-fail2ban-for-windows\/","title":{"rendered":"IPBan: fail2ban for Windows"},"content":{"rendered":"<p>I was looking for a tool to block IP addresses after a certain number of failed RDP login attempts, something like fail2ban but for Windows. I came across <a href=\"https:\/\/github.com\/DigitalRuby\/IPBan\">IPBan<\/a>. Calling IPBan a &#8220;fail2ban for Windows&#8221; unfairly minimizes what it can do, but it can handle that task quite nicely.<\/p>\n<p>As a test I installed it on a Windows 2019 Server running in Azure using the provided install instructions from the README:<\/p>\n<pre><code>[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; iex ((New-Object System.Net.WebClient).DownloadString('https:\/\/raw.githubusercontent.com\/DigitalRuby\/IPBan\/master\/IPBanCore\/Windows\/Scripts\/install_latest.ps1'))<\/code><\/pre>\n<p>You don&#8217;t want to blindly run a script that downloads and installs something on your machine without looking at it first, do you? Of course not. You take a look at the code first:<\/p>\n<p><a href=\"https:\/\/github.com\/DigitalRuby\/IPBan\/blob\/master\/IPBanCore\/Windows\/Scripts\/install_latest.ps1\">https:\/\/github.com\/DigitalRuby\/IPBan\/blob\/master\/IPBanCore\/Windows\/Scripts\/install_latest.ps1<\/a><\/p>\n<p>Essentially, the script does the following:<\/p>\n<ol>\n<li>Downloads a zip file<\/li>\n<li>Extracts the files<\/li>\n<li>Sets up a Windows service<\/li>\n<li>Suggests that you might want to edit the config by opening the config file in Notepad<\/li>\n<\/ol>\n<p>I took a look at the config file and made only one change to get started: I added a CenturyLink subnet to the allow list. CenturyLink is my ISP, and I didn&#8217;t want to accidentally lock myself out. Normally you wouldn&#8217;t want to add an entire \/12 of your residential ISP, but this was just a test on a temporary and disposable server:<\/p>\n<pre><code>&lt;add key=\"Whitelist\" value=\"75.160.0.0\/12\"\/&gt;<\/code><\/pre>\n<p>I noted a few other interesting things in the config file:<\/p>\n<ol>\n<li>It can also examine Linux logs (IPBan can run on Windows or Linux)<\/li>\n<li>It blocks failed RDP logins, but also blocks failed logins for other Windows services, such as MSSQL and Exchange<\/li>\n<li>It defaults to banning an IP address after 5 failed attempts, but the number of failed attempts can be configured<\/li>\n<li>The default ban duration is 1 day, but this can be configured<\/li>\n<li>The default ban duration can take into account recidivism, so that the second or third time an address is banned it can use a longer duration<\/li>\n<li>It can also use a username-based allow list (whitelist), so that attempted logins from usernames not on the list will ban the IP address immediately<\/li>\n<li>It can add blocks based on external threat intelligence feeds, and uses the <a href=\"https:\/\/rules.emergingthreats.net\/fwrules\/emerging-Block-IPs.txt\">Emerging Threats blocklist<\/a> by default<\/li>\n<li>It can pull the config file from a URL, allowing you to manage the configuration of many servers from one location.<\/li>\n<\/ol>\n<p>After I reviewed the config file (<code>C:\\Program Files\\IPBan\\ipban.config<\/code>) I restarted the IPBAN service via Services GUI. You could also restart it via Powershell:<\/p>\n<pre><code>sc.exe stop IPBAN\r\nsc.exe start IPBAN<\/code><\/pre>\n<p>Now that it was up-and-running, what changes did it make? I opened up Windows Defender Firewall and opened the Advanced Settings. Looking though the Inbound rules I found 4 rules prefixed with <code>IPBAN_<\/code>, three <em>Deny<\/em> rules and one <em>Allow<\/em> rule:<\/p>\n<ol>\n<li>[Deny] <code>IPBan_Block_0<\/code><\/li>\n<li>[Deny] <code>IPBan_EmergingThreats_0<\/code><\/li>\n<li>[Deny] <code>IPBan_EmergingThreats_1000<\/code><\/li>\n<li>[Allow] <code>IPBan_GlobalWhitelist_0<\/code><\/li>\n<\/ol>\n<p>I looked at the properties for <code>IPBan_Block_0<\/code>. Already, one IP address had been banned! (I checked several hours later and only 2 additional IP addresses had been banned, so I may have gotten lucky to see one within minutes.)<\/p>\n<p>It took me a while to figure out what the difference between <code>IPBan_EmergingThreats_0<\/code> and <code>IPBan_EmergingThreats_1000<\/code>. IPBan is creating a new firewall rule after 999 entries, so the 1000th IP address or subnet from the Emerging Threats feed was added to <code>IPBan_EmergingThreats_1000<\/code>. I&#8217;ve seen some arguments online about whether or not there is a limit to the number of addresses or subnets that can be included in the scope for a Windows Defender Firewall rule, and some sources indicate there is a limit of 1000 (and I&#8217;m fairly certain the author of IPBan is one of the people arguing there is a limit).<\/p>\n<p>The <code>IPBan_GlobalWhitelist_0<\/code> contained the CenturyLink subnet that I explicitly allowed.<\/p>\n<p>I was excited about pulling the configuration from a URL, so I added my configuration to <a href=\"https:\/\/osric.com\/chris\/ipban\/ipban.example.config\">https:\/\/osric.com\/chris\/ipban\/ipban.example.config<\/a>, initially with just one tweak:<\/p>\n<pre><code>&lt;add key=\"UserNameWhitelist\" value=\"chris\"\/&gt;<\/code><\/pre>\n<p>This would, in theory, ban any IP address immediately if they used an address like <em>admin<\/em> or <em>guest<\/em>. It uses a configurable Levenshtein distance to try to avoid banning an IP address based on a typo (for example, <em>chros<\/em> instead of <em>chris<\/em>), which is a clever approach.<\/p>\n<p>I then added the URL to the config file on the server itself:<\/p>\n<pre><code>&lt;add key=\"GetUrlConfig\" value=\"https:\/\/osric.com\/chris\/ipban\/ipban.example.config\"\/&gt;<\/code><\/pre>\n<p>After another service restart, I wanted to test to see if a login attempt with a bad username would cause an immediate block. I opened an RDP connection using the credentials <code>admin:admin<\/code> and from a VPN IP address that would be outside of the allowed CenturyLink subnet.<\/p>\n<p>I did not immediately see the VPN IP address in the <code>IPBAN_Block_0<\/code> deny rule. I checked the logs (<code>C:\\Program Files\\IPBan\\logfile.txt<\/code>):<\/p>\n<pre><code>2021-12-31 04:35:45.3336|INFO|DigitalRuby.IPBanCore.Logger|Firewall entries updated: \r\n2021-12-31 04:37:45.5791|WARN|DigitalRuby.IPBanCore.Logger|Login failure: 198.51.100.101, admin, RDP, 1\r\n2021-12-31 04:37:45.5791|INFO|DigitalRuby.IPBanCore.Logger|IP blacklisted: False, user name blacklisted: False, fails user name white list regex: False, user name edit distance blacklisted: True\r\n2021-12-31 04:37:45.5791|WARN|DigitalRuby.IPBanCore.Logger|Banning ip address: 198.51.100.101, user name: admin, config black listed: True, count: 1, extra info: , duration: 1.00:00:00\r\n2021-12-31 04:37:45.6024|WARN|DigitalRuby.IPBanCore.Logger|Updating firewall with 1 entries...\r\n2021-12-31 04:37:45.6024|INFO|DigitalRuby.IPBanCore.Logger|Firewall entries updated: 198.51.100.101<\/code><\/pre>\n<p>There was just a delay in adding it. I checked the <code>IPBan_Block_0<\/code> deny rule again in Windows Defender Firewall and the IP address was there. I must have been exceedingly quick, as the cycle time defaults to 15 seconds:<\/p>\n<pre><code>&lt;add key=\"CycleTime\" value=\"00:00:00:15\"\/&gt;<\/code><\/pre>\n<p>One other change I made to my config: I set this option to <em>false<\/em> so that any future tests would not send my VPN IP to the &#8220;global ipban database&#8221;:<\/p>\n<pre><code>&lt;add key=\"UseDefaultBannedIPAddressHandler\" value=\"true\" \/&gt;<\/code><\/pre>\n<p>Normally leaving that set to <em>true<\/em> should be fine, but during testing it would be good to avoid adding your own IP address to a block list. I have not yet discovered if the global IPBan database is publicly available.<\/p>\n<p>A couple other things I noted:<\/p>\n<p>The <code>ipban.config<\/code> file was completely overwritten by the version at the <code>GetUrlConfig<\/code>, including the <code>GetUrlConfig<\/code> value! The next time I restarted the IPBAN service, it did not pick up the config from that URL, as the <code>GetUrlConfig<\/code> option was now blank. I updated the <code>GetUrlConfig<\/code> value on <code>ipban.config<\/code> locally and on the remotely hosted <code>ipban.example.config<\/code> to include the URL to itself, so that it would persist after a restart.<\/p>\n<p>The <code>FirewallRules<\/code> config option has a somewhat confusing syntax, at least for <em>Deny<\/em> rules. I added the following rule, which then appeared in my Windows Defender Firewalls block rules as <code>IPBan_EXTRA_GoogleDNS_0<\/code>:<\/p>\n<pre><code>&lt;add key=\"FirewallRules\" value=\"\r\n    GoogleDNS;block;8.8.4.4;53;.\r\n\"\/&gt;<\/code><\/pre>\n<p>This rule <em>allows<\/em> inbound port 53\/tcp traffic from 8.8.4.4 and <em>blocks<\/em> it from all other ports (0-52\/tcp and 54-65535\/tcp). I&#8217;m still not sure how to specify that I want to block all ports, or block both TCP and UDP traffic using this config option.<\/p>\n<p>IPBan has a lot of other configuration options that I&#8217;m excited to test. For me, this tool fills a major gap for Windows servers!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>I was looking for a tool to block IP addresses after a certain number of failed RDP login attempts, something like fail2ban but for Windows. I came across IPBan. Calling IPBan a &#8220;fail2ban for Windows&#8221; unfairly minimizes what it can do, but it can handle that task quite nicely. As a test I installed it &hellip; <a href=\"https:\/\/osric.com\/chris\/accidental-developer\/2021\/12\/ipban-fail2ban-for-windows\/\" class=\"more-link\">Continue reading <span class=\"screen-reader-text\">IPBan: fail2ban for Windows<\/span><\/a><\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[48,422],"tags":[452,569,223],"class_list":["post-3570","post","type-post","status-publish","format-standard","hentry","category-security","category-sysadmin","tag-fail2ban","tag-ipban","tag-windows"],"_links":{"self":[{"href":"https:\/\/osric.com\/chris\/accidental-developer\/wp-json\/wp\/v2\/posts\/3570","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/osric.com\/chris\/accidental-developer\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/osric.com\/chris\/accidental-developer\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/osric.com\/chris\/accidental-developer\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/osric.com\/chris\/accidental-developer\/wp-json\/wp\/v2\/comments?post=3570"}],"version-history":[{"count":10,"href":"https:\/\/osric.com\/chris\/accidental-developer\/wp-json\/wp\/v2\/posts\/3570\/revisions"}],"predecessor-version":[{"id":3585,"href":"https:\/\/osric.com\/chris\/accidental-developer\/wp-json\/wp\/v2\/posts\/3570\/revisions\/3585"}],"wp:attachment":[{"href":"https:\/\/osric.com\/chris\/accidental-developer\/wp-json\/wp\/v2\/media?parent=3570"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/osric.com\/chris\/accidental-developer\/wp-json\/wp\/v2\/categories?post=3570"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/osric.com\/chris\/accidental-developer\/wp-json\/wp\/v2\/tags?post=3570"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}