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/changelog.php';
|
||||||
require_once 'src/colour.php';
|
require_once 'src/colour.php';
|
||||||
require_once 'src/comments.php';
|
require_once 'src/comments.php';
|
||||||
require_once 'src/csrf.php';
|
|
||||||
require_once 'src/manage.php';
|
require_once 'src/manage.php';
|
||||||
require_once 'src/news.php';
|
require_once 'src/news.php';
|
||||||
require_once 'src/perms.php';
|
require_once 'src/perms.php';
|
||||||
|
@ -456,10 +455,8 @@ MIG;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
csrf_settings(
|
CSRF::setGlobalSecretKey(Config::get('csrf.secret', Config::TYPE_STR, 'soup'));
|
||||||
Config::get('csrf.secret', Config::TYPE_STR, 'insecure'),
|
CSRF::setGlobalIdentity(empty($userDisplayInfo) ? ip_remote_address() : $cookieData['session_token']);
|
||||||
empty($userDisplayInfo) ? ip_remote_address() : $cookieData['session_token']
|
|
||||||
);
|
|
||||||
|
|
||||||
if(Config::get('private.enabled', Config::TYPE_BOOL)) {
|
if(Config::get('private.enabled', Config::TYPE_BOOL)) {
|
||||||
$onLoginPage = $_SERVER['PHP_SELF'] === url('auth-login');
|
$onLoginPage = $_SERVER['PHP_SELF'] === url('auth-login');
|
||||||
|
|
|
@ -24,7 +24,7 @@ $ipAddress = ip_remote_address();
|
||||||
$remainingAttempts = user_login_attempts_remaining($ipAddress);
|
$remainingAttempts = user_login_attempts_remaining($ipAddress);
|
||||||
|
|
||||||
while(!empty($_POST['login']) && is_array($_POST['login'])) {
|
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!';
|
$notices[] = 'Was unable to verify the request, please try again!';
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ if(!user_session_active()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(csrf_verify_request()) {
|
if(CSRF::validateRequest()) {
|
||||||
setcookie('msz_auth', '', -9001, '/', '', !empty($_SERVER['HTTPS']), true);
|
setcookie('msz_auth', '', -9001, '/', '', !empty($_SERVER['HTTPS']), true);
|
||||||
user_session_stop(true);
|
user_session_stop(true);
|
||||||
url_redirect('index');
|
url_redirect('index');
|
||||||
|
|
|
@ -30,7 +30,7 @@ $remainingAttempts = user_login_attempts_remaining($ipAddress);
|
||||||
|
|
||||||
while($canResetPassword) {
|
while($canResetPassword) {
|
||||||
if(!empty($reset) && $userId > 0) {
|
if(!empty($reset) && $userId > 0) {
|
||||||
if(!csrf_verify_request()) {
|
if(!CSRF::validateRequest()) {
|
||||||
$notices[] = 'Was unable to verify the request, please try again!';
|
$notices[] = 'Was unable to verify the request, please try again!';
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -73,7 +73,7 @@ while($canResetPassword) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!empty($forgot)) {
|
if(!empty($forgot)) {
|
||||||
if(!csrf_verify_request()) {
|
if(!CSRF::validateRequest()) {
|
||||||
$notices[] = 'Was unable to verify the request, please try again!';
|
$notices[] = 'Was unable to verify the request, please try again!';
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ $restricted = ip_blacklist_check(ip_remote_address()) ? 'blacklist'
|
||||||
: (user_warning_check_ip(ip_remote_address()) ? 'ban' : '');
|
: (user_warning_check_ip(ip_remote_address()) ? 'ban' : '');
|
||||||
|
|
||||||
while(!$restricted && !empty($register)) {
|
while(!$restricted && !empty($register)) {
|
||||||
if(!csrf_verify_request()) {
|
if(!CSRF::validateRequest()) {
|
||||||
$notices[] = 'Was unable to verify the request, please try again!';
|
$notices[] = 'Was unable to verify the request, please try again!';
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,7 @@ if(empty($tokenInfo['user_totp_key'])) {
|
||||||
}
|
}
|
||||||
|
|
||||||
while(!empty($twofactor)) {
|
while(!empty($twofactor)) {
|
||||||
if(!csrf_verify_request()) {
|
if(!CSRF::validateRequest()) {
|
||||||
$notices[] = 'Was unable to verify the request, please try again!';
|
$notices[] = 'Was unable to verify the request, please try again!';
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ if($isXHR) {
|
||||||
return;
|
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);
|
echo render_info_or_json($isXHR, "Couldn't verify this request, please refresh the page and try again.", 403);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -36,7 +36,7 @@ if(user_warning_check_expiration($currentUserId, MSZ_WARN_SILENCE) > 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
csrf_http_header();
|
header(CSRF::header());
|
||||||
$commentPerms = comments_get_perms($currentUserId);
|
$commentPerms = comments_get_perms($currentUserId);
|
||||||
|
|
||||||
$commentId = !empty($_GET['c']) && is_string($_GET['c']) ? (int)$_GET['c'] : 0;
|
$commentId = !empty($_GET['c']) && is_string($_GET['c']) ? (int)$_GET['c'] : 0;
|
||||||
|
|
|
@ -10,7 +10,7 @@ switch($indexMode) {
|
||||||
case 'mark':
|
case 'mark':
|
||||||
$markEntireForum = $forumId === 0;
|
$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));
|
forum_mark_read($markEntireForum ? null : $forumId, user_session_current('user_id', 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ if($isXHR) {
|
||||||
return;
|
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);
|
echo render_info_or_json($isXHR, "Couldn't verify this request, please refresh the page and try again.", 403);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -34,7 +34,7 @@ if(user_warning_check_expiration($currentUserId, MSZ_WARN_SILENCE) > 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
csrf_http_header();
|
header(CSRF::header());
|
||||||
|
|
||||||
if(empty($_POST['poll']['id']) || !ctype_digit($_POST['poll']['id'])) {
|
if(empty($_POST['poll']['id']) || !ctype_digit($_POST['poll']['id'])) {
|
||||||
echo render_info_or_json($isXHR, "Invalid request.", 400);
|
echo render_info_or_json($isXHR, "Invalid request.", 400);
|
||||||
|
|
|
@ -19,7 +19,7 @@ if($isXHR) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$postRequestVerified = csrf_verify_request();
|
$postRequestVerified = CSRF::validateRequest();
|
||||||
|
|
||||||
if(!empty($postMode) && !user_session_active()) {
|
if(!empty($postMode) && !user_session_active()) {
|
||||||
echo render_info_or_json($isXHR, 'You must be logged in to manage posts.', 401);
|
echo render_info_or_json($isXHR, 'You must be logged in to manage posts.', 401);
|
||||||
|
@ -47,7 +47,7 @@ if($isXHR) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
csrf_http_header();
|
header(CSRF::header());
|
||||||
}
|
}
|
||||||
|
|
||||||
$postInfo = forum_post_get($postId, true);
|
$postInfo = forum_post_get($postId, true);
|
||||||
|
|
|
@ -133,7 +133,7 @@ if(!empty($_POST)) {
|
||||||
$topicType = isset($_POST['post']['type']) ? (int)$_POST['post']['type'] : null;
|
$topicType = isset($_POST['post']['type']) ? (int)$_POST['post']['type'] : null;
|
||||||
$postSignature = isset($_POST['post']['signature']);
|
$postSignature = isset($_POST['post']['signature']);
|
||||||
|
|
||||||
if(!csrf_verify_request()) {
|
if(!CSRF::validateRequest()) {
|
||||||
$notices[] = 'Could not verify request.';
|
$notices[] = 'Could not verify request.';
|
||||||
} else {
|
} else {
|
||||||
$isEditingTopic = empty($topic) || ($mode === 'edit' && $post['is_opening_post']);
|
$isEditingTopic = empty($topic) || ($mode === 'edit' && $post['is_opening_post']);
|
||||||
|
|
|
@ -82,12 +82,12 @@ if(in_array($moderationMode, $validModerationModes, true)) {
|
||||||
return;
|
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);
|
echo render_info_or_json($isXHR, "Couldn't verify this request, please refresh the page and try again.", 403);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
csrf_http_header();
|
header(CSRF::header());
|
||||||
|
|
||||||
if(!user_session_active()) {
|
if(!user_session_active()) {
|
||||||
echo render_info_or_json($isXHR, 'You must be logged in to manage posts.', 401);
|
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);
|
$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(!empty($_POST['change']) && is_array($_POST['change'])) {
|
||||||
if($changeId > 0) {
|
if($changeId > 0) {
|
||||||
$postChange = DB::prepare('
|
$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);
|
$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) {
|
if($tagId > 0) {
|
||||||
$updateTag = DB::prepare('
|
$updateTag = DB::prepare('
|
||||||
UPDATE `msz_changelog_tags`
|
UPDATE `msz_changelog_tags`
|
||||||
|
|
|
@ -11,10 +11,10 @@ if(!perms_check_user(MSZ_PERMS_GENERAL, user_session_current('user_id'), General
|
||||||
$notices = [];
|
$notices = [];
|
||||||
|
|
||||||
if(!empty($_POST)) {
|
if(!empty($_POST)) {
|
||||||
if(!csrf_verify_request()) {
|
if(!CSRF::validateRequest()) {
|
||||||
$notices[] = 'Verification failed.';
|
$notices[] = 'Verification failed.';
|
||||||
} else {
|
} else {
|
||||||
csrf_http_header();
|
header(CSRF::header());
|
||||||
|
|
||||||
if(!empty($_POST['blacklist']['remove']) && is_array($_POST['blacklist']['remove'])) {
|
if(!empty($_POST['blacklist']['remove']) && is_array($_POST['blacklist']['remove'])) {
|
||||||
foreach($_POST['blacklist']['remove'] as $cidr) {
|
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;
|
$isNew = $emoteId <= 0;
|
||||||
$emoteInfo = !$isNew ? Emoticon::byId($emoteId) : new Emoticon;
|
$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'])
|
$emoteInfo->setUrl($_POST['emote_url'])
|
||||||
->setHierarchy($_POST['emote_hierarchy'])
|
->setHierarchy($_POST['emote_hierarchy'])
|
||||||
->setOrder($_POST['emote_order'])
|
->setOrder($_POST['emote_order'])
|
||||||
|
|
|
@ -8,7 +8,7 @@ if(!perms_check_user(MSZ_PERMS_GENERAL, user_session_current('user_id'), General
|
||||||
return;
|
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'];
|
$emoteId = (int)$_GET['emote'];
|
||||||
$emoteInfo = Emoticon::byId($emoteId);
|
$emoteInfo = Emoticon::byId($emoteId);
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ if(!perms_check_user(MSZ_PERMS_NEWS, user_session_current('user_id'), MSZ_PERM_N
|
||||||
$category = [];
|
$category = [];
|
||||||
$categoryId = (int)($_GET['c'] ?? null);
|
$categoryId = (int)($_GET['c'] ?? null);
|
||||||
|
|
||||||
if(!empty($_POST['category']) && csrf_verify_request()) {
|
if(!empty($_POST['category']) && CSRF::validateRequest()) {
|
||||||
$originalCategoryId = (int)($_POST['category']['id'] ?? null);
|
$originalCategoryId = (int)($_POST['category']['id'] ?? null);
|
||||||
$categoryId = news_category_create(
|
$categoryId = news_category_create(
|
||||||
$_POST['category']['name'] ?? null,
|
$_POST['category']['name'] ?? null,
|
||||||
|
|
|
@ -12,7 +12,7 @@ $post = [];
|
||||||
$postId = (int)($_GET['p'] ?? null);
|
$postId = (int)($_GET['p'] ?? null);
|
||||||
$categories = news_categories_get(0, 0, false, false, true);
|
$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);
|
$originalPostId = (int)($_POST['post']['id'] ?? null);
|
||||||
$currentUserId = user_session_current('user_id');
|
$currentUserId = user_session_current('user_id');
|
||||||
$title = $_POST['post']['title'] ?? null;
|
$title = $_POST['post']['title'] ?? null;
|
||||||
|
|
|
@ -20,7 +20,7 @@ if($canEditPerms) {
|
||||||
$permissions = manage_perms_list(perms_get_role_raw($roleId ?? 0));
|
$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);
|
$roleHierarchy = (int)($_POST['role']['hierarchy'] ?? -1);
|
||||||
|
|
||||||
if(!user_check_super($currentUserId) && ($roleId === null
|
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);
|
$canEditPerms = $canEdit && perms_check_user(MSZ_PERMS_USER, $currentUserId, MSZ_PERM_USER_MANAGE_PERMS);
|
||||||
$permissions = manage_perms_list(perms_get_user_raw($userId));
|
$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')) {
|
if(!empty($_POST['roles']) && is_array($_POST['roles']) && array_test($_POST['roles'], 'ctype_digit')) {
|
||||||
// Fetch existing roles
|
// Fetch existing roles
|
||||||
$existingRoles = DB::prepare('
|
$existingRoles = DB::prepare('
|
||||||
|
|
|
@ -62,7 +62,7 @@ if($isEditing) {
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if(!empty($_POST) && is_array($_POST)) {
|
if(!empty($_POST) && is_array($_POST)) {
|
||||||
if(!csrf_verify_request()) {
|
if(!CSRF::validateRequest()) {
|
||||||
$notices[] = MSZ_TMP_USER_ERROR_STRINGS['csrf'];
|
$notices[] = MSZ_TMP_USER_ERROR_STRINGS['csrf'];
|
||||||
} else {
|
} else {
|
||||||
if(!empty($_POST['profile']) && is_array($_POST['profile'])) {
|
if(!empty($_POST['profile']) && is_array($_POST['profile'])) {
|
||||||
|
|
|
@ -15,12 +15,12 @@ if($isXHR) {
|
||||||
return;
|
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);
|
echo render_info_or_json($isXHR, "Couldn't verify this request, please refresh the page and try again.", 403);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
csrf_http_header();
|
header(CSRF::header());
|
||||||
|
|
||||||
if(!user_session_active()) {
|
if(!user_session_active()) {
|
||||||
echo render_info_or_json($isXHR, 'You must be logged in to manage relations.', 401);
|
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);
|
$currentEmail = user_email_get($currentUserId);
|
||||||
$isRestricted = user_warning_check_restriction($currentUserId);
|
$isRestricted = user_warning_check_restriction($currentUserId);
|
||||||
$twoFactorInfo = user_totp_info($currentUserId);
|
$twoFactorInfo = user_totp_info($currentUserId);
|
||||||
$isVerifiedRequest = csrf_verify_request();
|
$isVerifiedRequest = CSRF::validateRequest();
|
||||||
|
|
||||||
if(!$isRestricted && $isVerifiedRequest && !empty($_POST['role'])) {
|
if(!$isRestricted && $isVerifiedRequest && !empty($_POST['role'])) {
|
||||||
$roleId = (int)($_POST['role']['id'] ?? 0);
|
$roleId = (int)($_POST['role']['id'] ?? 0);
|
||||||
|
|
|
@ -12,7 +12,7 @@ $errors = [];
|
||||||
$currentUserId = user_session_current('user_id');
|
$currentUserId = user_session_current('user_id');
|
||||||
$sessionActive = user_session_current('session_id');
|
$sessionActive = user_session_current('session_id');
|
||||||
|
|
||||||
if(!empty($_POST['session']) && csrf_verify_request()) {
|
if(!empty($_POST['session']) && CSRF::validateRequest()) {
|
||||||
$currentSessionKilled = false;
|
$currentSessionKilled = false;
|
||||||
|
|
||||||
if(is_array($_POST['session'])) {
|
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() {
|
public function getFunctions() {
|
||||||
return [
|
return [
|
||||||
new Twig_Function('get_browser', 'get_browser'),
|
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('url_construct', 'url_construct'),
|
||||||
new Twig_Function('warning_has_duration', 'user_warning_has_duration'),
|
new Twig_Function('warning_has_duration', 'user_warning_has_duration'),
|
||||||
new Twig_Function('url', 'url'),
|
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_children', 'forum_may_have_children'),
|
||||||
new Twig_Function('forum_may_have_topics', 'forum_may_have_topics'),
|
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('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_commit_hash', fn(bool $long = false) => GitInfo::hash($long)),
|
||||||
new Twig_Function('git_tag', fn() => GitInfo::tag()),
|
new Twig_Function('git_tag', fn() => GitInfo::tag()),
|
||||||
new Twig_Function('git_branch', fn() => GitInfo::branch()),
|
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, '[]'));
|
return constant(trim($value, '[]'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if(starts_with($value, '{') && ends_with($value, '}') && csrf_is_ready()) {
|
if(starts_with($value, '{') && ends_with($value, '}')) {
|
||||||
return csrf_token();
|
return \Misuzu\CSRF::token();
|
||||||
}
|
}
|
||||||
|
|
||||||
return $value;
|
return $value;
|
||||||
|
|
|
@ -4,9 +4,10 @@
|
||||||
{% endspaceless %}
|
{% endspaceless %}
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
{% macro input_csrf() %}{# so we don't have to specify |raw every time #}
|
{% macro input_csrf() %}
|
||||||
|
{% from _self import input_hidden %}
|
||||||
{% spaceless %}
|
{% spaceless %}
|
||||||
{{ csrf_input()|raw }}
|
{{ input_hidden('_csrf', csrf_token()) }}
|
||||||
{% endspaceless %}
|
{% endspaceless %}
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue