forked from foxbunny/CSRF4PHP
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathCsrfToken.php
242 lines (225 loc) · 8.78 KB
/
CsrfToken.php
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
<?php
/**
* CsrfToken.php
*
* This fine contains the CsrfToken class that handles genration and checking
* of Synchronization tokens (http://bit.ly/owasp_synctoken).
*
* The basic usage involves initializing an instance at some point, calling
* either the getHiddenField() or generateToken() methods. The former produces
* an XHTML-compliant input element, whereas the latter produces a raw
* Base64-encoded string. In another request, the request can be tested for
* authenticity (to the best of this script's author's knowledge) by calling
* the checkToken() method.
*
* The generateHiddenField() and generateToken() create a $_SESSION['csrf']
* array, which contains the material for token creation. This data is
* preserved so that the token can be checked later.
*
* DISCLAIMER: This script has not been widely tested (actually, it's been only
* tested on a local host), so I do not recommend using it without sufficient
* testing. That said, I do think it will work as expected.
*
* TODO: Write unit tests.
*
* @author Branko Vukelic <studio@brankovukelic.com>
* @version 0.1.2
* @package Csrf
*/
namespace Csrf;
/**
* Token generation and checking class
*
* This class encapsulates all of the functionality of the Csrf package. On
* initialization, it checks for session ID, and it will throw an exception is
* one is not found, so it is best you initialize right after session_start().
*
* Since the time used to generate the token is not the time when
* initialization takes place, you can initialize at any time before token
* generation.
*
* @package Csrf
* @subpackage classes
*/
class CsrfToken {
/**
* Flag to determine whether GET HTTP verb is checked for a token
*
* Otherwise, only POST will be checked (default).
*
* @access protected
* @var boolean
*/
protected $acceptGet = FALSE;
/**
* Default timeout for token check
*
* If the request is made outside of this time frame, it will be
* considered invalid. This parameter can be manually overriden at check
* time by supplying the appropriate arugment to the {@link checkToken()}
* method.
*
* @access protected
* @var integer
*/
protected $timeout = 300;
/**
* Class constructor
*
* While initializing this class, it is possible to specify the {@link
* $timeout} parameter. The timeout is 300 seconds (5 minutes) by default.
* The {@link acceptGet} argument can be set to TRUE if you wish to
* include GET requests in the check. Otherwise, all GET requests will be
* considered invalid (default).
*/
public function __construct($timeout=300, $acceptGet=\FALSE){
$this->timeout = $timeout;
$this->acceptGet = (bool) $acceptGet;
}
/**
* Random string gnerator
*
* Utility function for random string generation of the $len length.
*
* @param integer $len (defaults to 10) length of the generated string
* @return string
*/
public function randomString($len = 10) {
// Characters that may look like other characters in different fonts
// have been omitted.
$rString = '';
$chars = 'ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789';
$charsTotal = \strlen($chars);
for ($i = 0; $i < $len; $i++) {
$rInt = (integer) \mt_rand(0, $charsTotal);
$rString .= \substr($chars, $rInt, 1);
}
return $rString;
}
/**
* Calculates the SHA1 hash from the csrf token material
*
* The token material is found in $_SESSION['csrf'] array. This function
* is not used directly. It is called by other public CsrfToken method.
*
* @see generateToken(), generateHiddenField(), checkToken()
* @visibility protected
* @return string
*/
protected function calculateHash() {
return \sha1(\implode('', $_SESSION['csrf']));
}
/**
* Generates the token string encoded using Base64 algorythm
*
* When this method is called, it also resets any data in the
* $_SESSION['csrf'] array, so it can be called multiple times. It is not
* wise to call this method just before performing a chek for an earlier
* request, as it will overwrite any token material it finds.
*
* @see generateHiddenField()
* @visibility public
* @return string
*/
public function generateToken() {
// Create or overwrite the csrf entry in the seesion
$_SESSION['csrf'] = array();
$_SESSION['csrf']['time'] = \time();
$_SESSION['csrf']['salt'] = $this->randomString(32);
$_SESSION['csrf']['sessid'] = \session_id();
$_SESSION['csrf']['ip'] = $_SERVER['REMOTE_ADDR'];
// Generate the SHA1 hash
$hash = $this->calculateHash();
// Generate and return the token
return \base64_encode($hash);
}
/**
* Generates the entire hiddent form element containing the token
*
* Since Sychronize Token CSRF protection is most effective with POST
* requests, this convenience method allows you to generate a
* prefabricated hidden element that you will insert into your forms. The
* markup is XHTML compliant. Since it will not break regular HTML or
* HTML5 markup, there are no options for customization. You can use the
* {@link generateToken()} method if you want a custom markup, or just
* want the raw token string.
*
* @see generateToken()
* @visibility public
* @return string
*/
public function generateHiddenField() {
// Shortcut method to generate the entire form
// element containing the CSRF protection token
$token = $this->generateToken();
return "<input type=\"hidden\" name=\"csrf\" value=\"$token\" />";
}
/**
* Checks the timeliness of the request
*
* This method is not meant to be called directly, but is called by the
* {@link checkToken()} method. It checks the time recorded in the session
* against the time of request, and returns TRUE if the request was just
* in time, or FALSE if the request broke the time limit.
*
* @see checkToken()
* @visibility protected
* @param integer $timeout request timeout in seconds
* @return boolean
*/
protected function checkTimeout($timeout=\NULL) {
if (!$timeout) {
$timeout = $this->timeout;
}
return ($_SERVER['REQUEST_TIME'] - $_SESSION['csrf']['time']) < $timeout;
}
public function preCheckTimeout($timeout=\NULL) {
if (!$timeout) {
$timeout = $this->timeout;
}
return ($_SERVER['REQUEST_TIME'] - $_SESSION['csrf']['time']) < $timeout / 2;
}
/**
* Checks the token to authenticate the request
*
* The check will fail if the session wasn't started (or the session id
* got lost somehow), if the $_SESSION['csrf'] wasn't set (probably the
* form page didn't do its part in generating and using the token), if
* the request did not contain the 'csrf' parameter, or if the 'csrf'
* parameter does not match the generated from the information in the
* $_SESSION['csrf']. The check will also fail if the request was made
* outside of the time limit specified by the optional $timeout parameter
* or took longer than the default 5 minutes. For multi-page scenarios,
* or for longer forms (like blog posts and user comments) it is
* recommended that you manually extend the time limit to a more
* reasonable time frame.
*
* @visibility public
* @param integer $timeout
* @return boolean
*/
public function checkToken($timeout=\NULL) {
// Default timeout is 300 seconds (5 minutes)
// First check if csrf information is present in the session
if (isset($_SESSION['csrf'])) {
// Check if there is a session id
if (\session_id()) {
// Check if response contains a usable csrf token
$isCsrfGet = isset($_GET['csrf']);
$isCsrfPost = isset($_POST['csrf']);
if (($this->acceptGet and $isCsrfGet) or $isCsrfPost) {
// Decode the received token hash
$tokenHash = \base64_decode($_REQUEST['csrf']);
// Generate a new hash from the data we have
$generatedHash = $this->calculateHash();
// Compare and return the result
if ($tokenHash and $generatedHash) {
return $tokenHash == $generatedHash;
}
}
}
}
// In all other cases return FALSE
return \FALSE;
}
}