Bearsampp
2026.7.11
Toggle main menu visibility
Loading...
Searching...
No Matches
class.csrf.php
Go to the documentation of this file.
1
<?php
2
/*
3
* Copyright (c) 2021-2025 Bearsampp
4
* License: GNU General Public License version 3 or later; see LICENSE.txt
5
* Author: Bear
6
* Website: https://bearsampp.com
7
* Github: https://github.com/Bearsampp
8
*/
9
34
class
Csrf
35
{
39
const
SESSION_KEY
=
'bearsampp_csrf_tokens'
;
40
44
const
TOKEN_EXPIRATION
= 7200;
45
50
const
MAX_TOKENS
= 10;
51
58
public
static
function
init
()
59
{
60
// Start session if not already started
61
if
(session_status() === PHP_SESSION_NONE) {
62
session_start();
63
}
64
65
// Initialize token storage if not exists
66
if
(!isset($_SESSION[self::SESSION_KEY])) {
67
$_SESSION[self::SESSION_KEY] = [];
68
}
69
70
// Clean up expired tokens
71
self::cleanupExpiredTokens
();
72
}
73
80
public
static
function
generateToken
()
81
{
82
self::init
();
83
84
try
{
85
// Generate cryptographically secure random token
86
$token = bin2hex(random_bytes(32));
87
}
catch
(Exception $e) {
88
Log::error
(
'Failed to generate CSRF token: '
. $e->getMessage());
89
// Fallback to less secure but functional method
90
$token = hash(
'sha256'
, uniqid(
'bearsampp_csrf_'
,
true
) . microtime(
true
));
91
}
92
93
// Store token with timestamp
94
$_SESSION[self::SESSION_KEY][$token] = time();
95
96
// Limit number of stored tokens
97
if
(count($_SESSION[self::SESSION_KEY]) > self::MAX_TOKENS) {
98
// Remove oldest token
99
$oldestToken = array_key_first($_SESSION[self::SESSION_KEY]);
100
unset($_SESSION[self::SESSION_KEY][$oldestToken]);
101
}
102
103
// Log token generation without exposing token material
104
Log::debug
(
'CSRF token generated successfully'
);
105
106
return
$token;
107
}
108
114
public
static
function
getToken
()
115
{
116
self::init
();
117
118
// If no tokens exist, generate one
119
if
(empty($_SESSION[self::SESSION_KEY])) {
120
return
self::generateToken
();
121
}
122
123
// Return the most recent token
124
$tokens = $_SESSION[self::SESSION_KEY];
125
end($tokens);
126
$latestToken = key($tokens);
127
128
// Check if latest token is expired
129
if
(time() - $tokens[$latestToken] > self::TOKEN_EXPIRATION) {
130
// Generate new token if expired
131
return
self::generateToken
();
132
}
133
134
return
$latestToken;
135
}
136
144
public
static
function
validateToken
($token, $removeAfterValidation =
false
)
145
{
146
self::init
();
147
148
// Check if token is provided
149
if
(empty($token) || !is_string($token)) {
150
Log::warning
(
'CSRF validation failed: No token provided'
);
151
return
false
;
152
}
153
154
// Check if token exists in session
155
if
(!isset($_SESSION[self::SESSION_KEY][$token])) {
156
Log::warning
(
'CSRF validation failed: Token not found in session'
);
157
return
false
;
158
}
159
160
// Check if token is expired
161
$tokenTimestamp = $_SESSION[self::SESSION_KEY][$token];
162
if
(time() - $tokenTimestamp > self::TOKEN_EXPIRATION) {
163
Log::warning
(
'CSRF validation failed: Token expired'
);
164
unset($_SESSION[self::SESSION_KEY][$token]);
165
return
false
;
166
}
167
168
// Token is valid
169
Log::debug
(
'CSRF token validated successfully'
);
170
171
// Remove token if one-time use is requested
172
if
($removeAfterValidation) {
173
unset($_SESSION[self::SESSION_KEY][$token]);
174
}
175
176
return
true
;
177
}
178
186
public
static
function
validateRequest
($removeAfterValidation =
false
)
187
{
188
// Check POST first (most common for AJAX)
189
if
(isset($_POST[
'csrf_token'
])) {
190
return
self::validateToken
($_POST[
'csrf_token'
], $removeAfterValidation);
191
}
192
193
// Check GET as fallback
194
if
(isset($_GET[
'csrf_token'
])) {
195
return
self::validateToken
($_GET[
'csrf_token'
], $removeAfterValidation);
196
}
197
198
// Check custom header (for AJAX requests)
199
$headers =
self::getAllHeaders
();
200
201
// Check for X-CSRF-Token header (case-insensitive)
202
foreach
($headers as $key => $value) {
203
if
(strtolower($key) ===
'x-csrf-token'
) {
204
return
self::validateToken
($value, $removeAfterValidation);
205
}
206
}
207
208
Log::warning
(
'CSRF validation failed: No token in request'
);
209
return
false
;
210
}
211
218
private
static
function
getAllHeaders
()
219
{
220
// Use getallheaders() if available (Apache)
221
if
(function_exists(
'getallheaders'
)) {
222
$headers = getallheaders();
223
if
($headers !==
false
) {
224
return
$headers;
225
}
226
}
227
228
// Fallback for FastCGI/CGI environments
229
$headers = [];
230
foreach
($_SERVER as $key => $value) {
231
// Extract HTTP headers from $_SERVER
232
if
(substr($key, 0, 5) ===
'HTTP_'
) {
233
// Convert HTTP_X_CSRF_TOKEN to X-Csrf-Token
234
$headerName = str_replace(
' '
,
'-'
, ucwords(strtolower(str_replace(
'_'
,
' '
, substr($key, 5)))));
235
$headers[$headerName] = $value;
236
}
237
// Handle CONTENT_TYPE and CONTENT_LENGTH specially
238
elseif (in_array($key, [
'CONTENT_TYPE'
,
'CONTENT_LENGTH'
])) {
239
$headerName = str_replace(
' '
,
'-'
, ucwords(strtolower(str_replace(
'_'
,
' '
, $key))));
240
$headers[$headerName] = $value;
241
}
242
}
243
244
return
$headers;
245
}
246
252
private
static
function
cleanupExpiredTokens
()
253
{
254
if
(!isset($_SESSION[self::SESSION_KEY])) {
255
return
0;
256
}
257
258
$removed = 0;
259
$currentTime = time();
260
261
foreach
($_SESSION[self::SESSION_KEY] as $token => $timestamp) {
262
if
($currentTime - $timestamp > self::TOKEN_EXPIRATION) {
263
unset($_SESSION[self::SESSION_KEY][$token]);
264
$removed++;
265
}
266
}
267
268
if
($removed > 0) {
269
Log::debug
(
"Cleaned up $removed expired CSRF tokens"
);
270
}
271
272
return
$removed;
273
}
274
281
public
static
function
regenerateToken
()
282
{
283
self::init
();
284
285
// Clear all existing tokens
286
$_SESSION[self::SESSION_KEY] = [];
287
288
// Generate new token
289
return
self::generateToken
();
290
}
291
297
public
static
function
getTokenField
()
298
{
299
$token =
self::getToken
();
300
return
'<input type="hidden" name="csrf_token" value="'
. htmlspecialchars($token, ENT_QUOTES,
'UTF-8'
) .
'">'
;
301
}
302
309
public
static
function
getTokenMeta
()
310
{
311
$token =
self::getToken
();
312
return
'<meta name="csrf-token" content="'
. htmlspecialchars($token, ENT_QUOTES,
'UTF-8'
) .
'">'
;
313
}
314
322
public
static
function
validateOrDie
($removeAfterValidation =
false
)
323
{
324
if
(!self::validateRequest($removeAfterValidation)) {
325
http_response_code(403);
326
header(
'Content-Type: application/json'
);
327
echo json_encode([
328
'error'
=>
'CSRF validation failed'
,
329
'message'
=>
'Invalid or expired security token. Please refresh the page and try again.'
330
]);
331
exit
;
332
}
333
}
334
341
public
static
function
getStats
()
342
{
343
self::init
();
344
345
$tokens = $_SESSION[self::SESSION_KEY] ?? [];
346
$currentTime = time();
347
$expired = 0;
348
$valid = 0;
349
350
foreach
($tokens as $timestamp) {
351
if
($currentTime - $timestamp > self::TOKEN_EXPIRATION) {
352
$expired++;
353
}
else
{
354
$valid++;
355
}
356
}
357
358
return
[
359
'total'
=> count($tokens),
360
'valid'
=> $valid,
361
'expired'
=> $expired,
362
'max_tokens'
=> self::MAX_TOKENS,
363
'expiration_seconds'
=> self::TOKEN_EXPIRATION
364
];
365
}
366
}
367
exit
exit
Definition
ajax.apply.moduleconfig.php:63
Csrf
Definition
class.csrf.php:35
Csrf\generateToken
static generateToken()
Definition
class.csrf.php:80
Csrf\cleanupExpiredTokens
static cleanupExpiredTokens()
Definition
class.csrf.php:252
Csrf\MAX_TOKENS
const MAX_TOKENS
Definition
class.csrf.php:50
Csrf\TOKEN_EXPIRATION
const TOKEN_EXPIRATION
Definition
class.csrf.php:44
Csrf\regenerateToken
static regenerateToken()
Definition
class.csrf.php:281
Csrf\getTokenField
static getTokenField()
Definition
class.csrf.php:297
Csrf\getStats
static getStats()
Definition
class.csrf.php:341
Csrf\getToken
static getToken()
Definition
class.csrf.php:114
Csrf\validateOrDie
static validateOrDie($removeAfterValidation=false)
Definition
class.csrf.php:322
Csrf\validateRequest
static validateRequest($removeAfterValidation=false)
Definition
class.csrf.php:186
Csrf\getAllHeaders
static getAllHeaders()
Definition
class.csrf.php:218
Csrf\getTokenMeta
static getTokenMeta()
Definition
class.csrf.php:309
Csrf\validateToken
static validateToken($token, $removeAfterValidation=false)
Definition
class.csrf.php:144
Csrf\init
static init()
Definition
class.csrf.php:58
Csrf\SESSION_KEY
const SESSION_KEY
Definition
class.csrf.php:39
Log\debug
static debug($data, $file=null)
Definition
class.log.php:616
Log\warning
static warning($data, $file=null)
Definition
class.log.php:638
Log\error
static error($data, $file=null)
Definition
class.log.php:650
sandbox
core
classes
class.csrf.php
Generated by
1.17.0