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


Ignore:
Timestamp:
Nov 15, 2014 9:34:39 PM (10 years ago)
Author:
anonymous
Message:

Many auth and crypto changes; various other bugfixes while working on pulso.

File:
1 edited

Legend:

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

    r499 r500  
    8080        'ssl_enabled' => false,
    8181
     82        // Use CSRF tokens.
     83        'csrf_token_enabled' => true,
     84        '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..
     86
     87        // HMAC signing method
     88        'signing_method' => 'sha512+base64',
     89
    8290        // Character set for page output. Used in the Content-Type header and the HTML <meta content-type> tag.
    8391        'character_set' => 'utf-8',
     
    450458
    451459        if ('' == trim($message)) {
    452             $this->logMsg(sprintf('Raised message is an empty string.', __FUNCTION__), LOG_NOTICE, __FILE__, __LINE__);
     460            $this->logMsg(sprintf('Raised message is an empty string.', null), LOG_NOTICE, __FILE__, __LINE__);
    453461            return false;
    454462        }
     
    944952    }
    945953
    946     /*
    947     * Return a URL with a version number attached. This is useful for overriding network caches ("cache buster") for sourced media, e.g., /style.css?812763482
    948     *
    949     * @access   public
    950     * @param    string  $url    URL to media (e.g., /foo.js)
    951     * @return   string          URL with cache-busting version appended (/foo.js?v=1234567890)
    952     * @author   Quinn Comendant <quinn@strangecode.com>
    953     * @version  1.0
    954     * @since    03 Sep 2014 22:40:24
    955     */
    956     public function cacheBustURL($url)
    957     {
    958         // Get the first delimiter that is needed in the url.
    959         $delim = mb_strpos($url, '?') !== false ? ini_get('arg_separator.output') : '?';
    960         $v = crc32($this->getParam('codebase_version') . '|' . $this->getParam('site_version'));
    961         return sprintf('%s%sv=%s', $url, $delim, $v);
    962     }
    963 
    964954    /**
    965955     * Prints a hidden form element with the PHPSESSID when cookies are not used, as well
     
    971961     *                                      array('key1'=>'value', key2'='value')  <-- to set keys to default values if not present in form data.
    972962     *                                      false  <-- To not carry any queries. If URL already has queries those will be retained.
    973      */
    974     public function printHiddenSession($carry_args=null)
     963     * @param   bool    $include_csrf_token     Set to true to include the csrf_token in the form. Only use this for forms with action="post" to prevent the token from being revealed in the URL.
     964     */
     965    public function printHiddenSession($carry_args=null, $include_csrf_token=false)
    975966    {
    976967        if (!$this->running) {
     
    10231014            printf('<input type="hidden" name="%s" value="%s" />', session_name(), session_id());
    10241015        }
     1016
     1017        // Include the csrf_token in the form.
     1018        // This token can be validated upon form submission with $app->verifyCSRFToken() or $app->requireValidCSRFToken()
     1019        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        }
     1022    }
     1023
     1024    /*
     1025    * Return a URL with a version number attached. This is useful for overriding network caches ("cache buster") for sourced media, e.g., /style.css?812763482
     1026    *
     1027    * @access   public
     1028    * @param    string  $url    URL to media (e.g., /foo.js)
     1029    * @return   string          URL with cache-busting version appended (/foo.js?v=1234567890)
     1030    * @author   Quinn Comendant <quinn@strangecode.com>
     1031    * @version  1.0
     1032    * @since    03 Sep 2014 22:40:24
     1033    */
     1034    public function cacheBustURL($url)
     1035    {
     1036        // Get the first delimiter that is needed in the url.
     1037        $delim = mb_strpos($url, '?') !== false ? ini_get('arg_separator.output') : '?';
     1038        $v = crc32($this->getParam('codebase_version') . '|' . $this->getParam('site_version'));
     1039        return sprintf('%s%sv=%s', $url, $delim, $v);
     1040    }
     1041
     1042    /*
     1043    * Generate a csrf_token, saving it to the session and returning its value.
     1044    * 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.
     1059    *
     1060    * @access   public
     1061    * @return   string The current csrf_token
     1062    * @author   Quinn Comendant <quinn@strangecode.com>
     1063    * @version  1.0
     1064    * @since    15 Nov 2014 17:57:17
     1065    */
     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();
     1078        }
     1079        // Current token is not expired; return it.
     1080        $return = end($_SESSION['_app'][$this->_ns]['csrf_tokens']);
     1081        $app =& App::getInstance();
     1082        return $return;
     1083    }
     1084
     1085    /*
     1086    * Compares the given csrf_token with the current or previous one saved in the session.
     1087    *
     1088    * @access   public
     1089    * @param    string  $csrf_token     The token to compare with the session token.
     1090    * @return   bool    True if the tokens match, false otherwise.
     1091    * @author   Quinn Comendant <quinn@strangecode.com>
     1092    * @version  1.0
     1093    * @since    15 Nov 2014 18:06:55
     1094    */
     1095    public function verifyCSRFToken($csrf_token)
     1096    {
     1097        $app =& App::getInstance();
     1098
     1099        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__);
     1117        return true;
     1118    }
     1119
     1120    /*
     1121    * Bounce user if they submit a token that doesn't match the one saved in the session.
     1122    * Because this function calls dieURL() it must be called before any other HTTP header output.
     1123    *
     1124    * @access   public
     1125    * @param    string  $csrf_token The token to compare with the session token.
     1126    * @param    string  $message    Optional message to display to the user (otherwise default message will display). Set to an empty string to display no message.
     1127    * @param    int    $type    The type of message: MSG_NOTICE,
     1128    *                           MSG_SUCCESS, MSG_WARNING, or MSG_ERR.
     1129    * @param    string $file    __FILE__.
     1130    * @param    string $line    __LINE__.
     1131    * @return   void
     1132    * @author   Quinn Comendant <quinn@strangecode.com>
     1133    * @version  1.0
     1134    * @since    15 Nov 2014 18:10:17
     1135    */
     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();
     1144        }
    10251145    }
    10261146
Note: See TracChangeset for help on using the changeset viewer.