5 * Contains \Drupal\security_review\Checks\FailedLogins.
8 namespace Drupal\security_review\Checks;
10 use Drupal\Core\Logger\RfcLogLevel;
11 use Drupal\security_review\Check;
12 use Drupal\security_review\CheckResult;
15 * Checks for abundant failed logins.
17 class FailedLogins extends Check {
22 public function getNamespace() {
23 return 'Security Review';
29 public function getTitle() {
30 return 'Failed logins';
36 public function run() {
37 // If dblog is not enabled return with hidden INFO.
38 if (!$this->moduleHandler()->moduleExists('dblog')) {
39 return $this->createResult(CheckResult::INFO, [], FALSE);
42 $result = CheckResult::SUCCESS;
44 $last_result = $this->lastResult();
48 $query = $this->database()->select('watchdog', 'w');
57 $query->condition('type', 'user')
58 ->condition('severity', RfcLogLevel::NOTICE)
59 ->condition('message', 'Login attempt failed from %ip.');
60 if ($last_result instanceof CheckResult) {
61 // Only check entries that got recorded since the last run of the check.
62 $query->condition('timestamp', $last_result->time(), '>=');
66 $db_result = $query->execute();
68 // Count the number of failed logins per IP.
70 foreach ($db_result as $row) {
71 $ip = unserialize($row->variables)['%ip'];
72 $entry_for_ip = &$entries[$ip];
74 if (!isset($entry_for_ip)) {
80 // Filter the IPs with more than 10 failed logins.
81 if (!empty($entries)) {
82 foreach ($entries as $ip => $count) {
89 if (!empty($findings)) {
90 $result = CheckResult::FAIL;
94 return $this->createResult($result, $findings, $visible);
100 public function help() {
102 $paragraphs[] = $this->t('Failed login attempts from the same IP may be an artifact of a malicious user attempting to brute-force their way onto your site as an authenticated user to carry out nefarious deeds.');
105 '#theme' => 'check_help',
106 '#title' => $this->t('Abundant failed logins from the same IP'),
107 '#paragraphs' => $paragraphs,
114 public function evaluate(CheckResult $result) {
115 $findings = $result->findings();
116 if (empty($findings)) {
121 $paragraphs[] = $this->t('The following IPs were observed with an abundance of failed login attempts.');
124 '#theme' => 'check_evaluation',
125 '#paragraphs' => $paragraphs,
126 '#items' => $result->findings(),
133 public function evaluatePlain(CheckResult $result) {
134 $findings = $result->findings();
135 if (empty($findings)) {
139 $output = $this->t('Suspicious IP addresses:') . ":\n";
140 foreach ($findings as $ip) {
141 $output .= "\t" . $ip . "\n";
150 public function getMessage($result_const) {
151 switch ($result_const) {
152 case CheckResult::FAIL:
153 return $this->t('Failed login attempts from the same IP. These may be a brute-force attack to gain access to your site.');
156 return $this->t('Unexpected result.');