diff --git a/LarpixServer/Account/Requests.cs b/LarpixServer/Account/Requests.cs index b64c18d..d543382 100644 --- a/LarpixServer/Account/Requests.cs +++ b/LarpixServer/Account/Requests.cs @@ -456,8 +456,9 @@ public class Requests string id = Utils.GetValidIdOrZero(idQuery.ToString()); - //rate limit pobran nonce (konto) - if (RateLimited(NonceRateLimits, id, NONCE_RATE_LIMIT)) + //rate limit pobran nonce (IP+konto): jeden atakujący nie zjada budżetu konta innym IP + string clientIp = context.Connection.RemoteIpAddress?.ToString() ?? ""; + if (RateLimited(NonceRateLimits, $"{clientIp}_{id}", NONCE_RATE_LIMIT)) { string pass = await Utils.GetPassword(id); await context.Response.WriteAsync(Encryption.Encryption.EncryptForClient("error:rate.limit", pass)); diff --git a/LarpixServer/Utils/Utils.cs b/LarpixServer/Utils/Utils.cs index a99f96f..e31d63b 100644 --- a/LarpixServer/Utils/Utils.cs +++ b/LarpixServer/Utils/Utils.cs @@ -37,7 +37,7 @@ public class Utils public static ConcurrentDictionary AccountInitRateLimits = new(); public static int AUTH_RATE_LIMIT = 10; // failed password verifications per account+IP per minute - public static int NONCE_RATE_LIMIT = 240; // pobrania nonce per konto per minute + public static int NONCE_RATE_LIMIT = 120; // pobrania nonce per IP+konto per minute public static int KDFSALT_RATE_LIMIT = 60; // pobrania soli per IP per minute public static int ACCOUNT_INIT_RATE_LIMIT = 4; // utworzone konta per IP per hour (check w init, zapis w finish) @@ -45,11 +45,7 @@ public class Utils public static bool RateLimited(ConcurrentDictionary store, string key, int limit, int windowSeconds = 60) { if (limit <= 0) return false; // <=0 wyłącza limit - long nowSec = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); - var window = store.AddOrUpdate(key, - (1, nowSec + windowSeconds), - (_, old) => old.resetAt <= nowSec ? (1, nowSec + windowSeconds) : (old.count + 1, old.resetAt)); - return window.count > limit; + return RecordRateLimitHit(store, key, windowSeconds) > limit; } //Sprawdzenie bez inkrementacji: true jeśli klucz osiągnął już limit w bieżącym oknie @@ -67,13 +63,14 @@ public class Utils return entry.count >= limit; } - //Zapis jednego zdarzenia; okno startuje przy pierwszym zapisie - public static void RecordRateLimitHit(ConcurrentDictionary store, string key, int windowSeconds = 60) + //Zapis jednego zdarzenia; okno startuje przy pierwszym zapisie. Zwraca licznik okna + public static int RecordRateLimitHit(ConcurrentDictionary store, string key, int windowSeconds = 60) { long nowSec = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); - store.AddOrUpdate(key, + var window = store.AddOrUpdate(key, (1, nowSec + windowSeconds), (_, old) => old.resetAt <= nowSec ? (1, nowSec + windowSeconds) : (old.count + 1, old.resetAt)); + return window.count; } // Failed password verifications only, keyed "{id}:{ip}". A success clears the