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
![]() $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
![]() |
|||||||
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 ![]() |
|||||||
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;
![]() |
|||||||
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.