Seditio Source
Root |
./othercms/PHPFusion 9.10.20/includes/defender/token.php
<?php
/*-------------------------------------------------------+
| PHPFusion Content Management System
| Copyright (C) PHP Fusion Inc
| https://phpfusion.com/
+--------------------------------------------------------+
| Filename: includes/defender/token.php
| Author: Core Development Team
+--------------------------------------------------------+
| This program is released as free software under the
| Affero GPL license. You can redistribute it and/or
| modify it under the terms of this license which you
| can read by viewing the included agpl.txt or online
| at www.gnu.org/licenses/agpl.html. Removal of this
| copyright header is strictly prohibited without
| written permission from the original author(s).
+--------------------------------------------------------*/

namespace Defender;

/**
 * Class Token
 * CSRF protection layer for PHPFusion CMS
 *
 * @package Defender
 */
class Token extends \Defender {

   
/**
     * The remote file must begin with site_path
     *
     * @var string
     */
   
public static $remote_file = '';

   
/**
     * Allow using back a valid token by not consuming any tokens at all
     *
     * @var bool
     */
   
public static $allow_repost = FALSE;

   
/**
     * Set debug mode
     *
     * @var bool - true to debug
     */
   
private static $debug = FALSE;

   
/**
     * Error string
     *
     * @var string
     */
   
private $error = FALSE;

    public function
__construct() {

       
$locale = fusion_get_locale();
       
// Validate the Token When POST is not Empty Automatically
       
if (!empty($_POST)) {

            if (
$form_id = post('form_id')) {
               
$honeypot = (array)\Defender::getInstance()->getHoneypot($form_id.'_honeypot');
                if (!empty(
$honeypot['type']) && $honeypot['type'] == 'honeypot') {
                    if (
post($honeypot['input_name'])) {
                        \
Authenticate::logOut();
                       
redirect(BASEDIR.'error.php?code=403');
                    }
                }
            }

            if (!isset(
$_POST['fusion_token']) || !isset($_POST['form_id']) || !is_string(
                   
$_POST['fusion_token']
                ) || !
is_string($_POST['form_id'])) {
               
// Check if a token is being posted and make sure is a string
               
$this->error = $locale['token_error_2'];

            } else if (!isset(
$_SESSION['csrf_tokens'][self::pageHash()][$_POST['form_id']])) {
               
// Cannot find any token for this form
               
$this->error = $locale['token_error_9'];

            } else if (!
in_array(
               
$_POST['fusion_token'], $_SESSION['csrf_tokens'][self::pageHash()][$_POST['form_id']]
            )) {
               
// Check if the token exists in storage
               
$this->error = $locale['token_error_10'].stripinput($_POST['fusion_token']);

            } else if (
$error = self::verify_token()) {
               
$this->error = $error;
               
// Unable to Verify Token
                //$error = $locale['token_error_3'].stripinput($_POST['fusion_token']).$error;
           
}

           
$tokens_consumed = '';
            if (isset(
$_POST['form_id'])) {
                if (!empty(
$_SESSION['csrf_tokens'][self::pageHash()][$_POST['form_id']])) {
                   
$token_rings = $_SESSION['csrf_tokens'][self::pageHash()][$_POST['form_id']];
                    if (!empty(
$token_rings)) {
                        foreach (
$token_rings as $key => $token_storage) {
                            if (
$token_storage == $_POST['fusion_token']) {
                               
$tokens_consumed = $_SESSION['csrf_tokens'][self::pageHash()][$_POST['form_id']][$key];
                               
// addNotice('warning', "Token $tokens_consumed has been consumed", 'all');
                               
unset($tokens_consumed);
                                break;
                            }
                        }
                    }
                }
            }

            if (
self::$debug) {
               
define('STOP_REDIRECT', TRUE);
                if (isset(
$_POST['form_id'])) {
                   
$token_ring = $_SESSION['csrf_tokens'][self::pageHash()][$_POST['form_id']];
                   
$html = openmodal('debug_modal', 'Debug Token');
                   
$html .= alert(
                       
"<strong>The Form ID Submitted is '".stripinput(
                           
$_POST['form_id']
                        ).
"' having the following tokens: </strong><ul class='block'><li>".implode(
                           
"</li><li>", $token_ring
                       
)."</li></ul>\n", ['class' => 'alert-danger']
                    );
                   
$html .= alert(
                       
"Token posted now is ".stripinput(
                           
$_POST['fusion_token']
                        ).(!empty(
$tokens_consumed) ? " and has been consumed" : ''), ['class' => 'alert-warning']
                    );
                   
$html .= modalfooter(
                       
"<a class='btn btn-default' href='".FUSION_REQUEST."'>Click to Reload Page</a>"
                   
);
                   
$html .= closemodal();
                   
add_to_footer($html);
                }
            }
        }

        if (
$this->error) {
           
self::stop();
           
$token_notice = FALSE;
            if (
$token_notice === TRUE) {
               
addnotice('danger', $_SERVER['PHP_SELF']);
               
addnotice('danger', $this->error);
            }
        }
    }

   
/**
     * Plain Token Validation - executed at maincore.php through sniff_token() only.
     * Makes thorough checks of a posted token, and the token alone. It does not unset token.
     *
     * @return bool
     */
   
private static function verify_token() {
       
$locale = fusion_get_locale();
       
$userdata = fusion_get_userdata();
       
$error = FALSE;
       
$settings = fusion_get_settings();
       
$token_data = explode('-', stripinput($_POST['fusion_token']));
       
//if (!$post_time) {
        //  $post_time = $settings['flood_interval'];
        //}
        // check if the token has the correct format
       
if (count($token_data) == 3) {
            list(
$tuser_id, $token_time, $hash) = $token_data;
           
$user_id = $userdata['user_id'];
           
$algo = $settings['password_algorithm'];
           
$secret_key_salt = defined('SECRET_KEY_SALT') ? SECRET_KEY_SALT : 'secret_salt';
           
$salt = md5(isset($userdata['user_salt']) && !isset($_POST['login']) ? $userdata['user_salt'].$secret_key_salt : $secret_key_salt);
           
// check if the logged user has the same ID as the one in token
           
if ($tuser_id != $user_id) {
               
$error = $locale['token_error_4'];
               
// make sure the token datestamp is a number
           
} else if (!isnum($token_time)) {
               
$error = $locale['token_error_5'];
               
// check if the hash is valid
           
} else if ($hash !== hash_hmac(
                   
$algo, $user_id.$token_time.stripinput($_POST['form_id']).SECRET_KEY, $salt
               
)) {
               
$error = $locale['token_error_7'];
            }
        } else {
           
// token format is incorrect
           
$error = $locale['token_error_8'];
        }

        if (
$error) {
            return
$error;
        }

        return
FALSE;
    }

   
/**
     * Generates a unique token
     *
     * @param string $form_id
     * @param int    $max_tokens
     *
     * @return string
     */
   
public static function generate_token($form_id = 'phpfusion', $max_tokens = 5) {

       
$userdata = fusion_get_userdata();

       
$settings = fusion_get_settings();

       
$user_id = $userdata['user_id'];

       
$secret_key = defined('SECRET_KEY') ? SECRET_KEY : 'secret_key';

       
$secret_key_salt = defined('SECRET_KEY_SALT') ? SECRET_KEY_SALT : 'secret_salt';

       
$algo = !empty($settings['password_algorithm']) ? $settings['password_algorithm'] : 'sha256';

       
$key = $user_id.time().$form_id.$secret_key;

       
$salt = md5(isset($userdata['user_salt']) ? $userdata['user_salt'].$secret_key_salt : $secret_key_salt);

       
// generate a new token
       
$token = $user_id.'-'.time().'-'.hash_hmac($algo, $key, $salt);

       
$page_file = self::pageHash();

        if (
fusion_safe()) {
           
// Store into session
           
$_SESSION['csrf_tokens'][$page_file][$form_id][] = $token;

            if (
count($_SESSION['csrf_tokens'][$page_file][$form_id]) > $max_tokens) {
               
array_shift($_SESSION['csrf_tokens'][$page_file][$form_id]);
            }

        } else {

            if (!empty(
$_SESSION['csrf_tokens']) && !empty($_SESSION['csrf_tokens'][$page_file][$form_id])) {

               
$token_ring = $_SESSION['csrf_tokens'][$page_file][$form_id];

               
$ring = array_rand($token_ring);

               
$token = $token_ring[$ring];

            } else {

               
$_SESSION['csrf_tokens'][$page_file][$form_id][] = $token;

            }

        }

       
// Debugging section
       
if (self::$debug) {

            if (!
self::safe()) {
                echo
alert('FUSION NULL is DECLARED');
            }

            if (!empty(
$_SESSION['csrf_tokens'][$page_file][$form_id])) {
               
$token_ring = $_SESSION['csrf_tokens'][$page_file][$form_id];
               
$text = "<strong>New Valid tokens for Form ID <kbd>$form_id</kbd> for ".$page_file.": </strong><ul class='block'><li>".implode("</li><li>", $token_ring)."</li></ul>\n";
                echo
alert($text, ['class' => 'alert-success']);
            } else {
                echo
alert('There is no token for this page this round');
            }
        }

        return (string)
$token;
    }
}