security: faza 4 - ochrona CSRF panelu administracyjnego
- Nowa klasa \Shared\Security\CsrfToken (generate/validate/regenerate) - Token CSRF we wszystkich formularzach edycji (form-edit.php) - Walidacja CSRF w FormRequestHandler::handleSubmit() - Token CSRF w formularzu logowania i formularzach 2FA - Walidacja CSRF w App::special_actions() dla żądań POST - Regeneracja tokenu po udanym logowaniu (bezpośrednia i przez 2FA) - Fix XSS: htmlspecialchars na $alert w unlogged-layout.php - 7 nowych testów CsrfTokenTest (817 testów łącznie) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
26
autoload/Shared/Security/CsrfToken.php
Normal file
26
autoload/Shared/Security/CsrfToken.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
namespace Shared\Security;
|
||||
|
||||
class CsrfToken
|
||||
{
|
||||
const SESSION_KEY = 'csrf_token';
|
||||
|
||||
public static function getToken(): string
|
||||
{
|
||||
if (empty($_SESSION[self::SESSION_KEY])) {
|
||||
$_SESSION[self::SESSION_KEY] = bin2hex(random_bytes(32));
|
||||
}
|
||||
return (string) $_SESSION[self::SESSION_KEY];
|
||||
}
|
||||
|
||||
public static function validate(string $token): bool
|
||||
{
|
||||
$sessionToken = isset($_SESSION[self::SESSION_KEY]) ? (string) $_SESSION[self::SESSION_KEY] : '';
|
||||
return $sessionToken !== '' && hash_equals($sessionToken, $token);
|
||||
}
|
||||
|
||||
public static function regenerate(): void
|
||||
{
|
||||
$_SESSION[self::SESSION_KEY] = bin2hex(random_bytes(32));
|
||||
}
|
||||
}
|
||||
@@ -43,6 +43,15 @@ class App
|
||||
$sa = \Shared\Helpers\Helpers::get( 's-action' );
|
||||
if ( !$sa ) return;
|
||||
|
||||
if ( $_SERVER['REQUEST_METHOD'] === 'POST' ) {
|
||||
$csrfToken = isset( $_POST['_csrf_token'] ) ? (string) $_POST['_csrf_token'] : '';
|
||||
if ( !\Shared\Security\CsrfToken::validate( $csrfToken ) ) {
|
||||
\Shared\Helpers\Helpers::alert( 'Nieprawidłowy token bezpieczeństwa. Spróbuj ponownie.' );
|
||||
header( 'Location: /admin/' );
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
$domain = preg_replace( '/^www\./', '', $_SERVER['SERVER_NAME'] );
|
||||
$cookie_name = 'admin_remember_' . str_replace( '.', '-', $domain );
|
||||
$users = new \Domain\User\UserRepository( $mdb );
|
||||
@@ -84,6 +93,7 @@ class App
|
||||
exit;
|
||||
}
|
||||
|
||||
\Shared\Security\CsrfToken::regenerate();
|
||||
self::finalize_admin_login( $user, $domain, $cookie_name, (bool) \Shared\Helpers\Helpers::get( 'remember' ) );
|
||||
header( 'Location: /admin/articles/list/' );
|
||||
exit;
|
||||
@@ -127,6 +137,7 @@ class App
|
||||
header( 'Location: /admin/' );
|
||||
exit;
|
||||
}
|
||||
\Shared\Security\CsrfToken::regenerate();
|
||||
self::finalize_admin_login( $user, $domain, $cookie_name, !empty( $pending['remember'] ) );
|
||||
header( 'Location: /admin/articles/list/' );
|
||||
exit;
|
||||
|
||||
@@ -32,6 +32,13 @@ class FormRequestHandler
|
||||
'data' => []
|
||||
];
|
||||
|
||||
// Walidacja CSRF
|
||||
$csrfToken = isset($postData['_csrf_token']) ? (string) $postData['_csrf_token'] : '';
|
||||
if (!\Shared\Security\CsrfToken::validate($csrfToken)) {
|
||||
$result['errors'] = ['csrf' => 'Nieprawidłowy token bezpieczeństwa. Odśwież stronę i spróbuj ponownie.'];
|
||||
return $result;
|
||||
}
|
||||
|
||||
// Walidacja
|
||||
$errors = $this->validator->validate($postData, $formViewModel->fields, $formViewModel->languages);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user