1<?php 2/** 3 * XOOPS security handler 4 * 5 * You may not change or alter any portion of this comment or credits 6 * of supporting developers from this source code or any supporting source code 7 * which is considered copyrighted (c) material of the original comment or credit authors. 8 * This program is distributed in the hope that it will be useful, 9 * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 11 * 12 * @author Kazumi Ono <onokazu@xoops.org> 13 * @author Jan Pedersen <mithrandir@xoops.org> 14 * @author John Neill <catzwolf@xoops.org> 15 * @copyright (c) 2000-2016 XOOPS Project (www.xoops.org) 16 * @license GNU GPL 2 (http://www.gnu.org/licenses/gpl-2.0.html) 17 * @package kernel 18 * @since 2.0.0 19 */ 20 21defined('XOOPS_ROOT_PATH') || exit('Restricted access'); 22 23/** 24 * Class XoopsSecurity 25 */ 26class XoopsSecurity 27{ 28 public $errors = array(); 29 30 /** 31 * Check if there is a valid token in $_REQUEST[$name . '_REQUEST'] - can be expanded for more wide use, later (Mith) 32 * 33 * @param bool $clearIfValid whether to clear the token after validation 34 * @param string|false $token token to validate 35 * @param string $name name of session variable 36 * 37 * @return bool 38 */ 39 public function check($clearIfValid = true, $token = false, $name = 'XOOPS_TOKEN') 40 { 41 return $this->validateToken($token, $clearIfValid, $name); 42 } 43 44 /** 45 * Create a token in the user's session 46 * 47 * @param int|string $timeout time in seconds the token should be valid 48 * @param string $name name of session variable 49 * 50 * @return string token value 51 */ 52 public function createToken($timeout = 0, $name = 'XOOPS_TOKEN') 53 { 54 $this->garbageCollection($name); 55 if ($timeout == 0) { 56 $expire = @ini_get('session.gc_maxlifetime'); 57 $timeout = ($expire > 0) ? $expire : 900; 58 } 59 $token_id = md5(uniqid(mt_rand(), true)); 60 // save token data on the server 61 if (!isset($_SESSION[$name . '_SESSION'])) { 62 $_SESSION[$name . '_SESSION'] = array(); 63 } 64 $token_data = array( 65 'id' => $token_id, 66 'expire' => time() + (int)$timeout); 67 $_SESSION[$name . '_SESSION'][] = $token_data; 68 69 return md5($token_id . $_SERVER['HTTP_USER_AGENT'] . XOOPS_DB_PREFIX); 70 } 71 72 /** 73 * Check if a token is valid. If no token is specified, $_REQUEST[$name . '_REQUEST'] is checked 74 * 75 * @param string|false $token token to validate 76 * @param bool $clearIfValid whether to clear the token value if valid 77 * @param string $name session name to validate 78 * 79 * @return bool 80 */ 81 public function validateToken($token = false, $clearIfValid = true, $name = 'XOOPS_TOKEN') 82 { 83 global $xoopsLogger; 84 $token = ($token !== false) ? $token : (isset($_REQUEST[$name . '_REQUEST']) ? $_REQUEST[$name . '_REQUEST'] : ''); 85 if (empty($token) || empty($_SESSION[$name . '_SESSION'])) { 86 $xoopsLogger->addExtra('Token Validation', 'No valid token found in request/session'); 87 88 return false; 89 } 90 $validFound = false; 91 $token_data = &$_SESSION[$name . '_SESSION']; 92 foreach (array_keys($token_data) as $i) { 93 if ($token === md5($token_data[$i]['id'] . $_SERVER['HTTP_USER_AGENT'] . XOOPS_DB_PREFIX)) { 94 if ($this->filterToken($token_data[$i])) { 95 if ($clearIfValid) { 96 // token should be valid once, so clear it once validated 97 unset($token_data[$i]); 98 } 99 $xoopsLogger->addExtra('Token Validation', 'Valid token found'); 100 $validFound = true; 101 } else { 102 $str = 'Valid token expired'; 103 $this->setErrors($str); 104 $xoopsLogger->addExtra('Token Validation', $str); 105 } 106 } 107 } 108 if (!$validFound && !isset($str)) { 109 $str = 'No valid token found'; 110 $this->setErrors($str); 111 $xoopsLogger->addExtra('Token Validation', $str); 112 } 113 $this->garbageCollection($name); 114 115 return $validFound; 116 } 117 118 /** 119 * Clear all token values from user's session 120 * 121 * @param string $name session name 122 * 123 * @return void 124 */ 125 public function clearTokens($name = 'XOOPS_TOKEN') 126 { 127 $_SESSION[$name . '_SESSION'] = array(); 128 } 129 130 /** 131 * Check whether a token value is expired or not 132 * 133 * @param string $token token 134 * 135 * @return bool 136 */ 137 public function filterToken($token) 138 { 139 return (!empty($token['expire']) && $token['expire'] >= time()); 140 } 141 142 /** 143 * Perform garbage collection, clearing expired tokens 144 * 145 * @param string $name session name 146 * 147 * @return void 148 */ 149 public function garbageCollection($name = 'XOOPS_TOKEN') 150 { 151 $sessionName = $name . '_SESSION'; 152 if (!empty($_SESSION[$sessionName]) && is_array($_SESSION[$sessionName])) { 153 $_SESSION[$sessionName] = array_filter($_SESSION[$sessionName], array($this, 'filterToken')); 154 } 155 } 156 157 /** 158 * Check the user agent's HTTP REFERER against XOOPS_URL 159 * 160 * @param int $docheck 0 to not check the referer (used with XML-RPC), 1 to actively check it 161 * 162 * @return bool 163 */ 164 public function checkReferer($docheck = 1) 165 { 166 $ref = xoops_getenv('HTTP_REFERER'); 167 if ($docheck == 0) { 168 return true; 169 } 170 if ($ref == '') { 171 return false; 172 } 173 return !(strpos($ref, XOOPS_URL) !== 0); 174 } 175 176 /** 177 * Check superglobals for contamination 178 * 179 * @return void 180 **/ 181 public function checkSuperglobals() 182 { 183 foreach (array( 184 'GLOBALS', 185 '_SESSION', 186 'HTTP_SESSION_VARS', 187 '_GET', 188 'HTTP_GET_VARS', 189 '_POST', 190 'HTTP_POST_VARS', 191 '_COOKIE', 192 'HTTP_COOKIE_VARS', 193 '_REQUEST', 194 '_SERVER', 195 'HTTP_SERVER_VARS', 196 '_ENV', 197 'HTTP_ENV_VARS', 198 '_FILES', 199 'HTTP_POST_FILES', 200 'xoopsDB', 201 'xoopsUser', 202 'xoopsUserId', 203 'xoopsUserGroups', 204 'xoopsUserIsAdmin', 205 'xoopsConfig', 206 'xoopsOption', 207 'xoopsModule', 208 'xoopsModuleConfig', 209 'xoopsRequestUri') as $bad_global) { 210 if (isset($_REQUEST[$bad_global])) { 211 header('Location: ' . XOOPS_URL . '/'); 212 exit(); 213 } 214 } 215 } 216 217 /** 218 * Check if visitor's IP address is banned 219 * Should be changed to return bool and let the action be up to the calling script 220 * 221 * @return void 222 */ 223 public function checkBadips() 224 { 225 global $xoopsConfig; 226 if ($xoopsConfig['enable_badips'] == 1 && isset($_SERVER['REMOTE_ADDR']) && $_SERVER['REMOTE_ADDR'] != '') { 227 foreach ($xoopsConfig['bad_ips'] as $bi) { 228 if (!empty($bi) && preg_match('/' . $bi . '/', $_SERVER['REMOTE_ADDR'])) { 229 exit(); 230 } 231 } 232 } 233 unset($bi, $bad_ips, $xoopsConfig['badips']); 234 } 235 236 /** 237 * Get the HTML code for a XoopsFormHiddenToken object - used in forms that do not use XoopsForm elements 238 * 239 * @param string $name session token name 240 * 241 * @return string 242 */ 243 public function getTokenHTML($name = 'XOOPS_TOKEN') 244 { 245 require_once XOOPS_ROOT_PATH . '/class/xoopsformloader.php'; 246 $token = new XoopsFormHiddenToken($name); 247 248 return $token->render(); 249 } 250 251 /** 252 * Add an error 253 * 254 * @param string $error message 255 * 256 * @return void 257 */ 258 public function setErrors($error) 259 { 260 $this->errors[] = trim($error); 261 } 262 263 /** 264 * Get generated errors 265 * 266 * @param bool $ashtml Format using HTML? 267 * 268 * @return array|string Array of array messages OR HTML string 269 */ 270 public function &getErrors($ashtml = false) 271 { 272 if (!$ashtml) { 273 return $this->errors; 274 } else { 275 $ret = ''; 276 if (count($this->errors) > 0) { 277 foreach ($this->errors as $error) { 278 $ret .= $error . '<br>'; 279 } 280 } 281 282 return $ret; 283 } 284 } 285} 286