1: <?php
2: /*
3: You may not change or alter any portion of this comment or credits
4: of supporting developers from this source code or any supporting source code
5: which is considered copyrighted (c) material of the original comment or credit authors.
6:
7: This program is distributed in the hope that it will be useful,
8: but WITHOUT ANY WARRANTY; without even the implied warranty of
9: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
10: */
11:
12: namespace Xoops\Core\Session;
13:
14: use Xoops\Core\AttributeInterface;
15: use Xoops\Core\HttpRequest;
16:
17: /**
18: * Session management
19: *
20: * @category Xoops\Core\Session
21: * @package Fingerprint
22: * @author Richard Griffith <richard@geekwright.com>
23: * @copyright 2015 XOOPS Project (http://xoops.org)
24: * @license GNU GPL 2 or later (http://www.gnu.org/licenses/gpl-2.0.html)
25: * @link http://xoops.org
26: */
27: class Fingerprint implements FingerprintInterface
28: {
29: /**
30: * The current request's client IP
31: *
32: * @var string
33: */
34: protected $clientFingerprint = array();
35:
36: /**
37: * grab things from the http request we need to use.
38: *
39: * @return string[] array of fingerprint values
40: */
41: protected function takePrint()
42: {
43: $clientFingerprint = array();
44: $httpRequest = HttpRequest::getInstance();
45: $clientFingerprint['clientIp'] = $httpRequest->getClientIp();
46: $clientFingerprint['userAgent'] = $this->makeInert($httpRequest->getHeader('USER_AGENT'));
47: $clientFingerprint['acceptLanguage'] = $this->makeInert($httpRequest->getHeader('ACCEPT_LANGUAGE'));
48:
49: return $clientFingerprint;
50: }
51:
52: /**
53: * Neutralize some sequences that might be used to slip nefarious bits into our fingerprint.
54: * This does not impair the similarity check, but does interfere with serialized object injection.
55: *
56: * @param string $value fingerprint string to be escaped
57: *
58: * @return string
59: */
60: protected function makeInert($value)
61: {
62: return str_replace(['\\', '{', '}', ':'], '-', $value);
63: }
64:
65: /**
66: * This method manages the session fingerprint
67: *
68: * Check current client Fingerprint against the values saved in the session.
69: * Save the current Fingerprint to the session
70: * Rate the fingerprint match pass/fail based on any changes
71: * On fail, clear the session, leaving only the new client fingerprint
72: *
73: * @param AttributeInterface $session session manager object or another
74: * AttributeInterface implementing object
75: *
76: * @return bool true if matched, false if not
77: */
78: public function checkSessionPrint(AttributeInterface $session)
79: {
80: $score = 0; // combined levenshtein distance of changes
81: $changes = 0; // number of changed fields
82: $currentFingerprint = $this->takePrint();
83: $savedFingerprint = unserialize($session->get('SESSION_FINGERPRINT'));
84: if ($savedFingerprint === false) {
85: $savedFingerprint = $currentFingerprint;
86: $changes = empty($_SESSION) ? 0 : 3; // in a populated session - force fail;
87: }
88:
89: foreach ($currentFingerprint as $key => $current) {
90: $distance = levenshtein($current, $savedFingerprint[$key]);
91: $score += $distance;
92: $changes += ($distance>0) ? 1 : 0;
93: }
94:
95: $return = true;
96:
97: // if more than one field changed, or if that change is a distance greater than 30, fail it.
98: if (($changes > 1) || ($score > 30)) {
99: $session->clear(); // session data should not be preserved
100: $return = false;
101: }
102: $session->set('SESSION_FINGERPRINT', serialize($currentFingerprint));
103: return $return;
104: }
105: }
106: