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/UpgradeService.php (5 issues)

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
 * Upgrade service for Elgg
6
 *
7
 * This is a straight port of the procedural code used for upgrading before
8
 * Elgg 1.9.
9
 *
10
 * @access private
11
 *
12
 * @package    Elgg.Core
13
 * @subpackage Upgrade
14
 */
15
class UpgradeService {
16
17
	/**
18
	 * Global Elgg configuration
19
	 * 
20
	 * @var \stdClass
21
	 */
22
	private $CONFIG;
23
24
	/**
25
	 * Constructor
26
	 */
27
	public function __construct() {
28
		global $CONFIG;
29
		$this->CONFIG = $CONFIG;
30
	}
31
32
	/**
33
	 * Run the upgrade process
34
	 *
35
	 * @return array
36
	 */
37
	public function run() {
38
		$result = array(
39
			'failure' => false,
40
			'reason' => '',
41
		);
42
43
		// prevent someone from running the upgrade script in parallel (see #4643)
44
		if (!$this->getUpgradeMutex()) {
45
			$result['failure'] = true;
46
			$result['reason'] = _elgg_services()->translator->translate('upgrade:locked');
47
			return $result;
48
		}
49
50
		// disable the system log for upgrades to avoid exceptions when the schema changes.
51
		_elgg_services()->events->unregisterHandler('log', 'systemlog', 'system_log_default_logger');
52
		_elgg_services()->events->unregisterHandler('all', 'all', 'system_log_listener');
53
54
		// turn off time limit
55
		set_time_limit(0);
56
57
		if ($this->getUnprocessedUpgrades()) {
58
			$this->processUpgrades();
59
		}
60
61
		_elgg_services()->events->trigger('upgrade', 'system', null);
62
		elgg_flush_caches();
63
64
		$this->releaseUpgradeMutex();
65
66
		return $result;
67
	}
68
69
	/**
70
	 * Run any php upgrade scripts which are required
71
	 *
72
	 * @param int  $version Version upgrading from.
73
	 * @param bool $quiet   Suppress errors.  Don't use this.
74
	 *
75
	 * @return bool
76
	 */
77
	protected function upgradeCode($version, $quiet = false) {
78
		$version = (int) $version;
79
		$upgrade_path = _elgg_services()->config->get('path') . 'engine/lib/upgrades/';
80
		$processed_upgrades = $this->getProcessedUpgrades();
81
82
		// upgrading from 1.7 to 1.8. Need to bootstrap.
83
		if (!$processed_upgrades) {
84
			$this->bootstrap17to18();
85
86
			// grab accurate processed upgrades
87
			$processed_upgrades = $this->getProcessedUpgrades();
88
		}
89
90
		$upgrade_files = $this->getUpgradeFiles($upgrade_path);
91
92
		if ($upgrade_files === false) {
93
			return false;
94
		}
95
96
		$upgrades = $this->getUnprocessedUpgrades($upgrade_files, $processed_upgrades);
97
98
		// Sort and execute
99
		sort($upgrades);
100
101
		foreach ($upgrades as $upgrade) {
102
			$upgrade_version = $this->getUpgradeFileVersion($upgrade);
103
			$success = true;
104
105
			if ($upgrade_version <= $version) {
106
				// skip upgrade files from before the installation version of Elgg
107
				// because the upgrade files from before the installation version aren't
108
				// added to the database.
109
				continue;
110
			}
111
			
112
			// hide all errors.
113
			if ($quiet) {
114
				// hide include errors as well as any exceptions that might happen
115
				try {
116 View Code Duplication
					if (!@self::includeCode("$upgrade_path/$upgrade")) {
117
						$success = false;
118
						error_log("Could not include $upgrade_path/$upgrade");
119
					}
120
				} catch (\Exception $e) {
121
					$success = false;
122
					error_log($e->getMessage());
123
				}
124 View Code Duplication
			} else {
125
				if (!self::includeCode("$upgrade_path/$upgrade")) {
126
					$success = false;
127
					error_log("Could not include $upgrade_path/$upgrade");
128
				}
129
			}
130
131
			if ($success) {
132
				// don't set the version to a lower number in instances where an upgrade
133
				// has been merged from a lower version of Elgg
134
				if ($upgrade_version > $version) {
135
					_elgg_services()->datalist->set('version', $upgrade_version);
0 ignored issues
show
$upgrade_version is of type integer|false, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
136
				}
137
138
				// incrementally set upgrade so we know where to start if something fails.
139
				$this->setProcessedUpgrade($upgrade);
140
			} else {
141
				return false;
142
			}
143
		}
144
145
		return true;
146
	}
147
148
	/**
149
	 * PHP include a file with a very limited scope
150
	 *
151
	 * @param string $file File path to include
152
	 * @return mixed
153
	 */
154
	protected static function includeCode($file) {
155
		// do not remove - some upgrade scripts depend on this
156
		global $CONFIG;
157
158
		return include $file;
159
	}
160
161
	/**
162
	 * Saves a processed upgrade to a dataset.
163
	 *
164
	 * @param string $upgrade Filename of the processed upgrade
165
	 *                        (not the path, just the file)
166
	 * @return bool
167
	 */
168
	protected function setProcessedUpgrade($upgrade) {
169
		$processed_upgrades = $this->getProcessedUpgrades();
170
		$processed_upgrades[] = $upgrade;
171
		$processed_upgrades = array_unique($processed_upgrades);
172
		return _elgg_services()->datalist->set('processed_upgrades', serialize($processed_upgrades));
173
	}
174
175
	/**
176
	 * Gets a list of processes upgrades
177
	 *
178
	 * @return mixed Array of processed upgrade filenames or false
179
	 */
180
	protected function getProcessedUpgrades() {
181
		$upgrades = _elgg_services()->datalist->get('processed_upgrades');
182
		$unserialized = unserialize($upgrades);
183
		return $unserialized;
184
	}
185
186
	/**
187
	 * Returns the version of the upgrade filename.
188
	 *
189
	 * @param string $filename The upgrade filename. No full path.
190
	 * @return int|false
191
	 * @since 1.8.0
192
	 */
193 View Code Duplication
	protected function getUpgradeFileVersion($filename) {
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
194
		preg_match('/^([0-9]{10})([\.a-z0-9-_]+)?\.(php)$/i', $filename, $matches);
195
196
		if (isset($matches[1])) {
197
			return (int) $matches[1];
198
		}
199
200
		return false;
201
	}
202
203
	/**
204
	 * Returns a list of upgrade files relative to the $upgrade_path dir.
205
	 *
206
	 * @param string $upgrade_path The up
207
	 * @return array|false
208
	 */
209
	protected function getUpgradeFiles($upgrade_path = null) {
210
		if (!$upgrade_path) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $upgrade_path of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
211
			$upgrade_path = _elgg_services()->config->get('path') . 'engine/lib/upgrades/';
212
		}
213
		$upgrade_path = sanitise_filepath($upgrade_path);
214
		$handle = opendir($upgrade_path);
215
216
		if (!$handle) {
217
			return false;
218
		}
219
220
		$upgrade_files = array();
221
222 View Code Duplication
		while ($upgrade_file = readdir($handle)) {
223
			// make sure this is a wellformed upgrade.
224
			if (is_dir($upgrade_path . '$upgrade_file')) {
225
				continue;
226
			}
227
			$upgrade_version = $this->getUpgradeFileVersion($upgrade_file);
228
			if (!$upgrade_version) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $upgrade_version of type integer|false is loosely compared to false; this is ambiguous if the integer can be zero. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
229
				continue;
230
			}
231
			$upgrade_files[] = $upgrade_file;
232
		}
233
234
		sort($upgrade_files);
235
236
		return $upgrade_files;
237
	}
238
239
	/**
240
	 * Checks if any upgrades need to be run.
241
	 *
242
	 * @param null|array $upgrade_files      Optional upgrade files
243
	 * @param null|array $processed_upgrades Optional processed upgrades
244
	 *
245
	 * @return array
246
	 */
247
	protected function getUnprocessedUpgrades($upgrade_files = null, $processed_upgrades = null) {
248
		if ($upgrade_files === null) {
249
			$upgrade_files = $this->getUpgradeFiles();
250
		}
251
252
		if ($processed_upgrades === null) {
253
			$processed_upgrades = unserialize(_elgg_services()->datalist->get('processed_upgrades'));
254
			if (!is_array($processed_upgrades)) {
255
				$processed_upgrades = array();
256
			}
257
		}
258
259
		$unprocessed = array_diff($upgrade_files, $processed_upgrades);
260
		return $unprocessed;
261
	}
262
263
	/**
264
	 * Upgrades Elgg Database and code
265
	 *
266
	 * @return bool
267
	 */
268
	protected function processUpgrades() {
269
270
		$dbversion = (int) _elgg_services()->datalist->get('version');
271
272
		// No version number? Oh snap...this is an upgrade from a clean installation < 1.7.
273
		// Run all upgrades without error reporting and hope for the best.
274
		// See https://github.com/elgg/elgg/issues/1432 for more.
275
		$quiet = !$dbversion;
276
277
		// Note: Database upgrades are deprecated as of 1.8.  Use code upgrades.  See #1433
278
		if ($this->dbUpgrade($dbversion, '', $quiet)) {
0 ignored issues
show
Deprecated Code introduced by
The method Elgg\UpgradeService::dbUpgrade() has been deprecated with message: 1.8 Use PHP upgrades for sql changes.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
279
			system_message(_elgg_services()->translator->translate('upgrade:db'));
280
		}
281
282
		if ($this->upgradeCode($dbversion, $quiet)) {
283
			system_message(_elgg_services()->translator->translate('upgrade:core'));
284
285
			// Now we trigger an event to give the option for plugins to do something
286
			$upgrade_details = new \stdClass;
287
			$upgrade_details->from = $dbversion;
288
			$upgrade_details->to = elgg_get_version();
289
290
			_elgg_services()->events->trigger('upgrade', 'upgrade', $upgrade_details);
291
292
			return true;
293
		}
294
295
		return false;
296
	}
297
298
	/**
299
	 * Boot straps into 1.8 upgrade system from 1.7
300
	 *
301
	 * This runs all the 1.7 upgrades, then sets the processed_upgrades to all existing 1.7 upgrades.
302
	 * Control is then passed back to the main upgrade function which detects and runs the
303
	 * 1.8 upgrades, regardless of filename convention.
304
	 *
305
	 * @return bool
306
	 */
307
	protected function bootstrap17to18() {
308
		$db_version = (int) _elgg_services()->datalist->get('version');
309
310
		// the 1.8 upgrades before the upgrade system change that are interspersed with 1.7 upgrades.
311
		$upgrades_18 = array(
312
			'2010111501.php',
313
			'2010121601.php',
314
			'2010121602.php',
315
			'2010121701.php',
316
			'2010123101.php',
317
			'2011010101.php',
318
		);
319
320
		$upgrade_files = $this->getUpgradeFiles();
321
		$processed_upgrades = array();
322
323
		foreach ($upgrade_files as $upgrade_file) {
324
			// ignore if not in 1.7 format or if it's a 1.8 upgrade
325
			if (in_array($upgrade_file, $upgrades_18) || !preg_match("/[0-9]{10}\.php/", $upgrade_file)) {
326
				continue;
327
			}
328
329
			$upgrade_version = $this->getUpgradeFileVersion($upgrade_file);
330
331
			// this has already been run in a previous 1.7.X -> 1.7.X upgrade
332
			if ($upgrade_version < $db_version) {
333
				$this->setProcessedUpgrade($upgrade_file);
334
			}
335
		}
336
	}
337
338
	/**
339
	 * Creates a table {prefix}upgrade_lock that is used as a mutex for upgrades.
340
	 *
341
	 * @return bool
342
	 */
343
	protected function getUpgradeMutex() {
344
		
345
346
		if (!$this->isUpgradeLocked()) {
347
			// lock it
348
			_elgg_services()->db->insertData("create table {$this->CONFIG->dbprefix}upgrade_lock (id INT)");
349
			_elgg_services()->logger->notice('Locked for upgrade.');
350
			return true;
351
		}
352
353
		_elgg_services()->logger->warn('Cannot lock for upgrade: already locked');
354
		return false;
355
	}
356
357
	/**
358
	 * Unlocks upgrade.
359
	 *
360
	 * @return void
361
	 */
362
	public function releaseUpgradeMutex() {
363
		
364
		_elgg_services()->db->deleteData("drop table {$this->CONFIG->dbprefix}upgrade_lock");
365
		_elgg_services()->logger->notice('Upgrade unlocked.');
366
	}
367
368
	/**
369
	 * Checks if upgrade is locked
370
	 *
371
	 * @return bool
372
	 */
373
	public function isUpgradeLocked() {
374
		
375
376
		$is_locked = count(_elgg_services()->db->getData("SHOW TABLES LIKE '{$this->CONFIG->dbprefix}upgrade_lock'"));
377
378
		return (bool)$is_locked;
379
	}
380
381
	/**
382
	 * ***************************************************************************
383
	 * NOTE: If this is ever removed from Elgg, sites lose the ability to upgrade
384
	 * from 1.7.x and earlier to the latest version of Elgg without upgrading to
385
	 * 1.8 first.
386
	 * ***************************************************************************
387
	 *
388
	 * Upgrade the database schema in an ordered sequence.
389
	 *
390
	 * Executes all upgrade files in elgg/engine/schema/upgrades/ in sequential order.
391
	 * Upgrade files must be in the standard Elgg release format of YYYYMMDDII.sql
392
	 * where II is an incrementor starting from 01.
393
	 *
394
	 * Files that are < $version will be ignored.
395
	 *
396
	 * @param int    $version The version you are upgrading from in the format YYYYMMDDII.
397
	 * @param string $fromdir Optional directory to load upgrades from. default: engine/schema/upgrades/
398
	 * @param bool   $quiet   If true, suppress all error messages. Only use for the upgrade from <=1.6.
399
	 *
400
	 * @return int The number of upgrades run.
401
	 * @deprecated 1.8 Use PHP upgrades for sql changes.
402
	 */
403
	protected function dbUpgrade($version, $fromdir = "", $quiet = false) {
404
		
405
406
		$version = (int) $version;
407
408
		if (!$fromdir) {
409
			$fromdir = $this->CONFIG->path . 'engine/schema/upgrades/';
410
		}
411
412
		$i = 0;
413
414
		if ($handle = opendir($fromdir)) {
415
			$sqlupgrades = array();
416
417
			while ($sqlfile = readdir($handle)) {
418
				if (!is_dir($fromdir . $sqlfile)) {
419
					if (preg_match('/^([0-9]{10})\.(sql)$/', $sqlfile, $matches)) {
420
						$sql_version = (int) $matches[1];
421
						if ($sql_version > $version) {
422
							$sqlupgrades[] = $sqlfile;
423
						}
424
					}
425
				}
426
			}
427
428
			asort($sqlupgrades);
429
430
			if (sizeof($sqlupgrades) > 0) {
431
				foreach ($sqlupgrades as $sqlfile) {
432
433
					// hide all errors.
434
					if ($quiet) {
435
						try {
436
							_elgg_services()->db->runSqlScript($fromdir . $sqlfile);
437
						} catch (\DatabaseException $e) {
438
							error_log($e->getmessage());
439
						}
440
					} else {
441
						_elgg_services()->db->runSqlScript($fromdir . $sqlfile);
442
					}
443
					$i++;
444
				}
445
			}
446
		}
447
448
		return $i;
449
	}
450
451
}
452
453