Including security review as a submodule - with patched for Yaffs.
[yaffs-website] / web / modules / contrib / security_review / src / Checks / FailedLogins.php
1 <?php
2
3 /**
4  * @file
5  * Contains \Drupal\security_review\Checks\FailedLogins.
6  */
7
8 namespace Drupal\security_review\Checks;
9
10 use Drupal\Core\Logger\RfcLogLevel;
11 use Drupal\security_review\Check;
12 use Drupal\security_review\CheckResult;
13
14 /**
15  * Checks for abundant failed logins.
16  */
17 class FailedLogins extends Check {
18
19   /**
20    * {@inheritdoc}
21    */
22   public function getNamespace() {
23     return 'Security Review';
24   }
25
26   /**
27    * {@inheritdoc}
28    */
29   public function getTitle() {
30     return 'Failed logins';
31   }
32
33   /**
34    * {@inheritdoc}
35    */
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);
40     }
41
42     $result = CheckResult::SUCCESS;
43     $findings = [];
44     $last_result = $this->lastResult();
45     $visible = FALSE;
46
47     // Prepare the query.
48     $query = $this->database()->select('watchdog', 'w');
49     $query->fields('w', [
50       'severity',
51       'type',
52       'timestamp',
53       'message',
54       'variables',
55       'hostname',
56     ]);
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(), '>=');
63     }
64
65     // Execute the query.
66     $db_result = $query->execute();
67
68     // Count the number of failed logins per IP.
69     $entries = [];
70     foreach ($db_result as $row) {
71       $ip = unserialize($row->variables)['%ip'];
72       $entry_for_ip = &$entries[$ip];
73
74       if (!isset($entry_for_ip)) {
75         $entry_for_ip = 0;
76       }
77       $entry_for_ip++;
78     }
79
80     // Filter the IPs with more than 10 failed logins.
81     if (!empty($entries)) {
82       foreach ($entries as $ip => $count) {
83         if ($count > 10) {
84           $findings[] = $ip;
85         }
86       }
87     }
88
89     if (!empty($findings)) {
90       $result = CheckResult::FAIL;
91       $visible = TRUE;
92     }
93
94     return $this->createResult($result, $findings, $visible);
95   }
96
97   /**
98    * {@inheritdoc}
99    */
100   public function help() {
101     $paragraphs = [];
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.');
103
104     return [
105       '#theme' => 'check_help',
106       '#title' => $this->t('Abundant failed logins from the same IP'),
107       '#paragraphs' => $paragraphs,
108     ];
109   }
110
111   /**
112    * {@inheritdoc}
113    */
114   public function evaluate(CheckResult $result) {
115     $findings = $result->findings();
116     if (empty($findings)) {
117       return [];
118     }
119
120     $paragraphs = [];
121     $paragraphs[] = $this->t('The following IPs were observed with an abundance of failed login attempts.');
122
123     return [
124       '#theme' => 'check_evaluation',
125       '#paragraphs' => $paragraphs,
126       '#items' => $result->findings(),
127     ];
128   }
129
130   /**
131    * {@inheritdoc}
132    */
133   public function evaluatePlain(CheckResult $result) {
134     $findings = $result->findings();
135     if (empty($findings)) {
136       return '';
137     }
138
139     $output = $this->t('Suspicious IP addresses:') . ":\n";
140     foreach ($findings as $ip) {
141       $output .= "\t" . $ip . "\n";
142     }
143
144     return $output;
145   }
146
147   /**
148    * {@inheritdoc}
149    */
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.');
154
155       default:
156         return $this->t('Unexpected result.');
157     }
158   }
159
160 }