YetiForceCompany /
YetiForceCRM
This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include, or for example
via PHP's auto-loading mechanism.
| 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
|
|||||||
| 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
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
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. 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
Response Splitting AttacksAllowing 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 injectionIn 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 |
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.