CRM-20926 - CRM_Core_IDS - Generate configuration from an array
[civicrm-core.git] / CRM / Core / IDS.php
CommitLineData
6a488035
TO
1<?php
2/*
3 +--------------------------------------------------------------------+
7e9e8871 4 | CiviCRM version 4.7 |
6a488035 5 +--------------------------------------------------------------------+
0f03f337 6 | Copyright CiviCRM LLC (c) 2004-2017 |
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
0f03f337 31 * @copyright CiviCRM LLC (c) 2004-2017
6a488035
TO
32 */
33class CRM_Core_IDS {
34
35 /**
fe482240 36 * Define the threshold for the ids reactions.
6a488035
TO
37 */
38 private $threshold = array(
39 'log' => 25,
40 'warn' => 50,
41 'kick' => 75,
42 );
43
44 /**
7c877abd 45 * @var string
6a488035 46 */
7c877abd 47 private $path;
6a488035
TO
48
49 /**
ea3ddccf 50 * Check function.
51 *
6a488035
TO
52 * This function includes the IDS vendor parts and runs the
53 * detection routines on the request array.
54 *
7c877abd
CW
55 * @param array $args
56 * List of path parts.
6a488035 57 *
3bdca100 58 * @return bool
6a488035 59 */
7c877abd 60 public function check($args) {
6a488035 61 // lets bypass a few civicrm urls from this check
aa00da9b 62 $skip = array('civicrm/admin/setting/updateConfigBackend', 'civicrm/admin/messageTemplates');
63 CRM_Utils_Hook::idsException($skip);
7c877abd
CW
64 $this->path = implode('/', $args);
65 if (in_array($this->path, $skip)) {
3bdca100 66 return NULL;
6a488035
TO
67 }
68
ea3ddccf 69 // Add request url and user agent.
6a488035
TO
70 $_REQUEST['IDS_request_uri'] = $_SERVER['REQUEST_URI'];
71 if (isset($_SERVER['HTTP_USER_AGENT'])) {
72 $_REQUEST['IDS_user_agent'] = $_SERVER['HTTP_USER_AGENT'];
73 }
74
75 $configFile = self::createConfigFile(FALSE);
76
77 // init the PHPIDS and pass the REQUEST array
78 require_once 'IDS/Init.php';
79 try {
80 $init = IDS_Init::init($configFile);
353ffa53 81 $ids = new IDS_Monitor($_REQUEST, $init);
0db6c3e1
TO
82 }
83 catch (Exception $e) {
6a488035
TO
84 // might be an old stale copy of Config.IDS.ini
85 // lets try to rebuild it again and see if it works
86 $configFile = self::createConfigFile(TRUE);
87 $init = IDS_Init::init($configFile);
353ffa53 88 $ids = new IDS_Monitor($_REQUEST, $init);
6a488035
TO
89 }
90
91 $result = $ids->run();
92 if (!$result->isEmpty()) {
93 $this->react($result);
94 }
95
96 return TRUE;
97 }
98
99 /**
fe482240 100 * Create the default config file for the IDS system.
6a488035 101 *
6a0b768e
TO
102 * @param bool $force
103 * Should we recreate it irrespective if it exists or not.
6a488035 104 *
a6c01b45
CW
105 * @return string
106 * the full path to the config file
6a488035 107 */
00be9182 108 public static function createConfigFile($force = FALSE) {
6a488035
TO
109 $config = CRM_Core_Config::singleton();
110 $configFile = $config->configAndLogDir . 'Config.IDS.ini';
111 if (!$force && file_exists($configFile)) {
112 return $configFile;
113 }
114
6a488035
TO
115 // also clear the stat cache in case we are upgrading
116 clearstatcache();
117
b74c8634
TO
118 $config = self::createStandardConfig();
119 $contents = "\n";
120 $lineTpl = " %-19s = %s\n";
121 foreach ($config as $section => $fields) {
122 $contents .= "[$section]\n";
123 foreach ($fields as $key => $value) {
124 if ($key === 'scan_keys' && $value == '') {
125 $value = 'false';
126 }
127
128 if (is_array($value)) {
129 foreach ($value as $v) {
130 $contents .= sprintf($lineTpl, $key . '[]', $v);
131 }
132 }
133 else {
134 $contents .= sprintf($lineTpl, $key, $value);
135 }
136 }
137 }
138
6a488035
TO
139 if (file_put_contents($configFile, $contents) === FALSE) {
140 CRM_Core_Error::movedSiteError($configFile);
141 }
142
6a488035
TO
143 // also create the .htaccess file so we prevent the reading of the log and ini files
144 // via a browser, CRM-3875
145 CRM_Utils_File::restrictAccess($config->configAndLogDir);
146
147 return $configFile;
148 }
149
b74c8634
TO
150 /**
151 * Create conservative, minimalist IDS configuration.
152 *
153 * @return array
154 */
155 public static function createBaseConfig() {
156 $config = \CRM_Core_Config::singleton();
157 $tmpDir = empty($config->uploadDir) ? CIVICRM_TEMPLATE_COMPILEDIR : $config->uploadDir;
158 global $civicrm_root;
159
160 return array(
161 'General' => array(
162 'filter_type' => 'xml',
163 'filter_path' => "{$civicrm_root}/packages/IDS/default_filter.xml",
164 'tmp_path' => $tmpDir,
165 'HTML_Purifier_Path' => 'IDS/vendors/htmlpurifier/HTMLPurifier.auto.php',
166 'HTML_Purifier_Cache' => $tmpDir,
167 'scan_keys' => '',
168 'exceptions' => array('__utmz', '__utmc'),
169 ),
170 );
171 }
172
173 /**
174 * Create the standard, general-purpose IDS configuration used by many pages.
175 *
176 * @return array
177 */
178 public static function createStandardConfig() {
179 $excs = array(
180 'widget_code',
181 'html_message',
182 'text_message',
183 'body_html',
184 'msg_html',
185 'msg_text',
186 'msg_subject',
187 'description',
188 'intro',
189 'thankyou_text',
190 'intro_text',
191 'body_text',
192 'footer_text',
193 'thankyou_text',
194 'tf_thankyou_text',
195 'thankyou_footer',
196 'thankyou_footer_text',
197 'new_text',
198 'renewal_text',
199 'help_pre',
200 'help_post',
201 'confirm_title',
202 'confirm_text',
203 'confirm_footer_text',
204 'confirm_email_text',
205 'report_header',
206 'report_footer',
207 'data',
208 'json',
209 'instructions',
210 'suggested_message',
211 'page_text',
212 'details',
213 );
214
215 $result = self::createBaseConfig();
216
217 $result['General']['exceptions'] = array_merge(
218 $result['General']['exceptions'],
219 $excs
220 );
221
222 return $result;
223 }
224
6a488035 225 /**
ea3ddccf 226 * This function reacts on the values in the incoming results array.
6a488035
TO
227 *
228 * Depending on the impact value certain actions are
229 * performed.
230 *
231 * @param IDS_Report $result
232 *
3bdca100 233 * @return bool
6a488035 234 */
d3e86119 235 private function react(IDS_Report $result) {
6a488035
TO
236
237 $impact = $result->getImpact();
238 if ($impact >= $this->threshold['kick']) {
239 $this->log($result, 3, $impact);
7c877abd 240 $this->kick();
6a488035
TO
241 return TRUE;
242 }
243 elseif ($impact >= $this->threshold['warn']) {
244 $this->log($result, 2, $impact);
245 $this->warn($result);
246 return TRUE;
247 }
248 elseif ($impact >= $this->threshold['log']) {
249 $this->log($result, 0, $impact);
250 return TRUE;
251 }
252 else {
253 return TRUE;
254 }
255 }
256
257 /**
ea3ddccf 258 * This function writes an entry about the intrusion to the database.
6a488035 259 *
c490a46a 260 * @param array $result
77b97be7
EM
261 * @param int $reaction
262 *
3bdca100 263 * @return bool
6a488035
TO
264 */
265 private function log($result, $reaction = 0) {
266 $ip = (isset($_SERVER['SERVER_ADDR']) &&
3bdca100 267 $_SERVER['SERVER_ADDR'] != '127.0.0.1') ? $_SERVER['SERVER_ADDR'] : (
268 isset($_SERVER['HTTP_X_FORWARDED_FOR']) ? $_SERVER['HTTP_X_FORWARDED_FOR'] : '127.0.0.1'
006389de 269 );
6a488035
TO
270
271 $data = array();
272 $session = CRM_Core_Session::singleton();
273 foreach ($result as $event) {
274 $data[] = array(
275 'name' => $event->getName(),
276 'value' => stripslashes($event->getValue()),
277 'page' => $_SERVER['REQUEST_URI'],
278 'userid' => $session->get('userID'),
279 'session' => session_id() ? session_id() : '0',
280 'ip' => $ip,
281 'reaction' => $reaction,
282 'impact' => $result->getImpact(),
283 );
284 }
285
286 CRM_Core_Error::debug_var('IDS Detector Details', $data);
287 return TRUE;
288 }
289
290 /**
ea3ddccf 291 * Warn about IDS.
292 *
293 * @param array $result
294 *
295 * @return array
6a488035
TO
296 */
297 private function warn($result) {
298 return $result;
299 }
300
301 /**
7c877abd 302 * Create an error that prevents the user from continuing.
ea3ddccf 303 *
304 * @throws \Exception
6a488035 305 */
7c877abd 306 private function kick() {
6a488035
TO
307 $session = CRM_Core_Session::singleton();
308 $session->reset(2);
309
f3a27f08
DL
310 $msg = ts('There is a validation error with your HTML input. Your activity is a bit suspicious, hence aborting');
311
f3a27f08 312 if (in_array(
7c877abd 313 $this->path,
353ffa53
TO
314 array("civicrm/ajax/rest", "civicrm/api/json")
315 )) {
f3a27f08
DL
316 require_once "api/v3/utils.php";
317 $error = civicrm_api3_create_error(
318 $msg,
6a488035
TO
319 array(
320 'IP' => $_SERVER['REMOTE_ADDR'],
321 'error_code' => 'IDS_KICK',
322 'level' => 'security',
323 'referer' => $_SERVER['HTTP_REFERER'],
324 'reason' => 'XSS suspected',
325 )
326 );
ecdef330 327 CRM_Utils_JSON::output($error);
6a488035 328 }
f3a27f08 329 CRM_Core_Error::fatal($msg);
6a488035 330 }
96025800 331
6a488035 332}