Merge remote-tracking branch 'upstream/4.4' into 4.4-4.5-2014-09-29-13-10-47
[civicrm-core.git] / CRM / Core / IDS.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.5 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2014 |
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 +--------------------------------------------------------------------+
26 */
27
28 /**
29 *
30 * @package CRM
31 * @copyright CiviCRM LLC (c) 2004-2014
32 * $Id$
33 *
34 */
35 class CRM_Core_IDS {
36
37 /**
38 * define the threshold for the ids reactions
39 */
40 private $threshold = array(
41 'log' => 25,
42 'warn' => 50,
43 'kick' => 75,
44 );
45
46 /**
47 * the init object
48 */
49 private $init = NULL;
50
51 /**
52 * This function includes the IDS vendor parts and runs the
53 * detection routines on the request array.
54 *
55 * @param object cake controller object
56 *
57 * @return boolean
58 */
59 public function check(&$args) {
60 // lets bypass a few civicrm urls from this check
61 static $skip = array('civicrm/admin/setting/updateConfigBackend', 'civicrm/admin/messageTemplates');
62 $path = implode('/', $args);
63 if (in_array($path, $skip)) {
64 return;
65 }
66
67 #add request url and user agent
68 $_REQUEST['IDS_request_uri'] = $_SERVER['REQUEST_URI'];
69 if (isset($_SERVER['HTTP_USER_AGENT'])) {
70 $_REQUEST['IDS_user_agent'] = $_SERVER['HTTP_USER_AGENT'];
71 }
72
73 $configFile = self::createConfigFile(FALSE);
74
75 // init the PHPIDS and pass the REQUEST array
76 require_once 'IDS/Init.php';
77 try {
78 $init = IDS_Init::init($configFile);
79 $ids = new IDS_Monitor($_REQUEST, $init);
80 } catch (Exception $e) {
81 // might be an old stale copy of Config.IDS.ini
82 // lets try to rebuild it again and see if it works
83 $configFile = self::createConfigFile(TRUE);
84 $init = IDS_Init::init($configFile);
85 $ids = new IDS_Monitor($_REQUEST, $init);
86 }
87
88 $result = $ids->run();
89 if (!$result->isEmpty()) {
90 $this->react($result);
91 }
92
93 return TRUE;
94 }
95
96 /**
97 * Create the default config file for the IDS system
98 *
99 * @param boolean $force should we recreate it irrespective if it exists or not
100 *
101 * @return string the full path to the config file
102 * @static
103 */
104 static function createConfigFile($force = FALSE) {
105 $config = CRM_Core_Config::singleton();
106 $configFile = $config->configAndLogDir . 'Config.IDS.ini';
107 if (!$force && file_exists($configFile)) {
108 return $configFile;
109 }
110
111 $tmpDir = empty($config->uploadDir) ? CIVICRM_TEMPLATE_COMPILEDIR : $config->uploadDir;
112
113 // also clear the stat cache in case we are upgrading
114 clearstatcache();
115
116 global $civicrm_root;
117 $contents = "
118 [General]
119 filter_type = xml
120 filter_path = {$civicrm_root}/packages/IDS/default_filter.xml
121 tmp_path = $tmpDir
122 HTML_Purifier_Path = IDS/vendors/htmlpurifier/HTMLPurifier.auto.php
123 HTML_Purifier_Cache = $tmpDir
124 scan_keys = false
125 exceptions[] = __utmz
126 exceptions[] = __utmc
127 exceptions[] = widget_code
128 exceptions[] = html_message
129 exceptions[] = text_message
130 exceptions[] = body_html
131 exceptions[] = msg_html
132 exceptions[] = msg_text
133 exceptions[] = msg_subject
134 exceptions[] = description
135 exceptions[] = intro
136 exceptions[] = thankyou_text
137 exceptions[] = intro_text
138 exceptions[] = body_text
139 exceptions[] = footer_text
140 exceptions[] = thankyou_text
141 exceptions[] = tf_thankyou_text
142 exceptions[] = thankyou_footer
143 exceptions[] = thankyou_footer_text
144 exceptions[] = new_text
145 exceptions[] = renewal_text
146 exceptions[] = help_pre
147 exceptions[] = help_post
148 exceptions[] = confirm_title
149 exceptions[] = confirm_text
150 exceptions[] = confirm_footer_text
151 exceptions[] = confirm_email_text
152 exceptions[] = report_header
153 exceptions[] = report_footer
154 exceptions[] = data
155 exceptions[] = instructions
156 exceptions[] = suggested_message
157 exceptions[] = page_text
158 ";
159 if (file_put_contents($configFile, $contents) === FALSE) {
160 CRM_Core_Error::movedSiteError($configFile);
161 }
162
163
164 // also create the .htaccess file so we prevent the reading of the log and ini files
165 // via a browser, CRM-3875
166 CRM_Utils_File::restrictAccess($config->configAndLogDir);
167
168 return $configFile;
169 }
170
171 /**
172 * This function rects on the values in
173 * the incoming results array.
174 *
175 * Depending on the impact value certain actions are
176 * performed.
177 *
178 * @param IDS_Report $result
179 *
180 * @return boolean
181 */
182 private function react(IDS_Report$result) {
183
184 $impact = $result->getImpact();
185 if ($impact >= $this->threshold['kick']) {
186 $this->log($result, 3, $impact);
187 $this->kick($result);
188 return TRUE;
189 }
190 elseif ($impact >= $this->threshold['warn']) {
191 $this->log($result, 2, $impact);
192 $this->warn($result);
193 return TRUE;
194 }
195 elseif ($impact >= $this->threshold['log']) {
196 $this->log($result, 0, $impact);
197 return TRUE;
198 }
199 else {
200 return TRUE;
201 }
202 }
203
204 /**
205 * This function writes an entry about the intrusion
206 * to the intrusion database
207 *
208 * @param $result
209 * @param int $reaction
210 *
211 * @internal param array $results
212 *
213 * @return boolean
214 */
215 private function log($result, $reaction = 0) {
216 $ip = (isset($_SERVER['SERVER_ADDR']) &&
217 $_SERVER['SERVER_ADDR'] != '127.0.0.1'
218 ) ? $_SERVER['SERVER_ADDR'] : (isset($_SERVER['HTTP_X_FORWARDED_FOR']) ?
219 $_SERVER['HTTP_X_FORWARDED_FOR'] :
220 '127.0.0.1'
221 );
222
223 $data = array();
224 $session = CRM_Core_Session::singleton();
225 foreach ($result as $event) {
226 $data[] = array(
227 'name' => $event->getName(),
228 'value' => stripslashes($event->getValue()),
229 'page' => $_SERVER['REQUEST_URI'],
230 'userid' => $session->get('userID'),
231 'session' => session_id() ? session_id() : '0',
232 'ip' => $ip,
233 'reaction' => $reaction,
234 'impact' => $result->getImpact(),
235 );
236 }
237
238 CRM_Core_Error::debug_var('IDS Detector Details', $data);
239 return TRUE;
240 }
241
242 /**
243 * //todo
244 *
245 *
246 */
247 private function warn($result) {
248 return $result;
249 }
250
251 /**
252 * //todo
253 *
254 *
255 */
256 private function kick($result) {
257 $session = CRM_Core_Session::singleton();
258 $session->reset(2);
259
260 $msg = ts('There is a validation error with your HTML input. Your activity is a bit suspicious, hence aborting');
261
262 $path = implode('/', $args);
263 if (in_array(
264 $path,
265 array("civicrm/ajax/rest", "civicrm/api/json")
266 )) {
267 require_once "api/v3/utils.php";
268 $error = civicrm_api3_create_error(
269 $msg,
270 array(
271 'IP' => $_SERVER['REMOTE_ADDR'],
272 'error_code' => 'IDS_KICK',
273 'level' => 'security',
274 'referer' => $_SERVER['HTTP_REFERER'],
275 'reason' => 'XSS suspected',
276 )
277 );
278 CRM_Utils_JSON::output($error);
279 }
280 CRM_Core_Error::fatal($msg);
281 }
282 }
283