From a32e62d020a246e6fa5fe24243bd70746b62e2f4 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Fri, 11 Aug 2017 23:01:29 +0300 Subject: [PATCH] IndependentNTLMAuthenticationProvider: Added account lockout mechanism to hinder bruteforce attacks --- SMBLibrary/Authentication/LoginCounter.cs | 77 +++++++++++++++++++ .../IndependentNTLMAuthenticationProvider.cs | 33 +++++++- SMBLibrary/SMBLibrary.csproj | 1 + 3 files changed, 108 insertions(+), 3 deletions(-) create mode 100644 SMBLibrary/Authentication/LoginCounter.cs diff --git a/SMBLibrary/Authentication/LoginCounter.cs b/SMBLibrary/Authentication/LoginCounter.cs new file mode 100644 index 0000000..f5e9823 --- /dev/null +++ b/SMBLibrary/Authentication/LoginCounter.cs @@ -0,0 +1,77 @@ +/* Copyright (C) 2017 Tal Aloni . All rights reserved. + * + * You can redistribute this program and/or modify it under the terms of + * the GNU Lesser Public License as published by the Free Software Foundation, + * either version 3 of the License, or (at your option) any later version. + */ +using System; +using System.Collections.Generic; +using System.Text; + +namespace SMBLibrary.Authentication +{ + public class LoginCounter + { + public class LoginEntry + { + public DateTime LoginWindowStartDT; + public int NumberOfAttempts; + } + + private int m_maxLoginAttemptsInWindow; + private TimeSpan m_loginWindowDuration; + private Dictionary m_loginEntries = new Dictionary(); + + public LoginCounter(int maxLoginAttemptsInWindow, TimeSpan loginWindowDuration) + { + m_maxLoginAttemptsInWindow = maxLoginAttemptsInWindow; + m_loginWindowDuration = loginWindowDuration; + } + + public bool HasRemainingLoginAttempts(string userID) + { + return HasRemainingLoginAttempts(userID, false); + } + + public bool HasRemainingLoginAttempts(string userID, bool incrementCount) + { + lock (m_loginEntries) + { + LoginEntry entry; + if (m_loginEntries.TryGetValue(userID, out entry)) + { + if (entry.LoginWindowStartDT.Add(m_loginWindowDuration) >= DateTime.Now) + { + // Existing login Window + if (incrementCount) + { + entry.NumberOfAttempts++; + } + } + else + { + // New login Window + if (!incrementCount) + { + return true; + } + entry.LoginWindowStartDT = DateTime.Now; + entry.NumberOfAttempts = 1; + } + } + else + { + if (!incrementCount) + { + return true; + } + entry = new LoginEntry(); + entry.LoginWindowStartDT = DateTime.Now; + entry.NumberOfAttempts = 1; + m_loginEntries.Add(userID, entry); + } + return (entry.NumberOfAttempts < m_maxLoginAttemptsInWindow); + } + } + } +} diff --git a/SMBLibrary/Authentication/NTLM/IndependentNTLMAuthenticationProvider.cs b/SMBLibrary/Authentication/NTLM/IndependentNTLMAuthenticationProvider.cs index 0352854..1c09ee2 100644 --- a/SMBLibrary/Authentication/NTLM/IndependentNTLMAuthenticationProvider.cs +++ b/SMBLibrary/Authentication/NTLM/IndependentNTLMAuthenticationProvider.cs @@ -33,14 +33,22 @@ namespace SMBLibrary.Authentication.NTLM } } + private static readonly int DefaultMaxLoginAttemptsInWindow = 12; + private static readonly TimeSpan DefaultLoginWindowDuration = new TimeSpan(0, 5, 0); private GetUserPassword m_GetUserPassword; + private LoginCounter m_loginCounter; /// /// The NTLM challenge response will be compared against the provided password. /// - public IndependentNTLMAuthenticationProvider(GetUserPassword getUserPassword) + public IndependentNTLMAuthenticationProvider(GetUserPassword getUserPassword) : this(getUserPassword, DefaultMaxLoginAttemptsInWindow, DefaultLoginWindowDuration) + { + } + + public IndependentNTLMAuthenticationProvider(GetUserPassword getUserPassword, int maxLoginAttemptsInWindow, TimeSpan loginWindowDuration) { m_GetUserPassword = getUserPassword; + m_loginCounter = new LoginCounter(maxLoginAttemptsInWindow, loginWindowDuration); } public override NTStatus GetChallengeMessage(out object context, NegotiateMessage negotiateMessage, out ChallengeMessage challengeMessage) @@ -154,6 +162,11 @@ namespace SMBLibrary.Authentication.NTLM } } + if (!m_loginCounter.HasRemainingLoginAttempts(message.UserName.ToLower())) + { + return NTStatus.STATUS_ACCOUNT_LOCKED_OUT; + } + string password = m_GetUserPassword(message.UserName); if (password == null) { @@ -164,7 +177,14 @@ namespace SMBLibrary.Authentication.NTLM } else { - return NTStatus.STATUS_LOGON_FAILURE; + if (m_loginCounter.HasRemainingLoginAttempts(message.UserName.ToLower(), true)) + { + return NTStatus.STATUS_LOGON_FAILURE; + } + else + { + return NTStatus.STATUS_ACCOUNT_LOCKED_OUT; + } } } @@ -228,7 +248,14 @@ namespace SMBLibrary.Authentication.NTLM } else { - return NTStatus.STATUS_LOGON_FAILURE; + if (m_loginCounter.HasRemainingLoginAttempts(message.UserName.ToLower(), true)) + { + return NTStatus.STATUS_LOGON_FAILURE; + } + else + { + return NTStatus.STATUS_ACCOUNT_LOCKED_OUT; + } } } diff --git a/SMBLibrary/SMBLibrary.csproj b/SMBLibrary/SMBLibrary.csproj index ae687bf..9a15d62 100644 --- a/SMBLibrary/SMBLibrary.csproj +++ b/SMBLibrary/SMBLibrary.csproj @@ -38,6 +38,7 @@ +