CVE-2020-5237: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal') (CWE-22)
value: Multiple relative path traversal vulnerabilities in the oneup/uploader-bundle before 1.9.3 and 2.1.5 allow remote attackers to upload, copy, and modify files on the filesystem (potentially leading to arbitrary code execution) via the (1) filename parameter to BlueimpController.php; the (2) dzchunkindex, (3) dzuuid, or (4) filename parameter to DropzoneController.php; the (5) qqpartindex, (6) qqfilename, or (7) qquuid parameter to FineUploaderController.php; the (8) x-file-id or (9) x-file-name parameter to MooUploadController.php; or the (10) name or (11) chunk parameter to PluploadController.php. This is fixed in versions 1.9.3 and 2.1.5.
CWE description: The product uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a restricted parent directory, but the product does not properly neutralize special elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory.
CVSS v3 base score: 8.8
Published: 2020-02-05T14:15Z
Repository: https://github.com/1up-lab/OneupUploaderBundle
Language: PHP
Files changed: Controller/DropzoneController.php, Controller/FineUploaderController.php, Controller/PluploadController.php, Uploader/Chunk/Storage/FilesystemStorage.php, Uploader/Chunk/Storage/FlysystemStorage.php, Uploader/Chunk/Storage/GaufretteStorage.php
Commit date: 2020-02-04 11:37:36 +0100
Commit message: Merge pull request from GHSA-x8wj-6m73-gfqp


Diff: diff --git a/Controller/DropzoneController.php b/Controller/DropzoneController.php
index 95d7784..18709b9 100644
--- a/Controller/DropzoneController.php
+++ b/Controller/DropzoneController.php
@@ -40,12 +40,12 @@ class DropzoneController extends AbstractChunkedController
     }
 
     protected function parseChunkedRequest(Request $request)
     {
         $totalChunkCount = $request->get('dztotalchunkcount');
-        $index = $request->get('dzchunkindex');
-        $last = ((int) $index + 1) === (int) $totalChunkCount;
+        $index = (int) $request->get('dzchunkindex');
+        $last = ($index + 1) === (int) $totalChunkCount;
         $uuid = $request->get('dzuuid');
 
         /**
          * @var UploadedFile
          */
diff --git a/Controller/FineUploaderController.php b/Controller/FineUploaderController.php
index 3357cb7..64fec95 100644
--- a/Controller/FineUploaderController.php
+++ b/Controller/FineUploaderController.php
@@ -38,14 +38,14 @@ class FineUploaderController extends AbstractChunkedController
         return $this->createSupportedJsonResponse($response->assemble());
     }
 
     protected function parseChunkedRequest(Request $request)
     {
-        $index = $request->get('qqpartindex');
-        $total = $request->get('qqtotalparts');
+        $index = (int) $request->get('qqpartindex');
+        $total = (int) $request->get('qqtotalparts');
         $uuid = $request->get('qquuid');
         $orig = $request->get('qqfilename');
-        $last = ((int) $total - 1) === (int) $index;
+        $last = ($total - 1) === $index;
 
         return [$last, $uuid, $index, $orig];
     }
 }
diff --git a/Controller/PluploadController.php b/Controller/PluploadController.php
index 4697d38..aa43885 100644
--- a/Controller/PluploadController.php
+++ b/Controller/PluploadController.php
@@ -33,11 +33,11 @@ class PluploadController extends AbstractChunkedController
     protected function parseChunkedRequest(Request $request)
     {
         $session = $this->container->get('session');
 
         $orig = $request->get('name');
-        $index = $request->get('chunk');
+        $index = (int) $request->get('chunk');
         $last = (int) $request->get('chunks') - 1 === (int) $request->get('chunk');
 
         // it is possible, that two clients send a file with the
         // exact same filename, therefore we have to add the session
         // to the uuid otherwise we will get a mess
diff --git a/Uploader/Chunk/Storage/FilesystemStorage.php b/Uploader/Chunk/Storage/FilesystemStorage.php
index 57ae7f6..196fc6d 100644
--- a/Uploader/Chunk/Storage/FilesystemStorage.php
+++ b/Uploader/Chunk/Storage/FilesystemStorage.php
@@ -36,10 +36,13 @@ class FilesystemStorage implements ChunkStorageInterface
         }
     }
 
     public function addChunk($uuid, $index, UploadedFile $chunk, $original)
     {
+        // Prevent path traversal attacks
+        $uuid = basename($uuid);
+
         $filesystem = new Filesystem();
         $path = sprintf('%s/%s', $this->directory, $uuid);
         $name = sprintf('%s_%s', $index, $original);
 
         // create directory if it does not yet exist
@@ -104,10 +107,13 @@ class FilesystemStorage implements ChunkStorageInterface
         return true;
     }
 
     public function getChunks($uuid)
     {
+        // Prevent path traversal attacks
+        $uuid = basename($uuid);
+
         $finder = new Finder();
         $finder
             ->in(sprintf('%s/%s', $this->directory, $uuid))->files()->sort(function (\SplFileInfo $a, \SplFileInfo $b) {
                 $t = explode('_', $a->getBasename());
                 $s = explode('_', $b->getBasename());
diff --git a/Uploader/Chunk/Storage/FlysystemStorage.php b/Uploader/Chunk/Storage/FlysystemStorage.php
index f1ecb51..b715376 100644
--- a/Uploader/Chunk/Storage/FlysystemStorage.php
+++ b/Uploader/Chunk/Storage/FlysystemStorage.php
@@ -63,10 +63,13 @@ class FlysystemStorage implements ChunkStorageInterface
         }
     }
 
     public function addChunk($uuid, $index, UploadedFile $chunk, $original)
     {
+        // Prevent path traversal attacks
+        $uuid = basename($uuid);
+
         $this->unhandledChunk = [
             'uuid' => $uuid,
             'index' => $index,
             'chunk' => $chunk,
             'original' => $original,
@@ -134,10 +137,13 @@ class FlysystemStorage implements ChunkStorageInterface
         }
     }
 
     public function getChunks($uuid)
     {
+        // Prevent path traversal attacks
+        $uuid = basename($uuid);
+
         return $this->filesystem->listFiles($this->prefix.'/'.$uuid);
     }
 
     public function getFilesystem()
     {
diff --git a/Uploader/Chunk/Storage/GaufretteStorage.php b/Uploader/Chunk/Storage/GaufretteStorage.php
index 0b55de6..619dc66 100644
--- a/Uploader/Chunk/Storage/GaufretteStorage.php
+++ b/Uploader/Chunk/Storage/GaufretteStorage.php
@@ -96,10 +96,13 @@ class GaufretteStorage extends StreamManager implements ChunkStorageInterface
      * @param UploadedFile $chunk
      * @param              $original
      */
     public function addChunk($uuid, $index, UploadedFile $chunk, $original)
     {
+        // Prevent path traversal attacks
+        $uuid = basename($uuid);
+
         $this->unhandledChunk = [
             'uuid' => $uuid,
             'index' => $index,
             'chunk' => $chunk,
             'original' => $original,
@@ -168,10 +171,13 @@ class GaufretteStorage extends StreamManager implements ChunkStorageInterface
         $this->filesystem->delete($path);
     }
 
     public function getChunks($uuid)
     {
+        // Prevent path traversal attacks
+        $uuid = basename($uuid);
+
         $results = $this->filesystem->listKeys($this->prefix.'/'.$uuid);
 
         /* exclude files without an index, so if there is a completed file which
          * failed to upload it will not get mixed together with new one's chunks.
          */

Vulnerable code:         $index = $request->get('dzchunkindex');
        $last = ((int) $index + 1) === (int) $totalChunkCount;
        $index = $request->get('qqpartindex');
        $total = $request->get('qqtotalparts');
        $last = ((int) $total - 1) === (int) $index;
        $index = $request->get('chunk');
Fixed code:         $index = (int) $request->get('dzchunkindex');
        $last = ($index + 1) === (int) $totalChunkCount;
        $index = (int) $request->get('qqpartindex');
        $total = (int) $request->get('qqtotalparts');
        $last = ($total - 1) === $index;
        $index = (int) $request->get('chunk');
        // Prevent path traversal attacks
        $uuid = basename($uuid);

        // Prevent path traversal attacks
        $uuid = basename($uuid);

        // Prevent path traversal attacks
        $uuid = basename($uuid);

        // Prevent path traversal attacks
        $uuid = basename($uuid);

        // Prevent path traversal attacks
        $uuid = basename($uuid);

        // Prevent path traversal attacks
        $uuid = basename($uuid);