From e1fdb6803ddd7ed3e39dc0d414d7ad59d63ca0dd Mon Sep 17 00:00:00 2001 From: Flavio Copes Date: Fri, 6 Nov 2015 15:31:49 +0100 Subject: [PATCH] Add nonce functionality --- system/src/Grav/Common/Twig/TwigExtension.php | 25 ++++- system/src/Grav/Common/Uri.php | 16 ++++ system/src/Grav/Common/Utils.php | 96 ++++++++++++++++++- 3 files changed, 135 insertions(+), 2 deletions(-) diff --git a/system/src/Grav/Common/Twig/TwigExtension.php b/system/src/Grav/Common/Twig/TwigExtension.php index 2da411210..97723ce40 100644 --- a/system/src/Grav/Common/Twig/TwigExtension.php +++ b/system/src/Grav/Common/Twig/TwigExtension.php @@ -94,14 +94,17 @@ class TwigExtension extends \Twig_Extension new \Twig_simpleFunction('authorize', [$this, 'authorize']), new \Twig_SimpleFunction('debug', [$this, 'dump'], ['needs_context' => true, 'needs_environment' => true]), new \Twig_SimpleFunction('dump', [$this, 'dump'], ['needs_context' => true, 'needs_environment' => true]), + new \Twig_SimpleFunction('evaluate', [$this, 'evaluateFunc']), new \Twig_SimpleFunction('gist', [$this, 'gistFunc']), + new \Twig_SimpleFunction('nonce_field', [$this, 'nonceFieldFunc']), new \Twig_simpleFunction('random_string', [$this, 'randomStringFunc']), new \Twig_SimpleFunction('repeat', [$this, 'repeatFunc']), new \Twig_SimpleFunction('string', [$this, 'stringFunc']), new \Twig_simpleFunction('t', [$this, 'translate']), new \Twig_simpleFunction('ta', [$this, 'translateArray']), new \Twig_SimpleFunction('url', [$this, 'urlFunc']), - new \Twig_SimpleFunction('evaluate', [$this, 'evaluateFunc']), + + ]; } @@ -595,4 +598,24 @@ class TwigExtension extends \Twig_Extension return false; } + + /** + * Used to add a nonce to a form. Call {{ nonce_field('action') }} specifying a string representing the action. + * + * For maximum protection, ensure that the string representing the action is as specific as possible. + * + * @todo evaluate if adding referrer or not + * + * @param string action the action + * @param string nonceParamName a custom nonce param name + * + * @return string the nonce input field + */ + public function nonceFieldFunc($action, $nonceParamName = 'nonce') + { + $string = ''; + // $string += ''; + return $string; + + } } diff --git a/system/src/Grav/Common/Uri.php b/system/src/Grav/Common/Uri.php index 0473d3938..cf9ec4793 100644 --- a/system/src/Grav/Common/Uri.php +++ b/system/src/Grav/Common/Uri.php @@ -572,4 +572,20 @@ class Uri return $normalized_url; } } + + /** + * Adds the nonce to a URL for a specific action + * + * @param string $url the url + * @param string $action the action + * @param string $nonceParamName the param name to use + * + * @return string the url with the nonce + */ + public static function addNonce($url, $action, $nonceParamName = 'nonce') + { + $nonce = Utils::getNonce($action); + $urlWithNonce = $url . '/' . $nonceParamName . self::getGrav()['config']->get('system.param_sep', ':') . $nonce; + return $urlWithNonce; + } } diff --git a/system/src/Grav/Common/Utils.php b/system/src/Grav/Common/Utils.php index 8c55669a2..ae4479903 100644 --- a/system/src/Grav/Common/Utils.php +++ b/system/src/Grav/Common/Utils.php @@ -384,7 +384,101 @@ abstract class Utils * * @return boolean */ - public static function isPositive($value) { + public static function isPositive($value) + { return in_array($value, [true, 1, '1', 'yes', 'on', 'true'], true); } + + + /** + * Generates a nonce string to be hashed. Called by self::getNonce() + * + * @param string $action + * @param bool $plusOneTick if true, generates the token for the next tick (the next 12 hours) + * + * @return string the nonce string + */ + private static function generateNonceString($action, $plusOneTick) + { + $user = self::getGrav()['user']; + $username = $user->username; + + if ( ! $username ) { + $username = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : ''; + } + + $token = session_id(); + $i = self::nonceTick(); + + if ($plusOneTick) { + $i++; + } + + return ( $i . '|' . $action . '|' . $username . '|' . $token ); + } + + /** + * Get the time-dependent variable for nonce creation. + * + * @todo now a tick lasts a day. Once the day is passed, the nonce is not valid any more. Find a better way + * to ensure nonces issued near the end of the day do not expire in that small amount of time + * + * @return int the time part of the nonce. Changes once every 24 hours + */ + private static function nonceTick() + { + $secondsInHalfADay = 60 * 60 * 12; + return (int)ceil(time() / ( $secondsInHalfADay )); + } + + /** + * Get hash of given string. Uses BCrypt. The salt is taken from system.security.default_hash + * + * @param string $data string to hash + * + * @return string hashed value of $data, cut to 10 characters + */ + private static function hash($data) + { + $hash = password_hash($data, PASSWORD_BCRYPT, ['salt' => self::getGrav()['config']->get('system.security.default_hash')]); + $hash = substr($hash, -12, 10); + return $hash; + } + + /** + * Creates a hashed nonce tied to the passed action. Tied to the current user and time. The nonce for a given + * action is the same for 12 hours. + * + * @param string $action the action the nonce is tied to (e.g. save-user-admin or move-page-homepage) + * @param bool $plusOneTick if true, generates the token for the next tick (the next 12 hours) + * + * @return string the nonce, a 10 characters string + */ + public static function getNonce($action, $plusOneTick = false) + { + $nonce = self::hash(self::generateNonceString($action, $plusOneTick)); + return $nonce; + } + + /** + * Verify the passed nonce for the give action + * + * @param string $nonce the nonce to verify + * @param string $action the action to verify the nonce to + * + * @return boolean verified or not + */ + public static function verifyNonce($nonce, $action) + { + if ($nonce == self::getNonce($action)) { + return true; + } + + $plusOneTick = true; + if ($nonce == self::getNonce($action, $plusOneTick)) { + return true; + } + + return false; + } }