Changeset 500 for trunk/lib/App.inc.php
- Timestamp:
- Nov 15, 2014 9:34:39 PM (10 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/lib/App.inc.php
r499 r500 80 80 'ssl_enabled' => false, 81 81 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 82 90 // Character set for page output. Used in the Content-Type header and the HTML <meta content-type> tag. 83 91 'character_set' => 'utf-8', … … 450 458 451 459 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__); 453 461 return false; 454 462 } … … 944 952 } 945 953 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?812763482948 *949 * @access public950 * @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.0954 * @since 03 Sep 2014 22:40:24955 */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 964 954 /** 965 955 * Prints a hidden form element with the PHPSESSID when cookies are not used, as well … … 971 961 * array('key1'=>'value', key2'='value') <-- to set keys to default values if not present in form data. 972 962 * 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) 975 966 { 976 967 if (!$this->running) { … … 1023 1014 printf('<input type="hidden" name="%s" value="%s" />', session_name(), session_id()); 1024 1015 } 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 } 1025 1145 } 1026 1146
Note: See TracChangeset
for help on using the changeset viewer.