Issues (992)

Security Analysis    not enabled

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
Response Splitting can be used to send arbitrary responses.
  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.
  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.
  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
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
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.

class/PhotoUploader.php (1 issue)

1
<?php
2
3
namespace XoopsModules\Extgallery;
4
5
/**
6
 * ExtGallery Class Manager
7
 *
8
 * You may not change or alter any portion of this comment or credits
9
 * of supporting developers from this source code or any supporting source code
10
 * which is considered copyrighted (c) material of the original comment or credit authors.
11
 * This program is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14
 *
15
 * @copyright   {@link https://xoops.org/ XOOPS Project}
16
 * @license     GNU GPL 2 (https://www.gnu.org/licenses/old-licenses/gpl-2.0.html)
17
 * @author      Zoullou (http://www.zoullou.net)
18
 * @package     ExtGallery
19
 */
20
21
use Xmf\Request;
22
use XoopsModules\Extgallery;
23
24
/**
25
 * Class PhotoUploader
26
 * @package XoopsModules\Extgallery
27
 */
28
class PhotoUploader
29
{
30
    public $uploadDir;
31
    public $savedDestination;
32
    public $savedFilename;
33
    public $maxFileSize;
34
    public $maxWidth;
35
    public $maxHeight;
36
    public $isError;
37
    public $error;
38
    public $checkMd5;
39
40
    /**
41
     * Extgallery\PhotoUploader constructor.
42
     * @param      $uploadDir
43
     * @param int  $maxFileSize
44
     * @param null $maxWidth
45
     * @param null $maxHeight
46
     */
47
    public function __construct($uploadDir, $maxFileSize = 0, $maxWidth = null, $maxHeight = null)
48
    {
49
        $this->uploadDir   = $uploadDir;
50
        $this->maxFileSize = (int)$maxFileSize;
51
        if (isset($maxWidth)) {
52
            $this->maxWidth = (int)$maxWidth;
53
        }
54
        if (isset($maxHeight)) {
55
            $this->maxHeight = (int)$maxHeight;
56
        }
57
58
        $this->isError  = false;
59
        $this->error    = '';
60
        $this->checkMd5 = true;
61
    }
62
63
    /**
64
     * @param $file
65
     *
66
     * @return bool
67
     */
68
    public function fetchPhoto($file)
69
    {
70
        $jupart  = Request::getInt('jupart', 0, 'POST');
71
        $jufinal = Request::getInt('jufinal', 1, 'POST');
72
        $md5sums = $_POST['md5sum'][0] ?? null;
73
74
        if ('' == $this->uploadDir) {
75
            $this->abort('upload dir not defined');
76
77
            return false;
78
        }
79
80
        if (!\is_dir($this->uploadDir)) {
81
            $this->abort('fail to open upload dir');
82
83
            return false;
84
        }
85
86
        if (!\is_writable($this->uploadDir)) {
87
            $this->abort('upload dir not writable');
88
89
            return false;
90
        }
91
92
        if ($this->checkMd5 && !isset($md5sums)) {
93
            $this->abort('Expecting an MD5 checksum');
94
95
            return false;
96
        }
97
98
        $dstdir  = $this->uploadDir;
99
        $dstname = $dstdir . '/juvar.' . \session_id();
100
        $tmpname = $dstdir . '/juvar.tmp' . \session_id();
101
102
        if (!\move_uploaded_file($file['tmp_name'], $tmpname)) {
103
            $this->abort('Unable to move uploaded file');
104
105
            return false;
106
        }
107
108
        if ($jupart) {
109
            // got a chunk of a multi-part upload
110
            $len                       = \filesize($tmpname);
111
            $_SESSION['juvar.tmpsize'] += $len;
112
            if ($len > 0) {
113
                $src = \fopen($tmpname, 'rb');
114
                $dst = \fopen($dstname, (1 == $jupart) ? 'wb' : 'ab');
115
                while ($len > 0) {
116
                    $rlen = ($len > 8192) ? 8192 : $len;
117
                    $buf  = \fread($src, $rlen);
118
                    if (!$buf) {
119
                        \fclose($src);
120
                        \fclose($dst);
121
                        \unlink($dstname);
122
                        $this->abort('read IO error');
123
124
                        return false;
125
                    }
126
                    if (!\fwrite($dst, $buf, $rlen)) {
127
                        \fclose($src);
128
                        \fclose($dst);
129
                        \unlink($dstname);
130
                        $this->abort('write IO error');
131
132
                        return false;
133
                    }
134
                    $len -= $rlen;
135
                }
136
                \fclose($src);
137
                \fclose($dst);
138
                \unlink($tmpname);
139
            }
140
            if ($jufinal) {
141
                // This is the last chunk. Check total lenght and rename it to it's final name.
142
                $dlen = \filesize($dstname);
143
                if ($dlen != $_SESSION['juvar.tmpsize']) {
144
                    $this->abort('file size mismatch');
145
146
                    return false;
147
                }
148
                if ($this->checkMd5 && ($md5sums != \md5_file($dstname))) {
149
                    $this->abort('MD5 checksum mismatch');
150
151
                    return false;
152
                }
153
                // remove zero sized files
154
                if ($dlen > 0) {
155
                    if (!$this->_saveFile($dstname, $file['name'])) {
156
                        return false;
157
                    }
158
                } else {
159
                    $this->abort('0 file size');
160
161
                    return false;
162
                }
163
                // reset session var
164
                $_SESSION['juvar.tmpsize'] = 0;
165
            }
166
        } else {
167
            // Got a single file upload. Trivial.
168
            if ($this->checkMd5 && $md5sums != \md5_file($tmpname)) {
169
                $this->abort('MD5 checksum mismatch');
170
171
                return false;
172
            }
173
            if (!$this->_saveFile($tmpname, $file['name'])) {
174
                return false;
175
            }
176
        }
177
178
        return true;
179
    }
180
181
    /**
182
     * @param $tmpDestination
183
     * @param $fileName
184
     *
185
     * @return bool
186
     */
187
    public function _saveFile($tmpDestination, $fileName)
188
    {
189
        $this->savedFilename    = $fileName;
190
        $this->savedDestination = $this->uploadDir . $fileName;
191
192
        if (!$this->_checkFile($tmpDestination)) {
193
            return false;
194
        }
195
196
        if (!\rename($tmpDestination, $this->savedDestination)) {
197
            $this->abort('error renaming file');
198
199
            return false;
200
        }
201
202
        @\chmod($this->savedDestination, 0644);
203
204
        return true;
205
    }
206
207
    /**
208
     * @param $tmpDestination
209
     *
210
     * @return bool
211
     */
212
    public function _checkFile($tmpDestination)
213
    {
214
        //  $imageExtensions = array(IMAGETYPE_GIF => 'gif', IMAGETYPE_JPEG => 'jpeg', IMAGETYPE_JPG => 'jpg', IMAGETYPE_PNG => 'png');
215
216
        $valid_types = [IMAGETYPE_GIF, IMAGETYPE_JPEG, IMAGETYPE_PNG, IMAGETYPE_BMP];
0 ignored issues
show
The assignment to $valid_types is dead and can be removed.
Loading history...
217
218
        $imageExtensions = ['gif', 'jpg', 'jpeg', 'png'];
219
220
        // Check IE XSS before returning success
221
        $ext       = mb_strtolower(mb_substr(mb_strrchr($this->savedDestination, '.'), 1));
222
        $photoInfo = \getimagesize($tmpDestination);
223
        if (false === $photoInfo || $imageExtensions[(int)$photoInfo[2]] != $ext) {
224
            $this->abort('Suspicious image upload refused');
225
226
            return false;
227
        }
228
229
        if (!$this->checkMaxFileSize($tmpDestination)) {
230
            $this->abort('Max file size error');
231
232
            return false;
233
        }
234
235
        if (!$this->checkMaxWidth($photoInfo)) {
236
            $this->abort('Max width error');
237
238
            return false;
239
        }
240
241
        if (!$this->checkMaxHeight($photoInfo)) {
242
            $this->abort('Max height error');
243
244
            return false;
245
        }
246
247
        if (!$this->checkImageType($photoInfo)) {
248
            $this->abort('File type not allowed');
249
250
            return false;
251
        }
252
253
        return true;
254
    }
255
256
    /**
257
     * @param $file
258
     *
259
     * @return bool
260
     */
261
    public function checkMaxFileSize($file)
262
    {
263
        if (!isset($this->maxFileSize)) {
264
            return true;
265
        }
266
267
        if (\filesize($file) > $this->maxFileSize) {
268
            return false;
269
        }
270
271
        return true;
272
    }
273
274
    /**
275
     * @param $photoInfo
276
     *
277
     * @return bool
278
     */
279
    public function checkMaxWidth($photoInfo)
280
    {
281
        if (!isset($this->maxWidth)) {
282
            return true;
283
        }
284
285
        if ($photoInfo[0] > $this->maxWidth) {
286
            return false;
287
        }
288
289
        return true;
290
    }
291
292
    /**
293
     * @param $photoInfo
294
     *
295
     * @return bool
296
     */
297
    public function checkMaxHeight($photoInfo)
298
    {
299
        if (!isset($this->maxHeight)) {
300
            return true;
301
        }
302
303
        if ($photoInfo[1] > $this->maxHeight) {
304
            return false;
305
        }
306
307
        return true;
308
    }
309
310
    /**
311
     * @param $photoInfo
312
     *
313
     * @return bool
314
     */
315
    public function checkImageType($photoInfo)
316
    {
317
        //  $allowedMimeTypes = array(IMAGETYPE_GIF, IMAGETYPE_JPEG, IMAGETYPE_JPG, IMAGETYPE_PNG);
318
        $allowedMimeTypes = ['image/gif', 'image/jpg', 'image/jpeg', 'image/pjpeg', 'image/png', 'image/x-png'];
319
        if (!\in_array($photoInfo['mime'], $allowedMimeTypes)) {
320
            return false;
321
        }
322
323
        return true;
324
    }
325
326
    /**
327
     * @param string $msg
328
     */
329
    public function abort($msg = '')
330
    {
331
        // remove all uploaded files of *this* request
332
        if (isset($_FILES)) {
333
            foreach ($_FILES as $key => $val) {
334
                //@unlink($val['tmp_name']);
335
            }
336
        }
337
338
        // remove accumulated file, if any.
339
        //@unlink($this->uploadDir .'/juvar.'.session_id());
340
        //@unlink($this->uploadDir .'/juvar.tmp'.session_id());
341
342
        // reset session var
343
        $_SESSION['juvar.tmpsize'] = 0;
344
345
        $this->isError = true;
346
        $this->error   = $msg;
347
    }
348
349
    /**
350
     * @return bool
351
     */
352
    public function isError()
353
    {
354
        return $this->isError;
355
    }
356
357
    /**
358
     * @return string
359
     */
360
    public function getError()
361
    {
362
        return $this->error;
363
    }
364
365
    public function getSavedDestination()
366
    {
367
        return $this->savedDestination;
368
    }
369
370
    public function getSavedFilename()
371
    {
372
        return $this->savedFilename;
373
    }
374
}
375