private IPBanConfig(string xml, IDnsLookup dns) { this.dns = dns; // deserialize with XmlDocument, the .net core Configuration class is quite buggy XmlDocument doc = new XmlDocument(); doc.LoadXml(xml); foreach (XmlNode node in doc.SelectNodes("//appSettings/add")) { appSettings[node.Attributes["key"].Value] = node.Attributes["value"].Value; } GetConfig <int>("FailedLoginAttemptsBeforeBan", ref failedLoginAttemptsBeforeBan, 1, 50); GetConfig <bool>("ResetFailedLoginCountForUnbannedIPAddresses", ref resetFailedLoginCountForUnbannedIPAddresses); GetConfigArray <TimeSpan>("BanTime", ref banTimes, emptyTimeSpanArray); for (int i = 0; i < banTimes.Length; i++) { banTimes[i] = banTimes[i].Clamp(TimeSpan.FromMinutes(1.0), maxBanTimeSpan); } GetConfig <bool>("ClearBannedIPAddressesOnRestart", ref clearBannedIPAddressesOnRestart); GetConfig <TimeSpan>("ExpireTime", ref expireTime, TimeSpan.FromMinutes(1.0), maxBanTimeSpan); GetConfig <TimeSpan>("CycleTime", ref cycleTime, TimeSpan.FromSeconds(5.0), TimeSpan.FromMinutes(1.0), false); GetConfig <TimeSpan>("MinimumTimeBetweenFailedLoginAttempts", ref minimumTimeBetweenFailedLoginAttempts, TimeSpan.Zero, TimeSpan.FromSeconds(15.0), false); GetConfig <string>("FirewallRulePrefix", ref firewallRulePrefix); string whiteListString = GetConfig <string>("Whitelist", string.Empty); string whiteListRegexString = GetConfig <string>("WhitelistRegex", string.Empty); string blacklistString = GetConfig <string>("Blacklist", string.Empty); string blacklistRegexString = GetConfig <string>("BlacklistRegex", string.Empty); PopulateList(whiteList, whiteListRanges, ref whiteListRegex, whiteListString, whiteListRegexString); PopulateList(blackList, blackListRanges, ref blackListRegex, blacklistString, blacklistRegexString); if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { expressionsFailure = new XmlSerializer(typeof(EventViewerExpressionsToBlock)).Deserialize(new XmlNodeReader(doc.SelectSingleNode("//ExpressionsToBlock"))) as EventViewerExpressionsToBlock; if (expressionsFailure != null) { foreach (EventViewerExpressionGroup group in expressionsFailure.Groups) { foreach (EventViewerExpression expression in group.Expressions) { expression.Regex = (expression.Regex?.ToString() ?? string.Empty).Trim(); } } } expressionsSuccess = new XmlSerializer(typeof(EventViewerExpressionsToNotify)).Deserialize(new XmlNodeReader(doc.SelectSingleNode("//ExpressionsToNotify"))) as EventViewerExpressionsToNotify; if (expressionsSuccess != null) { foreach (EventViewerExpressionGroup group in expressionsSuccess.Groups) { group.NotifyOnly = true; foreach (EventViewerExpression expression in group.Expressions) { expression.Regex = (expression.Regex?.ToString() ?? string.Empty).Trim(); } } } } else { expressionsFailure = new EventViewerExpressionsToBlock(); expressionsSuccess = new EventViewerExpressionsToNotify(); } try { if (new XmlSerializer(typeof(IPBanLogFilesToParse)).Deserialize(new XmlNodeReader(doc.SelectSingleNode("//LogFilesToParse"))) is IPBanLogFilesToParse logFilesToParse) { logFiles = logFilesToParse.LogFiles; } else { logFiles = emptyLogFilesToParseArray; } } catch (Exception ex) { Logger.Error(ex); logFiles = new IPBanLogFileToParse[0]; } GetConfig <string>("ProcessToRunOnBan", ref processToRunOnBan); GetConfig <bool>("UseDefaultBannedIPAddressHandler", ref useDefaultBannedIPAddressHandler); // retrieve firewall configuration string[] firewallTypes = GetConfig <string>("FirewallType", string.Empty).Split(',', StringSplitOptions.RemoveEmptyEntries); foreach (string firewallOSAndType in firewallTypes) { string[] pieces = firewallOSAndType.Split(':'); if (pieces.Length == 2) { osAndFirewallType[pieces[0]] = pieces[1]; } } string userNameWhiteListString = GetConfig <string>("UserNameWhiteList", string.Empty); foreach (string userName in userNameWhiteListString.Split(',')) { string userNameTrimmed = userName.Normalize().ToUpperInvariant().Trim(); if (userNameTrimmed.Length > 0) { userNameWhitelist.Add(userNameTrimmed); } } string userNameWhiteListRegexString = GetConfig <string>("UserNameWhiteListRegex", string.Empty); if (!string.IsNullOrWhiteSpace(userNameWhiteListRegexString)) { userNameWhitelistRegex = new Regex(userNameWhiteListRegexString, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Singleline); } GetConfig <int>("UserNameWhiteListMinimumEditDistance", ref userNameWhitelistMaximumEditDistance); GetConfig <int>("FailedLoginAttemptsBeforeBanUserNameWhitelist", ref failedLoginAttemptsBeforeBanUserNameWhitelist); GetConfig <string>("GetUrlUpdate", ref getUrlUpdate); GetConfig <string>("GetUrlStart", ref getUrlStart); GetConfig <string>("GetUrlStop", ref getUrlStop); GetConfig <string>("GetUrlConfig", ref getUrlConfig); GetConfig <string>("ExternalIPAddressUrl", ref externalIPAddressUrl); // parse firewall block rules, one per line ParseFirewallBlockRules(); }
private Task ProcessPendingLogEvents() { // get copy of pending log events quickly in a lock and clear list List <IPAddressLogEvent> events = null; lock (pendingLogEvents) { events = new List <IPAddressLogEvent>(pendingLogEvents); pendingLogEvents.Clear(); } List <IPAddressLogEvent> bannedIPs = new List <IPAddressLogEvent>(); object transaction = BeginTransaction(); try { // loop through events, for failed and successful logins, we want to group / aggregate the same event from the // same remote ip address foreach (IPAddressLogEvent evt in events) { if (!IPBanFirewallUtility.TryNormalizeIPAddress(evt.IPAddress, out string normalizedIPAddress)) { continue; } evt.IPAddress = normalizedIPAddress; switch (evt.Type) { case IPAddressEventType.FailedLogin: // if we are not already banned... if (!DB.TryGetIPAddressState(evt.IPAddress, out IPBanDB.IPAddressState? state, transaction) || state.Value == IPBanDB.IPAddressState.FailedLogin) { ProcessIPAddressEvent(evt, pendingFailedLogins, Config.MinimumTimeBetweenFailedLoginAttempts, "failed"); } break; case IPAddressEventType.SuccessfulLogin: ProcessIPAddressEvent(evt, pendingSuccessfulLogins, Config.MinimumTimeBetweenSuccessfulLoginAttempts, "successful"); break; case IPAddressEventType.Blocked: // if we are not already banned... if (!DB.TryGetIPAddressState(evt.IPAddress, out IPBanDB.IPAddressState? state2, transaction) || state2.Value == IPBanDB.IPAddressState.FailedLogin) { // make sure the ip address is ban pending AddBannedIPAddress(evt.IPAddress, evt.Source, evt.UserName, bannedIPs, evt.Timestamp, false, evt.Count, string.Empty, transaction, evt.External); } break; case IPAddressEventType.Unblocked: DB.SetIPAddressesState(new string[] { evt.IPAddress }, IPBanDB.IPAddressState.RemovePending, transaction); firewallNeedsBlockedIPAddressesUpdate = true; break; } } CommitTransaction(transaction); } catch (Exception ex) { RollbackTransaction(transaction); Logger.Error(ex); } ExecuteExternalProcessForBannedIPAddresses(bannedIPs); return(Task.CompletedTask); }
protected virtual async Task <bool> GetUrl(UrlType urlType) { if ((urlType == UrlType.Start && GotStartUrl) || string.IsNullOrWhiteSpace(LocalIPAddressString) || string.IsNullOrWhiteSpace(FQDN)) { return(false); } else if (urlType == UrlType.Stop) { GotStartUrl = false; } string url; switch (urlType) { case UrlType.Start: url = Config.GetUrlStart; break; case UrlType.Stop: url = Config.GetUrlStop; break; case UrlType.Update: url = Config.GetUrlUpdate; break; case UrlType.Config: url = Config.GetUrlConfig; break; default: return(false); } if (!string.IsNullOrWhiteSpace(url)) { url = ReplaceUrl(url); try { KeyValuePair <string, object>[] headers = (Authorization is null ? null : new KeyValuePair <string, object>[] { new KeyValuePair <string, object>("Authorization", Authorization) }); byte[] bytes = await RequestMaker.MakeRequestAsync(new Uri(url), headers : headers); if (urlType == UrlType.Start) { GotStartUrl = true; } else if (urlType == UrlType.Update) { // if the update url sends bytes, we assume a software update, and run the result as an .exe if (bytes.Length != 0) { string tempFile = Path.Combine(OSUtility.TempFolder, "IPBanServiceUpdate.exe"); File.WriteAllBytes(tempFile, bytes); // however you are doing the update, you must allow -c and -d parameters // pass -c to tell the update executable to delete itself when done // pass -d for a directory which tells the .exe where this service lives string args = "-c \"-d=" + AppContext.BaseDirectory + "\""; Process.Start(tempFile, args); } } else if (urlType == UrlType.Config && bytes.Length != 0) { await WriteConfigAsync(Encoding.UTF8.GetString(bytes)); } } catch (Exception ex) { Logger.Error(ex, "Error getting url of type {0} at {1}", urlType, url); } } return(true); }
private async Task ProcessPendingFailedLogins(IReadOnlyList <IPAddressLogEvent> ipAddresses) { List <IPAddressLogEvent> bannedIpAddresses = new List <IPAddressLogEvent>(); object transaction = BeginTransaction(); try { foreach (IPAddressLogEvent failedLogin in ipAddresses) { try { string ipAddress = failedLogin.IPAddress; string userName = failedLogin.UserName; string source = failedLogin.Source; if (IsWhitelisted(ipAddress)) { Logger.Warn("Login failure, ignoring whitelisted ip address {0}, {1}, {2}", ipAddress, userName, source); } else { int maxFailedLoginAttempts; if (Config.IsWhitelisted(userName)) { maxFailedLoginAttempts = Config.FailedLoginAttemptsBeforeBanUserNameWhitelist; } else { // see if there is an override for max failed login attempts maxFailedLoginAttempts = (failedLogin.FailedLoginThreshold > 0 ? failedLogin.FailedLoginThreshold : Config.FailedLoginAttemptsBeforeBan); } DateTime now = failedLogin.Timestamp; // check for the target user name for additional blacklisting checks bool ipBlacklisted = Config.IsBlackListed(ipAddress); bool userBlacklisted = (ipBlacklisted ? false : Config.IsBlackListed(userName)); bool userFailsWhitelistRegex = (userBlacklisted ? false : Config.UserNameFailsUserNameWhitelistRegex(userName)); bool editDistanceBlacklisted = (ipBlacklisted || userBlacklisted || userFailsWhitelistRegex ? false : !Config.IsUserNameWithinMaximumEditDistanceOfUserNameWhitelist(userName)); bool configBlacklisted = ipBlacklisted || userBlacklisted || userFailsWhitelistRegex || editDistanceBlacklisted; // if the event came in with a count of 0 that means it is an automatic ban int incrementCount = (failedLogin.Count < 1 ? maxFailedLoginAttempts : failedLogin.Count); int newCount = ipDB.IncrementFailedLoginCount(ipAddress, userName, source, UtcNow, incrementCount, transaction); Logger.Warn(now, "Login failure: {0}, {1}, {2}, {3}", ipAddress, userName, source, newCount); // if the ip address is black listed or the ip address has reached the maximum failed login attempts before ban, ban the ip address if (configBlacklisted || newCount >= maxFailedLoginAttempts) { Logger.Info("IP blacklisted: {0}, user name blacklisted: {1}, fails user name white list regex: {2}, user name edit distance blacklisted: {3}", ipBlacklisted, userBlacklisted, userFailsWhitelistRegex, editDistanceBlacklisted); if (ipDB.TryGetIPAddressState(ipAddress, out IPBanDB.IPAddressState? state, transaction) && (state.Value == IPBanDB.IPAddressState.Active || state.Value == IPBanDB.IPAddressState.AddPending)) { Logger.Warn(now, "IP {0}, {1}, {2} ban pending.", ipAddress, userName, source); } else { Logger.Debug("Failed login count {0} >= ban count {1}{2}", newCount, maxFailedLoginAttempts, (configBlacklisted ? " config blacklisted" : string.Empty)); // if delegate and non-zero count, forward on - count of 0 means it was from external source, like a delegate if (IPBanDelegate != null && !failedLogin.External) { await IPBanDelegate.LoginAttemptFailed(ipAddress, source, userName, MachineGuid, OSName, OSVersion, UtcNow); } AddBannedIPAddress(ipAddress, source, userName, bannedIpAddresses, now, configBlacklisted, newCount, string.Empty, transaction, failedLogin.External); } } else { Logger.Debug("Failed login count {0} <= ban count {1}", newCount, maxFailedLoginAttempts); if (OSUtility.UserIsActive(userName)) { Logger.Warn("Login failed for known active user {0}", userName); } // if delegate and non-zero count, forward on - count of 0 means it was from external source, like a delegate if (IPBanDelegate != null && !failedLogin.External) { await IPBanDelegate.LoginAttemptFailed(ipAddress, source, userName, MachineGuid, OSName, OSVersion, UtcNow); } } } } catch (Exception ex) { Logger.Error(ex); } } CommitTransaction(transaction); ExecuteExternalProcessForBannedIPAddresses(bannedIpAddresses); } catch (Exception ex) { RollbackTransaction(transaction); Logger.Error(ex); } }
private bool GetOrCreateRule(string ruleName, string remoteIPAddresses, NetFwAction action, IEnumerable <PortRange> allowedPorts = null) { remoteIPAddresses = (remoteIPAddresses ?? string.Empty).Trim(); bool emptyIPAddressString = string.IsNullOrWhiteSpace(remoteIPAddresses) || remoteIPAddresses == "*"; bool ruleNeedsToBeAdded = false; lock (policy) { recreateRule: INetFwRule rule = null; try { rule = policy.Rules.Item(ruleName); } catch { // ignore exception, assume does not exist } if (rule is null) { rule = Activator.CreateInstance(ruleType) as INetFwRule; rule.Name = ruleName; rule.Enabled = true; rule.Action = action; rule.Description = "Automatically created by IPBan"; rule.Direction = NetFwRuleDirection.Inbound; rule.EdgeTraversal = false; rule.Grouping = "IPBan"; rule.LocalAddresses = "*"; rule.Profiles = int.MaxValue; // all ruleNeedsToBeAdded = true; } // do not ever set an empty string, Windows treats this as * which means everything if (!emptyIPAddressString) { try { PortRange[] allowedPortsArray = (allowedPorts?.ToArray()); if (allowedPortsArray != null && allowedPortsArray.Length != 0) { rule.Protocol = (int)NetFwIPProtocol.TCP; string localPorts; if (action == NetFwAction.Block) { localPorts = IPBanFirewallUtility.GetBlockPortRangeString(allowedPortsArray); } else { localPorts = IPBanFirewallUtility.GetPortRangeStringAllow(allowedPortsArray); } rule.LocalPorts = localPorts; } else { try { rule.Protocol = (int)NetFwIPProtocol.Any; } catch { // failed to set protocol to any, we are switching from tcp back to any without ports, the only option is to // recreate the rule if (!ruleNeedsToBeAdded) { policy.Rules.Remove(ruleName); goto recreateRule; } } } rule.RemoteAddresses = (remoteIPAddresses == "0.0.0.0/0,::/0" ? "*" : remoteIPAddresses); } catch (Exception ex) { // if something failed, do not create the rule emptyIPAddressString = true; Logger.Error(ex); } } if (emptyIPAddressString || string.IsNullOrWhiteSpace(rule.RemoteAddresses) || (rule.RemoteAddresses == "*" && remoteIPAddresses != "0.0.0.0/0,::/0")) { // if no ip addresses, remove the rule as it will allow or block everything with an empty RemoteAddresses string try { rule = null; policy.Rules.Remove(ruleName); } catch { } } else if (ruleNeedsToBeAdded) { policy.Rules.Add(rule); } return(rule != null); } }
private Task <bool> BlockOrAllowIPAddresses(string ruleNamePrefix, bool block, IEnumerable <string> ipAddresses, IEnumerable <PortRange> allowedPorts = null, CancellationToken cancelToken = default) { #if ENABLE_FIREWALL_PROFILING Stopwatch timer = Stopwatch.StartNew(); #endif int i = 0; string prefix = ruleNamePrefix.TrimEnd('_') + "_"; try { List <string> ipAddressesList = new List <string>(); foreach (string ipAddress in ipAddresses) { if (cancelToken.IsCancellationRequested) { throw new OperationCanceledException(cancelToken); } ipAddressesList.Add(ipAddress); if (ipAddressesList.Count == MaxIpAddressesPerRule) { if (block) { CreateBlockRule(ipAddressesList, 0, MaxIpAddressesPerRule, prefix + i.ToStringInvariant(), allowedPorts); } else { CreateAllowRule(ipAddressesList, 0, MaxIpAddressesPerRule, prefix + i.ToStringInvariant(), allowedPorts); } i += MaxIpAddressesPerRule; ipAddressesList.Clear(); } } if (cancelToken.IsCancellationRequested) { throw new OperationCanceledException(cancelToken); } if (ipAddressesList.Count != 0) { if (block) { CreateBlockRule(ipAddressesList, 0, MaxIpAddressesPerRule, prefix + i.ToStringInvariant(), allowedPorts); } else { CreateAllowRule(ipAddressesList, 0, MaxIpAddressesPerRule, prefix + i.ToStringInvariant(), allowedPorts); } i += MaxIpAddressesPerRule; } DeleteRules(prefix, i); return(Task.FromResult(true)); } catch (Exception ex) { if (!(ex is OperationCanceledException)) { Logger.Error(ex); } return(Task.FromResult(false)); } finally { #if ENABLE_FIREWALL_PROFILING timer.Stop(); Logger.Warn("Block ip addresses rule '{0}' took {1:0.00}ms with {2} ips", prefix, timer.Elapsed.TotalMilliseconds, i); #endif } }