More updates to stop using dev or alpha or beta versions.
[yaffs-website] / web / modules / contrib / security_review / src / Checks / QueryErrors.php
1 <?php
2
3 namespace Drupal\security_review\Checks;
4
5 use Drupal\Core\Logger\RfcLogLevel;
6 use Drupal\security_review\Check;
7 use Drupal\security_review\CheckResult;
8
9 /**
10  * Checks for abundant query errors.
11  */
12 class QueryErrors extends Check {
13
14   /**
15    * {@inheritdoc}
16    */
17   public function getNamespace() {
18     return 'Security Review';
19   }
20
21   /**
22    * {@inheritdoc}
23    */
24   public function getTitle() {
25     return 'Query errors';
26   }
27
28   /**
29    * {@inheritdoc}
30    */
31   public function run() {
32     // If dblog is not enabled return with hidden INFO.
33     if (!$this->moduleHandler()->moduleExists('dblog')) {
34       return $this->createResult(CheckResult::INFO, [], FALSE);
35     }
36
37     $result = CheckResult::SUCCESS;
38     $findings = [];
39     $last_result = $this->lastResult();
40     $visible = FALSE;
41
42     // Prepare the query.
43     $query = $this->database()->select('watchdog', 'w');
44     $query->fields('w', [
45       'severity',
46       'type',
47       'timestamp',
48       'message',
49       'variables',
50       'hostname',
51     ]);
52     $query->condition('type', 'php')->condition('severity', RfcLogLevel::ERROR);
53     if ($last_result instanceof CheckResult) {
54       // Only check entries that got recorded since the last run of the check.
55       $query->condition('timestamp', $last_result->time(), '>=');
56     }
57
58     // Execute the query.
59     $db_result = $query->execute();
60
61     // Count the number of query errors per IP.
62     $entries = [];
63     foreach ($db_result as $row) {
64       // Get the message.
65       if ($row->variables === 'N;') {
66         $message = $row->message;
67       }
68       else {
69         $message = $this->t($row->message, unserialize($row->variables));
70       }
71
72       // Get the IP.
73       $ip = $row->hostname;
74
75       // Search for query errors.
76       $message_contains_sql = strpos($message, 'SQL') !== FALSE;
77       $message_contains_select = strpos($message, 'SELECT') !== FALSE;
78       if ($message_contains_sql && $message_contains_select) {
79         $entry_for_ip = &$entries[$ip];
80
81         if (!isset($entry_for_ip)) {
82           $entry_for_ip = 0;
83         }
84         $entry_for_ip++;
85       }
86     }
87
88     // Filter the IPs with more than 10 query errors.
89     if (!empty($entries)) {
90       foreach ($entries as $ip => $count) {
91         if ($count > 10) {
92           $findings[] = $ip;
93         }
94       }
95     }
96
97     if (!empty($findings)) {
98       $result = CheckResult::FAIL;
99       $visible = TRUE;
100     }
101
102     return $this->createResult($result, $findings, $visible);
103   }
104
105   /**
106    * {@inheritdoc}
107    */
108   public function help() {
109     $paragraphs = [];
110     $paragraphs[] = $this->t('Database errors triggered from the same IP may be an artifact of a malicious user attempting to probe the system for weaknesses like SQL injection or information disclosure.');
111
112     return [
113       '#theme' => 'check_help',
114       '#title' => $this->t('Abundant query errors from the same IP'),
115       '#paragraphs' => $paragraphs,
116     ];
117   }
118
119   /**
120    * {@inheritdoc}
121    */
122   public function evaluate(CheckResult $result) {
123     $findings = $result->findings();
124     if (empty($findings)) {
125       return [];
126     }
127
128     $paragraphs = [];
129     $paragraphs[] = $this->t('The following IPs were observed with an abundance of query errors.');
130
131     return [
132       '#theme' => 'check_evaluation',
133       '#paragraphs' => $paragraphs,
134       '#items' => $result->findings(),
135     ];
136   }
137
138   /**
139    * {@inheritdoc}
140    */
141   public function evaluatePlain(CheckResult $result) {
142     $findings = $result->findings();
143     if (empty($findings)) {
144       return '';
145     }
146
147     $output = $this->t('Suspicious IP addresses:') . ":\n";
148     foreach ($findings as $ip) {
149       $output .= "\t" . $ip . "\n";
150     }
151
152     return $output;
153   }
154
155   /**
156    * {@inheritdoc}
157    */
158   public function getMessage($result_const) {
159     switch ($result_const) {
160       case CheckResult::FAIL:
161         return $this->t('Query errors from the same IP. These may be a SQL injection attack or an attempt at information disclosure.');
162
163       default:
164         return $this->t('Unexpected result.');
165     }
166   }
167
168 }