From 946d10b696ec13c9731a47b23a9e68b37f033230 Mon Sep 17 00:00:00 2001 From: FallenDev Date: Sat, 4 Jan 2025 16:03:16 -0500 Subject: [PATCH] Enhanced reporting DDoS & Web Attack --- .../Skills/SharpShooterSkillTree.cs | 8 +- Zolian.Server.Base/Network/Server/BadActor.cs | 158 +++++++++++++++++- .../Network/Server/LoginServer.cs | 2 +- .../Network/Server/ServerSetup.cs | 1 + .../Network/Server/WorldServer.cs | 8 +- 5 files changed, 158 insertions(+), 19 deletions(-) diff --git a/Zolian.Server.Base/GameScripts/Skills/SharpShooterSkillTree.cs b/Zolian.Server.Base/GameScripts/Skills/SharpShooterSkillTree.cs index 3ed51c11..1df2d7ef 100644 --- a/Zolian.Server.Base/GameScripts/Skills/SharpShooterSkillTree.cs +++ b/Zolian.Server.Base/GameScripts/Skills/SharpShooterSkillTree.cs @@ -145,17 +145,17 @@ protected override void OnSuccess(Sprite sprite) Task.Run(async () => { - foreach (var i in enemy.Where(i => sprite.Serial != i.Serial)) + foreach (var i in enemy.Where(i => i != null && sprite.Serial != i.Serial)) { - if (!i.Alive) return; + if (!i.Alive) continue; damageDealer.SendAnimationNearby(374, i.Position); } await Task.Delay(5000); - foreach (var i in enemy.Where(i => sprite.Serial != i.Serial)) + foreach (var i in enemy.Where(i => i != null && sprite.Serial != i.Serial)) { - if (!i.Alive) return; + if (!i.Alive) continue; var dmgCalc = DamageCalc(sprite, i); if (sprite is Aisling aisling) diff --git a/Zolian.Server.Base/Network/Server/BadActor.cs b/Zolian.Server.Base/Network/Server/BadActor.cs index ad070b53..b40c8a65 100644 --- a/Zolian.Server.Base/Network/Server/BadActor.cs +++ b/Zolian.Server.Base/Network/Server/BadActor.cs @@ -1,4 +1,5 @@ -using System.Net; +using System.Collections.Concurrent; +using System.Net; using Darkages.Models; @@ -12,10 +13,27 @@ namespace Darkages.Network.Server; +public class ReportInfo +{ + public string RemoteIp { get; init; } + public string Comment { get; init; } + public DateTime FirstAttemptTime { get; init; } +} + public static class BadActor { + private static readonly ConcurrentQueue RetryQueue = []; + private static bool _isProcessingQueue; private const string InternalIP = "192.168.50.1"; // Cannot use ServerConfig due to value needing to be constant + public static void StartProcessingQueue() + { + if (_isProcessingQueue) return; + + _isProcessingQueue = true; + Task.Run(ProcessRetryQueue); + } + public static bool ClientOnBlackList(string remoteIp) { if (remoteIp.IsNullOrEmpty() || !IPAddress.TryParse(remoteIp, out _)) return true; @@ -46,16 +64,17 @@ public static bool ClientOnBlackList(string remoteIp) var tor = ipdb?.Data?.IsTor; var usageType = ipdb?.Data?.UsageType; + // Block if using tor, potentially malicious if (tor == true) { - LogBadActor(remoteIp, "using tor"); + LogBadActor(remoteIp, "using tor, potentially malicious"); return true; } - // Block if known malicious usage type - if (IsMaliciousUsageType(usageType)) + // Block if an unauthorized usage type + if (IsBlockedUsageType(usageType)) { - LogBadActor(remoteIp, $"using {usageType}"); + LogBlockedType(remoteIp, $"using {usageType}"); return true; } @@ -112,10 +131,16 @@ private static void LogBadActor(string remoteIp, string reason) { ServerSetup.ConnectionLogger($"Blocking {remoteIp} - Reason: {reason}", LogLevel.Warning); SentrySdk.CaptureMessage($"{remoteIp} blocked due to {reason}"); - ReportEndpoint(remoteIp, $"Blocked due to {reason}"); + ReportMaliciousEndpoint(remoteIp, $"Blocked due to {reason}"); } - private static bool IsMaliciousUsageType(string usageType) + private static void LogBlockedType(string remoteIp, string reason) + { + ServerSetup.ConnectionLogger($"Blocking {remoteIp} - Using: {reason}", LogLevel.Warning); + ReportSuspiciousEndpoint(remoteIp, "Blocked due to Web Spam, Port Scanning"); + } + + private static bool IsBlockedUsageType(string usageType) { return usageType switch { @@ -124,7 +149,7 @@ private static bool IsMaliciousUsageType(string usageType) }; } - public static void ReportEndpoint(string remoteIp, string comment) + public static void ReportMaliciousEndpoint(string remoteIp, string comment) { var keyCode = ServerSetup.Instance.KeyCode; if (keyCode is null || keyCode.Length == 0) @@ -147,10 +172,127 @@ public static void ReportEndpoint(string remoteIp, string comment) ServerSetup.ConnectionLogger($"Error reporting {remoteIp} : {comment}"); SentrySdk.CaptureMessage($"Error reporting {remoteIp} : {comment}"); } + catch (HttpRequestException httpEx) when (httpEx.Message.Contains("TooManyRequests")) + { + ServerSetup.ConnectionLogger($"Exception while reporting {remoteIp}: Too many requests.", LogLevel.Warning); + // add to queue where we report them also for DDoS (4) + AddToRetryQueue(remoteIp, comment); + } catch (Exception ex) { ServerSetup.ConnectionLogger($"Exception while reporting {remoteIp}: {ex.Message}", LogLevel.Warning); SentrySdk.CaptureException(ex); } } + + private static void ReportSuspiciousEndpoint(string remoteIp, string comment) + { + var keyCode = ServerSetup.Instance.KeyCode; + if (keyCode is null || keyCode.Length == 0) + { + ServerSetup.ConnectionLogger("Keycode not valid or not set within ServerConfig.json"); + return; + } + + try + { + var request = new RestRequest("", Method.Post); + request.AddHeader("Key", keyCode); + request.AddHeader("Accept", "application/json"); + request.AddParameter("ip", remoteIp); + request.AddParameter("categories", "10, 14"); + request.AddParameter("comment", comment); + var response = ExecuteWithRetry(() => ServerSetup.Instance.RestReport.Execute(request)); + + if (response.Result?.IsSuccessful == true) return; + ServerSetup.ConnectionLogger($"Error reporting {remoteIp} : {comment}"); + SentrySdk.CaptureMessage($"Error reporting {remoteIp} : {comment}"); + } + catch (HttpRequestException httpEx) when (httpEx.Message.Contains("TooManyRequests")) + { + ServerSetup.ConnectionLogger($"Exception while reporting {remoteIp}: Too many requests.", LogLevel.Warning); + // add to queue where we report them also for DDoS (4) + AddToRetryQueue(remoteIp, comment); + } + catch (Exception ex) + { + ServerSetup.ConnectionLogger($"Exception while reporting {remoteIp}: {ex.Message}", LogLevel.Warning); + SentrySdk.CaptureException(ex); + } + } + + private static void AddToRetryQueue(string remoteIp, string comment) + { + RetryQueue.Enqueue(new ReportInfo + { + RemoteIp = remoteIp, + Comment = comment, + FirstAttemptTime = DateTime.Now + }); + } + + private static async Task ProcessRetryQueue() + { + while (_isProcessingQueue) + { + if (!RetryQueue.IsEmpty) + { + // Process each report in the queue + if (RetryQueue.TryDequeue(out var report)) + { + var elapsedTime = DateTime.Now - report.FirstAttemptTime; + + if (elapsedTime.TotalMinutes >= 1) + { + var success = await ReportSuspiciousEndpointWithDDoS(report); + if (success) + { + ServerSetup.ConnectionLogger($"Successfully reported {report.RemoteIp} after retry."); + } + else + { + RetryQueue.Enqueue(report); + ServerSetup.ConnectionLogger($"Retry failed for {report.RemoteIp}, re-queued."); + } + } + else + { + await Task.Delay(TimeSpan.FromMinutes(1) - elapsedTime); + RetryQueue.Enqueue(report); + } + } + } + + await Task.Delay(5000); + } + } + + private static async Task ReportSuspiciousEndpointWithDDoS(ReportInfo report) + { + var adjustedComment = $"{report.Comment} - DDoS"; + + try + { + var keyCode = ServerSetup.Instance.KeyCode; + if (string.IsNullOrEmpty(keyCode)) + { + ServerSetup.ConnectionLogger("Keycode not valid or not set within ServerConfig.json"); + return false; + } + + var request = new RestRequest("", Method.Post); + request.AddHeader("Key", keyCode); + request.AddHeader("Accept", "application/json"); + request.AddParameter("ip", report.RemoteIp); + request.AddParameter("categories", "4, 10, 14"); + request.AddParameter("comment", adjustedComment); + + var response = await ServerSetup.Instance.RestReport.ExecuteAsync(request); + return response.IsSuccessful; + } + catch + { + return false; + } + } } \ No newline at end of file diff --git a/Zolian.Server.Base/Network/Server/LoginServer.cs b/Zolian.Server.Base/Network/Server/LoginServer.cs index 23750570..b46bb358 100644 --- a/Zolian.Server.Base/Network/Server/LoginServer.cs +++ b/Zolian.Server.Base/Network/Server/LoginServer.cs @@ -539,7 +539,7 @@ protected override void OnConnected(Socket clientSocket) ServerSetup.ConnectionLogger("---------Login-Server---------"); var comment = $"{ipAddress} has been blocked for violating security protocols through improper port access."; ServerSetup.ConnectionLogger(comment, LogLevel.Warning); - BadActor.ReportEndpoint(ipAddress.ToString(), comment); + BadActor.ReportMaliciousEndpoint(ipAddress.ToString(), comment); return; } diff --git a/Zolian.Server.Base/Network/Server/ServerSetup.cs b/Zolian.Server.Base/Network/Server/ServerSetup.cs index 2561a20d..7b51d6f1 100644 --- a/Zolian.Server.Base/Network/Server/ServerSetup.cs +++ b/Zolian.Server.Base/Network/Server/ServerSetup.cs @@ -103,6 +103,7 @@ public ServerSetup(IOptions options) var restSettings = SetupRestClients(); RestClient = new RestClient(restSettings.Item1); RestReport = new RestClient(restSettings.Item2); + BadActor.StartProcessingQueue(); const string logTemplate = "[{Timestamp:MMM-dd HH:mm:ss} {Level:u3}] {Message}{NewLine}{Exception}"; _packetLogger = new LoggerConfiguration() diff --git a/Zolian.Server.Base/Network/Server/WorldServer.cs b/Zolian.Server.Base/Network/Server/WorldServer.cs index 8394d7da..737d14d5 100644 --- a/Zolian.Server.Base/Network/Server/WorldServer.cs +++ b/Zolian.Server.Base/Network/Server/WorldServer.cs @@ -1386,8 +1386,7 @@ private static async ValueTask LoadAislingAsync(IWorldClient client, IRedirect r return; } - client.SendServerMessage(ServerMessageType.ActiveMessage, - $"{ServerSetup.Instance.Config.ServerWelcomeMessage}: {client.Aisling.Username}"); + client.SendServerMessage(ServerMessageType.ActiveMessage, $"{ServerSetup.Instance.Config.ServerWelcomeMessage}: {client.Aisling.Username}"); client.SendAttributes(StatUpdateType.Full); client.LoggedIn(true); @@ -1399,10 +1398,7 @@ private static async ValueTask LoadAislingAsync(IWorldClient client, IRedirect r } if (client.Aisling.AreaId == ServerSetup.Instance.Config.TransitionZone) - { - var portal = new PortalSession(); PortalSession.TransitionToMap(client.Aisling.Client); - } } catch (Exception e) { @@ -3281,7 +3277,7 @@ protected override void OnConnected(Socket clientSocket) ServerSetup.ConnectionLogger("---------World-Server---------"); var comment = $"{ipAddress} has been blocked for violating security protocols through improper port access."; ServerSetup.ConnectionLogger(comment, LogLevel.Warning); - BadActor.ReportEndpoint(ipAddress.ToString(), comment); + BadActor.ReportMaliciousEndpoint(ipAddress.ToString(), comment); return; }