mirror of
https://github.com/plan-player-analytics/Plan.git
synced 2024-12-28 12:07:35 +01:00
[Vuln] Login now blocked for 90s after 5 failed attempts.
This commit is contained in:
parent
75782562f9
commit
ad2208d7ff
@ -25,11 +25,14 @@ import com.djrapitops.plan.system.webserver.auth.Authentication;
|
||||
import com.djrapitops.plan.system.webserver.auth.BasicAuthentication;
|
||||
import com.djrapitops.plan.system.webserver.response.PromptAuthorizationResponse;
|
||||
import com.djrapitops.plan.system.webserver.response.Response;
|
||||
import com.djrapitops.plugin.benchmarking.Timings;
|
||||
import com.djrapitops.plan.system.webserver.response.ResponseFactory;
|
||||
import com.djrapitops.plan.system.webserver.response.errors.ForbiddenResponse;
|
||||
import com.djrapitops.plugin.logging.L;
|
||||
import com.djrapitops.plugin.logging.console.PluginLogger;
|
||||
import com.djrapitops.plugin.logging.error.ErrorHandler;
|
||||
import com.djrapitops.plugin.utilities.Verify;
|
||||
import com.github.benmanes.caffeine.cache.Cache;
|
||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||
import com.sun.net.httpserver.Headers;
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
import com.sun.net.httpserver.HttpHandler;
|
||||
@ -37,6 +40,8 @@ import com.sun.net.httpserver.HttpHandler;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* HttpHandler for WebServer request management.
|
||||
@ -51,10 +56,14 @@ public class RequestHandler implements HttpHandler {
|
||||
private final Theme theme;
|
||||
private final DBSystem dbSystem;
|
||||
private final ResponseHandler responseHandler;
|
||||
private final Timings timings;
|
||||
private final ResponseFactory responseFactory;
|
||||
private final PluginLogger logger;
|
||||
private final ErrorHandler errorHandler;
|
||||
|
||||
private final Cache<String, Integer> failedLoginAttempts = Caffeine.newBuilder()
|
||||
.expireAfterWrite(90, TimeUnit.SECONDS)
|
||||
.build();
|
||||
|
||||
@Inject
|
||||
RequestHandler(
|
||||
Locale locale,
|
||||
@ -62,7 +71,7 @@ public class RequestHandler implements HttpHandler {
|
||||
Theme theme,
|
||||
DBSystem dbSystem,
|
||||
ResponseHandler responseHandler,
|
||||
Timings timings,
|
||||
ResponseFactory responseFactory,
|
||||
PluginLogger logger,
|
||||
ErrorHandler errorHandler
|
||||
) {
|
||||
@ -71,7 +80,7 @@ public class RequestHandler implements HttpHandler {
|
||||
this.theme = theme;
|
||||
this.dbSystem = dbSystem;
|
||||
this.responseHandler = responseHandler;
|
||||
this.timings = timings;
|
||||
this.responseFactory = responseFactory;
|
||||
this.logger = logger;
|
||||
this.errorHandler = errorHandler;
|
||||
}
|
||||
@ -84,7 +93,16 @@ public class RequestHandler implements HttpHandler {
|
||||
request.setAuth(getAuthorization(requestHeaders));
|
||||
|
||||
try {
|
||||
Response response = responseHandler.getResponse(request);
|
||||
Response response = shouldPreventRequest(request.getRemoteAddress()) // Forbidden response (Optional)
|
||||
.orElse(responseHandler.getResponse(request)); // Or the actual requested response
|
||||
|
||||
// Increase attempt count and block if too high
|
||||
Optional<Response> forbid = handlePasswordBruteForceAttempts(request, response);
|
||||
if (forbid.isPresent()) {
|
||||
response = forbid.get();
|
||||
}
|
||||
|
||||
// Authentication failed, but was not blocked
|
||||
if (response instanceof PromptAuthorizationResponse) {
|
||||
responseHeaders.set("WWW-Authenticate", response.getHeader("WWW-Authenticate").orElse("Basic realm=\"Plan WebUser (/plan register)\";"));
|
||||
}
|
||||
@ -101,6 +119,52 @@ public class RequestHandler implements HttpHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private Optional<Response> shouldPreventRequest(String accessor) {
|
||||
Integer attempts = failedLoginAttempts.getIfPresent(accessor);
|
||||
if (attempts == null) {
|
||||
attempts = 0;
|
||||
}
|
||||
|
||||
// Too many attempts, forbid further attempts.
|
||||
if (attempts >= 5) {
|
||||
return createForbiddenResponse();
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private Optional<Response> handlePasswordBruteForceAttempts(Request request, Response response) {
|
||||
if (request.getAuth().isPresent() && response instanceof PromptAuthorizationResponse) {
|
||||
// Authentication was attempted, but failed so new attempt is going to be given if not forbidden
|
||||
|
||||
failedLoginAttempts.cleanUp();
|
||||
|
||||
String accessor = request.getRemoteAddress();
|
||||
Integer attempts = failedLoginAttempts.getIfPresent(accessor);
|
||||
if (attempts == null) {
|
||||
attempts = 0;
|
||||
}
|
||||
|
||||
// Too many attempts, forbid further attempts.
|
||||
if (attempts >= 5) {
|
||||
logger.warn(accessor + " failed to login 5 times. Their access is blocked for 90 seconds.");
|
||||
return createForbiddenResponse();
|
||||
}
|
||||
|
||||
// Attempts only increased if less than 5 attempts to prevent frustration from the cache value not
|
||||
// getting removed.
|
||||
failedLoginAttempts.put(accessor, attempts + 1);
|
||||
} else if (!(response instanceof PromptAuthorizationResponse) && !(response instanceof ForbiddenResponse)) {
|
||||
// Successful login
|
||||
failedLoginAttempts.invalidate(request.getRemoteAddress());
|
||||
}
|
||||
// First connection, no authentication headers present.
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private Optional<Response> createForbiddenResponse() {
|
||||
return Optional.of(responseFactory.forbidden403("You have too many failed login attempts. Please wait 2 minutes until attempting again."));
|
||||
}
|
||||
|
||||
private Authentication getAuthorization(Headers requestHeaders) {
|
||||
List<String> authorization = requestHeaders.get("Authorization");
|
||||
if (Verify.isEmpty(authorization)) {
|
||||
|
Loading…
Reference in New Issue
Block a user