Merge pull request #24187 from colemanw/removeCiviAuction
[civicrm-core.git] / CRM / Core / IDS.php
CommitLineData
6a488035
TO
1<?php
2/*
3 +--------------------------------------------------------------------+
bc77d7c0 4 | Copyright CiviCRM LLC. All rights reserved. |
6a488035 5 | |
bc77d7c0
TO
6 | This work is published under the GNU AGPLv3 license with some |
7 | permitted exceptions and without any warranty. For full license |
8 | and copyright information, see https://civicrm.org/licensing |
6a488035 9 +--------------------------------------------------------------------+
d25dd0ee 10 */
6a488035
TO
11
12/**
13 *
14 * @package CRM
ca5cec67 15 * @copyright CiviCRM LLC https://civicrm.org/licensing
6a488035
TO
16 */
17class CRM_Core_IDS {
18
19 /**
fe482240 20 * Define the threshold for the ids reactions.
518fa0ee 21 * @var array
6a488035 22 */
be2fb01f 23 private $threshold = [
6a488035
TO
24 'log' => 25,
25 'warn' => 50,
26 'kick' => 75,
be2fb01f 27 ];
6a488035
TO
28
29 /**
7c877abd 30 * @var string
6a488035 31 */
7c877abd 32 private $path;
6a488035
TO
33
34 /**
ea3ddccf 35 * Check function.
36 *
6a488035
TO
37 * This function includes the IDS vendor parts and runs the
38 * detection routines on the request array.
39 *
76adcecc 40 * @param array $route
6a488035 41 *
3bdca100 42 * @return bool
6a488035 43 */
76adcecc
TO
44 public function check($route) {
45 if (CRM_Core_Permission::check('skip IDS check')) {
46 return NULL;
47 }
48
6a488035 49 // lets bypass a few civicrm urls from this check
505c710a 50 $skip = ['civicrm/admin/setting/updateConfigBackend', 'civicrm/admin/messageTemplates', 'civicrm/ajax/api4'];
aa00da9b 51 CRM_Utils_Hook::idsException($skip);
76adcecc 52 $this->path = $route['path'];
7c877abd 53 if (in_array($this->path, $skip)) {
3bdca100 54 return NULL;
6a488035
TO
55 }
56
36d4fa1b 57 $init = self::create(self::createRouteConfig($route));
76adcecc 58
ea3ddccf 59 // Add request url and user agent.
6a488035
TO
60 $_REQUEST['IDS_request_uri'] = $_SERVER['REQUEST_URI'];
61 if (isset($_SERVER['HTTP_USER_AGENT'])) {
62 $_REQUEST['IDS_user_agent'] = $_SERVER['HTTP_USER_AGENT'];
63 }
64
76adcecc
TO
65 require_once 'IDS/Monitor.php';
66 $ids = new \IDS_Monitor($_REQUEST, $init);
6a488035
TO
67
68 $result = $ids->run();
69 if (!$result->isEmpty()) {
70 $this->react($result);
71 }
72
73 return TRUE;
74 }
75
76 /**
76adcecc 77 * Create a new PHPIDS configuration object.
6a488035 78 *
76adcecc
TO
79 * @param array $config
80 * PHPIDS configuration array (per INI format).
81 * @return \IDS_Init
6a488035 82 */
76adcecc
TO
83 protected static function create($config) {
84 require_once 'IDS/Init.php';
85 $init = \IDS_Init::init(NULL);
86 $init->setConfig($config, TRUE);
6a488035 87
76adcecc
TO
88 // Cleanup
89 $reflection = new \ReflectionProperty('IDS_Init', 'instances');
90 $reflection->setAccessible(TRUE);
91 $value = $reflection->getValue(NULL);
92 unset($value[NULL]);
93 $reflection->setValue(NULL, $value);
6a488035 94
76adcecc 95 return $init;
6a488035
TO
96 }
97
b74c8634
TO
98 /**
99 * Create conservative, minimalist IDS configuration.
100 *
101 * @return array
102 */
103 public static function createBaseConfig() {
104 $config = \CRM_Core_Config::singleton();
9a13b5fd 105 $tmpDir = empty($config->uploadDir) ? Civi::paths()->getVariable('civicrm.compile', 'path') : $config->uploadDir;
cb95749b 106 $pkgs = Civi::paths()->getVariable('civicrm.packages', 'path');
b74c8634 107
be2fb01f
CW
108 return [
109 'General' => [
b74c8634 110 'filter_type' => 'xml',
cb95749b 111 'filter_path' => "{$pkgs}/IDS/default_filter.xml",
b74c8634 112 'tmp_path' => $tmpDir,
463edc66
CW
113 // Ignored, uses autoloader
114 'HTML_Purifier_Path' => TRUE,
b74c8634
TO
115 'HTML_Purifier_Cache' => $tmpDir,
116 'scan_keys' => '',
be2fb01f
CW
117 'exceptions' => ['__utmz', '__utmc'],
118 ],
119 ];
b74c8634
TO
120 }
121
122 /**
123 * Create the standard, general-purpose IDS configuration used by many pages.
124 *
125 * @return array
126 */
127 public static function createStandardConfig() {
be2fb01f 128 $excs = [
b74c8634
TO
129 'widget_code',
130 'html_message',
131 'text_message',
132 'body_html',
133 'msg_html',
134 'msg_text',
135 'msg_subject',
136 'description',
137 'intro',
138 'thankyou_text',
139 'intro_text',
140 'body_text',
141 'footer_text',
142 'thankyou_text',
143 'tf_thankyou_text',
144 'thankyou_footer',
145 'thankyou_footer_text',
146 'new_text',
147 'renewal_text',
148 'help_pre',
149 'help_post',
150 'confirm_title',
151 'confirm_text',
152 'confirm_footer_text',
153 'confirm_email_text',
154 'report_header',
155 'report_footer',
156 'data',
157 'json',
158 'instructions',
159 'suggested_message',
160 'page_text',
161 'details',
be2fb01f 162 ];
b74c8634
TO
163
164 $result = self::createBaseConfig();
165
166 $result['General']['exceptions'] = array_merge(
167 $result['General']['exceptions'],
168 $excs
169 );
170
171 return $result;
172 }
173
36d4fa1b
TO
174 /**
175 * @param array $route
176 * @return array
177 */
178 public static function createRouteConfig($route) {
179 $config = \CRM_Core_IDS::createStandardConfig();
be2fb01f 180 foreach (['json', 'html', 'exceptions'] as $section) {
36d4fa1b
TO
181 if (isset($route['ids_arguments'][$section])) {
182 if (!isset($config['General'][$section])) {
be2fb01f 183 $config['General'][$section] = [];
36d4fa1b
TO
184 }
185 foreach ($route['ids_arguments'][$section] as $v) {
186 $config['General'][$section][] = $v;
187 }
188 $config['General'][$section] = array_unique($config['General'][$section]);
189 }
190 }
191 return $config;
192 }
193
6a488035 194 /**
ea3ddccf 195 * This function reacts on the values in the incoming results array.
6a488035
TO
196 *
197 * Depending on the impact value certain actions are
198 * performed.
199 *
200 * @param IDS_Report $result
201 *
3bdca100 202 * @return bool
6a488035 203 */
76adcecc 204 public function react(IDS_Report $result) {
6a488035
TO
205
206 $impact = $result->getImpact();
207 if ($impact >= $this->threshold['kick']) {
208 $this->log($result, 3, $impact);
7c877abd 209 $this->kick();
6a488035
TO
210 return TRUE;
211 }
212 elseif ($impact >= $this->threshold['warn']) {
213 $this->log($result, 2, $impact);
214 $this->warn($result);
215 return TRUE;
216 }
217 elseif ($impact >= $this->threshold['log']) {
218 $this->log($result, 0, $impact);
219 return TRUE;
220 }
221 else {
222 return TRUE;
223 }
224 }
225
226 /**
ea3ddccf 227 * This function writes an entry about the intrusion to the database.
6a488035 228 *
e0f5b841 229 * @param IDS_Report $result
77b97be7
EM
230 * @param int $reaction
231 *
3bdca100 232 * @return bool
6a488035
TO
233 */
234 private function log($result, $reaction = 0) {
e7ecda75
SL
235 // Include X_FORWARD_FOR ip address if set as per IDS patten.
236 $ip = $_SERVER['REMOTE_ADDR'] . (isset($_SERVER['HTTP_X_FORWARDED_FOR']) ? ' (' . $_SERVER['HTTP_X_FORWARDED_FOR'] . ')' : '');
6a488035 237
be2fb01f 238 $data = [];
6a488035 239 $session = CRM_Core_Session::singleton();
c3fe0791 240 $session_id = CRM_Core_Config::singleton()->userSystem->getSessionId() ? CRM_Core_Config::singleton()->userSystem->getSessionId() : '0';
6a488035 241 foreach ($result as $event) {
be2fb01f 242 $data[] = [
6a488035
TO
243 'name' => $event->getName(),
244 'value' => stripslashes($event->getValue()),
245 'page' => $_SERVER['REQUEST_URI'],
246 'userid' => $session->get('userID'),
c3fe0791 247 'session' => $session_id,
6a488035
TO
248 'ip' => $ip,
249 'reaction' => $reaction,
250 'impact' => $result->getImpact(),
be2fb01f 251 ];
6a488035
TO
252 }
253
254 CRM_Core_Error::debug_var('IDS Detector Details', $data);
255 return TRUE;
256 }
257
258 /**
ea3ddccf 259 * Warn about IDS.
260 *
261 * @param array $result
262 *
263 * @return array
6a488035
TO
264 */
265 private function warn($result) {
266 return $result;
267 }
268
269 /**
7c877abd 270 * Create an error that prevents the user from continuing.
ea3ddccf 271 *
272 * @throws \Exception
6a488035 273 */
7c877abd 274 private function kick() {
6a488035
TO
275 $session = CRM_Core_Session::singleton();
276 $session->reset(2);
277
f3a27f08
DL
278 $msg = ts('There is a validation error with your HTML input. Your activity is a bit suspicious, hence aborting');
279
f3a27f08 280 if (in_array(
7c877abd 281 $this->path,
be2fb01f 282 ["civicrm/ajax/rest", "civicrm/api/json"]
353ffa53 283 )) {
f3a27f08
DL
284 require_once "api/v3/utils.php";
285 $error = civicrm_api3_create_error(
286 $msg,
be2fb01f 287 [
6a488035
TO
288 'IP' => $_SERVER['REMOTE_ADDR'],
289 'error_code' => 'IDS_KICK',
290 'level' => 'security',
291 'referer' => $_SERVER['HTTP_REFERER'],
292 'reason' => 'XSS suspected',
be2fb01f 293 ]
6a488035 294 );
ecdef330 295 CRM_Utils_JSON::output($error);
6a488035 296 }
79e11805 297 throw new CRM_Core_Exception($msg);
6a488035 298 }
96025800 299
6a488035 300}