5 * Contains \Drupal\security_review\Checks\QueryErrors.
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 query errors.
17 class QueryErrors extends Check {
22 public function getNamespace() {
23 return 'Security Review';
29 public function getTitle() {
30 return 'Query errors';
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', 'php')->condition('severity', RfcLogLevel::ERROR);
58 if ($last_result instanceof CheckResult) {
59 // Only check entries that got recorded since the last run of the check.
60 $query->condition('timestamp', $last_result->time(), '>=');
64 $db_result = $query->execute();
66 // Count the number of query errors per IP.
68 foreach ($db_result as $row) {
70 if ($row->variables === 'N;') {
71 $message = $row->message;
74 $message = $this->t($row->message, unserialize($row->variables));
80 // Search for query errors.
81 $message_contains_sql = strpos($message, 'SQL') !== FALSE;
82 $message_contains_select = strpos($message, 'SELECT') !== FALSE;
83 if ($message_contains_sql && $message_contains_select) {
84 $entry_for_ip = &$entries[$ip];
86 if (!isset($entry_for_ip)) {
93 // Filter the IPs with more than 10 query errors.
94 if (!empty($entries)) {
95 foreach ($entries as $ip => $count) {
102 if (!empty($findings)) {
103 $result = CheckResult::FAIL;
107 return $this->createResult($result, $findings, $visible);
113 public function help() {
115 $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.');
118 '#theme' => 'check_help',
119 '#title' => $this->t('Abundant query errors from the same IP'),
120 '#paragraphs' => $paragraphs,
127 public function evaluate(CheckResult $result) {
128 $findings = $result->findings();
129 if (empty($findings)) {
134 $paragraphs[] = $this->t('The following IPs were observed with an abundance of query errors.');
137 '#theme' => 'check_evaluation',
138 '#paragraphs' => $paragraphs,
139 '#items' => $result->findings(),
146 public function evaluatePlain(CheckResult $result) {
147 $findings = $result->findings();
148 if (empty($findings)) {
152 $output = $this->t('Suspicious IP addresses:') . ":\n";
153 foreach ($findings as $ip) {
154 $output .= "\t" . $ip . "\n";
163 public function getMessage($result_const) {
164 switch ($result_const) {
165 case CheckResult::FAIL:
166 return $this->t('Query errors from the same IP. These may be a SQL injection attack or an attempt at information disclosure.');
169 return $this->t('Unexpected result.');