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/lib/system_log.php (4 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
/**
3
 * Elgg system log.
4
 * Listens to events and writes crud events into the system log database.
5
 *
6
 * @package Elgg.Core
7
 * @subpackage Logging
8
 */
9
10
/**
11
 * Retrieve the system log based on a number of parameters.
12
 *
13
 * @todo too many args, and the first arg is too confusing
14
 *
15
 * @param int|array $by_user    The guid(s) of the user(s) who initiated the event.
16
 *                              Use 0 for unowned entries. Anything else falsey means anyone.
17
 * @param string    $event      The event you are searching on.
18
 * @param string    $class      The class of object it effects.
19
 * @param string    $type       The type
20
 * @param string    $subtype    The subtype.
21
 * @param int       $limit      Maximum number of responses to return. (default from settings)
22
 * @param int       $offset     Offset of where to start.
23
 * @param bool      $count      Return count or not
24
 * @param int       $timebefore Lower time limit
25
 * @param int       $timeafter  Upper time limit
26
 * @param int       $object_id  GUID of an object
27
 * @param string    $ip_address The IP address.
28
 * @return mixed
29
 */
30
function get_system_log($by_user = "", $event = "", $class = "", $type = "", $subtype = "", $limit = null,
31
						$offset = 0, $count = false, $timebefore = 0, $timeafter = 0, $object_id = 0,
32
						$ip_address = "") {
33
34
	global $CONFIG;
35
36
	$by_user_orig = $by_user;
37
	if (is_array($by_user) && sizeof($by_user) > 0) {
38
		foreach ($by_user as $key => $val) {
39
			$by_user[$key] = (int) $val;
40
		}
41
	} else {
42
		$by_user = (int)$by_user;
43
	}
44
	
45
	$event = sanitise_string($event);
46
	$class = sanitise_string($class);
47
	$type = sanitise_string($type);
48
	$subtype = sanitise_string($subtype);
49
	$ip_address = sanitise_string($ip_address);
50
	if ($limit === null) {
51
		$limit = elgg_get_config('default_limit');
52
	}
53
	$limit = (int)$limit;
54
	$offset = (int)$offset;
55
56
	$where = array();
57
58
	if ($by_user_orig !== "" && $by_user_orig !== false && $by_user_orig !== null) {
59
		if (is_int($by_user)) {
60
			$where[] = "performed_by_guid=$by_user";
61
		} else if (is_array($by_user)) {
62
			$where [] = "performed_by_guid in (" . implode(",", $by_user) . ")";
63
		}
64
	}
65
	if ($event != "") {
66
		$where[] = "event='$event'";
67
	}
68
	if ($class !== "") {
69
		$where[] = "object_class='$class'";
70
	}
71
	if ($type != "") {
72
		$where[] = "object_type='$type'";
73
	}
74
	if ($subtype !== "") {
75
		$where[] = "object_subtype='$subtype'";
76
	}
77
78
	if ($timebefore) {
79
		$where[] = "time_created < " . ((int) $timebefore);
80
	}
81
	if ($timeafter) {
82
		$where[] = "time_created > " . ((int) $timeafter);
83
	}
84
	if ($object_id) {
85
		$where[] = "object_id = " . ((int) $object_id);
86
	}
87
	if ($ip_address) {
88
		$where[] = "ip_address = '$ip_address'";
89
	}
90
91
	$select = "*";
92
	if ($count) {
93
		$select = "count(*) as count";
94
	}
95
	$query = "SELECT $select from {$CONFIG->dbprefix}system_log where 1 ";
96
	foreach ($where as $w) {
97
		$query .= " and $w";
98
	}
99
100
	if (!$count) {
101
		$query .= " order by time_created desc";
102
		$query .= " limit $offset, $limit"; // Add order and limit
103
	}
104
105 View Code Duplication
	if ($count) {
106
		$numrows = get_data_row($query);
107
		if ($numrows) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $numrows of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
108
			return $numrows->count;
109
		}
110
	} else {
111
		return get_data($query);
112
	}
113
114
	return false;
115
}
116
117
/**
118
 * Return a specific log entry.
119
 *
120
 * @param int $entry_id The log entry
121
 *
122
 * @return mixed
123
 */
124
function get_log_entry($entry_id) {
125
	global $CONFIG;
126
127
	$entry_id = (int)$entry_id;
128
129
	return get_data_row("SELECT * from {$CONFIG->dbprefix}system_log where id=$entry_id");
130
}
131
132
/**
133
 * Return the object referred to by a given log entry
134
 *
135
 * @param \stdClass|int $entry The log entry row or its ID
136
 *
137
 * @return mixed
138
 */
139
function get_object_from_log_entry($entry) {
140
	if (is_numeric($entry)) {
141
		$entry = get_log_entry($entry);
142
		if (!$entry) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $entry of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
143
			return false;
144
		}
145
	}
146
147
	$class = $entry->object_class;
148
	$id = $entry->object_id;
149
150
	if (!class_exists($class)) {
151
		// failed autoload
152
		return false;
153
	}
154
155
	$getters = array(
156
		'ElggAnnotation' => 'elgg_get_annotation_from_id',
157
		'ElggMetadata' => 'elgg_get_metadata_from_id',
158
		'ElggRelationship' => 'get_relationship',
159
	);
160
161
	if (isset($getters[$class]) && is_callable($getters[$class])) {
162
		$object = call_user_func($getters[$class], $id);
163
	} elseif (preg_match('~^Elgg[A-Z]~', $class)) {
164
		$object = get_entity($id);
165
	} else {
166
		// surround with try/catch because object could be disabled
167
		try {
168
			$object = new $class($entry->object_id);
169
			return $object;
170
		} catch (Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
171
			
172
		}
173
	}
174
175
	if (!is_object($object) || get_class($object) !== $class) {
176
		return false;
177
	}
178
179
	return $object;
180
}
181
182
/**
183
 * Log a system event related to a specific object.
184
 *
185
 * This is called by the event system and should not be called directly.
186
 *
187
 * @param object $object The object you're talking about.
188
 * @param string $event  The event being logged
189
 * @return void
190
 */
191
function system_log($object, $event) {
192
	global $CONFIG;
193
	static $log_cache;
194
	static $cache_size = 0;
195
196
	if ($object instanceof Loggable) {
197
198
		/* @var \ElggEntity|\ElggExtender $object */
199
		if (datalist_get('version') < 2012012000) {
200
			// this is a site that doesn't have the ip_address column yet
201
			return;
202
		}
203
204
		// reset cache if it has grown too large
205
		if (!is_array($log_cache) || $cache_size > 500) {
206
			$log_cache = array();
207
			$cache_size = 0;
208
		}
209
210
		// Has loggable interface, extract the necessary information and store
211
		$object_id = (int)$object->getSystemLogID();
212
		$object_class = get_class($object);
213
		$object_type = $object->getType();
214
		$object_subtype = $object->getSubtype();
215
		$event = sanitise_string($event);
216
		$time = time();
217
		$ip_address = sanitize_string(_elgg_services()->request->getClientIp());
218
		if (!$ip_address) {
219
			$ip_address = '0.0.0.0';
220
		}
221
		$performed_by = elgg_get_logged_in_user_guid();
222
223
		if (isset($object->access_id)) {
224
			$access_id = $object->access_id;
225
		} else {
226
			$access_id = ACCESS_PUBLIC;
227
		}
228
		if (isset($object->enabled)) {
229
			$enabled = $object->enabled;
230
		} else {
231
			$enabled = 'yes';
232
		}
233
234
		if (isset($object->owner_guid)) {
235
			$owner_guid = $object->owner_guid;
236
		} else {
237
			$owner_guid = 0;
238
		}
239
240
		// Create log if we haven't already created it
241
		if (!isset($log_cache[$time][$object_id][$event])) {
242
		
243
		// replaced "insert delayed into" -> "insert into" - insert delayed not supported with InnoDB - CdG
244
			$query = "INSERT into {$CONFIG->dbprefix}system_log
245
				(object_id, object_class, object_type, object_subtype, event,
246
				performed_by_guid, owner_guid, access_id, enabled, time_created, ip_address)
247
			VALUES
248
				('$object_id','$object_class','$object_type', '$object_subtype', '$event',
249
				$performed_by, $owner_guid, $access_id, '$enabled', '$time', '$ip_address')";
250
251
			insert_data($query);
252
253
			$log_cache[$time][$object_id][$event] = true;
254
			$cache_size += 1;
255
		}
256
	}
257
}
258
259
/**
260
 * This function creates an archive copy of the system log.
261
 *
262
 * @param int $offset An offset in seconds from now to archive (useful for log rotation)
263
 *
264
 * @return bool
265
 */
266
function archive_log($offset = 0) {
267
	global $CONFIG;
268
269
	$offset = (int)$offset;
270
	$now = time(); // Take a snapshot of now
271
272
	$ts = $now - $offset;
273
274
	// create table
275
	$query = "CREATE TABLE {$CONFIG->dbprefix}system_log_$now as
276
		SELECT * from {$CONFIG->dbprefix}system_log WHERE time_created<$ts";
277
278
	if (!update_data($query)) {
279
		return false;
280
	}
281
282
	// delete
283
	// Don't delete on time since we are running in a concurrent environment
284
	if (delete_data("DELETE from {$CONFIG->dbprefix}system_log WHERE time_created<$ts") === false) {
285
		return false;
286
	}
287
288
	// alter table to engine
289
	if (!update_data("ALTER TABLE {$CONFIG->dbprefix}system_log_$now engine=archive")) {
290
		return false;
291
	}
292
293
	return true;
294
}
295
296
/**
297
 * Default system log handler, allows plugins to override, extend or disable logging.
298
 *
299
 * @param string   $event       Event name
300
 * @param string   $object_type Object type
301
 * @param Loggable $object      Object to log
302
 *
303
 * @return true
304
 */
305
function system_log_default_logger($event, $object_type, $object) {
306
	system_log($object['object'], $object['event']);
307
308
	return true;
309
}
310
311
/**
312
 * System log listener.
313
 * This function listens to all events in the system and logs anything appropriate.
314
 *
315
 * @param String   $event       Event name
316
 * @param String   $object_type Type of object
317
 * @param Loggable $object      Object to log
318
 *
319
 * @return true
320
 * @access private
321
 */
322
function system_log_listener($event, $object_type, $object) {
323
	if (($object_type != 'systemlog') && ($event != 'log')) {
324
		elgg_trigger_event('log', 'systemlog', array('object' => $object, 'event' => $event));
0 ignored issues
show
array('object' => $object, 'event' => $event) is of type array<string,object<Logg...le>","event":"string"}>, but the function expects a string|null.

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...
325
	}
326
327
	return true;
328
}
329
330
return function(\Elgg\EventsService $events, \Elgg\HooksRegistrationService $hooks) {
331
	/** Register event to listen to all events **/
332
	$events->registerHandler('all', 'all', 'system_log_listener', 400);
333
334
	/** Register a default system log handler */
335
	$events->registerHandler('log', 'systemlog', 'system_log_default_logger', 999);
336
};
337