Issues (4122)

Security Analysis    not enabled

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.

includes/filerepo/FileBackendDBRepoWrapper.php (1 issue)

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
 * Proxy backend that manages file layout rewriting for FileRepo.
4
 *
5
 * This program is free software; you can redistribute it and/or modify
6
 * it under the terms of the GNU General Public License as published by
7
 * the Free Software Foundation; either version 2 of the License, or
8
 * (at your option) any later version.
9
 *
10
 * This program is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
 * GNU General Public License for more details.
14
 *
15
 * You should have received a copy of the GNU General Public License along
16
 * with this program; if not, write to the Free Software Foundation, Inc.,
17
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18
 * http://www.gnu.org/copyleft/gpl.html
19
 *
20
 * @file
21
 * @ingroup FileRepo
22
 * @ingroup FileBackend
23
 * @author Aaron Schulz
24
 */
25
26
/**
27
 * @brief Proxy backend that manages file layout rewriting for FileRepo.
28
 *
29
 * LocalRepo may be configured to store files under their title names or by SHA-1.
30
 * This acts as a shim in the latter case, providing backwards compatability for
31
 * most callers. All "public"/"deleted" zone files actually go in an "original"
32
 * container and are never changed.
33
 *
34
 * This requires something like thumb_handler.php and img_auth.php for client viewing of files.
35
 *
36
 * @ingroup FileRepo
37
 * @ingroup FileBackend
38
 * @since 1.25
39
 */
40
class FileBackendDBRepoWrapper extends FileBackend {
41
	/** @var FileBackend */
42
	protected $backend;
43
	/** @var string */
44
	protected $repoName;
45
	/** @var Closure */
46
	protected $dbHandleFunc;
47
	/** @var ProcessCacheLRU */
48
	protected $resolvedPathCache;
49
	/** @var DBConnRef[] */
50
	protected $dbs;
51
52
	public function __construct( array $config ) {
53
		/** @var FileBackend $backend */
54
		$backend = $config['backend'];
55
		$config['name'] = $backend->getName();
56
		$config['wikiId'] = $backend->getWikiId();
57
		parent::__construct( $config );
58
		$this->backend = $config['backend'];
59
		$this->repoName = $config['repoName'];
60
		$this->dbHandleFunc = $config['dbHandleFactory'];
61
		$this->resolvedPathCache = new ProcessCacheLRU( 100 );
62
	}
63
64
	/**
65
	 * Get the underlying FileBackend that is being wrapped
66
	 *
67
	 * @return FileBackend
68
	 */
69
	public function getInternalBackend() {
70
		return $this->backend;
71
	}
72
73
	/**
74
	 * Translate a legacy "title" path to it's "sha1" counterpart
75
	 *
76
	 * E.g. mwstore://local-backend/local-public/a/ab/<name>.jpg
77
	 * => mwstore://local-backend/local-original/x/y/z/<sha1>.jpg
78
	 *
79
	 * @param string $path
80
	 * @param bool $latest
81
	 * @return string
82
	 */
83
	public function getBackendPath( $path, $latest = true ) {
84
		$paths = $this->getBackendPaths( [ $path ], $latest );
85
		return current( $paths );
86
	}
87
88
	/**
89
	 * Translate legacy "title" paths to their "sha1" counterparts
90
	 *
91
	 * E.g. mwstore://local-backend/local-public/a/ab/<name>.jpg
92
	 * => mwstore://local-backend/local-original/x/y/z/<sha1>.jpg
93
	 *
94
	 * @param array $paths
95
	 * @param bool $latest
96
	 * @return array Translated paths in same order
97
	 */
98
	public function getBackendPaths( array $paths, $latest = true ) {
99
		$db = $this->getDB( $latest ? DB_MASTER : DB_REPLICA );
100
101
		// @TODO: batching
102
		$resolved = [];
103
		foreach ( $paths as $i => $path ) {
104
			if ( !$latest && $this->resolvedPathCache->has( $path, 'target', 10 ) ) {
105
				$resolved[$i] = $this->resolvedPathCache->get( $path, 'target' );
106
				continue;
107
			}
108
109
			list( , $container ) = FileBackend::splitStoragePath( $path );
110
111
			if ( $container === "{$this->repoName}-public" ) {
112
				$name = basename( $path );
113
				if ( strpos( $path, '!' ) !== false ) {
114
					$sha1 = $db->selectField( 'oldimage', 'oi_sha1',
115
						[ 'oi_archive_name' => $name ],
116
						__METHOD__
117
					);
118
				} else {
119
					$sha1 = $db->selectField( 'image', 'img_sha1',
120
						[ 'img_name' => $name ],
121
						__METHOD__
122
					);
123
				}
124
				if ( !strlen( $sha1 ) ) {
125
					$resolved[$i] = $path; // give up
126
					continue;
127
				}
128
				$resolved[$i] = $this->getPathForSHA1( $sha1 );
129
				$this->resolvedPathCache->set( $path, 'target', $resolved[$i] );
130
			} elseif ( $container === "{$this->repoName}-deleted" ) {
131
				$name = basename( $path ); // <hash>.<ext>
132
				$sha1 = substr( $name, 0, strpos( $name, '.' ) ); // ignore extension
133
				$resolved[$i] = $this->getPathForSHA1( $sha1 );
134
				$this->resolvedPathCache->set( $path, 'target', $resolved[$i] );
135
			} else {
136
				$resolved[$i] = $path;
137
			}
138
		}
139
140
		$res = [];
141
		foreach ( $paths as $i => $path ) {
142
			$res[$i] = $resolved[$i];
143
		}
144
145
		return $res;
146
	}
147
148
	protected function doOperationsInternal( array $ops, array $opts ) {
149
		return $this->backend->doOperationsInternal( $this->mungeOpPaths( $ops ), $opts );
150
	}
151
152
	protected function doQuickOperationsInternal( array $ops ) {
153
		return $this->backend->doQuickOperationsInternal( $this->mungeOpPaths( $ops ) );
154
	}
155
156
	protected function doPrepare( array $params ) {
157
		return $this->backend->doPrepare( $params );
158
	}
159
160
	protected function doSecure( array $params ) {
161
		return $this->backend->doSecure( $params );
162
	}
163
164
	protected function doPublish( array $params ) {
165
		return $this->backend->doPublish( $params );
166
	}
167
168
	protected function doClean( array $params ) {
169
		return $this->backend->doClean( $params );
170
	}
171
172
	public function concatenate( array $params ) {
173
		return $this->translateSrcParams( __FUNCTION__, $params );
174
	}
175
176
	public function fileExists( array $params ) {
177
		return $this->translateSrcParams( __FUNCTION__, $params );
178
	}
179
180
	public function getFileTimestamp( array $params ) {
181
		return $this->translateSrcParams( __FUNCTION__, $params );
182
	}
183
184
	public function getFileSize( array $params ) {
185
		return $this->translateSrcParams( __FUNCTION__, $params );
186
	}
187
188
	public function getFileStat( array $params ) {
189
		return $this->translateSrcParams( __FUNCTION__, $params );
190
	}
191
192
	public function getFileXAttributes( array $params ) {
193
		return $this->translateSrcParams( __FUNCTION__, $params );
194
	}
195
196
	public function getFileSha1Base36( array $params ) {
197
		return $this->translateSrcParams( __FUNCTION__, $params );
198
	}
199
200
	public function getFileProps( array $params ) {
201
		return $this->translateSrcParams( __FUNCTION__, $params );
202
	}
203
204
	public function streamFile( array $params ) {
205
		// The stream methods use the file extension to determine the
206
		// Content-Type (as MediaWiki should already validate it on upload).
207
		// The translated SHA1 path has no extension, so this needs to use
208
		// the untranslated path extension.
209
		$type = StreamFile::contentTypeFromPath( $params['src'] );
210
		if ( $type && $type != 'unknown/unknown' ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $type of type string|null is loosely compared to true; 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
			$params['headers'][] = "Content-type: $type";
212
		}
213
		return $this->translateSrcParams( __FUNCTION__, $params );
214
	}
215
216
	public function getFileContentsMulti( array $params ) {
217
		return $this->translateArrayResults( __FUNCTION__, $params );
218
	}
219
220
	public function getLocalReferenceMulti( array $params ) {
221
		return $this->translateArrayResults( __FUNCTION__, $params );
222
	}
223
224
	public function getLocalCopyMulti( array $params ) {
225
		return $this->translateArrayResults( __FUNCTION__, $params );
226
	}
227
228
	public function getFileHttpUrl( array $params ) {
229
		return $this->translateSrcParams( __FUNCTION__, $params );
230
	}
231
232
	public function directoryExists( array $params ) {
233
		return $this->backend->directoryExists( $params );
234
	}
235
236
	public function getDirectoryList( array $params ) {
237
		return $this->backend->getDirectoryList( $params );
238
	}
239
240
	public function getFileList( array $params ) {
241
		return $this->backend->getFileList( $params );
242
	}
243
244
	public function getFeatures() {
245
		return $this->backend->getFeatures();
246
	}
247
248
	public function clearCache( array $paths = null ) {
249
		$this->backend->clearCache( null ); // clear all
250
	}
251
252
	public function preloadCache( array $paths ) {
253
		$paths = $this->getBackendPaths( $paths );
254
		$this->backend->preloadCache( $paths );
255
	}
256
257
	public function preloadFileStat( array $params ) {
258
		return $this->translateSrcParams( __FUNCTION__, $params );
259
	}
260
261
	public function getScopedLocksForOps( array $ops, StatusValue $status ) {
262
		return $this->backend->getScopedLocksForOps( $ops, $status );
263
	}
264
265
	/**
266
	 * Get the ultimate original storage path for a file
267
	 *
268
	 * Use this when putting a new file into the system
269
	 *
270
	 * @param string $sha1 File SHA-1 base36
271
	 * @return string
272
	 */
273
	public function getPathForSHA1( $sha1 ) {
274
		if ( strlen( $sha1 ) < 3 ) {
275
			throw new InvalidArgumentException( "Invalid file SHA-1." );
276
		}
277
		return $this->backend->getContainerStoragePath( "{$this->repoName}-original" ) .
278
			"/{$sha1[0]}/{$sha1[1]}/{$sha1[2]}/{$sha1}";
279
	}
280
281
	/**
282
	 * Get a connection to the repo file registry DB
283
	 *
284
	 * @param integer $index
285
	 * @return DBConnRef
286
	 */
287
	protected function getDB( $index ) {
288
		if ( !isset( $this->dbs[$index] ) ) {
289
			$func = $this->dbHandleFunc;
290
			$this->dbs[$index] = $func( $index );
291
		}
292
		return $this->dbs[$index];
293
	}
294
295
	/**
296
	 * Translates paths found in the "src" or "srcs" keys of a params array
297
	 *
298
	 * @param string $function
299
	 * @param array $params
300
	 */
301
	protected function translateSrcParams( $function, array $params ) {
302
		$latest = !empty( $params['latest'] );
303
304
		if ( isset( $params['src'] ) ) {
305
			$params['src'] = $this->getBackendPath( $params['src'], $latest );
306
		}
307
308
		if ( isset( $params['srcs'] ) ) {
309
			$params['srcs'] = $this->getBackendPaths( $params['srcs'], $latest );
310
		}
311
312
		return $this->backend->$function( $params );
313
	}
314
315
	/**
316
	 * Translates paths when the backend function returns results keyed by paths
317
	 *
318
	 * @param string $function
319
	 * @param array $params
320
	 * @return array
321
	 */
322
	protected function translateArrayResults( $function, array $params ) {
323
		$origPaths = $params['srcs'];
324
		$params['srcs'] = $this->getBackendPaths( $params['srcs'], !empty( $params['latest'] ) );
325
		$pathMap = array_combine( $params['srcs'], $origPaths );
326
327
		$results = $this->backend->$function( $params );
328
329
		$contents = [];
330
		foreach ( $results as $path => $result ) {
331
			$contents[$pathMap[$path]] = $result;
332
		}
333
334
		return $contents;
335
	}
336
337
	/**
338
	 * Translate legacy "title" source paths to their "sha1" counterparts
339
	 *
340
	 * This leaves destination paths alone since we don't want those to mutate
341
	 *
342
	 * @param array $ops
343
	 * @return array
344
	 */
345
	protected function mungeOpPaths( array $ops ) {
346
		// Ops that use 'src' and do not mutate core file data there
347
		static $srcRefOps = [ 'store', 'copy', 'describe' ];
348
		foreach ( $ops as &$op ) {
349
			if ( isset( $op['src'] ) && in_array( $op['op'], $srcRefOps ) ) {
350
				$op['src'] = $this->getBackendPath( $op['src'], true );
351
			}
352
			if ( isset( $op['srcs'] ) ) {
353
				$op['srcs'] = $this->getBackendPaths( $op['srcs'], true );
354
			}
355
		}
356
		return $ops;
357
	}
358
}
359