1
|
|
|
<?php |
|
|
|
|
2
|
|
|
/** |
3
|
|
|
* @title Token CSRF (Cross-site request forgery) |
4
|
|
|
* @desc Protects against Cross-site request forgery attack. |
5
|
|
|
* |
6
|
|
|
* @author Pierre-Henry Soria <[email protected]> |
7
|
|
|
* @copyright (c) 2012-2017, Pierre-Henry Soria. All Rights Reserved. |
8
|
|
|
* @license GNU General Public License; See PH7.LICENSE.txt and PH7.COPYRIGHT.txt in the root directory. |
9
|
|
|
* @package PH7 / Framework / Security / CSRF |
10
|
|
|
* @version 1.2 |
11
|
|
|
*/ |
12
|
|
|
|
13
|
|
|
namespace PH7\Framework\Security\CSRF; |
14
|
|
|
|
15
|
|
|
defined('PH7') or exit('Restricted access'); |
16
|
|
|
|
17
|
|
|
use PH7\Framework\Session\Session; |
18
|
|
|
use PH7\Framework\Navigation\Browser; |
19
|
|
|
use PH7\Framework\Util\Various; |
20
|
|
|
use PH7\Framework\Mvc\Model\DbConfig; |
21
|
|
|
use PH7\Framework\Mvc\Request\Http; |
22
|
|
|
use PH7\Framework\Ip\Ip; |
23
|
|
|
use PH7\UserCore; |
24
|
|
|
use PH7\AdminCore; |
25
|
|
|
use PH7\AffiliateCore; |
26
|
|
|
|
27
|
|
|
/** |
28
|
|
|
* This class provides functions of numbers against the XSS (Cross-site scripting) vulnerability. |
29
|
|
|
* PH Security Token (PHST) |
30
|
|
|
*/ |
31
|
|
|
final class Token |
32
|
|
|
{ |
33
|
|
|
/** |
34
|
|
|
* @internal We have commented on "security_token_http_referer_*" because it causes bugs and it doesn't |
35
|
|
|
* play a big role for safety because this variable can be changed by users (and the web browser). |
36
|
|
|
*/ |
37
|
|
|
|
38
|
|
|
const VAR_NAME = 'pHST'; |
39
|
|
|
|
40
|
|
|
/** @var Session */ |
41
|
|
|
private $_oSession; |
42
|
|
|
|
43
|
|
|
/** @var null|string */ |
44
|
|
|
private $_sHttpReferer; |
45
|
|
|
|
46
|
|
|
/** @var null|string */ |
47
|
|
|
private $_sUserAgent; |
48
|
|
|
|
49
|
|
|
public function __construct() |
50
|
|
|
{ |
51
|
|
|
$this->_oSession = new Session; |
52
|
|
|
|
53
|
|
|
$oBrowser = new Browser; |
54
|
|
|
$this->_sHttpReferer = $oBrowser->getHttpReferer(); |
|
|
|
|
55
|
|
|
$this->_sUserAgent = $oBrowser->getUserAgent(); |
|
|
|
|
56
|
|
|
unset($oBrowser); |
57
|
|
|
} |
58
|
|
|
|
59
|
|
|
/** |
60
|
|
|
* Generate a random token. |
61
|
|
|
* |
62
|
|
|
* @param string $sName |
63
|
|
|
* |
64
|
|
|
* @return string The Token generated random. |
65
|
|
|
*/ |
66
|
|
|
public function generate($sName) |
67
|
|
|
{ |
68
|
|
|
// If the token is still valid, it returns the correct token |
69
|
|
|
if ($this->_oSession->exists('security_token_' . $sName)) { |
70
|
|
|
return $this->_oSession->get('security_token_' . $sName); |
71
|
|
|
} else { |
72
|
|
|
$sToken = Various::genRnd($sName); |
73
|
|
|
|
74
|
|
|
$aSessionData = [ |
75
|
|
|
'security_token_' . $sName => $sToken, |
76
|
|
|
'security_token_time_' . $sName => time(), |
77
|
|
|
//'security_token_http_referer_' . $sName => $this->_sHttpReferer, |
|
|
|
|
78
|
|
|
'security_token_ip_' . $sName => Ip::get(), |
79
|
|
|
'security_token_http_user_agent_' . $sName => $this->_sUserAgent |
80
|
|
|
]; |
81
|
|
|
|
82
|
|
|
$this->_oSession->set($aSessionData); |
83
|
|
|
return $sToken; |
84
|
|
|
} |
85
|
|
|
} |
86
|
|
|
|
87
|
|
|
/** |
88
|
|
|
* @param string $sName Name of the Token. |
89
|
|
|
* |
90
|
|
|
* @param string $sInputToken The name of the token inserted in the hidden tag of the form. |
91
|
|
|
* (e.g. for a from with method "post" and the field "<input type="hidden" name="my_token" />" the name of the token is "$_POST['my_token']" Default NULL |
92
|
|
|
* |
93
|
|
|
* @param integer $iTime Lifetime of token in seconds. Default NULL (value specified in the database settings). |
94
|
|
|
* |
95
|
|
|
* @return boolean Returns TRUE if the token is validated, FALSE otherwise. |
96
|
|
|
*/ |
97
|
|
|
public function check($sName, $sInputToken = null, $iTime = null) |
98
|
|
|
{ |
99
|
|
|
$iTime = (empty($iTime)) ? DbConfig::getSetting('securityTokenLifetime') : $iTime; |
100
|
|
|
|
101
|
|
|
// The default tag name for the security token |
102
|
|
|
$sInputToken = (empty($sInputToken)) ? (new Http)->post('security_token') : $sInputToken; |
103
|
|
|
|
104
|
|
|
$aCheckSession = [ |
105
|
|
|
'security_token_' . $sName, |
106
|
|
|
'security_token_time_' . $sName, |
107
|
|
|
//'security_token_http_referer_' . $sName, |
|
|
|
|
108
|
|
|
'security_token_ip_' . $sName, |
109
|
|
|
'security_token_http_user_agent_' . $sName |
110
|
|
|
]; |
111
|
|
|
|
112
|
|
|
if ($this->_oSession->exists($aCheckSession) && !empty($sInputToken)) |
113
|
|
|
if ($this->_oSession->get('security_token_' . $sName) === $sInputToken) |
114
|
|
|
if ($this->_oSession->get('security_token_time_' . $sName) >= (time() - $iTime)) |
115
|
|
|
//if ($this->_sHttpReferer === $this->_oSession->get('security_token_http_referer_' . $sName)) |
|
|
|
|
116
|
|
|
if (Ip::get() === $this->_oSession->get('security_token_ip_' . $sName)) |
117
|
|
|
if ($this->_sUserAgent === $this->_oSession->get('security_token_http_user_agent_' . $sName)) |
118
|
|
|
{ |
119
|
|
|
// Delete the token and data sessions expired |
120
|
|
|
$this->_oSession->remove($aCheckSession); |
121
|
|
|
return true; |
122
|
|
|
} |
123
|
|
|
|
124
|
|
|
// Delete the token and data sessions expired |
125
|
|
|
$this->_oSession->remove($aCheckSession); |
126
|
|
|
return false; |
127
|
|
|
} |
128
|
|
|
|
129
|
|
|
/** |
130
|
|
|
* The Get Token parameter for the URL if someone is logged (User, Admin or Affiliate), nothing otherwise. |
131
|
|
|
* |
132
|
|
|
* @return string |
133
|
|
|
*/ |
134
|
|
|
public function url() |
135
|
|
|
{ |
136
|
|
|
return ($this->currentSess() !== true) ? '?' . static::VAR_NAME . '=' . $this->currentSess() : ''; |
137
|
|
|
} |
138
|
|
|
|
139
|
|
|
/** |
140
|
|
|
* Checks the URL Token. |
141
|
|
|
* |
142
|
|
|
* @return boolean |
143
|
|
|
*/ |
144
|
|
|
public function checkUrl() |
145
|
|
|
{ |
146
|
|
|
$oHttpRequest = new Http; |
147
|
|
|
$bRet = ( ($this->currentSess() === true) || $oHttpRequest->currentUrl() === PH7_URL_ROOT || ($oHttpRequest->get(static::VAR_NAME) === $this->currentSess()) ); |
148
|
|
|
unset($oHttpRequest); |
149
|
|
|
|
150
|
|
|
return $bRet; |
151
|
|
|
} |
152
|
|
|
|
153
|
|
|
/** |
154
|
|
|
* Gets The Current Session Token. |
155
|
|
|
* |
156
|
|
|
* @return string|boolean The "token" if a user is logged or "true" if no user is logged. |
157
|
|
|
*/ |
158
|
|
|
protected function currentSess() |
159
|
|
|
{ |
160
|
|
|
if (UserCore::auth()) |
161
|
|
|
$sToken = $this->_oSession->get('member_token'); |
162
|
|
|
elseif (AdminCore::auth()) |
163
|
|
|
$sToken = $this->_oSession->get('admin_token'); |
164
|
|
|
elseif (AffiliateCore::auth()) |
165
|
|
|
$sToken = $this->_oSession->get('affiliate_token'); |
166
|
|
|
else $sToken = true; // If nobody is logged on, we did not need to do this test, so it returns true |
167
|
|
|
|
168
|
|
|
return $sToken; |
169
|
|
|
} |
170
|
|
|
|
171
|
|
|
/** |
172
|
|
|
* Clone is set to private to stop cloning. |
173
|
|
|
*/ |
174
|
|
|
private function __clone() {} |
175
|
|
|
} |
176
|
|
|
|
The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.
The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.
To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.