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