Changeset 501 for trunk/lib/App.inc.php


Ignore:
Timestamp:
Nov 16, 2014 11:07:01 AM (10 years ago)
Author:
anonymous
Message:

Optimizing auth and csrf token.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/lib/App.inc.php

    r500 r501  
    8080        'ssl_enabled' => false,
    8181
    82         // Use CSRF tokens.
     82        // Use CSRF tokens. See notes in the getCSRFToken() method.
    8383        'csrf_token_enabled' => true,
     84        // Form tokens will expire after this duration, in seconds.
     85        'csrf_token_timeout' => 259200, // 259200 seconds = 3 days.
    8486        'csrf_token_name' => 'csrf_token',
    85         'csrf_token_timeout' => 86400, // In seconds. This causes form tokens to be unusable after this duration. This might only cause problems when opening forms in multiple tabs left open beyond the timeout duration. But usually their session will timeout first, and they'll receive new tokens when they load the form again..
    8687
    8788        // HMAC signing method
     
    10181019        // This token can be validated upon form submission with $app->verifyCSRFToken() or $app->requireValidCSRFToken()
    10191020        if ($this->getParam('csrf_token_enabled') && $include_csrf_token) {
    1020             printf('<input type="hidden" name="%s" value="%s" />', $this->getParam('csrf_token_name'), $this->recycleCSRFToken());
     1021            printf('<input type="hidden" name="%s" value="%s" />', $this->getParam('csrf_token_name'), $this->getCSRFToken());
    10211022        }
    10221023    }
     
    10411042
    10421043    /*
    1043     * Generate a csrf_token, saving it to the session and returning its value.
     1044    * Generate a csrf_token if it doesn't exist or is expired, save it to the session and return its value.
     1045    * Otherwise just return the current token.
     1046    * Details on the synchronizer token pattern:
    10441047    * https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet#General_Recommendation:_Synchronizer_Token_Pattern
    1045     * @access   public
    1046     * @return   string  The new csrf_token.
    1047     * @author   Quinn Comendant <quinn@strangecode.com>
    1048     * @version  1.0
    1049     * @since    15 Nov 2014 17:53:51
    1050     */
    1051     public function generateCSRFToken()
    1052     {
    1053         return $_SESSION['_app'][$this->_ns]['csrf_tokens'][] = addSignature(time(), null, 64);
    1054     }
    1055 
    1056     /*
    1057     * Update the csrf_token to a new value if it hasn't been set yet or has expired.
    1058     * Save the previous csrf_token in the session to ensure continuity of currently open sessions.
    10591048    *
    10601049    * @access   public
    1061     * @return   string The current csrf_token
     1050    * @return   string The new or current csrf_token
    10621051    * @author   Quinn Comendant <quinn@strangecode.com>
    10631052    * @version  1.0
    10641053    * @since    15 Nov 2014 17:57:17
    10651054    */
    1066     public function recycleCSRFToken()
    1067     {
    1068         if (!isset($_SESSION['_app'][$this->_ns]['csrf_tokens'])) {
    1069             // No token yet; generate one and return it.
    1070             $_SESSION['_app'][$this->_ns]['csrf_tokens'] = array();
    1071             $return = $this->generateCSRFToken();
    1072         }
    1073         if (removeSignature(end($_SESSION['_app'][$this->_ns]['csrf_tokens'])) + $this->getParam('csrf_token_timeout') < time()) {
    1074             // Newest token is expired; prune array of tokens and generate new token.
    1075             // We'll save the 10-most-recent tokens. This allows the user to submit up to 5 forms saved in previously opened tabs with expired tokens (loading a form will prune one token, and submitting a form will prune one token, thus 10 = 5).
    1076             $_SESSION['_app'][$this->_ns]['csrf_tokens'] = array_slice($_SESSION['_app'][$this->_ns]['csrf_tokens'], -10, 10);
    1077             $return = $this->generateCSRFToken();
     1055    public function getCSRFToken()
     1056    {
     1057        if (!isset($_SESSION['_app'][$this->_ns]['csrf_token']) || (removeSignature($_SESSION['_app'][$this->_ns]['csrf_token']) + $this->getParam('csrf_token_timeout') < time())) {
     1058            // No token, or token is expired; generate one and return it.
     1059            return $_SESSION['_app'][$this->_ns]['csrf_token'] = addSignature(time(), null, 64);
    10781060        }
    10791061        // Current token is not expired; return it.
    1080         $return = end($_SESSION['_app'][$this->_ns]['csrf_tokens']);
    1081         $app =& App::getInstance();
    1082         return $return;
     1062        return $_SESSION['_app'][$this->_ns]['csrf_token'];
    10831063    }
    10841064
     
    10931073    * @since    15 Nov 2014 18:06:55
    10941074    */
    1095     public function verifyCSRFToken($csrf_token)
    1096     {
    1097         $app =& App::getInstance();
     1075    public function verifyCSRFToken($user_submitted_csrf_token)
     1076    {
    10981077
    10991078        if (!$this->getParam('csrf_token_enabled')) {
    1100             $app->logMsg(sprintf('%s method called, but csrf_token_enabled=false', __FUNCTION__), LOG_ERR, __FILE__, __LINE__);
    1101             return false;
    1102         }
    1103         if ('' == trim($csrf_token)) {
    1104             $app->logMsg(sprintf('Empty string failed CSRF verification.', null), LOG_NOTICE, __FILE__, __LINE__);
    1105             return false;
    1106         }
    1107         if (!verifySignature($csrf_token, null, 64)) {
    1108             $app->logMsg(sprintf('Input failed CSRF verification (invalid signature in %s).', $csrf_token), LOG_WARNING, __FILE__, __LINE__);
    1109             return false;
    1110         }
    1111         $this->recycleCSRFToken();
    1112         if (!in_array($csrf_token, $_SESSION['_app'][$this->_ns]['csrf_tokens'])) {
    1113             $app->logMsg(sprintf('Input failed CSRF verification (%s not in %s).', $csrf_token, getDump($_SESSION['_app'][$this->_ns]['csrf_tokens'])), LOG_WARNING, __FILE__, __LINE__);
    1114             return false;
    1115         }
    1116         // $app->logMsg(sprintf('Verified token %s is in %s', $csrf_token, getDump($_SESSION['_app'][$this->_ns]['csrf_tokens'])), LOG_DEBUG, __FILE__, __LINE__);
     1079            $this->logMsg(sprintf('%s method called, but csrf_token_enabled=false', __FUNCTION__), LOG_ERR, __FILE__, __LINE__);
     1080            return true;
     1081        }
     1082        if ('' == trim($user_submitted_csrf_token)) {
     1083            $this->logMsg(sprintf('Empty string failed CSRF verification.', null), LOG_NOTICE, __FILE__, __LINE__);
     1084            return false;
     1085        }
     1086        if (!verifySignature($user_submitted_csrf_token, null, 64)) {
     1087            $this->logMsg(sprintf('Input failed CSRF verification (invalid signature in %s).', $user_submitted_csrf_token), LOG_WARNING, __FILE__, __LINE__);
     1088            return false;
     1089        }
     1090        $csrf_token = $this->getCSRFToken();
     1091        if ($user_submitted_csrf_token != $csrf_token) {
     1092            $this->logMsg(sprintf('Input failed CSRF verification (%s not in %s).', $user_submitted_csrf_token, $csrf_token), LOG_WARNING, __FILE__, __LINE__);
     1093            return false;
     1094        }
     1095        $this->logMsg(sprintf('Verified CSRF token %s is in %s', $user_submitted_csrf_token, $csrf_token), LOG_DEBUG, __FILE__, __LINE__);
    11171096        return true;
    11181097    }
     
    11231102    *
    11241103    * @access   public
    1125     * @param    string  $csrf_token The token to compare with the session token.
     1104    * @param    string  $user_submitted_csrf_token The user-submitted token to compare with the session token.
    11261105    * @param    string  $message    Optional message to display to the user (otherwise default message will display). Set to an empty string to display no message.
    11271106    * @param    int    $type    The type of message: MSG_NOTICE,
     
    11341113    * @since    15 Nov 2014 18:10:17
    11351114    */
    1136     public function requireValidCSRFToken($csrf_token, $message=null, $type=MSG_NOTICE, $file=null, $line=null)
    1137     {
    1138         $app =& App::getInstance();
    1139 
    1140         if (!$this->verifyCSRFToken($csrf_token)) {
    1141             $message = isset($message) ? $message : _("Something went wrong; please try again.");
    1142             $app->raiseMsg($message, $type, $file, $line);
    1143             $app->dieBoomerangURL();
     1115    public function requireValidCSRFToken($message=null, $type=MSG_NOTICE, $file=null, $line=null)
     1116    {
     1117        if (!$this->verifyCSRFToken(getFormData($this->getParam('csrf_token_name')))) {
     1118            $message = isset($message) ? $message : _("Sorry, the form token expired. Please try again.");
     1119            $this->raiseMsg($message, $type, $file, $line);
     1120            $this->dieBoomerangURL();
    11441121        }
    11451122    }
Note: See TracChangeset for help on using the changeset viewer.