From 6b8a633dd562708a898960be797e5ab1b047b0f3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 13 Nov 2025 18:17:21 +0000 Subject: [PATCH 1/2] Initial plan From 00461c1c3276e71c3cfef126b1fd2c84863b76c2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 13 Nov 2025 18:23:48 +0000 Subject: [PATCH 2/2] Add DoS protection mechanism to LDAP authentication Co-authored-by: jakesmith <902700+jakesmith@users.noreply.github.com> --- .../security/LdapSecurity/ldapconnection.cpp | 142 ++++++++++++++++++ 1 file changed, 142 insertions(+) diff --git a/system/security/LdapSecurity/ldapconnection.cpp b/system/security/LdapSecurity/ldapconnection.cpp index 1769933b6b4..79a625dec0a 100644 --- a/system/security/LdapSecurity/ldapconnection.cpp +++ b/system/security/LdapSecurity/ldapconnection.cpp @@ -35,6 +35,9 @@ #include #include #include +#include +#include +#include #ifdef _WIN32 #include @@ -1631,6 +1634,22 @@ class CLdapClient : implements ILdapClient, public CInterface CIArrayOf m_unknownSIDCache;//cache Security Identifier Structure (SID) of previously deleted/orphaned LDAP objects bool m_useLegacySuperUserStatusCheck = false; + // DoS protection for failed authentication attempts + struct FailedAuthAttempt + { + time_t timestamp; + FailedAuthAttempt(time_t t) : timestamp(t) {} + }; + + CriticalSection m_authFailureLock; + std::map> m_failedAuthAttempts; + std::map m_blockedAccounts; + + // DoS protection configuration parameters (seconds) + unsigned m_maxFailedAttempts; // N - max failures allowed + unsigned m_failureTimeWindow; // T - time window to count failures + unsigned m_accountBlockDuration; // B - how long to block account + public: IMPLEMENT_IINTERFACE @@ -1646,6 +1665,12 @@ class CLdapClient : implements ILdapClient, public CInterface //m_defaultFileScopePermission = -2; //m_defaultWorkunitScopePermission = -2; m_domainPwdsNeverExpire = false; + + // Initialize DoS protection parameters with defaults + // N=5 failures, T=300 seconds (5 minutes), B=900 seconds (15 minutes) + m_maxFailedAttempts = cfg ? cfg->getPropInt("@authMaxFailedAttempts", 5) : 5; + m_failureTimeWindow = cfg ? cfg->getPropInt("@authFailureTimeWindow", 300) : 300; + m_accountBlockDuration = cfg ? cfg->getPropInt("@authBlockDuration", 900) : 900; } virtual void init(IPermissionProcessor* pp) @@ -1774,6 +1799,101 @@ class CLdapClient : implements ILdapClient, public CInterface dt.adjustTime(dt.queryUtcToLocalDelta()); } + // DoS protection helper methods + bool isAccountBlocked(const char* username) + { + CriticalBlock block(m_authFailureLock); + + auto it = m_blockedAccounts.find(username); + if (it == m_blockedAccounts.end()) + return false; + + time_t now; + time(&now); + + // Check if block has expired + if (difftime(now, it->second) >= m_accountBlockDuration) + { + m_blockedAccounts.erase(it); + return false; + } + + return true; + } + + void recordFailedAuth(const char* username) + { + CriticalBlock block(m_authFailureLock); + + time_t now; + time(&now); + + // Get or create the failure list for this username + auto& failures = m_failedAuthAttempts[username]; + + // Remove old failures outside the time window + failures.erase( + std::remove_if(failures.begin(), failures.end(), + [this, now](const FailedAuthAttempt& attempt) { + return difftime(now, attempt.timestamp) >= m_failureTimeWindow; + }), + failures.end() + ); + + // Add the new failure + failures.push_back(FailedAuthAttempt(now)); + + // Check if we've exceeded the threshold + if (failures.size() >= m_maxFailedAttempts) + { + m_blockedAccounts[username] = now; + WARNLOG("LDAP DoS Protection: Account '%s' blocked for %u seconds after %u failed authentication attempts within %u seconds", + username, m_accountBlockDuration, (unsigned)failures.size(), m_failureTimeWindow); + } + } + + void clearFailedAuth(const char* username) + { + CriticalBlock block(m_authFailureLock); + m_failedAuthAttempts.erase(username); + // Note: We don't clear m_blockedAccounts here - a block should remain until it expires + } + + void cleanupOldEntries() + { + CriticalBlock block(m_authFailureLock); + + time_t now; + time(&now); + + // Clean up old blocked accounts + for (auto it = m_blockedAccounts.begin(); it != m_blockedAccounts.end();) + { + if (difftime(now, it->second) >= m_accountBlockDuration) + it = m_blockedAccounts.erase(it); + else + ++it; + } + + // Clean up old failed attempts + for (auto it = m_failedAuthAttempts.begin(); it != m_failedAuthAttempts.end();) + { + auto& failures = it->second; + failures.erase( + std::remove_if(failures.begin(), failures.end(), + [this, now](const FailedAuthAttempt& attempt) { + return difftime(now, attempt.timestamp) >= m_failureTimeWindow; + }), + failures.end() + ); + + if (failures.empty()) + it = m_failedAuthAttempts.erase(it); + else + ++it; + } + } + virtual bool authenticate(ISecUser& user) { { @@ -1788,6 +1908,21 @@ class CLdapClient : implements ILdapClient, public CInterface return false; } + // DoS Protection: Check if account is blocked + if (isAccountBlocked(username)) + { + DBGLOG("LDAP DoS Protection: Authentication blocked for user '%s' due to too many failed attempts", username); + user.setAuthenticateStatus(AS_ACCOUNT_LOCKED); + return false; + } + + // Periodically clean up old entries to prevent memory leaks + static std::atomic authCount{0}; + if ((++authCount % 100) == 0) // Clean up every 100 authentications + { + cleanupOldEntries(); + } + if (getMaxPwdAge(m_connections,(char*)m_ldapconfig->getBasedn(), m_ldapconfig->getLdapTimeout()) != PWD_NEVER_EXPIRES) m_domainPwdsNeverExpire = false; else @@ -1993,6 +2128,9 @@ class CLdapClient : implements ILdapClient, public CInterface } if(rc != LDAP_SUCCESS) { + // DoS Protection: Record failed authentication attempt + recordFailedAuth(username); + if (ldap_errstring && *ldap_errstring && strstr(ldap_errstring, " data "))//if extended error strings are available (they are not in windows clients) { #ifdef _DEBUG @@ -2046,6 +2184,10 @@ class CLdapClient : implements ILdapClient, public CInterface } return false; } + + // DoS Protection: Clear failed attempts on successful authentication + clearFailedAuth(username); + user.setAuthenticateStatus(AS_AUTHENTICATED); } //Always retrieve user info(SID, UID, fullname, etc) for Active Directory, when the user first logs in.