From d588f90781c7013fc2dd9757b2d6aa83f5082338 Mon Sep 17 00:00:00 2001 From: Sergey Kovalskiy Date: Tue, 15 Nov 2016 16:33:47 +0300 Subject: [PATCH 1/3] Added time limit (config key 'auth.bruteforcingProtectMaxTime') instead of max integer value --- Sources/net/rujel/auth/BruteforceProtection.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Sources/net/rujel/auth/BruteforceProtection.java b/Sources/net/rujel/auth/BruteforceProtection.java index 9aa9522..b76c8db 100644 --- a/Sources/net/rujel/auth/BruteforceProtection.java +++ b/Sources/net/rujel/auth/BruteforceProtection.java @@ -220,6 +220,7 @@ protected class TimeoutTask extends java.util.TimerTask { private NSMutableDictionary inDict; private String key; private int count; + private int max_time; public TimeoutTask(NSMutableDictionary dict, String dictKey, int timeout) { super(); @@ -230,6 +231,8 @@ public TimeoutTask(NSMutableDictionary dict, String dictKey, int timeout) { count = timeout; timer.schedule(this,(long)timeout*1000); inDict.setObjectForKey(this,key); + max_time = net.rujel.reusables.SettingsReader. + intForKeyPath("auth.bruteforcingProtectMaxTime", 60*5); } public void run() { @@ -238,7 +241,7 @@ public void run() { } public TimeoutTask recycle() { - int nextCount = (count < Integer.MAX_VALUE / 2)? count*2 : Integer.MAX_VALUE; + int nextCount = (count < max_time / 2)? count*2 : max_time; TimeoutTask newTask = new TimeoutTask(inDict,key,nextCount); //timer.shedule(newTask,count*2000); //inDict.setObjectForKey(newTask,key); From dfbb49a97bb36af0f944e580320caf5013a9a497 Mon Sep 17 00:00:00 2001 From: Sergey Kovalskiy Date: Wed, 16 Nov 2016 10:34:07 +0300 Subject: [PATCH 2/3] Changed logic. Added minimal lockdown time. After lockdown is complete, the entry is erased. --- .../net/rujel/auth/BruteforceProtection.java | 47 ++++++++++++------- 1 file changed, 31 insertions(+), 16 deletions(-) diff --git a/Sources/net/rujel/auth/BruteforceProtection.java b/Sources/net/rujel/auth/BruteforceProtection.java index b76c8db..7513595 100644 --- a/Sources/net/rujel/auth/BruteforceProtection.java +++ b/Sources/net/rujel/auth/BruteforceProtection.java @@ -45,8 +45,15 @@ public class BruteforceProtection { "auth.bruteforcingProtect",true); protected String[] trustedProxies; - protected NSMutableDictionary suspiciousUsers = new NSMutableDictionary(); - protected NSMutableDictionary suspiciousHosts = new NSMutableDictionary(); + protected NSMutableDictionary suspiciousUsers = + new NSMutableDictionary(); + protected NSMutableDictionary suspiciousHosts = + new NSMutableDictionary(); + + protected int max_time = net.rujel.reusables.SettingsReader. + intForKeyPath("auth.bruteforcingProtectMaxTime", 60*5); + protected int min_time = net.rujel.reusables.SettingsReader. + intForKeyPath("auth.bruteforcingProtectMinTime", 10); public int hostCounter(String host) { Object counter = suspiciousHosts.objectForKey(host); @@ -67,21 +74,24 @@ public int userCounter(String user) { } } - public int raiseCounter(NSMutableDictionary dict,String key) { + public int raiseCounter(NSMutableDictionary dict, String key) { if(key == null) return 0; Object counter = dict.objectForKey(key); int result; - if(counter instanceof TimeoutTask) { + if(!(counter == null)) { result = -((TimeoutTask)counter).recycle().getCount(); } else { - result = (counter==null)?1:((Integer)counter).intValue() + 1; - TimeoutTask task = new TimeoutTask(dict,key,result); + /* Used to return an integer. This is now changed, + * counter is either NULL or TimeoutTask */ + //result = (counter==null)?1:((Integer)counter).intValue() + 1; + TimeoutTask task = new TimeoutTask(dict, key, 0); + result = min_time; dict.setObjectForKey(task, key); } return result; } - public void resetCounter(NSMutableDictionary dict,String key) { + public void resetCounter(NSMutableDictionary dict, String key) { Object counter = dict.removeObjectForKey(key); if(counter instanceof TimeoutTask) { ((TimeoutTask)counter).cancel(); @@ -217,32 +227,37 @@ public void success (String host, String user) { } protected class TimeoutTask extends java.util.TimerTask { - private NSMutableDictionary inDict; + private NSMutableDictionary inDict; private String key; private int count; - private int max_time; - public TimeoutTask(NSMutableDictionary dict, String dictKey, int timeout) { + public TimeoutTask(NSMutableDictionary dict, String dictKey, int timeout) { super(); inDict = dict; key = dictKey; if(key == null) key = "null"; + if(timeout < min_time) + timeout = min_time; + if(timeout > max_time) + timeout = max_time; count = timeout; - timer.schedule(this,(long)timeout*1000); inDict.setObjectForKey(this,key); - max_time = net.rujel.reusables.SettingsReader. - intForKeyPath("auth.bruteforcingProtectMaxTime", 60*5); + timer.schedule(this,(long)timeout*1000); } public void run() { - inDict.setObjectForKey(new Integer(count),key); + /* This is called when the time is up - + * meaning no more lockdown is required */ + inDict.removeObjectForKey(key); + // inDict.setObjectForKey(new Integer(count),key); cancel(); } public TimeoutTask recycle() { - int nextCount = (count < max_time / 2)? count*2 : max_time; - TimeoutTask newTask = new TimeoutTask(inDict,key,nextCount); + // Now checked in constructor + // int nextCount = (count < max_time / 2)? count*2 : max_time; + TimeoutTask newTask = new TimeoutTask(inDict,key,count * 2); //timer.shedule(newTask,count*2000); //inDict.setObjectForKey(newTask,key); cancel(); From e9b87e325150b6fc63c6a1711eac570be20435ea Mon Sep 17 00:00:00 2001 From: Sergey Kovalskiy Date: Wed, 16 Nov 2016 12:41:59 +0300 Subject: [PATCH 3/3] It is now possible to choose between classic and simple/insecure bruteforce protection method using 'auth.bruteforcingProtectClass' config key (which should be a class name, default to net.rujel.auth.ClassicBruteforceProtection) --- .../net/rujel/auth/BruteforceProtection.java | 212 +++------------- .../auth/ClassicBruteforceProtection.java | 211 ++++++++++++++++ Sources/net/rujel/auth/LoginProcessor.java | 51 +++- .../auth/SimpleBruteforceProtection.java | 236 ++++++++++++++++++ 4 files changed, 525 insertions(+), 185 deletions(-) create mode 100644 Sources/net/rujel/auth/ClassicBruteforceProtection.java create mode 100644 Sources/net/rujel/auth/SimpleBruteforceProtection.java diff --git a/Sources/net/rujel/auth/BruteforceProtection.java b/Sources/net/rujel/auth/BruteforceProtection.java index 7513595..b392105 100644 --- a/Sources/net/rujel/auth/BruteforceProtection.java +++ b/Sources/net/rujel/auth/BruteforceProtection.java @@ -31,72 +31,38 @@ import com.webobjects.appserver.WORequest; import com.webobjects.foundation.NSMutableDictionary; + +import net.rujel.auth.SimpleBruteforceProtection.TimeoutTask; + import java.util.Timer; import java.util.logging.Logger; import java.util.logging.Level; //import com.apple.cocoa.application.*; -public class BruteforceProtection { +abstract public class BruteforceProtection { protected static Logger logger = Logger.getLogger("auth"); - protected Timer timer = new Timer(true); - + protected boolean bruteforcingProtect = net.rujel.reusables.SettingsReader.boolForKeyPath( "auth.bruteforcingProtect",true); protected String[] trustedProxies; - protected NSMutableDictionary suspiciousUsers = - new NSMutableDictionary(); - protected NSMutableDictionary suspiciousHosts = - new NSMutableDictionary(); + @SuppressWarnings("rawtypes") + protected NSMutableDictionary suspiciousUsers = + new NSMutableDictionary(); // Is used directly by Reset procedures - protected int max_time = net.rujel.reusables.SettingsReader. - intForKeyPath("auth.bruteforcingProtectMaxTime", 60*5); - protected int min_time = net.rujel.reusables.SettingsReader. - intForKeyPath("auth.bruteforcingProtectMinTime", 10); + @SuppressWarnings("rawtypes") + protected NSMutableDictionary suspiciousHosts = + new NSMutableDictionary(); // Is used directly by Reset procedures - public int hostCounter(String host) { - Object counter = suspiciousHosts.objectForKey(host); - if(counter instanceof TimeoutTask) { - return -((TimeoutTask)counter).getCount(); - } else { - return (counter==null)?0:((Integer)counter).intValue(); - } - } + public abstract int hostCounter(String host); // Returns lock-down timer for user + public abstract int userCounter(String user); // Returns lock-down timer for host + @SuppressWarnings("rawtypes") + public abstract int raiseCounter(NSMutableDictionary dict, String key); //Returns new counter - public int userCounter(String user) { - Object counter = suspiciousUsers.objectForKey(user); - if(counter instanceof TimeoutTask) { - return -((TimeoutTask)counter).getCount(); - } else { - return (counter==null)?0:((Integer)counter).intValue(); - } - } + public abstract void resetCounter(NSMutableDictionary dict,String key); - public int raiseCounter(NSMutableDictionary dict, String key) { - if(key == null) return 0; - Object counter = dict.objectForKey(key); - int result; - if(!(counter == null)) { - result = -((TimeoutTask)counter).recycle().getCount(); - } else { - /* Used to return an integer. This is now changed, - * counter is either NULL or TimeoutTask */ - //result = (counter==null)?1:((Integer)counter).intValue() + 1; - TimeoutTask task = new TimeoutTask(dict, key, 0); - result = min_time; - dict.setObjectForKey(task, key); - } - return result; - } - - public void resetCounter(NSMutableDictionary dict, String key) { - Object counter = dict.removeObjectForKey(key); - if(counter instanceof TimeoutTask) { - ((TimeoutTask)counter).cancel(); - } - } public String hostID(WORequest req) { String hostIP = com.apress.practicalwo.practicalutilities. @@ -124,152 +90,30 @@ public String hostID(WORequest req) { return hostIP; } - //public void checkHost(String host) throws LoginHandler.AuthenticationFailedException { public void checkAttempt(WORequest req,Object uid) throws LoginHandler.AuthenticationFailedException { - checkAttempt(hostID(req), uid); + if(bruteforcingProtect) + checkAttempt(hostID(req), uid); } - public void checkAttempt(String host,Object uid) - throws LoginHandler.AuthenticationFailedException { - if(bruteforcingProtect) { - if(uid != null) { - Object counter = suspiciousUsers.objectForKey(uid); - if(counter instanceof TimeoutTask) { - raiseBoth(host, uid.toString()); - logger.log(Level.WARNING,"Bruteforcing attempt from user: " + uid + - " host: " + host); - LoginHandler.AuthenticationFailedException ex = - new LoginHandler.AuthenticationFailedException( - LoginHandler.REFUSED,"Too many login attempts for user"); - ex.setUserId(uid.toString()); - throw ex; - } - } else { - if(host == null) return; - Object counter = suspiciousHosts.objectForKey(host); - if(counter instanceof TimeoutTask) { - ((TimeoutTask)counter).recycle(); - logger.warning("Bruteforcing attempt from host: " + host); - throw new LoginHandler.AuthenticationFailedException(LoginHandler.REFUSED); - } - } - } - } + public abstract void checkAttempt(String host,Object uid) + throws LoginHandler.AuthenticationFailedException; public Integer badAttempt(WORequest req,LoginHandler.AuthenticationFailedException aex) { - return badAttempt(hostID(req), aex); - } - - public Integer badAttempt(String host,LoginHandler.AuthenticationFailedException aex) { - int result = 0; if(bruteforcingProtect) { - if(aex.getReason() == LoginHandler.IDENTITY) { - int count = raiseCounter(suspiciousHosts,host); - result = StrictMath.abs(count); - } - if(aex.getReason() == LoginHandler.CREDENTIAL) { - String user = aex.getUserId(); - result = raiseBoth(host, user); - } - if(aex.getReason() == LoginHandler.REFUSED) { - if(host != null) - result = new Integer(StrictMath.abs(hostCounter(host))); - else - result = new Integer(StrictMath.abs(userCounter(aex.getUserId()))); - } - } - return new Integer(result); - } - - - public int raiseBoth(String host, String user) { - int byHost = StrictMath.abs(raiseCounter(suspiciousHosts,host)); - int byUser = StrictMath.abs(raiseCounter(suspiciousUsers,user)); - int result = StrictMath.max(byHost,byUser); - if(host != null) { - if(byUser < result) { - resetCounter(suspiciousUsers,user); - new TimeoutTask(suspiciousUsers,user,result); - } else if(byHost < result) { - resetCounter(suspiciousHosts,host); - new TimeoutTask(suspiciousHosts,host,result); - } + return badAttempt(hostID(req), aex); + } else { + return new Integer(0); } - return result; } + public abstract Integer badAttempt(String host,LoginHandler.AuthenticationFailedException aex); + // Not sure what this returns and where is the result used + public void success (WORequest req, String user) { success(hostID(req), user); } - public void success (String host, String user) { - if(bruteforcingProtect) { - Object hm = (host==null)?null:suspiciousHosts.objectForKey(host); - if(hm instanceof Integer) { - resetCounter(suspiciousHosts,host); - } else if (hm instanceof TimeoutTask) { - logger.log(Level.INFO,"Login succeded on first attempt for user \"" + user + - "\" while the host " + host + " was still on quaranteen for " + - ((TimeoutTask)hm).getCount()); - resetCounter(suspiciousUsers,user); - return; - } - Object um = suspiciousUsers.objectForKey(user); - if(hm != null || um != null) { - if(!(hm == null || (hm instanceof Number && ((Number)hm).intValue() <= 3)) || - !(um == null || (um instanceof Number && ((Number)um).intValue() <= 3))) - logger.logp(Level.INFO,"BruteforceProtection","success", - "Login succeded after several attempts- user: " + um +"; host: " + hm); - resetCounter(suspiciousUsers,user); - } - } - } - - protected class TimeoutTask extends java.util.TimerTask { - private NSMutableDictionary inDict; - private String key; - private int count; - - public TimeoutTask(NSMutableDictionary dict, String dictKey, int timeout) { - super(); - inDict = dict; - key = dictKey; - if(key == null) - key = "null"; - if(timeout < min_time) - timeout = min_time; - if(timeout > max_time) - timeout = max_time; - count = timeout; - inDict.setObjectForKey(this,key); - timer.schedule(this,(long)timeout*1000); - } - - public void run() { - /* This is called when the time is up - - * meaning no more lockdown is required */ - inDict.removeObjectForKey(key); - // inDict.setObjectForKey(new Integer(count),key); - cancel(); - } - - public TimeoutTask recycle() { - // Now checked in constructor - // int nextCount = (count < max_time / 2)? count*2 : max_time; - TimeoutTask newTask = new TimeoutTask(inDict,key,count * 2); - //timer.shedule(newTask,count*2000); - //inDict.setObjectForKey(newTask,key); - cancel(); - return newTask; - } - - public int getCount() { - return count; - } - - public String toString() { - return "timeout -" + count; - } - } + public abstract void success (String host, String user); + // Resets counters for people who were able to login } diff --git a/Sources/net/rujel/auth/ClassicBruteforceProtection.java b/Sources/net/rujel/auth/ClassicBruteforceProtection.java new file mode 100644 index 0000000..2008ff5 --- /dev/null +++ b/Sources/net/rujel/auth/ClassicBruteforceProtection.java @@ -0,0 +1,211 @@ +// BruteforceProtection.java + +/* + * Copyright (c) 2008, Gennady & Michael Kushnir + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * • Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * • Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * • Neither the name of the RUJEL nor the names of its contributors may be used + * to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT + * SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY + * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package net.rujel.auth; + +import com.webobjects.appserver.WORequest; +import com.webobjects.foundation.NSMutableDictionary; +import java.util.Timer; +import java.util.logging.Logger; +import java.util.logging.Level; +//import com.apple.cocoa.application.*; + + +public class ClassicBruteforceProtection extends BruteforceProtection { + protected Timer timer = new Timer(true); + + protected int max_time = net.rujel.reusables.SettingsReader. + intForKeyPath("auth.bruteforcingProtectMaxTime", 60*5); + + public int hostCounter(String host) { + Object counter = suspiciousHosts.objectForKey(host); + if(counter instanceof TimeoutTask) { + return -((TimeoutTask)counter).getCount(); + } else { + return (counter==null)?0:((Integer)counter).intValue(); + } + } + + public int userCounter(String user) { + Object counter = suspiciousUsers.objectForKey(user); + if(counter instanceof TimeoutTask) { + return -((TimeoutTask)counter).getCount(); + } else { + return (counter==null)?0:((Integer)counter).intValue(); + } + } + + public int raiseCounter(NSMutableDictionary dict,String key) { + if(key == null) return 0; + Object counter = dict.objectForKey(key); + int result; + if(counter instanceof TimeoutTask) { + result = -((TimeoutTask)counter).recycle().getCount(); + } else { + result = (counter==null)?1:((Integer)counter).intValue() + 1; + TimeoutTask task = new TimeoutTask(dict,key,result); + dict.setObjectForKey(task, key); + } + return result; + } + + public void resetCounter(NSMutableDictionary dict,String key) { + Object counter = dict.removeObjectForKey(key); + if(counter instanceof TimeoutTask) { + ((TimeoutTask)counter).cancel(); + } + } + + public void checkAttempt(String host,Object uid) + throws LoginHandler.AuthenticationFailedException { + if(bruteforcingProtect) { + if(uid != null) { + Object counter = suspiciousUsers.objectForKey(uid); + if(counter instanceof TimeoutTask) { + raiseBoth(host, uid.toString()); + logger.log(Level.WARNING,"Bruteforcing attempt from user: " + uid + + " host: " + host); + LoginHandler.AuthenticationFailedException ex = + new LoginHandler.AuthenticationFailedException( + LoginHandler.REFUSED,"Too many login attempts for user"); + ex.setUserId(uid.toString()); + throw ex; + } + } else { + if(host == null) return; + Object counter = suspiciousHosts.objectForKey(host); + if(counter instanceof TimeoutTask) { + ((TimeoutTask)counter).recycle(); + logger.warning("Bruteforcing attempt from host: " + host); + throw new LoginHandler.AuthenticationFailedException(LoginHandler.REFUSED); + } + } + } + } + + public Integer badAttempt(String host,LoginHandler.AuthenticationFailedException aex) { + int result = 0; + if(bruteforcingProtect) { + if(aex.getReason() == LoginHandler.IDENTITY) { + int count = raiseCounter(suspiciousHosts,host); + result = StrictMath.abs(count); + } + if(aex.getReason() == LoginHandler.CREDENTIAL) { + String user = aex.getUserId(); + result = raiseBoth(host, user); + } + if(aex.getReason() == LoginHandler.REFUSED) { + if(host != null) + result = new Integer(StrictMath.abs(hostCounter(host))); + else + result = new Integer(StrictMath.abs(userCounter(aex.getUserId()))); + } + } + return new Integer(result); + } + + + public int raiseBoth(String host, String user) { + int byHost = StrictMath.abs(raiseCounter(suspiciousHosts,host)); + int byUser = StrictMath.abs(raiseCounter(suspiciousUsers,user)); + int result = StrictMath.max(byHost,byUser); + if(host != null) { + if(byUser < result) { + resetCounter(suspiciousUsers,user); + new TimeoutTask(suspiciousUsers,user,result); + } else if(byHost < result) { + resetCounter(suspiciousHosts,host); + new TimeoutTask(suspiciousHosts,host,result); + } + } + return result; + } + + public void success (String host, String user) { + if(bruteforcingProtect) { + Object hm = (host==null)?null:suspiciousHosts.objectForKey(host); + if(hm instanceof Integer) { + resetCounter(suspiciousHosts,host); + } else if (hm instanceof TimeoutTask) { + logger.log(Level.INFO,"Login succeded on first attempt for user \"" + user + + "\" while the host " + host + " was still on quaranteen for " + + ((TimeoutTask)hm).getCount()); + resetCounter(suspiciousUsers,user); + return; + } + Object um = suspiciousUsers.objectForKey(user); + if(hm != null || um != null) { + if(!(hm == null || (hm instanceof Number && ((Number)hm).intValue() <= 3)) || + !(um == null || (um instanceof Number && ((Number)um).intValue() <= 3))) + logger.logp(Level.INFO,"BruteforceProtection","success", + "Login succeded after several attempts- user: " + um +"; host: " + hm); + resetCounter(suspiciousUsers,user); + } + } + } + + protected class TimeoutTask extends java.util.TimerTask { + private NSMutableDictionary inDict; + private String key; + private int count; + + public TimeoutTask(NSMutableDictionary dict, String dictKey, int timeout) { + super(); + inDict = dict; + key = dictKey; + if(key == null) + key = "null"; + count = timeout; + timer.schedule(this,(long)timeout*1000); + inDict.setObjectForKey(this,key); + } + + public void run() { + inDict.setObjectForKey(new Integer(count),key); + cancel(); + } + + public TimeoutTask recycle() { + int nextCount = (count < max_time / 2)? count*2 : max_time; + TimeoutTask newTask = new TimeoutTask(inDict,key,nextCount); + //timer.shedule(newTask,count*2000); + //inDict.setObjectForKey(newTask,key); + cancel(); + return newTask; + } + + public int getCount() { + return count; + } + + public String toString() { + return "timeout -" + count; + } + } +} diff --git a/Sources/net/rujel/auth/LoginProcessor.java b/Sources/net/rujel/auth/LoginProcessor.java index 8f8ab4d..661d25f 100644 --- a/Sources/net/rujel/auth/LoginProcessor.java +++ b/Sources/net/rujel/auth/LoginProcessor.java @@ -38,6 +38,8 @@ import com.webobjects.foundation.*; import com.webobjects.appserver.*; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; import java.security.MessageDigest; import java.util.logging.Logger; import java.util.logging.Level; @@ -48,11 +50,58 @@ public class LoginProcessor { protected static final SettingsReader prefs = SettingsReader.settingsForPath("auth",true); protected static LoginHandler loginHandler = LoginHandler.Generator.generate(); - protected static BruteforceProtection bfp = new BruteforceProtection(); + + protected static BruteforceProtection bfp = LoginProcessor.initBruteForceProtection(); + static { logger.config("LoginHandler: " + loginHandler.getClass().getName()); } + public static BruteforceProtection initBruteForceProtection() { + BruteforceProtection protection = null; + Class bruteforcingProtectClass = null; + try { + String bruteforcingProtectClassName = net.rujel.reusables.SettingsReader. + stringForKeyPath("auth.bruteforcingProtectClass", + "net.rujel.auth.ClassicBruteforceProtection"); + bruteforcingProtectClass = Class.forName(bruteforcingProtectClassName); + } catch (ClassNotFoundException e) { + logger.log(Level.WARNING,"ClassNotFoundException trying to create Bruteforce Protection instance"); + } catch (SecurityException e) { + logger.log(Level.WARNING,"SecurityException trying to create Bruteforce Protection instance"); + } + if (bruteforcingProtectClass != null) { + try { + Constructor bruteforcingProtectClassConstructor = + bruteforcingProtectClass.getConstructor(String.class); + protection = (BruteforceProtection)bruteforcingProtectClassConstructor.newInstance(new Object[] {}); + } catch (NoSuchMethodException e) { + if (bruteforcingProtectClass != null) { + try { + protection = (BruteforceProtection)bruteforcingProtectClass.newInstance(); + } catch (InstantiationException e1) { + logger.log(Level.WARNING,"InstantiationException trying to create Bruteforce Protection instance"); + } catch (IllegalAccessException e1) { + logger.log(Level.WARNING,"IllegalAccessException trying to create Bruteforce Protection instance"); + } + } + } catch (IllegalArgumentException e) { + logger.log(Level.WARNING,"IllegalArgumentException trying to create Bruteforce Protection instance"); + } catch (InstantiationException e) { + logger.log(Level.WARNING,"InstantiationException trying to create Bruteforce Protection instance"); + } catch (IllegalAccessException e) { + logger.log(Level.WARNING,"IllegalAccessException trying to create Bruteforce Protection instance"); + } catch (InvocationTargetException e) { + logger.log(Level.WARNING,"InvocationTargetException trying to create Bruteforce Protection instance"); + } + } + if (protection == null) { + logger.log(Level.WARNING,"Creating a classic bruteforce protection instance instead"); + protection = (BruteforceProtection) new ClassicBruteforceProtection(); + } + return protection; + }; + public static WOComponent loginComponent(WOContext aContext, String message) { // WORedirect redirect; String pageName = prefs.get("loginPageName","LoginDialog"); diff --git a/Sources/net/rujel/auth/SimpleBruteforceProtection.java b/Sources/net/rujel/auth/SimpleBruteforceProtection.java new file mode 100644 index 0000000..b7d632b --- /dev/null +++ b/Sources/net/rujel/auth/SimpleBruteforceProtection.java @@ -0,0 +1,236 @@ +// BruteforceProtection.java + +/* + * Copyright (c) 2008, Gennady & Michael Kushnir + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * • Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * • Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * • Neither the name of the RUJEL nor the names of its contributors may be used + * to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT + * SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY + * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package net.rujel.auth; + +import com.webobjects.appserver.WORequest; +import com.webobjects.foundation.NSMutableDictionary; +import java.util.Timer; +import java.util.logging.Logger; +import java.util.logging.Level; +//import com.apple.cocoa.application.*; + +public class SimpleBruteforceProtection extends BruteforceProtection { + protected static Logger logger = Logger.getLogger("auth"); + protected Timer timer = new Timer(true); + + protected boolean bruteforcingProtect = net.rujel.reusables.SettingsReader.boolForKeyPath( + "auth.bruteforcingProtect",true); + protected String[] trustedProxies; + + protected NSMutableDictionary suspiciousUsers = + new NSMutableDictionary(); + protected NSMutableDictionary suspiciousHosts = + new NSMutableDictionary(); + + protected int max_time = net.rujel.reusables.SettingsReader. + intForKeyPath("auth.bruteforcingProtectMaxTime", 60*5); + protected int min_time = net.rujel.reusables.SettingsReader. + intForKeyPath("auth.bruteforcingProtectMinTime", 10); + + public int hostCounter(String host) { + Object counter = suspiciousHosts.objectForKey(host); + if(counter instanceof TimeoutTask) { + return -((TimeoutTask)counter).getCount(); + } else { + return (counter==null)?0:((Integer)counter).intValue(); + } + } + + + public int userCounter(String user) { + Object counter = suspiciousUsers.objectForKey(user); + if(counter instanceof TimeoutTask) { + return -((TimeoutTask)counter).getCount(); + } else { + return (counter==null)?0:((Integer)counter).intValue(); + } + } + + @SuppressWarnings("rawtypes") + public int raiseCounter(NSMutableDictionary dict, String key) { + if(key == null) return 0; + Object counter = dict.objectForKey(key); + int result; + if(!(counter == null) & (counter instanceof TimeoutTask)) { + result = -((TimeoutTask)counter).recycle().getCount(); + } else { + /* Used to return an integer. This is now changed, + * counter is either NULL or TimeoutTask */ + //result = (counter==null)?1:((Integer)counter).intValue() + 1; + TimeoutTask task = new TimeoutTask(dict, key, 0); + result = min_time; + dict.setObjectForKey(task, key); + } + return result; + } + + @SuppressWarnings("rawtypes") + public void resetCounter(NSMutableDictionary dict, String key) { + Object counter = dict.removeObjectForKey(key); + if(counter instanceof TimeoutTask) { + ((TimeoutTask)counter).cancel(); + } + } + + public void checkAttempt(String host,Object uid) + throws LoginHandler.AuthenticationFailedException { + if(bruteforcingProtect) { + if(uid != null) { + Object counter = suspiciousUsers.objectForKey(uid); + if(counter instanceof TimeoutTask) { + raiseBoth(host, uid.toString()); + logger.log(Level.WARNING,"Bruteforcing attempt from user: " + uid + + " host: " + host); + LoginHandler.AuthenticationFailedException ex = + new LoginHandler.AuthenticationFailedException( + LoginHandler.REFUSED,"Too many login attempts for user"); + ex.setUserId(uid.toString()); + throw ex; + } + } else { + if(host == null) return; + Object counter = suspiciousHosts.objectForKey(host); + if(counter instanceof TimeoutTask) { + ((TimeoutTask)counter).recycle(); + logger.warning("Bruteforcing attempt from host: " + host); + throw new LoginHandler.AuthenticationFailedException(LoginHandler.REFUSED); + } + } + } + } + + public Integer badAttempt(String host,LoginHandler.AuthenticationFailedException aex) { + int result = 0; + if(bruteforcingProtect) { + if(aex.getReason() == LoginHandler.IDENTITY) { + int count = raiseCounter(suspiciousHosts,host); + result = StrictMath.abs(count); + } + if(aex.getReason() == LoginHandler.CREDENTIAL) { + String user = aex.getUserId(); + result = raiseBoth(host, user); + } + if(aex.getReason() == LoginHandler.REFUSED) { + if(host != null) + result = new Integer(StrictMath.abs(hostCounter(host))); + else + result = new Integer(StrictMath.abs(userCounter(aex.getUserId()))); + } + } + return new Integer(result); + } + + + public int raiseBoth(String host, String user) { + int byHost = StrictMath.abs(raiseCounter(suspiciousHosts,host)); + int byUser = StrictMath.abs(raiseCounter(suspiciousUsers,user)); + int result = StrictMath.max(byHost,byUser); + if(host != null) { + if(byUser < result) { + resetCounter(suspiciousUsers,user); + new TimeoutTask(suspiciousUsers,user,result); + } else if(byHost < result) { + resetCounter(suspiciousHosts,host); + new TimeoutTask(suspiciousHosts,host,result); + } + } + return result; + } + + public void success (String host, String user) { + if(bruteforcingProtect) { + Object hm = (host==null)?null:suspiciousHosts.objectForKey(host); + if(hm instanceof Integer) { + resetCounter(suspiciousHosts,host); + } else if (hm instanceof TimeoutTask) { + logger.log(Level.INFO,"Login succeded on first attempt for user \"" + user + + "\" while the host " + host + " was still on quaranteen for " + + ((TimeoutTask)hm).getCount()); + resetCounter(suspiciousUsers,user); + return; + } + Object um = suspiciousUsers.objectForKey(user); + if(hm != null || um != null) { + if(!(hm == null || (hm instanceof Number && ((Number)hm).intValue() <= 3)) || + !(um == null || (um instanceof Number && ((Number)um).intValue() <= 3))) + logger.logp(Level.INFO,"BruteforceProtection","success", + "Login succeded after several attempts- user: " + um +"; host: " + hm); + resetCounter(suspiciousUsers,user); + } + } + } + + protected class TimeoutTask extends java.util.TimerTask { + private NSMutableDictionary inDict; + private String key; + private int count; + + public TimeoutTask(NSMutableDictionary dict, String dictKey, int timeout) { + super(); + inDict = dict; + key = dictKey; + if(key == null) + key = "null"; + if(timeout < min_time) + timeout = min_time; + if(timeout > max_time) + timeout = max_time; + count = timeout; + inDict.setObjectForKey(this,key); + timer.schedule(this,(long)timeout*1000); + } + + public void run() { + /* This is called when the time is up - + * meaning no more lockdown is required */ + inDict.removeObjectForKey(key); + // inDict.setObjectForKey(new Integer(count),key); + cancel(); + } + + public TimeoutTask recycle() { + // Now checked in constructor + // int nextCount = (count < max_time / 2)? count*2 : max_time; + TimeoutTask newTask = new TimeoutTask(inDict,key,count * 2); + //timer.shedule(newTask,count*2000); + //inDict.setObjectForKey(newTask,key); + cancel(); + return newTask; + } + + public int getCount() { + return count; + } + + public String toString() { + return "timeout -" + count; + } + } +}