Issues (3882)

Security Analysis    39 potential vulnerabilities

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

  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.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  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.
  Response Splitting (9)
Response Splitting can be used to send arbitrary responses.
  File Manipulation (2)
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.
  File Exposure (7)
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.
  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.
  Code Injection (13)
Code Injection enables an attacker to execute arbitrary code on the server.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
  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.
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  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.
  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.
  Cross-Site Scripting (8)
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.
  Header Injection
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.

app/Zip.php (5 issues)

1
<?php
2
/**
3
 * A file archive, compressed with Zip.
4
 *
5
 * @package App
6
 *
7
 * @copyright YetiForce S.A.
8
 * @license   YetiForce Public License 6.5 (licenses/LicenseEN.txt or yetiforce.com)
9
 * @author    Mariusz Krzaczkowski <[email protected]>
10
 * @author    RadosÅ‚aw Skrzypczak <[email protected]>
11
 */
12
13
namespace App;
14
15
/**
16
 * Zip class.
17
 */
18
class Zip extends \ZipArchive
19
{
20
	/**
21
	 * Files extension for extract.
22
	 *
23
	 * @var array
24
	 */
25
	protected $onlyExtensions;
26
27
	/**
28
	 * Illegal extensions for extract.
29
	 *
30
	 * @var array
31
	 */
32
	protected $illegalExtensions;
33
34
	/**
35
	 * Check files before unpacking.
36
	 *
37
	 * @var bool
38
	 */
39
	protected $checkFiles = true;
40
41
	/**
42
	 * Open file and initialization unpack.
43
	 *
44
	 * @param bool  $fileName
45
	 * @param array $options
46
	 *
47
	 * @throws Exceptions\AppException
48 13
	 *
49
	 * @return bool|Zip
50 13
	 */
51 1
	public static function openFile($fileName = false, $options = [])
52
	{
53 12
		if (!$fileName) {
54 12
			throw new \App\Exceptions\AppException('No file name');
55 1
		}
56
		$zip = new self($fileName, $options);
0 ignored issues
show
The call to App\Zip::__construct() has too many arguments starting with $fileName. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

56
		$zip = /** @scrutinizer ignore-call */ new self($fileName, $options);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
57 11
		if (!file_exists($fileName) || !$zip->open($fileName)) {
0 ignored issues
show
$fileName of type true is incompatible with the type string expected by parameter $filename of file_exists(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

57
		if (!file_exists(/** @scrutinizer ignore-type */ $fileName) || !$zip->open($fileName)) {
Loading history...
$fileName of type true is incompatible with the type string expected by parameter $filename of ZipArchive::open(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

57
		if (!file_exists($fileName) || !$zip->open(/** @scrutinizer ignore-type */ $fileName)) {
Loading history...
58
			throw new \App\Exceptions\AppException('Unable to open the zip file');
59
		}
60 11
		if (!$zip->checkFreeSpace()) {
61 8
			throw new \App\Exceptions\AppException('The content of the zip file is too large');
62
		}
63 11
		foreach ($options as $key => $value) {
64
			$zip->{$key} = $value;
65
		}
66
		return $zip;
67
	}
68
69
	/**
70
	 * Open file for create zip file.
71
	 *
72
	 * @param string $fileName
73
	 *
74
	 * @throws \App\Exceptions\AppException
75 3
	 *
76
	 * @return \App\Zip
77 3
	 */
78 3
	public static function createFile($fileName)
79
	{
80
		$zip = new self();
81 3
		if (true !== $zip->open($fileName, self::CREATE | self::OVERWRITE)) {
82
			throw new \App\Exceptions\AppException('Unable to create the zip file');
83
		}
84
		return $zip;
85
	}
86
87
	/**
88
	 * Function to extract files.
89
	 *
90
	 * @param      $toDir Target directory
0 ignored issues
show
The type App\Target was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
91
	 * @param bool $close
92 7
	 *
93
	 * @return string|string[] Unpacked files
94 7
	 */
95 1
	public function unzip($toDir, bool $close = true)
96
	{
97 7
		if (\is_string($toDir)) {
98 7
			$toDir = [$toDir];
99 7
		}
100 7
		$files = $created = [];
101 7
		foreach ($toDir as $dir => $target) {
102 7
			for ($i = 0; $i < $this->numFiles; ++$i) {
103 4
				$zipPath = $this->getNameIndex($i);
104
				$path = \str_replace('\\', '/', $zipPath);
105 7
				if ((!\is_numeric($dir) && 0 !== strpos($path, $dir . '/')) || $this->validateFile($zipPath)) {
106 7
					continue;
107 7
				}
108 7
				$files[] = $zipPath;
109 7
				$file = $target . '/' . (\is_numeric($dir) ? $path : substr($path, \strlen($dir) + 1));
110 7
				$fileDir = \dirname($file);
111
				if (!isset($created[$fileDir])) {
112 7
					if (!is_dir($fileDir)) {
113
						mkdir($fileDir, 0755, true);
114 7
					}
115
					$created[$fileDir] = true;
116 7
				}
117 7
				if (!$this->isDir($path)) {
118 7
					// Read from Zip and write to disk
119 7
					$fpr = $this->getStream($zipPath);
120
					$fpw = fopen($file, 'w');
121 7
					while ($data = fread($fpr, 1024)) {
122 7
						fwrite($fpw, $data);
123
					}
124
					fclose($fpr);
125
					fclose($fpw);
126 7
				}
127 7
			}
128
		}
129 7
		if ($close) {
130
			$this->close();
131
		}
132
		return $files;
133
	}
134
135
	/**
136
	 * Simple extract the archive contents.
137
	 *
138
	 * @param string $toDir
139
	 *
140
	 * @throws \App\Exceptions\AppException
141 1
	 *
142
	 * @return array
143 1
	 */
144
	public function extract(string $toDir)
145
	{
146 1
		if (!is_dir($toDir) && !mkdir($toDir, 0755, true) && !is_dir($toDir)) {
147 1
			throw new \App\Exceptions\AppException('Directory unable to create it');
148 1
		}
149 1
		$fileList = [];
150 1
		for ($i = 0; $i < $this->numFiles; ++$i) {
151
			$path = $this->getNameIndex($i);
152 1
			if ($this->validateFile(\str_replace('\\', '/', $path))) {
153
				continue;
154 1
			}
155 1
			$fileList[] = $path;
156
		}
157
		$this->extractTo($toDir, $fileList);
158
		return $fileList;
159
	}
160
161
	/**
162
	 * Check illegal characters.
163
	 *
164
	 * @param string $path
165 8
	 *
166
	 * @return bool
167 8
	 */
168
	public function validateFile(string $path)
169
	{
170 8
		if (!Validator::path($path)) {
171 8
			return true;
172 3
		}
173 3
		$validate = false;
174
		if ($this->checkFiles && !$this->isDir($path)) {
175
			$extension = pathinfo($path, PATHINFO_EXTENSION);
176 3
			if (isset($this->onlyExtensions) && !\in_array($extension, $this->onlyExtensions)) {
177
				$validate = true;
178
			}
179 3
			if (isset($this->illegalExtensions) && \in_array($extension, $this->illegalExtensions)) {
180 3
				$validate = true;
181 3
			}
182 3
			$stat = $this->statName($path);
183 3
			$fileInstance = \App\Fields\File::loadFromInfo([
184 3
				'content' => $this->getFromName($path),
185
				'path' => $this->getLocalPath($path),
186
				'name' => basename($path),
187 3
				'size' => $stat['size'],
188 2
				'validateAllCodeInjection' => true,
189
			]);
190
			if (!$fileInstance->validate()) {
191 8
				$validate = true;
192
			}
193
		}
194
		return $validate;
195
	}
196
197
	/**
198
	 * Check if the file path is directory.
199
	 *
200
	 * @param string $filePath
201 8
	 *
202
	 * @return bool
203 8
	 */
204 6
	public function isDir($filePath)
205
	{
206 8
		if ('/' === substr($filePath, -1, 1)) {
207
			return true;
208
		}
209
		return false;
210
	}
211
212
	/**
213
	 * Function to extract single file.
214
	 *
215
	 * @param string $compressedFileName
216
	 * @param string $targetFileName
217
	 *
218
	 * @return bool
219
	 */
220
	public function unzipFile($compressedFileName, $targetFileName)
221
	{
222
		return copy($this->getLocalPath($compressedFileName), $targetFileName);
223
	}
224
225
	/**
226
	 * Get compressed file path.
227
	 *
228
	 * @param string $compressedFileName
229 3
	 *
230
	 * @return string
231 3
	 */
232
	public function getLocalPath($compressedFileName)
233
	{
234
		return "zip://{$this->filename}#{$compressedFileName}";
235
	}
236
237
	/**
238
	 * Check free disk space.
239 11
	 *
240
	 * @return bool
241 11
	 */
242 11
	public function checkFreeSpace()
243 11
	{
244 11
		$df = disk_free_space(ROOT_DIRECTORY . \DIRECTORY_SEPARATOR);
245 11
		$size = 0;
246
		for ($i = 0; $i < $this->numFiles; ++$i) {
247 11
			$stat = $this->statIndex($i);
248
			$size += $stat['size'];
249
		}
250
		return $df > $size;
251
	}
252
253
	/**
254
	 * Copy the directory on the disk into zip file.
255
	 *
256
	 * @param string $dir
257 2
	 * @param string $localName
258
	 * @param bool   $relativePath
259 2
	 */
260
	public function addDirectory(string $dir, string $localName = '', bool $relativePath = false)
261
	{
262 2
		if ($localName) {
263 2
			$localName .= '/';
264 2
		}
265 2
		$path = realpath($dir);
266 2
		$files = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path), \RecursiveIteratorIterator::LEAVES_ONLY);
267 2
		$pathToTrim = $relativePath ? $path : ROOT_DIRECTORY;
268 2
		foreach ($files as $file) {
269
			if (!$file->isDir()) {
270
				$filePath = $file->getRealPath();
271 2
				$zipPath = str_replace(\DIRECTORY_SEPARATOR, '/', Fields\File::getLocalPath($filePath, $pathToTrim));
272
				$this->addFile($filePath, $localName . $zipPath);
273
			}
274
		}
275
	}
276
277
	/**
278
	 * Push out the file content for download.
279
	 *
280
	 * @param string $name
281
	 */
282
	public function download(string $name)
283
	{
284
		$fileName = $this->filename;
285
		$this->close();
286
		header('cache-control: private, max-age=120, must-revalidate');
287
		header('pragma: no-cache');
288
		header('expires: 0');
289
		header('content-type: application/zip');
290
		header('content-disposition: attachment; filename="' . $name . '.zip";');
0 ignored issues
show
Security Response Splitting introduced by
'content-disposition: at...e="' . $name . '.zip";' can contain request data and is used in response header context(s) leading to a potential security vulnerability.

3 paths for user data to reach this point

  1. Path: Read from $_REQUEST, and Request::__construct() is called in api/webservice/Core/Request.php on line 56
  1. Read from $_REQUEST, and Request::__construct() is called
    in api/webservice/Core/Request.php on line 56
  2. Enters via parameter $rawValues
    in app/Request.php on line 110
  3. $rawValues is assigned to property Request::$rawValues
    in app/Request.php on line 112
  4. Read from property Request::$rawValues, and Data is passed through purifyByType(), and $this->purifiedValuesByType[$key][$type] = App\Purifier::purifyByType($this->rawValues[$key], $type, $convert) is returned
    in app/Request.php on line 170
  5. Time::formatToDB() is called
    in modules/Settings/BusinessHours/actions/Save.php on line 29
  6. Enters via parameter $time
    in app/Fields/Time.php on line 41
  7. DateTimeField::__construct() is called
    in app/Fields/Time.php on line 44
  8. Enters via parameter $value
    in include/fields/DateTimeField.php on line 31
  9. $value is assigned to property DateTimeField::$datetime
    in include/fields/DateTimeField.php on line 38
  10. Read from property DateTimeField::$datetime, and Data is passed through explode(), and explode(' ', $this->datetime, 2) is assigned to $value
    in include/fields/DateTimeField.php on line 47
  11. Data is passed through convertToDBFormat(), and self::convertToDBFormat($value[0]) is assigned to $insert_date
    in include/fields/DateTimeField.php on line 53
  12. $insert_date is returned
    in include/fields/DateTimeField.php on line 55
  13. new DateTimeField($value)->getDBInsertDateValue() is returned
    in app/Fields/Date.php on line 168
  14. App\Validator::dateInUserFormat($input) ? $convert ? App\Fields\Date::formatToDB($input) : $input : null is assigned to $value
    in app/Purifier.php on line 451
  15. $value is returned
    in app/Purifier.php on line 569
  16. App\Purifier::purifyByType($this->rawValues[$key], $type, $convert) is assigned to property Request::$purifiedValuesByType
    in app/Request.php on line 170
  17. Read from property Request::$purifiedValuesByType, and $this->purifiedValuesByType[$key][$type] is returned
    in app/Request.php on line 167
  18. $request->getByType('lang', 1) is assigned to $lang
    in modules/Settings/LangManagement/actions/Export.php on line 21
  19. LanguageExport::exportLanguage() is called
    in modules/Settings/LangManagement/actions/Export.php on line 24
  20. Enters via parameter $languageCode
    in vtlib/Vtiger/LanguageExport.php on line 44
  21. Zip::download() is called
    in vtlib/Vtiger/LanguageExport.php on line 67
  22. Enters via parameter $name
    in app/Zip.php on line 282
  2. Path: Read from $_REQUEST, and Request::__construct() is called in app/Request.php on line 728
  1. Read from $_REQUEST, and Request::__construct() is called
    in app/Request.php on line 728
  2. Enters via parameter $rawValues
    in app/Request.php on line 110
  3. $rawValues is assigned to property Request::$rawValues
    in app/Request.php on line 112
  4. Read from property Request::$rawValues, and Data is passed through purifyByType(), and $this->purifiedValuesByType[$key][$type] = App\Purifier::purifyByType($this->rawValues[$key], $type, $convert) is returned
    in app/Request.php on line 170
  5. Time::formatToDB() is called
    in modules/Settings/BusinessHours/actions/Save.php on line 29
  6. Enters via parameter $time
    in app/Fields/Time.php on line 41
  7. DateTimeField::__construct() is called
    in app/Fields/Time.php on line 44
  8. Enters via parameter $value
    in include/fields/DateTimeField.php on line 31
  9. $value is assigned to property DateTimeField::$datetime
    in include/fields/DateTimeField.php on line 38
  10. Read from property DateTimeField::$datetime, and Data is passed through explode(), and explode(' ', $this->datetime, 2) is assigned to $value
    in include/fields/DateTimeField.php on line 47
  11. Data is passed through convertToDBFormat(), and self::convertToDBFormat($value[0]) is assigned to $insert_date
    in include/fields/DateTimeField.php on line 53
  12. $insert_date is returned
    in include/fields/DateTimeField.php on line 55
  13. new DateTimeField($value)->getDBInsertDateValue() is returned
    in app/Fields/Date.php on line 168
  14. App\Validator::dateInUserFormat($input) ? $convert ? App\Fields\Date::formatToDB($input) : $input : null is assigned to $value
    in app/Purifier.php on line 451
  15. $value is returned
    in app/Purifier.php on line 569
  16. App\Purifier::purifyByType($this->rawValues[$key], $type, $convert) is assigned to property Request::$purifiedValuesByType
    in app/Request.php on line 170
  17. Read from property Request::$purifiedValuesByType, and $this->purifiedValuesByType[$key][$type] is returned
    in app/Request.php on line 167
  18. $request->getByType('lang', 1) is assigned to $lang
    in modules/Settings/LangManagement/actions/Export.php on line 21
  19. LanguageExport::exportLanguage() is called
    in modules/Settings/LangManagement/actions/Export.php on line 24
  20. Enters via parameter $languageCode
    in vtlib/Vtiger/LanguageExport.php on line 44
  21. Zip::download() is called
    in vtlib/Vtiger/LanguageExport.php on line 67
  22. Enters via parameter $name
    in app/Zip.php on line 282
  3. Path: DateTimeField::__construct() is called in app/Fields/Time.php on line 44
  1. DateTimeField::__construct() is called
    in app/Fields/Time.php on line 44
  2. Enters via parameter $value
    in include/fields/DateTimeField.php on line 31
  3. $value is assigned to property DateTimeField::$datetime
    in include/fields/DateTimeField.php on line 38
  4. Read from property DateTimeField::$datetime, and Data is passed through explode(), and explode(' ', $this->datetime) is assigned to $date_value
    in include/fields/DateTimeField.php on line 299
  5. Data is passed through convertToUserFormat(), and self::convertToUserFormat($date_value) is returned
    in include/fields/DateTimeField.php on line 308
  6. new DateTimeField($value)->getDisplayDate() is returned
    in app/Fields/Date.php on line 113
  7. $convertTimeZone ? App\Fields\Date::formatToDisplay(date('Y-m-d'), false) : date('Y-m-d') is assigned to $date
    in app/Fields/Time.php on line 43
  8. DateTimeField::__construct() is called
    in app/Fields/Time.php on line 44
  9. Enters via parameter $value
    in include/fields/DateTimeField.php on line 31
  10. $value is assigned to property DateTimeField::$datetime
    in include/fields/DateTimeField.php on line 38
  11. Read from property DateTimeField::$datetime, and Data is passed through explode(), and explode(' ', $this->datetime, 2) is assigned to $value
    in include/fields/DateTimeField.php on line 47
  12. Data is passed through convertToDBFormat(), and self::convertToDBFormat($value[0]) is assigned to $insert_date
    in include/fields/DateTimeField.php on line 53
  13. $insert_date is returned
    in include/fields/DateTimeField.php on line 55
  14. new DateTimeField($value)->getDBInsertDateValue() is returned
    in app/Fields/Date.php on line 168
  15. App\Validator::dateInUserFormat($input) ? $convert ? App\Fields\Date::formatToDB($input) : $input : null is assigned to $value
    in app/Purifier.php on line 451
  16. $value is returned
    in app/Purifier.php on line 569
  17. App\Purifier::purifyByType($this->rawValues[$key], $type, $convert) is assigned to property Request::$purifiedValuesByType
    in app/Request.php on line 170
  18. Read from property Request::$purifiedValuesByType, and $this->purifiedValuesByType[$key][$type] is returned
    in app/Request.php on line 167
  19. $request->getByType('lang', 1) is assigned to $lang
    in modules/Settings/LangManagement/actions/Export.php on line 21
  20. LanguageExport::exportLanguage() is called
    in modules/Settings/LangManagement/actions/Export.php on line 24
  21. Enters via parameter $languageCode
    in vtlib/Vtiger/LanguageExport.php on line 44
  22. Zip::download() is called
    in vtlib/Vtiger/LanguageExport.php on line 67
  23. Enters via parameter $name
    in app/Zip.php on line 282

Response Splitting Attacks

Allowing an attacker to set a response header, opens your application to response splitting attacks; effectively allowing an attacker to send any response, he would like.

General Strategies to prevent injection

In general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:

if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) {
    throw new \InvalidArgumentException('This input is not allowed.');
}

For numeric data, we recommend to explicitly cast the data:

$sanitized = (integer) $tainted;
Loading history...
291
		header('accept-ranges: bytes');
292
		header('content-length: ' . filesize($fileName));
293
		readfile($fileName);
294
		unlink($fileName);
295
	}
296
}
297