This repository was archived by the owner on May 1, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 117
Expand file tree
/
Copy pathDefaultIntrusionDetector.php
More file actions
executable file
·368 lines (328 loc) · 12.2 KB
/
DefaultIntrusionDetector.php
File metadata and controls
executable file
·368 lines (328 loc) · 12.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
<?php
/**
* OWASP Enterprise Security API (ESAPI)
*
* This file is part of the Open Web Application Security Project (OWASP)
* Enterprise Security API (ESAPI) project.
*
* PHP version 5.2
*
* LICENSE: This source file is subject to the New BSD license. You should read
* and accept the LICENSE before you use, modify, and/or redistribute this
* software.
*
* @category OWASP
* @package ESAPI_Reference
* @author Jeff Williams <jeff.williams@aspectsecurity.com>
* @author jah <jah@jahboite.co.uk>
* @copyright 2009-2010 The OWASP Foundation
* @license http://www.opensource.org/licenses/bsd-license.php New BSD license
* @version SVN: $Id$
* @link http://www.owasp.org/index.php/ESAPI
*/
/**
* DefaultIntrusionDetector requires the IntrusionDetector interface.
*/
require_once dirname(__FILE__) . '/../IntrusionDetector.php';
/**
* Reference implementation of the IntrusionDetector interface.
*
* This implementation monitors EnterpriseSecurityExceptions, custom Exceptions
* and other custom events to see if any user exceeds a configurable threshold
* in a configurable time period.
* For example, it can monitor to see if a user exceeds 10 input validation
* issues in a 1 minute period. Or if there are more than 3 authentication
* problems in a 10 second period. More complex implementations are certainly
* possible, such as one that establishes a baseline of expected behaviour, and
* then detects deviations from that baseline.
* Events are persisted in the PHP Session, if one is available at the time they
* are generated, to allow tracking of events across requests. If a PHP Session
* is not available then the events are persisted only as long as the current
* DefaultIntrusionDetector instance.
*
* @category OWASP
* @package ESAPI_Reference
* @author Jeff Williams <jeff.williams@aspectsecurity.com>
* @author jah <jah@jahboite.co.uk>
* @copyright 2009-2010 The OWASP Foundation
* @license http://www.opensource.org/licenses/bsd-license.php New BSD license
* @version Release: @package_version@
* @link http://www.owasp.org/index.php/ESAPI
*/
class DefaultIntrusionDetector implements IntrusionDetector
{
private $_auditor = null;
private $_userEvents = null;
/**
* Constructor stores an instance of Auditor for logging and initialises the
* storage for events generated for a user.
*
* @return null
*/
function __construct()
{
$this->_auditor = ESAPI::getAuditor('IntrusionDetector');
$this->_userEvents = array();
}
/**
* Adds an exception to the IntrusionDetector.
*
* This method immediately logs the supplied exception and stores it in
* order to check if the request causes a threshold to be reached for any
* EnterpriseSecurity Exceptions. If any security thresholds are reached
* then the resultant IntrusionException is handled and the appropriate
* security action taken and logged.
*
* @param Exception $exception The exception to add.
*
* @return null
*/
public function addException($exception)
{
$secConfig = ESAPI::getSecurityConfiguration();
if ($secConfig->getDisableIntrusionDetection()) {
return;
}
if ($exception instanceof EnterpriseSecurityException) {
$this->_auditor->warning(
Auditor::SECURITY, false,
$exception->getLogMessage(), $exception
);
} else {
$this->_auditor->warning(
Auditor::SECURITY, false,
$exception->getMessage(), $exception
);
}
// add the exception, which may trigger a detector
$eventName = get_class($exception);
try
{
$this->_addSecurityEvent($eventName);
}
catch (IntrusionException $intrusionException)
{
$quota = ESAPI::getSecurityConfiguration()->getQuota($eventName);
$message = 'User exceeded quota of ' . $quota->count . ' per ' .
$quota->interval . ' seconds for event ' . $eventName .
sprintf(
'. Taking the following %d action%s - ',
count($quota->actions),
count($quota->actions) > 1 ? 's' : ''
)
. implode(', ', $quota->actions) . '.';
foreach ($quota->actions as $action) {
$this->_takeSecurityAction($action, $message);
}
}
}
/**
* Adds an event to the IntrusionDetector.
*
* This method immediately logs the event and stores it in order to check if
* the request causes a threshold to be reached for any Enterprise Security
* Exceptions. If any security thresholds are reached then the resultant
* IntrusionException is handled and the appropriate security action taken
* and logged.
*
* @param string $eventName The event to add.
* @param string $logMessage Message to log with the event.
*
* @return null
*/
public function addEvent($eventName, $logMessage)
{
$secConfig = ESAPI::getSecurityConfiguration();
if ($secConfig->getDisableIntrusionDetection()) {
return;
}
$this->_auditor->warning(
Auditor::SECURITY,
false,
"Security event {$eventName} received - {$logMessage}"
);
// add the event, which may trigger an IntrusionException
try
{
$this->_addSecurityEvent($eventName);
}
catch (IntrusionException $intrusionException)
{
$quota = $secConfig->getQuota($eventName);
$message = 'User exceeded quota of ' . $quota->count . ' per ' .
$quota->interval . ' seconds for event ' . $eventName .
sprintf(
'. Taking the following %d action%s - ',
count($quota->actions),
count($quota->actions) > 1 ? 's' : ''
)
. implode(', ', $quota->actions) . '.';
foreach ($quota->actions as $action) {
$this->_takeSecurityAction($action, $message);
}
}
}
/**
* Take a specified security action.
*
* At the moment the only acceptable action in this implementation is: log.
* Other actions will be ignored.
*
* @param string $action The action to take.
* @param string $message Message to log where the action is 'log'.
*
* @return null
*/
private function _takeSecurityAction($action, $message)
{
if ($action == 'log' ) {
$this->_auditor->fatal(
Auditor::SECURITY,
false,
"INTRUSION - {$message}"
);
}
}
/**
* Adds a security event. These events are used to check that the user has
* not reached the security thresholds set in the properties file. If a PHP
* session has been started the events are stored there, otherwise they are
* merely stored as an instance property. This means that if a session has
* not been started prior to calling this function then events will not be
* tracked across requests.
*
* @param string $eventName The name of the event that occurred.
*
* @return null
*/
private function _addSecurityEvent($eventName)
{
// if there is a threshold, then track this event
$threshold = ESAPI::getSecurityConfiguration()->getQuota($eventName);
if ($threshold === null) {
return;
}
// determine the storage for events
if (isset($_SESSION)) {
if (! array_key_exists('ESAPI', $_SESSION)) {
$_SESSION['ESAPI'] = array();
}
if (! array_key_exists('IntrusionDetector', $_SESSION['ESAPI'])) {
$_SESSION['ESAPI']['IntrusionDetector'] = array();
}
if (! array_key_exists('UserEvents', $_SESSION['ESAPI']['IntrusionDetector'])) {
$_SESSION['ESAPI']['IntrusionDetector']['UserEvents'] = array();
}
// If a session was started after events existed then ensure those
// events are added to the session store
if ( is_array($this->_userEvents)
&& $this->_userEvents !== $_SESSION['ESAPI']['IntrusionDetector']['UserEvents']
) {
$_SESSION['ESAPI']['IntrusionDetector']['UserEvents']
= $this->_userEvents;
}
// Assign a reference to the session store
$this->_userEvents =&
$_SESSION['ESAPI']['IntrusionDetector']['UserEvents'];
} else if (! isset($this->_userEvents)) {
$this->_userEvents = array();
}
$event = null;
if (array_key_exists($eventName, $this->_userEvents)) {
$event = $this->_userEvents[$eventName];
}
if ($event == null) {
$this->_userEvents[$eventName] = new Event($eventName);
$event = $this->_userEvents[$eventName];
}
if ($threshold->count > 0) {
$event->increment($threshold->count, $threshold->interval);
}
}
}
/**
* Reference implementation of an Intrusion Event.
*
* Represents the count of and times at which a user generated an event that
* corresponds to a defined IntrusionDetector threshold. The intrusion detector
* stores instances of events and invokes their increment method which
* determines whether the corresponding threshold has been reached.
*
* @category OWASP
* @package ESAPI_Reference
* @author Jeff Williams <jeff.williams@aspectsecurity.com>
* @author jah <jah@jahboite.co.uk>
* @copyright 2009-2010 The OWASP Foundation
* @license http://www.opensource.org/licenses/bsd-license.php New BSD license
* @version Release: @package_version@
* @link http://www.owasp.org/index.php/ESAPI
*/
class Event
{
private $_key;
private $_times = array();
/**
* @var int $count The number of times this event occurred for a given user.
*/
public $count = 0;
/**
* Constructor stores the supplied key as the event name.
*
* @param string $key A name by which the event is known e.g.
* 'IntegrityException'.
*
* @return null
*/
public function __construct($key)
{
$this->_key = $key;
}
/**
* The increment method increments the number of times this event occurred
* for this user.
*
* Each time increment is called it will decide whether or not to throw an
* IntrusionException based on the supplied count and interval parameters.
* If $count is exceeded within $interval seconds then the exception will be
* thrown. This implementation maintains a kind of sliding window of
* timestamps so that it can track event occurrences over time.
*
* @param int $count The event count that will trigger Intrusion Detection
* within the supplied interval.
* @param int $interval The number of seconds within which the supplied quota of
* event occurrences will trigger Intrusion Detection.
*
* @return null
*/
public function increment($count, $interval)
{
$now = null;
if (function_exists('microtime')) {
$now = microtime(true);
$interval = (float) $interval;
} else {
$now = time();
}
$this->count++;
array_push($this->_times, $now);
// if the threshold has been exceeded
while (sizeof($this->_times) > $count) {
array_shift($this->_times);
}
if (sizeof($this->_times) == $count) {
$past = reset($this->_times);
if ($past === false) {
// this should not happen because events are validated in
// SecurityConfiguration...
$past = $now;
}
$present = $now;
if ($present - $past < $interval) {
throw new IntrusionException(
"Threshold exceeded",
"Exceeded threshold for " . $this->_key
);
}
}
}
}