From 1e73ab55c57b7753e22550bfc9cd0f629e5649b1 Mon Sep 17 00:00:00 2001 From: Stanley Dimant Date: Sat, 6 Aug 2022 19:26:35 +0200 Subject: [PATCH] add temp ban on failed auth --- .../SecretKeyAuthenticationHandler.cs | 71 +++++++++++++++++-- .../MareSynchronosServer/Startup.cs | 2 +- .../MareSynchronosServer/appsettings.json | 5 +- 3 files changed, 71 insertions(+), 7 deletions(-) diff --git a/MareSynchronosServer/MareSynchronosServer/Authentication/SecretKeyAuthenticationHandler.cs b/MareSynchronosServer/MareSynchronosServer/Authentication/SecretKeyAuthenticationHandler.cs index 96531ba..7a9ec8e 100644 --- a/MareSynchronosServer/MareSynchronosServer/Authentication/SecretKeyAuthenticationHandler.cs +++ b/MareSynchronosServer/MareSynchronosServer/Authentication/SecretKeyAuthenticationHandler.cs @@ -10,19 +10,38 @@ using System.Threading; using System.Threading.Tasks; using MareSynchronosServer.Data; using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Http; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; namespace MareSynchronosServer.Authentication { + public class FailedAuthorization + { + private int failedAttempts = 1; + public int FailedAttempts => failedAttempts; + public Task ResetTask { get; set; } + public CancellationTokenSource ResetCts { get; set; } = new(); + public void IncreaseFailedAttempts() + { + Interlocked.Increment(ref failedAttempts); + } + } + public class SecretKeyAuthenticationHandler : AuthenticationHandler { + private readonly IHttpContextAccessor _accessor; private readonly MareDbContext _mareDbContext; + private readonly IConfiguration _configuration; public const string AuthScheme = "SecretKeyAuth"; private const string unauthorized = "Unauthorized"; public static ConcurrentDictionary Authentications = new(); + private static ConcurrentDictionary FailedAuthorizations = new(); private static SemaphoreSlim dbLockSemaphore = new SemaphoreSlim(20); + private int failedAttemptsForTempBan; + private int tempBanMinutes; public static void ClearUnauthorizedUsers() { @@ -47,22 +66,53 @@ namespace MareSynchronosServer.Authentication protected override async Task HandleAuthenticateAsync() { if (!Request.Headers.ContainsKey("Authorization")) + { return AuthenticateResult.Fail("Failed Authorization"); + } var authHeader = Request.Headers["Authorization"].ToString(); if (string.IsNullOrEmpty(authHeader)) return AuthenticateResult.Fail("Failed Authorization"); + var ip = _accessor.GetIpAddress(); + + if (FailedAuthorizations.TryGetValue(ip, out var failedAuth)) + { + if (failedAuth.FailedAttempts > failedAttemptsForTempBan) + { + failedAuth.ResetCts.Cancel(); + failedAuth.ResetCts = new CancellationTokenSource(); + var token = failedAuth.ResetCts.Token; + failedAuth.ResetTask = Task.Run(async () => + { + await Task.Delay(TimeSpan.FromMinutes(tempBanMinutes), token); + if (token.IsCancellationRequested) return; + FailedAuthorizations.Remove(ip, out _); + }, token); + Logger.LogWarning("TempBan " + ip + " for authorization spam"); + return AuthenticateResult.Fail("Failed Authorization"); + } + } + using var sha256 = SHA256.Create(); var hashedHeader = BitConverter.ToString(sha256.ComputeHash(Encoding.UTF8.GetBytes(authHeader))).Replace("-", ""); if (Authentications.TryGetValue(hashedHeader, out string uid)) { if (uid == unauthorized) + { + Logger.LogWarning("Failed authorization from " + ip); + if (FailedAuthorizations.TryGetValue(ip, out var auth)) + { + auth.IncreaseFailedAttempts(); + } + else + { + FailedAuthorizations[ip] = new FailedAuthorization(); + } return AuthenticateResult.Fail("Failed Authorization"); - else - Logger.LogDebug("Found cached entry for " + uid); + } } if (string.IsNullOrEmpty(uid)) @@ -82,6 +132,15 @@ namespace MareSynchronosServer.Authentication if (uid == null) { Authentications[hashedHeader] = unauthorized; + Logger.LogWarning("Failed authorization from " + ip); + if (FailedAuthorizations.TryGetValue(ip, out var auth)) + { + auth.IncreaseFailedAttempts(); + } + else + { + FailedAuthorizations[ip] = new FailedAuthorization(); + } return AuthenticateResult.Fail("Failed Authorization"); } else @@ -101,10 +160,14 @@ namespace MareSynchronosServer.Authentication return AuthenticateResult.Success(ticket); } - public SecretKeyAuthenticationHandler(IOptionsMonitor options, - MareDbContext mareDbContext, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) : base(options, logger, encoder, clock) + public SecretKeyAuthenticationHandler(IOptionsMonitor options, IHttpContextAccessor accessor, + MareDbContext mareDbContext, IConfiguration configuration, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) : base(options, logger, encoder, clock) { + _accessor = accessor; _mareDbContext = mareDbContext; + _configuration = configuration; + failedAttemptsForTempBan = _configuration.GetValue("FailedAuthForTempBan", 5); + tempBanMinutes = _configuration.GetValue("TempBanDurationInMinutes", 30); } } } diff --git a/MareSynchronosServer/MareSynchronosServer/Startup.cs b/MareSynchronosServer/MareSynchronosServer/Startup.cs index a6656af..7d8e199 100644 --- a/MareSynchronosServer/MareSynchronosServer/Startup.cs +++ b/MareSynchronosServer/MareSynchronosServer/Startup.cs @@ -66,7 +66,7 @@ namespace MareSynchronosServer { options.DefaultScheme = SecretKeyAuthenticationHandler.AuthScheme; }) - .AddScheme(SecretKeyAuthenticationHandler.AuthScheme, options => { }); + .AddScheme(SecretKeyAuthenticationHandler.AuthScheme, options => {}); services.AddAuthorization(options => options.FallbackPolicy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build()); services.AddSingleton(); diff --git a/MareSynchronosServer/MareSynchronosServer/appsettings.json b/MareSynchronosServer/MareSynchronosServer/appsettings.json index fc54e92..a03d25f 100644 --- a/MareSynchronosServer/MareSynchronosServer/appsettings.json +++ b/MareSynchronosServer/MareSynchronosServer/appsettings.json @@ -23,7 +23,8 @@ ] } }, - "DiscordServerUrl": "", + "FailedAuthForTempBan": 5, + "TempBanDurationInMinutes": 30, "DiscordBotToken": "", "UnusedFileRetentionPeriodInDays": 7, "PurgeUnusedAccounts": true, @@ -62,6 +63,6 @@ ] }, "IPRateLimitPolicies": { - "IpRules": [] + "IpRules": [] } }