AntiBrutePlugin.php 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384
  1. <?php
  2. if (!defined('GNUSOCIAL')) { exit(1); }
  3. class AntiBrutePlugin extends Plugin {
  4. const PLUGIN_VERSION = '2.0.0';
  5. protected $failed_attempts = 0;
  6. protected $unauthed_user = null;
  7. protected $client_ip = null;
  8. const FAILED_LOGIN_IP_SECTION = 'failed_login_ip';
  9. public function initialize()
  10. {
  11. // This probably needs some work. For example with IPv6 you can easily generate new IPs...
  12. $client_ip = common_client_ip();
  13. $this->client_ip = $client_ip[0] ?: $client_ip[1]; // [0] is proxy, [1] should be the real IP
  14. }
  15. public function onStartCheckPassword($nickname, $password, &$authenticatedUser)
  16. {
  17. if (common_is_email($nickname)) {
  18. $this->unauthed_user = User::getKV('email', common_canonical_email($nickname));
  19. } else {
  20. $this->unauthed_user = User::getKV('nickname', Nickname::normalize($nickname));
  21. }
  22. if (!$this->unauthed_user instanceof User) {
  23. // Unknown username continue processing StartCheckPassword (maybe uninitialized LDAP user etc?)
  24. return true;
  25. }
  26. $this->failed_attempts = (int)$this->unauthed_user->getPref(self::FAILED_LOGIN_IP_SECTION, $this->client_ip);
  27. switch (true) {
  28. case $this->failed_attempts >= 5:
  29. common_log(LOG_WARNING, sprintf('Multiple failed login attempts for user %s from IP %s - brute force attack?',
  30. $this->unauthed_user->getNickname(), $this->client_ip));
  31. // 5 seconds is a good max waiting time anyway...
  32. sleep($this->failed_attempts % 5 + 1);
  33. break;
  34. case $this->failed_attempts > 0:
  35. common_debug(sprintf('Previously failed login on user %s from IP %s - sleeping %u seconds.',
  36. $this->unauthed_user->getNickname(), $this->client_ip, $this->failed_attempts));
  37. sleep($this->failed_attempts);
  38. break;
  39. default:
  40. // No sleeping if it's our first failed attempt.
  41. }
  42. return true;
  43. }
  44. public function onEndCheckPassword($nickname, $password, $authenticatedUser)
  45. {
  46. if ($authenticatedUser instanceof User) {
  47. // We'll trust this IP for this user and remove failed logins for the database..
  48. $authenticatedUser->delPref(self::FAILED_LOGIN_IP_SECTION, $this->client_ip);
  49. return true;
  50. }
  51. // See if we have an unauthed user from before. If not, it might be because the User did
  52. // not exist yet (such as autoregistering with LDAP, OpenID etc.).
  53. if ($this->unauthed_user instanceof User) {
  54. // And if the login failed, we'll increment the attempt count.
  55. common_debug(sprintf('Failed login tests for user %s from IP %s',
  56. $this->unauthed_user->getNickname(), $this->client_ip));
  57. $this->unauthed_user->setPref(self::FAILED_LOGIN_IP_SECTION, $this->client_ip, ++$this->failed_attempts);
  58. }
  59. return true;
  60. }
  61. public function onPluginVersion(array &$versions): bool
  62. {
  63. $versions[] = array('name' => 'AntiBrute',
  64. 'version' => self::PLUGIN_VERSION,
  65. 'author' => 'Mikael Nordfeldth',
  66. 'homepage' => GNUSOCIAL_ENGINE_URL,
  67. 'description' =>
  68. // TRANS: Plugin description.
  69. _m('Anti bruteforce method(s).'));
  70. return true;
  71. }
  72. }