From c1ad416f12f08d967220949d155727206a50c1d0 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Wed, 21 Jan 2015 23:35:48 +0100 Subject: [PATCH] AntiBrute plugin, delay + log multiple fail logins --- plugins/AntiBrute/AntiBrutePlugin.php | 77 +++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100755 plugins/AntiBrute/AntiBrutePlugin.php diff --git a/plugins/AntiBrute/AntiBrutePlugin.php b/plugins/AntiBrute/AntiBrutePlugin.php new file mode 100755 index 0000000000..342c323016 --- /dev/null +++ b/plugins/AntiBrute/AntiBrutePlugin.php @@ -0,0 +1,77 @@ +unauthed_user = User::getKV('email', common_canonical_email($nickname)); + } else { + $this->unauthed_user = User::getKV('nickname', Nickname::normalize($nickname)); + } + + if (!$this->unauthed_user instanceof User) { + // Unknown username continue processing StartCheckPassword (maybe uninitialized LDAP user etc?) + return true; + } + + // This probably needs some work. For example with IPv6 you can easily generate new IPs... + $this->client_ip = common_client_ip()[0] ?: common_client_ip()[1]; // [0] is proxy, [1] should be the real IP + $this->failed_attempts = (int)$this->unauthed_user->getPref(self::FAILED_LOGIN_IP_SECTION, $this->client_ip); + switch (true) { + case $this->failed_attempts >= 5: + common_log(LOG_WARNING, sprintf('Multiple failed login attempts for user %s from IP %s - brute force attack?', + $this->unauthed_user->getNickname(), $this->client_ip)); + // 5 seconds is a good max waiting time anyway... + sleep($this->failed_attempts % 5 + 1); + break; + case $this->failed_attempts > 0: + common_debug(sprintf('Previously failed login on user %s from IP %s - sleeping %u seconds.', + $this->unauthed_user->getNickname(), $this->client_ip, $this->failed_attempts)); + sleep($this->failed_attempts); + break; + default: + // No sleeping if it's our first failed attempt. + } + + return true; + } + + public function onEndCheckPassword($nickname, $password, $authenticatedUser) + { + if ($authenticatedUser instanceof User) { + // We'll trust this IP for this user and remove failed logins for the database.. + $authenticatedUser->delPref(self::FAILED_LOGIN_IP_SECTION, $this->client_ip); + return true; + } + + // See if we have an unauthed user from before. If not, it might be because the User did + // not exist yet (such as autoregistering with LDAP, OpenID etc.). + if ($this->unauthed_user instanceof User) { + // And if the login failed, we'll increment the attempt count. + common_debug(sprintf('Failed login tests for user %s from IP %s', + $this->unauthed_user->getNickname(), $this->client_ip)); + $this->unauthed_user->setPref(self::FAILED_LOGIN_IP_SECTION, $this->client_ip, ++$this->failed_attempts); + } + return true; + } + + public function onPluginVersion(&$versions) + { + $versions[] = array('name' => 'AntiBrute', + 'version' => GNUSOCIAL_VERSION, + 'author' => 'Mikael Nordfeldth', + 'homepage' => 'http://gnu.io/', + 'description' => + // TRANS: Plugin description. + _m('Anti bruteforce method(s).')); + return true; + } +}