CSRF from Hanyuu.
This commit is contained in:
parent
2728a1fca9
commit
51f3c47a31
30 changed files with 151 additions and 169 deletions
|
@ -39,7 +39,6 @@ require_once 'src/audit_log.php';
|
|||
require_once 'src/changelog.php';
|
||||
require_once 'src/colour.php';
|
||||
require_once 'src/comments.php';
|
||||
require_once 'src/csrf.php';
|
||||
require_once 'src/manage.php';
|
||||
require_once 'src/news.php';
|
||||
require_once 'src/perms.php';
|
||||
|
@ -456,10 +455,8 @@ MIG;
|
|||
}
|
||||
}
|
||||
|
||||
csrf_settings(
|
||||
Config::get('csrf.secret', Config::TYPE_STR, 'insecure'),
|
||||
empty($userDisplayInfo) ? ip_remote_address() : $cookieData['session_token']
|
||||
);
|
||||
CSRF::setGlobalSecretKey(Config::get('csrf.secret', Config::TYPE_STR, 'soup'));
|
||||
CSRF::setGlobalIdentity(empty($userDisplayInfo) ? ip_remote_address() : $cookieData['session_token']);
|
||||
|
||||
if(Config::get('private.enabled', Config::TYPE_BOOL)) {
|
||||
$onLoginPage = $_SERVER['PHP_SELF'] === url('auth-login');
|
||||
|
|
|
@ -24,7 +24,7 @@ $ipAddress = ip_remote_address();
|
|||
$remainingAttempts = user_login_attempts_remaining($ipAddress);
|
||||
|
||||
while(!empty($_POST['login']) && is_array($_POST['login'])) {
|
||||
if(!csrf_verify_request()) {
|
||||
if(!CSRF::validateRequest()) {
|
||||
$notices[] = 'Was unable to verify the request, please try again!';
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ if(!user_session_active()) {
|
|||
return;
|
||||
}
|
||||
|
||||
if(csrf_verify_request()) {
|
||||
if(CSRF::validateRequest()) {
|
||||
setcookie('msz_auth', '', -9001, '/', '', !empty($_SERVER['HTTPS']), true);
|
||||
user_session_stop(true);
|
||||
url_redirect('index');
|
||||
|
|
|
@ -30,7 +30,7 @@ $remainingAttempts = user_login_attempts_remaining($ipAddress);
|
|||
|
||||
while($canResetPassword) {
|
||||
if(!empty($reset) && $userId > 0) {
|
||||
if(!csrf_verify_request()) {
|
||||
if(!CSRF::validateRequest()) {
|
||||
$notices[] = 'Was unable to verify the request, please try again!';
|
||||
break;
|
||||
}
|
||||
|
@ -73,7 +73,7 @@ while($canResetPassword) {
|
|||
}
|
||||
|
||||
if(!empty($forgot)) {
|
||||
if(!csrf_verify_request()) {
|
||||
if(!CSRF::validateRequest()) {
|
||||
$notices[] = 'Was unable to verify the request, please try again!';
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ $restricted = ip_blacklist_check(ip_remote_address()) ? 'blacklist'
|
|||
: (user_warning_check_ip(ip_remote_address()) ? 'ban' : '');
|
||||
|
||||
while(!$restricted && !empty($register)) {
|
||||
if(!csrf_verify_request()) {
|
||||
if(!CSRF::validateRequest()) {
|
||||
$notices[] = 'Was unable to verify the request, please try again!';
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ if(empty($tokenInfo['user_totp_key'])) {
|
|||
}
|
||||
|
||||
while(!empty($twofactor)) {
|
||||
if(!csrf_verify_request()) {
|
||||
if(!CSRF::validateRequest()) {
|
||||
$notices[] = 'Was unable to verify the request, please try again!';
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ if($isXHR) {
|
|||
return;
|
||||
}
|
||||
|
||||
if(!csrf_verify_request()) {
|
||||
if(!CSRF::validateRequest()) {
|
||||
echo render_info_or_json($isXHR, "Couldn't verify this request, please refresh the page and try again.", 403);
|
||||
return;
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ if(user_warning_check_expiration($currentUserId, MSZ_WARN_SILENCE) > 0) {
|
|||
return;
|
||||
}
|
||||
|
||||
csrf_http_header();
|
||||
header(CSRF::header());
|
||||
$commentPerms = comments_get_perms($currentUserId);
|
||||
|
||||
$commentId = !empty($_GET['c']) && is_string($_GET['c']) ? (int)$_GET['c'] : 0;
|
||||
|
|
|
@ -10,7 +10,7 @@ switch($indexMode) {
|
|||
case 'mark':
|
||||
$markEntireForum = $forumId === 0;
|
||||
|
||||
if(user_session_active() && csrf_verify_request()) {
|
||||
if(user_session_active() && CSRF::validateRequest()) {
|
||||
forum_mark_read($markEntireForum ? null : $forumId, user_session_current('user_id', 0));
|
||||
}
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ if($isXHR) {
|
|||
return;
|
||||
}
|
||||
|
||||
if(!csrf_verify_request()) {
|
||||
if(!CSRF::validateRequest()) {
|
||||
echo render_info_or_json($isXHR, "Couldn't verify this request, please refresh the page and try again.", 403);
|
||||
return;
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ if(user_warning_check_expiration($currentUserId, MSZ_WARN_SILENCE) > 0) {
|
|||
return;
|
||||
}
|
||||
|
||||
csrf_http_header();
|
||||
header(CSRF::header());
|
||||
|
||||
if(empty($_POST['poll']['id']) || !ctype_digit($_POST['poll']['id'])) {
|
||||
echo render_info_or_json($isXHR, "Invalid request.", 400);
|
||||
|
|
|
@ -19,7 +19,7 @@ if($isXHR) {
|
|||
return;
|
||||
}
|
||||
|
||||
$postRequestVerified = csrf_verify_request();
|
||||
$postRequestVerified = CSRF::validateRequest();
|
||||
|
||||
if(!empty($postMode) && !user_session_active()) {
|
||||
echo render_info_or_json($isXHR, 'You must be logged in to manage posts.', 401);
|
||||
|
@ -47,7 +47,7 @@ if($isXHR) {
|
|||
return;
|
||||
}
|
||||
|
||||
csrf_http_header();
|
||||
header(CSRF::header());
|
||||
}
|
||||
|
||||
$postInfo = forum_post_get($postId, true);
|
||||
|
|
|
@ -133,7 +133,7 @@ if(!empty($_POST)) {
|
|||
$topicType = isset($_POST['post']['type']) ? (int)$_POST['post']['type'] : null;
|
||||
$postSignature = isset($_POST['post']['signature']);
|
||||
|
||||
if(!csrf_verify_request()) {
|
||||
if(!CSRF::validateRequest()) {
|
||||
$notices[] = 'Could not verify request.';
|
||||
} else {
|
||||
$isEditingTopic = empty($topic) || ($mode === 'edit' && $post['is_opening_post']);
|
||||
|
|
|
@ -82,12 +82,12 @@ if(in_array($moderationMode, $validModerationModes, true)) {
|
|||
return;
|
||||
}
|
||||
|
||||
if(!csrf_verify_request()) {
|
||||
if(!CSRF::validateRequest()) {
|
||||
echo render_info_or_json($isXHR, "Couldn't verify this request, please refresh the page and try again.", 403);
|
||||
return;
|
||||
}
|
||||
|
||||
csrf_http_header();
|
||||
header(CSRF::header());
|
||||
|
||||
if(!user_session_active()) {
|
||||
echo render_info_or_json($isXHR, 'You must be logged in to manage posts.', 401);
|
||||
|
|
|
@ -10,7 +10,7 @@ if(!perms_check_user(MSZ_PERMS_CHANGELOG, user_session_current('user_id'), MSZ_P
|
|||
|
||||
$changeId = (int)($_GET['c'] ?? 0);
|
||||
|
||||
if($_SERVER['REQUEST_METHOD'] === 'POST' && csrf_verify_request()) {
|
||||
if($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
|
||||
if(!empty($_POST['change']) && is_array($_POST['change'])) {
|
||||
if($changeId > 0) {
|
||||
$postChange = DB::prepare('
|
||||
|
|
|
@ -10,7 +10,7 @@ if(!perms_check_user(MSZ_PERMS_CHANGELOG, user_session_current('user_id'), MSZ_P
|
|||
|
||||
$tagId = (int)($_GET['t'] ?? 0);
|
||||
|
||||
if(!empty($_POST['tag']) && is_array($_POST['tag']) && csrf_verify_request()) {
|
||||
if(!empty($_POST['tag']) && is_array($_POST['tag']) && CSRF::validateRequest()) {
|
||||
if($tagId > 0) {
|
||||
$updateTag = DB::prepare('
|
||||
UPDATE `msz_changelog_tags`
|
||||
|
|
|
@ -11,10 +11,10 @@ if(!perms_check_user(MSZ_PERMS_GENERAL, user_session_current('user_id'), General
|
|||
$notices = [];
|
||||
|
||||
if(!empty($_POST)) {
|
||||
if(!csrf_verify_request()) {
|
||||
if(!CSRF::validateRequest()) {
|
||||
$notices[] = 'Verification failed.';
|
||||
} else {
|
||||
csrf_http_header();
|
||||
header(CSRF::header());
|
||||
|
||||
if(!empty($_POST['blacklist']['remove']) && is_array($_POST['blacklist']['remove'])) {
|
||||
foreach($_POST['blacklist']['remove'] as $cidr) {
|
||||
|
|
|
@ -12,7 +12,7 @@ $emoteId = !empty($_GET['e']) && is_string($_GET['e']) ? (int)$_GET['e'] : 0;
|
|||
$isNew = $emoteId <= 0;
|
||||
$emoteInfo = !$isNew ? Emoticon::byId($emoteId) : new Emoticon;
|
||||
|
||||
if(csrf_verify_request() && isset($_POST['emote_order']) && isset($_POST['emote_hierarchy']) && !empty($_POST['emote_url']) && !empty($_POST['emote_strings'])) {
|
||||
if(CSRF::validateRequest() && isset($_POST['emote_order']) && isset($_POST['emote_hierarchy']) && !empty($_POST['emote_url']) && !empty($_POST['emote_strings'])) {
|
||||
$emoteInfo->setUrl($_POST['emote_url'])
|
||||
->setHierarchy($_POST['emote_hierarchy'])
|
||||
->setOrder($_POST['emote_order'])
|
||||
|
|
|
@ -8,7 +8,7 @@ if(!perms_check_user(MSZ_PERMS_GENERAL, user_session_current('user_id'), General
|
|||
return;
|
||||
}
|
||||
|
||||
if(csrf_verify_request() && !empty($_GET['emote']) && is_string($_GET['emote'])) {
|
||||
if(CSRF::validateRequest() && !empty($_GET['emote']) && is_string($_GET['emote'])) {
|
||||
$emoteId = (int)$_GET['emote'];
|
||||
$emoteInfo = Emoticon::byId($emoteId);
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ if(!perms_check_user(MSZ_PERMS_NEWS, user_session_current('user_id'), MSZ_PERM_N
|
|||
$category = [];
|
||||
$categoryId = (int)($_GET['c'] ?? null);
|
||||
|
||||
if(!empty($_POST['category']) && csrf_verify_request()) {
|
||||
if(!empty($_POST['category']) && CSRF::validateRequest()) {
|
||||
$originalCategoryId = (int)($_POST['category']['id'] ?? null);
|
||||
$categoryId = news_category_create(
|
||||
$_POST['category']['name'] ?? null,
|
||||
|
|
|
@ -12,7 +12,7 @@ $post = [];
|
|||
$postId = (int)($_GET['p'] ?? null);
|
||||
$categories = news_categories_get(0, 0, false, false, true);
|
||||
|
||||
if(!empty($_POST['post']) && csrf_verify_request()) {
|
||||
if(!empty($_POST['post']) && CSRF::validateRequest()) {
|
||||
$originalPostId = (int)($_POST['post']['id'] ?? null);
|
||||
$currentUserId = user_session_current('user_id');
|
||||
$title = $_POST['post']['title'] ?? null;
|
||||
|
|
|
@ -20,7 +20,7 @@ if($canEditPerms) {
|
|||
$permissions = manage_perms_list(perms_get_role_raw($roleId ?? 0));
|
||||
}
|
||||
|
||||
if(!empty($_POST['role']) && is_array($_POST['role']) && csrf_verify_request()) {
|
||||
if(!empty($_POST['role']) && is_array($_POST['role']) && CSRF::validateRequest()) {
|
||||
$roleHierarchy = (int)($_POST['role']['hierarchy'] ?? -1);
|
||||
|
||||
if(!user_check_super($currentUserId) && ($roleId === null
|
||||
|
|
|
@ -22,7 +22,7 @@ $canEdit = $isSuperUser || user_check_authority($currentUserId, $userId);
|
|||
$canEditPerms = $canEdit && perms_check_user(MSZ_PERMS_USER, $currentUserId, MSZ_PERM_USER_MANAGE_PERMS);
|
||||
$permissions = manage_perms_list(perms_get_user_raw($userId));
|
||||
|
||||
if(csrf_verify_request() && $canEdit) {
|
||||
if(CSRF::validateRequest() && $canEdit) {
|
||||
if(!empty($_POST['roles']) && is_array($_POST['roles']) && array_test($_POST['roles'], 'ctype_digit')) {
|
||||
// Fetch existing roles
|
||||
$existingRoles = DB::prepare('
|
||||
|
|
|
@ -62,7 +62,7 @@ if($isEditing) {
|
|||
]);
|
||||
|
||||
if(!empty($_POST) && is_array($_POST)) {
|
||||
if(!csrf_verify_request()) {
|
||||
if(!CSRF::validateRequest()) {
|
||||
$notices[] = MSZ_TMP_USER_ERROR_STRINGS['csrf'];
|
||||
} else {
|
||||
if(!empty($_POST['profile']) && is_array($_POST['profile'])) {
|
||||
|
|
|
@ -15,12 +15,12 @@ if($isXHR) {
|
|||
return;
|
||||
}
|
||||
|
||||
if(!csrf_verify_request()) {
|
||||
if(!CSRF::validateRequest()) {
|
||||
echo render_info_or_json($isXHR, "Couldn't verify this request, please refresh the page and try again.", 403);
|
||||
return;
|
||||
}
|
||||
|
||||
csrf_http_header();
|
||||
header(CSRF::header());
|
||||
|
||||
if(!user_session_active()) {
|
||||
echo render_info_or_json($isXHR, 'You must be logged in to manage relations.', 401);
|
||||
|
|
|
@ -16,7 +16,7 @@ $currentUserId = user_session_current('user_id');
|
|||
$currentEmail = user_email_get($currentUserId);
|
||||
$isRestricted = user_warning_check_restriction($currentUserId);
|
||||
$twoFactorInfo = user_totp_info($currentUserId);
|
||||
$isVerifiedRequest = csrf_verify_request();
|
||||
$isVerifiedRequest = CSRF::validateRequest();
|
||||
|
||||
if(!$isRestricted && $isVerifiedRequest && !empty($_POST['role'])) {
|
||||
$roleId = (int)($_POST['role']['id'] ?? 0);
|
||||
|
|
|
@ -12,7 +12,7 @@ $errors = [];
|
|||
$currentUserId = user_session_current('user_id');
|
||||
$sessionActive = user_session_current('session_id');
|
||||
|
||||
if(!empty($_POST['session']) && csrf_verify_request()) {
|
||||
if(!empty($_POST['session']) && CSRF::validateRequest()) {
|
||||
$currentSessionKilled = false;
|
||||
|
||||
if(is_array($_POST['session'])) {
|
||||
|
|
112
src/CSRF.php
Normal file
112
src/CSRF.php
Normal file
|
@ -0,0 +1,112 @@
|
|||
<?php
|
||||
namespace Misuzu;
|
||||
|
||||
use Exception;
|
||||
use InvalidArgumentException;
|
||||
|
||||
final class CSRF {
|
||||
public const TOLERANCE = 30 * 60;
|
||||
public const HASH_ALGO = 'sha1';
|
||||
public const EPOCH = 1575158400;
|
||||
|
||||
private $timestamp = 0;
|
||||
private $tolerance = 0;
|
||||
|
||||
private static $globalIdentity = '';
|
||||
private static $globalSecretKey = '';
|
||||
|
||||
public function __construct(int $tolerance = self::TOLERANCE, ?int $timestamp = null) {
|
||||
$this->setTolerance($tolerance);
|
||||
$this->setTimestamp($timestamp ?? self::timestamp());
|
||||
}
|
||||
|
||||
public static function timestamp(): int {
|
||||
return time() - self::EPOCH;
|
||||
}
|
||||
|
||||
public static function setGlobalIdentity(string $identity): void {
|
||||
self::$globalIdentity = $identity;
|
||||
}
|
||||
public static function setGlobalSecretKey(string $secretKey): void {
|
||||
self::$globalSecretKey = $secretKey;
|
||||
}
|
||||
public static function validate(string $token, ?string $identity = null, ?string $secretKey = null): bool {
|
||||
try {
|
||||
return self::decode($token, $identity ?? self::$globalIdentity, $secretKey ?? self::$globalSecretKey)->isValid();
|
||||
} catch(Exception $ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public static function token(?string $identity = null, int $tolerance = self::TOLERANCE, ?string $secretKey = null, ?int $timestamp = null): string {
|
||||
return (new static($tolerance, $timestamp))->encode($identity ?? self::$globalIdentity, $secretKey ?? self::$globalSecretKey);
|
||||
}
|
||||
|
||||
// Should be replaced by filters eventually <
|
||||
public static function header(...$args): string {
|
||||
return 'X-Misuzu-CSRF: ' . self::token(...$args);
|
||||
}
|
||||
public static function validateRequest(?string $identity = null, ?string $secretKey = null): bool {
|
||||
if(isset($_SERVER['HTTP_X_MISUZU_CSRF'])) {
|
||||
$token = $_SERVER['HTTP_X_MISUZU_CSRF'];
|
||||
} elseif(isset($_POST['_csrf']) && is_string($_POST['_csrf'])) {
|
||||
$token = $_POST['_csrf'];
|
||||
} elseif(isset($_REQUEST['csrf']) && is_string($_REQUEST['csrf'])) {
|
||||
$token = $_REQUEST['csrf'];
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
return self::validate($token, $identity, $secretKey);
|
||||
}
|
||||
// >
|
||||
|
||||
public static function decode(string $token, string $identity, string $secretKey): CSRF {
|
||||
$hash = substr($token, 12);
|
||||
$unpacked = unpack('Vtimestamp/vtolerance', hex2bin(substr($token, 0, 12)));
|
||||
|
||||
if(empty($hash) || empty($unpacked['timestamp']) || empty($unpacked['tolerance']))
|
||||
throw new InvalidArgumentException('Invalid token provided.');
|
||||
|
||||
$csrf = new static($unpacked['tolerance'], $unpacked['timestamp']);
|
||||
|
||||
if(!hash_equals($csrf->getHash($identity, $secretKey), $hash))
|
||||
throw new InvalidArgumentException('Modified token.');
|
||||
|
||||
return $csrf;
|
||||
}
|
||||
|
||||
public function encode(string $identity, string $secretKey): string {
|
||||
$token = bin2hex(pack('Vv', $this->getTimestamp(), $this->getTolerance()));
|
||||
$token .= $this->getHash($identity, $secretKey);
|
||||
return $token;
|
||||
}
|
||||
|
||||
public function getHash(string $identity, string $secretKey): string {
|
||||
return hash_hmac(self::HASH_ALGO, "{$identity}|{$this->getTimestamp()}|{$this->getTolerance()}", $secretKey);
|
||||
}
|
||||
|
||||
public function getTimestamp(): int {
|
||||
return $this->timestamp;
|
||||
}
|
||||
public function setTimestamp(int $timestamp): self {
|
||||
if($timestamp < 0 || $timestamp > 0xFFFFFFFF)
|
||||
throw new InvalidArgumentException('Timestamp must be within the constaints of an unsigned 32-bit integer.');
|
||||
$this->timestamp = $timestamp;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getTolerance(): int {
|
||||
return $this->tolerance;
|
||||
}
|
||||
public function setTolerance(int $tolerance): self {
|
||||
if($tolerance < 0 || $tolerance > 0xFFFF)
|
||||
throw new InvalidArgumentException('Tolerance must be within the constaints of an unsigned 16-bit integer.');
|
||||
$this->tolerance = $tolerance;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function isValid(): bool {
|
||||
$currentTime = self::timestamp();
|
||||
return $currentTime >= $this->getTimestamp() && $currentTime <= $this->getTimestamp() + $this->getTolerance();
|
||||
}
|
||||
}
|
|
@ -32,8 +32,6 @@ final class TwigMisuzu extends Twig_Extension {
|
|||
public function getFunctions() {
|
||||
return [
|
||||
new Twig_Function('get_browser', 'get_browser'),
|
||||
new Twig_Function('csrf_token', 'csrf_token'),
|
||||
new Twig_Function('csrf_input', 'csrf_html'),
|
||||
new Twig_Function('url_construct', 'url_construct'),
|
||||
new Twig_Function('warning_has_duration', 'user_warning_has_duration'),
|
||||
new Twig_Function('url', 'url'),
|
||||
|
@ -44,6 +42,7 @@ final class TwigMisuzu extends Twig_Extension {
|
|||
new Twig_Function('forum_may_have_children', 'forum_may_have_children'),
|
||||
new Twig_Function('forum_may_have_topics', 'forum_may_have_topics'),
|
||||
new Twig_Function('forum_has_priority_voting', 'forum_has_priority_voting'),
|
||||
new Twig_Function('csrf_token', fn() => CSRF::token()),
|
||||
new Twig_Function('git_commit_hash', fn(bool $long = false) => GitInfo::hash($long)),
|
||||
new Twig_Function('git_tag', fn() => GitInfo::tag()),
|
||||
new Twig_Function('git_branch', fn() => GitInfo::branch()),
|
||||
|
|
127
src/csrf.php
127
src/csrf.php
|
@ -1,127 +0,0 @@
|
|||
<?php
|
||||
define('MSZ_CSRF_TOLERANCE', 30 * 60); // DO NOT EXCEED 16-BIT INTEGER SIZES, SHIT _WILL_ BREAK
|
||||
define('MSZ_CSRF_HTML', '<input type="hidden" name="csrf" value="%1$s">');
|
||||
define('MSZ_CSRF_HASH_ALGO', 'sha256');
|
||||
define('MSZ_CSRF_TOKEN_LENGTH', 76); // 8 + 4 + 64
|
||||
|
||||
// the following three functions DO NOT depend on csrf_init().
|
||||
// $identity = When the user is logged in I recommend just using their session key, otherwise IP will be fine.
|
||||
function csrf_token_create(
|
||||
string $identity,
|
||||
string $secretKey,
|
||||
?int $timestamp = null,
|
||||
int $tolerance = MSZ_CSRF_TOLERANCE
|
||||
): string {
|
||||
$timestamp = $timestamp ?? time();
|
||||
$token = bin2hex(pack('Vv', $timestamp, $tolerance));
|
||||
|
||||
return $token . csrf_token_hash(
|
||||
MSZ_CSRF_HASH_ALGO,
|
||||
$identity,
|
||||
$secretKey,
|
||||
$timestamp,
|
||||
$tolerance
|
||||
);
|
||||
}
|
||||
|
||||
function csrf_token_hash(
|
||||
string $algo,
|
||||
string $identity,
|
||||
string $secretKey,
|
||||
int $timestamp,
|
||||
int $tolerance
|
||||
): string {
|
||||
return hash_hmac(
|
||||
$algo,
|
||||
implode(',', [$identity, $timestamp, $tolerance]),
|
||||
$secretKey
|
||||
);
|
||||
}
|
||||
|
||||
function csrf_token_verify(
|
||||
string $token,
|
||||
string $identity,
|
||||
string $secretKey
|
||||
): bool {
|
||||
if(empty($token) || strlen($token) !== MSZ_CSRF_TOKEN_LENGTH) {
|
||||
return false;
|
||||
}
|
||||
|
||||
[$timestamp, $tolerance] = [0, 0];
|
||||
extract(unpack('Vtimestamp/vtolerance', hex2bin(substr($token, 0, 12))));
|
||||
|
||||
if(time() > $timestamp + $tolerance) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// remove timestamp + tolerance from token
|
||||
$token = substr($token, 12);
|
||||
|
||||
$compare = csrf_token_hash(
|
||||
MSZ_CSRF_HASH_ALGO,
|
||||
$identity,
|
||||
$secretKey,
|
||||
$timestamp,
|
||||
$tolerance
|
||||
);
|
||||
|
||||
return hash_equals($compare, $token);
|
||||
}
|
||||
|
||||
// Sets some defaults
|
||||
function csrf_settings(?string $secretKey = null, ?string $identity = null): array {
|
||||
static $settings = [];
|
||||
|
||||
if(!empty($secretKey) && !empty($identity)) {
|
||||
$settings = [
|
||||
'secret_key' => $secretKey,
|
||||
'identity' => $identity,
|
||||
];
|
||||
}
|
||||
|
||||
return $settings;
|
||||
}
|
||||
|
||||
function csrf_is_ready(): bool {
|
||||
return !empty(csrf_settings());
|
||||
}
|
||||
|
||||
function csrf_token(): string {
|
||||
static $token = null;
|
||||
|
||||
if(empty($token)) {
|
||||
$settings = csrf_settings();
|
||||
$token = csrf_token_create(
|
||||
$settings['identity'],
|
||||
$settings['secret_key']
|
||||
);
|
||||
}
|
||||
|
||||
return $token;
|
||||
}
|
||||
|
||||
function csrf_verify(string $token): bool {
|
||||
$settings = csrf_settings();
|
||||
|
||||
return csrf_token_verify(
|
||||
$token,
|
||||
$settings['identity'],
|
||||
$settings['secret_key']
|
||||
);
|
||||
}
|
||||
|
||||
function csrf_verify_request(?string $token = null): bool {
|
||||
if(empty($token)) {
|
||||
$token = $_SERVER['HTTP_X_MISUZU_CSRF'] ?? $_REQUEST['csrf'] ?? '';
|
||||
}
|
||||
|
||||
return csrf_verify($token);
|
||||
}
|
||||
|
||||
function csrf_html(): string {
|
||||
return sprintf(MSZ_CSRF_HTML, csrf_token());
|
||||
}
|
||||
|
||||
function csrf_http_header(string $name = 'X-Misuzu-CSRF'): void {
|
||||
header("{$name}: " . csrf_token());
|
||||
}
|
|
@ -189,8 +189,8 @@ function url_variable(string $value, array $variables): string {
|
|||
return constant(trim($value, '[]'));
|
||||
}
|
||||
|
||||
if(starts_with($value, '{') && ends_with($value, '}') && csrf_is_ready()) {
|
||||
return csrf_token();
|
||||
if(starts_with($value, '{') && ends_with($value, '}')) {
|
||||
return \Misuzu\CSRF::token();
|
||||
}
|
||||
|
||||
return $value;
|
||||
|
|
|
@ -4,9 +4,10 @@
|
|||
{% endspaceless %}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro input_csrf() %}{# so we don't have to specify |raw every time #}
|
||||
{% macro input_csrf() %}
|
||||
{% from _self import input_hidden %}
|
||||
{% spaceless %}
|
||||
{{ csrf_input()|raw }}
|
||||
{{ input_hidden('_csrf', csrf_token()) }}
|
||||
{% endspaceless %}
|
||||
{% endmacro %}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue