Issues (2473)

Branch: master

Security Analysis    no vulnerabilities found

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

engine/classes/Elgg/ActionsService.php (1 issue)

Severity

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
namespace Elgg;
3
4
/**
5
 * WARNING: API IN FLUX. DO NOT USE DIRECTLY.
6
 *
7
 * Use the elgg_* versions instead.
8
 *
9
 * @access private
10
 * 
11
 * @package    Elgg.Core
12
 * @subpackage Actions
13
 * @since      1.9.0
14
 */
15
class ActionsService {
16
	
17
	/**
18
	 * Registered actions storage
19
	 * @var array
20
	 */
21
	private $actions = array();
22
23
	/** 
24
	 * The current action being processed
25
	 * @var string 
26
	 */
27
	private $currentAction = null;
28
	
29
	/**
30
	 * @see action
31
	 * @access private
32
	 */
33
	public function execute($action, $forwarder = "") {
34
		$action = rtrim($action, '/');
35
		$this->currentAction = $action;
36
	
37
		// @todo REMOVE THESE ONCE #1509 IS IN PLACE.
38
		// Allow users to disable plugins without a token in order to
39
		// remove plugins that are incompatible.
40
		// Login and logout are for convenience.
41
		// file/download (see #2010)
42
		$exceptions = array(
43
			'admin/plugins/disable',
44
			'logout',
45
			'file/download',
46
		);
47
	
48
		if (!in_array($action, $exceptions)) {
49
			// All actions require a token.
50
			$this->gatekeeper($action);
51
		}
52
	
53
		$forwarder = str_replace(_elgg_services()->config->getSiteUrl(), "", $forwarder);
54
		$forwarder = str_replace("http://", "", $forwarder);
55
		$forwarder = str_replace("@", "", $forwarder);
56
		if (substr($forwarder, 0, 1) == "/") {
57
			$forwarder = substr($forwarder, 1);
58
		}
59
	
60
		if (!isset($this->actions[$action])) {
61
			register_error(_elgg_services()->translator->translate('actionundefined', array($action)));
62
		} elseif (!_elgg_services()->session->isAdminLoggedIn() && ($this->actions[$action]['access'] === 'admin')) {
63
			register_error(_elgg_services()->translator->translate('actionunauthorized'));
64
		} elseif (!_elgg_services()->session->isLoggedIn() && ($this->actions[$action]['access'] !== 'public')) {
65
			register_error(_elgg_services()->translator->translate('actionloggedout'));
66
		} else {
67
			// To quietly cancel the action file, return a falsey value in the "action" hook.
68
			if (_elgg_services()->hooks->trigger('action', $action, null, true)) {
69
				if (is_file($this->actions[$action]['file']) && is_readable($this->actions[$action]['file'])) {
70
					self::includeFile($this->actions[$action]['file']);
71
				} else {
72
					register_error(_elgg_services()->translator->translate('actionnotfound', array($action)));
73
				}
74
			}
75
		}
76
	
77
		$forwarder = empty($forwarder) ? REFERER : $forwarder;
78
		forward($forwarder);
79
	}
80
81
	/**
82
	 * Include an action file with isolated scope
83
	 *
84
	 * @param string $file File to be interpreted by PHP
85
	 * @return void
86
	 */
87
	protected static function includeFile($file) {
88
		include $file;
89
	}
90
	
91
	/**
92
	 * @see elgg_register_action
93
	 * @access private
94
	 */
95 2
	public function register($action, $filename = "", $access = 'logged_in') {
96
		// plugins are encouraged to call actions with a trailing / to prevent 301
97
		// redirects but we store the actions without it
98 2
		$action = rtrim($action, '/');
99
	
100 2
		if (empty($filename)) {
101
			
102
			$path = _elgg_services()->config->get('path');
103
			if ($path === null) {
104
				$path = "";
105
			}
106
	
107
			$filename = $path . "actions/" . $action . ".php";
108
		}
109
	
110 2
		$this->actions[$action] = array(
111 2
			'file' => $filename,
112 2
			'access' => $access,
113
		);
114 2
		return true;
115
	}
116
	
117
	/**
118
	 * @see elgg_unregister_action
119
	 * @access private
120
	 */
121 1
	public function unregister($action) {
122 1
		if (isset($this->actions[$action])) {
123 1
			unset($this->actions[$action]);
124 1
			return true;
125
		} else {
126 1
			return false;
127
		}
128
	}
129
130
	/**
131
	 * @see validate_action_token
132
	 * @access private
133
	 */
134
	public function validateActionToken($visible_errors = true, $token = null, $ts = null) {
135
		if (!$token) {
136
			$token = get_input('__elgg_token');
137
		}
138
	
139
		if (!$ts) {
140
			$ts = get_input('__elgg_ts');
141
		}
142
143
		$session_id = _elgg_services()->session->getId();
144
	
145
		if (($token) && ($ts) && ($session_id)) {
146
			if ($this->validateTokenOwnership($token, $ts)) {
147
				if ($this->validateTokenTimestamp($ts)) {
148
					// We have already got this far, so unless anything
149
					// else says something to the contrary we assume we're ok
150
					$returnval = _elgg_services()->hooks->trigger('action_gatekeeper:permissions:check', 'all', array(
151
						'token' => $token,
152
						'time' => $ts
153
					), true);
154
155
					if ($returnval) {
156
						return true;
157
					} else if ($visible_errors) {
158
						register_error(_elgg_services()->translator->translate('actiongatekeeper:pluginprevents'));
159
					}
160 View Code Duplication
				} else if ($visible_errors) {
161
					// this is necessary because of #5133
162
					if (elgg_is_xhr()) {
163
						register_error(_elgg_services()->translator->translate('js:security:token_refresh_failed', array(_elgg_services()->config->getSiteUrl())));
164
					} else {
165
						register_error(_elgg_services()->translator->translate('actiongatekeeper:timeerror'));
166
					}
167
				}
168 View Code Duplication
			} else if ($visible_errors) {
169
				// this is necessary because of #5133
170
				if (elgg_is_xhr()) {
171
					register_error(_elgg_services()->translator->translate('js:security:token_refresh_failed', array(_elgg_services()->config->getSiteUrl())));
172
				} else {
173
					register_error(_elgg_services()->translator->translate('actiongatekeeper:tokeninvalid'));
174
				}
175
			}
176
		} else {
177
			$req = _elgg_services()->request;
178
			$length = $req->server->get('CONTENT_LENGTH');
179
			$post_count = count($req->request);
180
			if ($length && $post_count < 1) {
181
				// The size of $_POST or uploaded file has exceed the size limit
182
				$error_msg = _elgg_services()->hooks->trigger('action_gatekeeper:upload_exceeded_msg', 'all', array(
183
					'post_size' => $length,
184
					'visible_errors' => $visible_errors,
185
				), _elgg_services()->translator->translate('actiongatekeeper:uploadexceeded'));
186
			} else {
187
				$error_msg = _elgg_services()->translator->translate('actiongatekeeper:missingfields');
188
			}
189
			if ($visible_errors) {
190
				register_error($error_msg);
191
			}
192
		}
193
194
		return false;
195
	}
196
197
	/**
198
	 * Is the token timestamp within acceptable range?
199
	 * 
200
	 * @param int $ts timestamp from the CSRF token
201
	 * 
202
	 * @return bool
203
	 */
204
	protected function validateTokenTimestamp($ts) {
205
		$timeout = $this->getActionTokenTimeout();
206
		$now = time();
207
		return ($timeout == 0 || ($ts > $now - $timeout) && ($ts < $now + $timeout));
208
	}
209
210
	/**
211
	 * @see \Elgg\ActionsService::validateActionToken
212
	 * @access private
213
	 * @since 1.9.0
214
	 * @return int number of seconds that action token is valid
215
	 */
216
	public function getActionTokenTimeout() {
217
		if (($timeout = _elgg_services()->config->get('action_token_timeout')) === null) {
218
			// default to 2 hours
219
			$timeout = 2;
220
		}
221
		$hour = 60 * 60;
222
		return (int)((float)$timeout * $hour);
223
	}
224
225
	/**
226
	 * @see action_gatekeeper
227
	 * @access private
228
	 */
229
	public function gatekeeper($action) {
230
		if ($action === 'login') {
231
			if ($this->validateActionToken(false)) {
232
				return true;
233
			}
234
235
			$token = get_input('__elgg_token');
236
			$ts = (int)get_input('__elgg_ts');
237
			if ($token && $this->validateTokenTimestamp($ts)) {
238
				// The tokens are present and the time looks valid: this is probably a mismatch due to the 
239
				// login form being on a different domain.
240
				register_error(_elgg_services()->translator->translate('actiongatekeeper:crosssitelogin'));
241
242
				forward('login', 'csrf');
243
			}
244
245
			// let the validator send an appropriate msg
246
			$this->validateActionToken();
247
248
		} else if ($this->validateActionToken()) {
249
			return true;
250
		}
251
252
		forward(REFERER, 'csrf');
253
	}
254
255
	/**
256
	 * Was the given token generated for the session defined by session_token?
257
	 *
258
	 * @param string $token         CSRF token
259
	 * @param int    $timestamp     Unix time
260
	 * @param string $session_token Session-specific token
261
	 *
262
	 * @return bool
263
	 * @access private
264
	 */
265
	public function validateTokenOwnership($token, $timestamp, $session_token = '') {
266
		$required_token = $this->generateActionToken($timestamp, $session_token);
267
268
		return _elgg_services()->crypto->areEqual($token, $required_token);
0 ignored issues
show
It seems like $required_token defined by $this->generateActionTok...estamp, $session_token) on line 266 can also be of type false; however, ElggCrypto::areEqual() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
269
	}
270
	
271
	/**
272
	 * Generate a token from a session token (specifying the user), the timestamp, and the site key.
273
	 *
274
	 * @see generate_action_token
275
	 *
276
	 * @param int    $timestamp     Unix timestamp
277
	 * @param string $session_token Session-specific token
278
	 *
279
	 * @return string
280
	 * @access private
281
	 */
282
	public function generateActionToken($timestamp, $session_token = '') {
283
		if (!$session_token) {
284
			$session_token = elgg_get_session()->get('__elgg_session');
285
			if (!$session_token) {
286
				return false;
287
			}
288
		}
289
290
		return _elgg_services()->crypto->getHmac([(int)$timestamp, $session_token], 'md5')
291
			->getToken();
292
	}
293
	
294
	/**
295
	 * @see elgg_action_exists
296
	 * @access private
297
	 */
298 3
	public function exists($action) {
299 3
		return (isset($this->actions[$action]) && file_exists($this->actions[$action]['file']));
300
	}
301
	
302
	/**
303
	 * @see ajax_forward_hook
304
	 * @access private
305
	 */
306
	public function ajaxForwardHook($hook, $reason, $return, $params) {
307
		if (elgg_is_xhr()) {
308
			// always pass the full structure to avoid boilerplate JS code.
309
			$params = array_merge($params, array(
310
				'output' => '',
311
				'status' => 0,
312
				'system_messages' => array(
313
					'error' => array(),
314
					'success' => array()
315
				)
316
			));
317
	
318
			//grab any data echo'd in the action
319
			$output = ob_get_clean();
320
	
321
			//Avoid double-encoding in case data is json
322
			$json = json_decode($output);
323
			if (isset($json)) {
324
				$params['output'] = $json;
325
			} else {
326
				$params['output'] = $output;
327
			}
328
	
329
			//Grab any system messages so we can inject them via ajax too
330
			$system_messages = _elgg_services()->systemMessages->dumpRegister();
331
	
332
			if (isset($system_messages['success'])) {
333
				$params['system_messages']['success'] = $system_messages['success'];
334
			}
335
	
336
			if (isset($system_messages['error'])) {
337
				$params['system_messages']['error'] = $system_messages['error'];
338
				$params['status'] = -1;
339
			}
340
341
			if ($reason == 'walled_garden') {
342
				$reason = '403';
343
			}
344
			$httpCodes = array(
345
				'400' => 'Bad Request',
346
				'401' => 'Unauthorized',
347
				'403' => 'Forbidden',
348
				'404' => 'Not Found',
349
				'407' => 'Proxy Authentication Required',
350
				'500' => 'Internal Server Error',
351
				'503' => 'Service Unavailable',
352
			);
353
354
			if (isset($httpCodes[$reason])) {
355
				header("HTTP/1.1 $reason {$httpCodes[$reason]}", true);
356
			}
357
358
			$context = array('action' => $this->currentAction);
359
			$params = _elgg_services()->hooks->trigger('output', 'ajax', $context, $params);
360
	
361
			// Check the requester can accept JSON responses, if not fall back to
362
			// returning JSON in a plain-text response.  Some libraries request
363
			// JSON in an invisible iframe which they then read from the iframe,
364
			// however some browsers will not accept the JSON MIME type.
365
			$http_accept = _elgg_services()->request->server->get('HTTP_ACCEPT');
366
			if (stripos($http_accept, 'application/json') === false) {
367
				header("Content-type: text/plain;charset=utf-8");
368
			} else {
369
				header("Content-type: application/json;charset=utf-8");
370
			}
371
	
372
			echo json_encode($params);
373
			exit;
374
		}
375
	}
376
	
377
	/**
378
	 * @see ajax_action_hook
379
	 * @access private
380
	 */
381
	public function ajaxActionHook() {
382
		if (elgg_is_xhr()) {
383
			ob_start();
384
		}
385
	}
386
387
	/**
388
	 * Get all actions
389
	 * 
390
	 * @return array
391
	 */
392
	public function getAllActions() {
393
		return $this->actions;
394
	}
395
}
396
397