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