Blocking/limiting brute force attacks

As of revision 5.1.16 it is possible to implement a logic for blocking/limiting “brute force” attacks or any abusive number of attempts to log in using the internal authentication.

A typical implémentation relies on the preAuth and postAuth platform hooks.

The simple example bellow quarantines a login for a configurable number of minutes after a configurable number of failed login:

package com.simplicite.commons.TestJava;

import java.util.Date;

import com.simplicite.util.AppLog;
import com.simplicite.util.Grant;
import com.simplicite.util.Tool;

public class PlatformHooks extends com.simplicite.util.engine.PlatformHooksInterface {
	private static final int NB_FAILURES = 3;
	private static final int NB_MINUTES = 5;
	
	private class Quarantine {
		public int count;
		public long date;

		public Quarantine(int count) {
			this.count = count;
			this.date = new Date().getTime();
		}
		
		public Quarantine(String q) {
			String[] qs = q.split(";");
			count = Tool.parseInt(qs[0]);
			date = Tool.parseLong(qs[1]);
		}
		
		@Override
		public String toString() {
			return count + ";" + date;
		}
	}

	private static final String QUARANTINE = "LOGIN_QUARANTINE_";

	private Quarantine getQuarantine(Grant sys, String login) {
		String q = sys.getParameter(QUARANTINE + login); // non persistent
		//String q = sys.getSystemParam(QUARANTINE + login); // persistent
		return Tool.isEmpty(q) ? null : new Quarantine(q);
	}

	private void setQuarantine(Grant sys, String login, Quarantine quarantine) {
		// Store as a private system parameter
		sys.setParameter(QUARANTINE + login, quarantine.toString()); // non persitent
		//sys.setSystemParam(QUARANTINE + login, quarantine.toString(), true, false, false); // persistent
	}

	private void delQuarantine(Grant sys, String login) {
		sys.removeParameter(QUARANTINE + login); // non persistent
		//sys.setSystemParam(QUARANTINE + login, null); // persistent
	}

	@Override
	public String preAuth(Grant sys, String provider, String login, String password) {
		Quarantine q = getQuarantine(sys, login);
		// If the number of failures hass reached the limit...
		if (q != null && q.count >= sys.getIntParameter("LOGIN_QUARANTINE_NB_FAILURES", NB_FAILURES)) {
			// ...and the quarantine delay is not expired...
			int nbMin = sys.getIntParameter("LOGIN_QUARANTINE_NB_MINUTES", NB_MINUTES);
			if (new Date(q.date + nbMin * 60000).after(new Date()))
				// ...reject auth request
				return "Too many failures, try again in " + nbMin + " minutes";
			else
				// ...de-quarantine
				delQuarantine(sys, login);
		}	
		return null;
	}

	@Override
	public String postAuth(Grant sys, String provider, String login, boolean success) {
		// In case of failure...
		if (!success) {
			// ...count nb of failures...
			Quarantine q = getQuarantine(sys, login);
			setQuarantine(sys, login, new Quarantine(q == null ?  1 : q.count + 1));
		} else {
			// De-quarantine in case of success
			delQuarantine(sys, login);
		}
		return null;
	}
}

During the first erroneous sign in attempts, the usual message indicating a credentials check failure is returned:

Then after a configured number of erroneous sign in attempts, no further credential check is done for the configured next number of minutes and a custom message is returned:

2 Likes

I guess this implementation needs also a garbage collection of “dead” logon when user did not retry after nbMin minutes.

It might be a cron job that destroys old quarantine/locks from memory or DB after nbMin minutes.

Not in this example, the blacklist is temporary and reset after N minutes, especially if the sign in is successful