This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | /* |
||
4 | * This file is part of the Bouncer package. |
||
5 | * |
||
6 | * (c) François Hodierne <[email protected]> |
||
7 | * |
||
8 | * For the full copyright and license information, please view the LICENSE |
||
9 | * file that was distributed with this source code. |
||
10 | */ |
||
11 | |||
12 | namespace Bouncer; |
||
13 | |||
14 | use Bouncer\Resource\Address; |
||
15 | use Bouncer\Resource\Identity; |
||
16 | |||
17 | class Bouncer |
||
18 | { |
||
19 | |||
20 | const NICE = 'nice'; |
||
21 | const OK = 'ok'; |
||
22 | const SUSPICIOUS = 'suspicious'; |
||
23 | const BAD = 'bad'; |
||
24 | |||
25 | const ROBOT = 'robot'; |
||
26 | const BROWSER = 'browser'; |
||
27 | const UNKNOWN = 'unknown'; |
||
28 | |||
29 | /** |
||
30 | * @var array |
||
31 | */ |
||
32 | public static $supportedOptions = array( |
||
33 | 'cache', |
||
34 | 'request', |
||
35 | 'logger', |
||
36 | 'profile', |
||
37 | 'cookieName', |
||
38 | 'cookiePath', |
||
39 | 'exitHandler', |
||
40 | 'responseCodeHandler' |
||
41 | ); |
||
42 | |||
43 | /** |
||
44 | * @var string|object |
||
45 | */ |
||
46 | protected $profile; |
||
47 | |||
48 | /** |
||
49 | * @var boolean |
||
50 | */ |
||
51 | protected $throwExceptions = false; |
||
52 | |||
53 | /** |
||
54 | * @var boolean |
||
55 | */ |
||
56 | protected $logErrors = true; |
||
57 | |||
58 | /** |
||
59 | * @var string |
||
60 | */ |
||
61 | protected $cookieName = 'bsid'; |
||
62 | |||
63 | /** |
||
64 | * @var string |
||
65 | */ |
||
66 | protected $cookiePath = '/'; |
||
67 | |||
68 | /** |
||
69 | * The exit callable to use when blocking a request |
||
70 | * |
||
71 | * @var callable |
||
72 | */ |
||
73 | protected $exitHandler; |
||
74 | |||
75 | /** |
||
76 | * The callable to use to set the HTTP Response Code |
||
77 | * |
||
78 | * @var callable |
||
79 | */ |
||
80 | protected $responseCodeHandler; |
||
81 | |||
82 | /** |
||
83 | * @var \Bouncer\Cache\CacheInterface |
||
84 | */ |
||
85 | protected $cache; |
||
86 | |||
87 | /** |
||
88 | * @var \Bouncer\Logger\LoggerInterface |
||
89 | */ |
||
90 | protected $logger; |
||
91 | |||
92 | /** |
||
93 | * @var Request |
||
94 | */ |
||
95 | protected $request; |
||
96 | |||
97 | /** |
||
98 | * @var array |
||
99 | */ |
||
100 | protected $response; |
||
101 | |||
102 | /** |
||
103 | * @var array |
||
104 | */ |
||
105 | protected $analyzers = array(); |
||
106 | |||
107 | /** |
||
108 | * @var Identity |
||
109 | */ |
||
110 | protected $identity; |
||
111 | |||
112 | /** |
||
113 | * Store internal metadata |
||
114 | * |
||
115 | * @var array |
||
116 | */ |
||
117 | protected $context; |
||
118 | |||
119 | /** |
||
120 | * @var boolean |
||
121 | */ |
||
122 | protected $started = false; |
||
123 | |||
124 | /** |
||
125 | * @var boolean |
||
126 | */ |
||
127 | protected $ended = false; |
||
128 | |||
129 | /** |
||
130 | * Constructor. |
||
131 | * |
||
132 | * @param array $options |
||
133 | */ |
||
134 | public function __construct(array $options = array()) |
||
135 | { |
||
136 | if (!empty($options)) { |
||
137 | $this->setOptions($options); |
||
138 | } |
||
139 | |||
140 | // Load Profile |
||
141 | if (!$this->profile) { |
||
142 | $this->profile = new \Bouncer\Profile\DefaultProfile; |
||
143 | } |
||
144 | |||
145 | call_user_func_array(array($this->profile, 'load'), array($this)); |
||
146 | } |
||
147 | |||
148 | /* |
||
149 | * Set the supported options |
||
150 | */ |
||
151 | public function setOptions(array $options = array()) |
||
152 | { |
||
153 | foreach (static::$supportedOptions as $key) { |
||
154 | if (isset($options[$key])) { |
||
155 | $this->$key = $options[$key]; |
||
156 | } |
||
157 | } |
||
158 | } |
||
159 | |||
160 | /** |
||
161 | * @throw Exception |
||
162 | */ |
||
163 | public function error($message) |
||
164 | { |
||
165 | if ($this->throwExceptions) { |
||
166 | throw new Exception($message); |
||
167 | } |
||
168 | if ($this->logErrors) { |
||
169 | error_log("Bouncer: {$message}"); |
||
170 | } |
||
171 | } |
||
172 | |||
173 | /** |
||
174 | * @return \Bouncer\Cache\CacheInterface |
||
175 | */ |
||
176 | public function getCache($reportError = false) |
||
177 | { |
||
178 | if (empty($this->cache)) { |
||
179 | if ($reportError) { |
||
180 | $this->error('No cache available.'); |
||
181 | } |
||
182 | return; |
||
183 | } |
||
184 | |||
185 | return $this->cache; |
||
186 | } |
||
187 | |||
188 | /** |
||
189 | * @return \Bouncer\Logger\LoggerInterface |
||
190 | */ |
||
191 | public function getLogger($reportError = false) |
||
192 | { |
||
193 | if (empty($this->logger)) { |
||
194 | if ($reportError) { |
||
195 | $this->error('No logger available.'); |
||
196 | } |
||
197 | return; |
||
198 | } |
||
199 | |||
200 | return $this->logger; |
||
201 | } |
||
202 | |||
203 | /** |
||
204 | * @return Request |
||
205 | */ |
||
206 | public function getRequest() |
||
207 | { |
||
208 | if (isset($this->request)) { |
||
209 | return $this->request; |
||
210 | } |
||
211 | |||
212 | $request = Request::createFromGlobals(); |
||
213 | $request->setTrustedProxies(array('127.0.0.1')); |
||
214 | $request->setTrustedHeaderName(Request::HEADER_FORWARDED, null); |
||
215 | |||
216 | return $this->request = $request; |
||
217 | } |
||
218 | |||
219 | /** |
||
220 | * @return array |
||
221 | */ |
||
222 | public function getResponse() |
||
223 | { |
||
224 | return $this->response; |
||
225 | } |
||
226 | |||
227 | /** |
||
228 | * @return string |
||
229 | */ |
||
230 | public function getUserAgent() |
||
231 | { |
||
232 | return $this->getRequest()->getUserAgent(); |
||
233 | } |
||
234 | |||
235 | /** |
||
236 | * @return string |
||
237 | */ |
||
238 | public function getAddr() |
||
239 | { |
||
240 | return $this->getRequest()->getAddr(); |
||
241 | } |
||
242 | |||
243 | /** |
||
244 | * @return Address |
||
245 | */ |
||
246 | public function getAddress() |
||
247 | { |
||
248 | $addr = $this->getRequest()->getAddr(); |
||
249 | |||
250 | $address = new Address($addr); |
||
251 | |||
252 | return $address; |
||
253 | } |
||
254 | |||
255 | /** |
||
256 | * @return array |
||
257 | */ |
||
258 | public function getHeaders() |
||
259 | { |
||
260 | $request = $this->getRequest(); |
||
261 | |||
262 | $headers = $request->getHeaders(); |
||
263 | |||
264 | return $headers; |
||
265 | } |
||
266 | |||
267 | /** |
||
268 | * @return Signature |
||
269 | */ |
||
270 | public function getSignature() |
||
271 | { |
||
272 | $headers = $this->getHeaders(); |
||
273 | |||
274 | $signature = new Signature(array('headers' => $headers)); |
||
275 | |||
276 | return $signature; |
||
277 | } |
||
278 | |||
279 | /** |
||
280 | * @return array |
||
281 | */ |
||
282 | public function getCookies() |
||
283 | { |
||
284 | $names = array($this->cookieName, '__utmz', '__utma'); |
||
285 | |||
286 | $request = $this->getRequest(); |
||
287 | |||
288 | return $request->getCookies($names); |
||
289 | } |
||
290 | |||
291 | /** |
||
292 | * Return the current session id (from Cookie) |
||
293 | * |
||
294 | * @return string|null |
||
295 | */ |
||
296 | public function getSessionId() |
||
297 | { |
||
298 | $request = $this->getRequest(); |
||
299 | |||
300 | return $request->getCookie($this->cookieName); |
||
301 | } |
||
302 | |||
303 | /** |
||
304 | * Return the protocol of the request: HTTP/1.0 or HTTP/1.1 |
||
305 | * |
||
306 | * @return string|null |
||
307 | */ |
||
308 | public function getProtocol() |
||
309 | { |
||
310 | $request = $this->getRequest(); |
||
311 | |||
312 | return $request->getProtocol(); |
||
313 | } |
||
314 | |||
315 | /** |
||
316 | * @return Identity |
||
317 | */ |
||
318 | public function getIdentity() |
||
319 | { |
||
320 | if (isset($this->identity)) { |
||
321 | return $this->identity; |
||
322 | } |
||
323 | |||
324 | $cache = $this->getCache(); |
||
325 | |||
326 | $identity = new Identity(array( |
||
327 | 'address' => $this->getAddress(), |
||
328 | 'headers' => $this->getHeaders(), |
||
329 | 'session' => $this->getSessionId(), |
||
330 | )); |
||
331 | |||
332 | $id = $identity->getId(); |
||
333 | |||
334 | // Try to get Identity from cache |
||
335 | if ($cache) { |
||
336 | $cacheIdentity = $cache->getIdentity($id); |
||
337 | if ($cacheIdentity instanceof Identity) { |
||
338 | return $this->identity = $cacheIdentity; |
||
339 | } |
||
340 | } |
||
341 | |||
342 | // Process Analyzers |
||
343 | if (!$this->ended) { |
||
344 | $identity = $this->processAnalyzers('identity', $identity); |
||
345 | // Store Identity in cache |
||
346 | if ($cache) { |
||
347 | $cache->setIdentity($id, $identity); |
||
348 | } |
||
349 | else { |
||
350 | $this->error('No cache available. Caching identity is needed to keep performances acceptable.'); |
||
351 | } |
||
352 | } |
||
353 | |||
354 | return $this->identity = $identity; |
||
355 | } |
||
356 | |||
357 | /** |
||
358 | * @return array |
||
359 | */ |
||
360 | public function getContext() |
||
361 | { |
||
362 | if (!isset($this->context)) { |
||
363 | $this->initContext(); |
||
364 | } |
||
365 | |||
366 | return $this->context; |
||
367 | } |
||
368 | |||
369 | /* |
||
370 | * Init the context with time and pid. |
||
371 | */ |
||
372 | public function initContext() |
||
373 | { |
||
374 | $this->context = array(); |
||
375 | |||
376 | $this->addContext('time', microtime(true)); |
||
377 | |||
378 | $this->addContext('bouncer', array('pid' => getmypid())); |
||
379 | } |
||
380 | |||
381 | /* |
||
382 | * @param string $key |
||
383 | * @param boolean|string|array $properties |
||
384 | */ |
||
385 | public function addContext($key, $properties) |
||
386 | { |
||
387 | if (isset($this->context[$key]) && is_array($this->context[$key])) { |
||
388 | $this->context[$key] = array_merge($this->context[$key], $properties); |
||
389 | } else { |
||
390 | $this->context[$key] = $properties; |
||
391 | } |
||
392 | } |
||
393 | |||
394 | /* |
||
395 | * Complete the context with session, exec_time and memory_usage. |
||
396 | */ |
||
397 | public function completeContext() |
||
398 | { |
||
399 | $context = $this->getContext(); |
||
400 | |||
401 | // Session Id (from Cookie) |
||
402 | $sessionId = $this->getSessionId(); |
||
403 | if (isset($sessionId)) { |
||
404 | $this->addContext('session', $sessionId); |
||
405 | } |
||
406 | |||
407 | // Measure execution time |
||
408 | $execution_time = microtime(true) - $context['time']; |
||
409 | if (!empty($context['bouncer']['throttle_time'])) { |
||
410 | $execution_time -= $context['bouncer']['throttle_time']; |
||
411 | } |
||
412 | |||
413 | $this->addContext('bouncer', array( |
||
414 | 'execution_time' => round($execution_time, 4), |
||
415 | 'memory_usage' => memory_get_peak_usage(), |
||
416 | )); |
||
417 | } |
||
418 | |||
419 | /* |
||
420 | * Complete the response with status code |
||
421 | */ |
||
422 | public function completeResponse() |
||
423 | { |
||
424 | if (!isset($this->response)) { |
||
425 | $this->response = array(); |
||
426 | } |
||
427 | |||
428 | if (is_callable($this->responseCodeHandler)) { |
||
429 | $responseCodeHandler = $this->responseCodeHandler; |
||
430 | $responseStatus = $responseCodeHandler(); |
||
431 | if ($responseStatus) { |
||
432 | $this->response['status'] = $responseStatus; |
||
433 | } |
||
434 | } |
||
435 | } |
||
436 | /* |
||
437 | * Register an analyzer for a given type. |
||
438 | * |
||
439 | * @param string |
||
440 | * @param callable |
||
441 | * @param int |
||
442 | */ |
||
443 | public function registerAnalyzer($type, $callable, $priority = 100) |
||
444 | { |
||
445 | $this->analyzers[$type][] = array($callable, $priority); |
||
446 | } |
||
447 | |||
448 | /* |
||
449 | * Process Analyzers for a given type. Return the modified array or object. |
||
450 | * |
||
451 | * @param string |
||
452 | * @param object |
||
453 | * |
||
454 | * @return object |
||
455 | */ |
||
456 | protected function processAnalyzers($type, $value) |
||
457 | { |
||
458 | if (isset($this->analyzers[$type])) { |
||
459 | // TODO: order analyzers by priority |
||
460 | foreach ($this->analyzers[$type] as $array) { |
||
461 | list($callable) = $array; |
||
462 | $value = call_user_func_array($callable, array($value)); |
||
463 | } |
||
464 | } |
||
465 | return $value; |
||
466 | } |
||
467 | |||
468 | /* |
||
469 | * Start Bouncer, init context and register end function |
||
470 | */ |
||
471 | public function start() |
||
472 | { |
||
473 | // Already started, skip |
||
474 | if ($this->started === true) { |
||
475 | return; |
||
476 | } |
||
477 | |||
478 | $this->initContext(); |
||
479 | |||
480 | register_shutdown_function(array($this, 'end')); |
||
481 | |||
482 | $this->started = true; |
||
483 | } |
||
484 | |||
485 | /* |
||
486 | * Set a cookie containing the session id |
||
487 | */ |
||
488 | public function initSession() |
||
489 | { |
||
490 | $identity = $this->getIdentity(); |
||
491 | |||
492 | $identitySession = $identity->getSession(); |
||
493 | if ($identitySession) { |
||
494 | $curentSessionId = $this->getSessionId(); |
||
495 | $identitySessionId = $identitySession->getId(); |
||
496 | if (empty($curentSessionId) || $curentSessionId !== $identitySessionId) { |
||
497 | setcookie($this->cookieName, $identitySessionId, time() + (60 * 60 * 24 * 365 * 2), $this->cookiePath); |
||
498 | } |
||
499 | } |
||
500 | } |
||
501 | |||
502 | /* |
||
503 | * Throttle |
||
504 | * |
||
505 | * @param int $minimum in milliseconds |
||
506 | * @param int $maximum in milliseconds |
||
507 | * |
||
508 | */ |
||
509 | public function throttle($minimum = 1000, $maximum = 2500) |
||
510 | { |
||
511 | // In microseconds |
||
512 | $throttleTime = rand($minimum * 1000, $maximum * 1000); |
||
513 | usleep($throttleTime); |
||
514 | |||
515 | // In seconds |
||
516 | $this->addContext('bouncer', array( |
||
517 | 'throttle_time' => ($throttleTime / 1000 / 1000) |
||
518 | )); |
||
519 | } |
||
520 | |||
521 | /* |
||
522 | * @deprecated deprecated since version 2.1.0 |
||
523 | */ |
||
524 | public function sleep($statuses = array(), $minimum = 1000, $maximum = 2500) |
||
525 | { |
||
526 | $identity = $this->getIdentity(); |
||
527 | |||
528 | if (in_array($identity->getStatus(), $statuses)) { |
||
529 | return $this->throttle($minimum, $maximum); |
||
530 | } |
||
531 | } |
||
532 | |||
533 | /* |
||
534 | * Block |
||
535 | * |
||
536 | * @param string $type |
||
537 | * @param array $extra |
||
538 | * |
||
539 | */ |
||
540 | public function block($type = null, $extra = null) |
||
541 | { |
||
542 | $this->addContext('blocked', true); |
||
543 | |||
544 | if (isset($type)) { |
||
545 | $this->registerEvent($type, $extra); |
||
546 | } |
||
547 | |||
548 | if (is_callable($this->responseCodeHandler)) { |
||
549 | $responseCodeHandler = $this->responseCodeHandler; |
||
550 | $responseCodeHandler(403, 'Forbidden'); |
||
551 | } |
||
552 | else { |
||
553 | $this->error('No response code handler available.'); |
||
554 | } |
||
555 | |||
556 | if (is_callable($this->exitHandler)) { |
||
557 | $exitHandler = $this->exitHandler; |
||
558 | $exitHandler(); |
||
559 | } |
||
560 | else { |
||
561 | // $this->error('No exit callable set. PHP exit construct will be used.'); |
||
562 | exit; |
||
0 ignored issues
–
show
|
|||
563 | } |
||
564 | } |
||
565 | |||
566 | /* |
||
567 | * @deprecated deprecated since version 2.1.0 |
||
568 | */ |
||
569 | public function ban($statuses = array()) |
||
570 | { |
||
571 | $identity = $this->getIdentity(); |
||
572 | |||
573 | if (in_array($identity->getStatus(), $statuses)) { |
||
574 | return $this->block(); |
||
575 | } |
||
576 | |||
577 | } |
||
578 | |||
579 | /* |
||
580 | * @param string $type |
||
581 | * @param array $extra |
||
582 | */ |
||
583 | public function registerEvent($type, $extra = null) |
||
584 | { |
||
585 | $this->context['event']['type'] = $type; |
||
586 | if (!empty($extra)) { |
||
587 | $this->context['event']['extra'] = $extra; |
||
588 | } |
||
589 | } |
||
590 | |||
591 | /* |
||
592 | * Complete the connection then attempt to log. |
||
593 | */ |
||
594 | public function end() |
||
595 | { |
||
596 | // Already ended, skip |
||
597 | if ($this->ended === true) { |
||
598 | return; |
||
599 | } |
||
600 | |||
601 | if (function_exists('fastcgi_finish_request')) { |
||
602 | fastcgi_finish_request(); |
||
603 | } |
||
604 | |||
605 | $this->ended = true; |
||
606 | |||
607 | $this->completeContext(); |
||
608 | $this->completeResponse(); |
||
609 | |||
610 | // We really want to avoid throwing exceptions there |
||
611 | try { |
||
612 | $this->log(); |
||
613 | } catch (Exception $e) { |
||
614 | error_log($e->getMessage()); |
||
615 | } |
||
616 | } |
||
617 | |||
618 | /* |
||
619 | * Log the connection to the logging backend. |
||
620 | */ |
||
621 | public function log() |
||
622 | { |
||
623 | $logEntry = array( |
||
624 | 'address' => $this->getAddress(), |
||
625 | 'request' => $this->getRequest(), |
||
626 | 'response' => $this->getResponse(), |
||
627 | 'identity' => $this->getIdentity(), |
||
628 | 'session' => $this->getSessionId(), |
||
629 | 'context' => $this->getContext(), |
||
630 | ); |
||
631 | |||
632 | $logger = $this->getLogger(); |
||
633 | if ($logger) { |
||
634 | $logger->log($logEntry); |
||
635 | } |
||
636 | } |
||
637 | |||
638 | // Static |
||
639 | |||
640 | public static function hash($value) |
||
641 | { |
||
642 | return md5($value); |
||
643 | } |
||
644 | |||
645 | } |
||
646 |
An exit expression should only be used in rare cases. For example, if you write a short command line script.
In most cases however, using an
exit
expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.