CVE-2022-1850: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal') (CWE-22)
value: Path Traversal in GitHub repository filegator/filegator prior to 7.8.0.
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.1
Published: 2022-05-24T15:15Z
Repository: https://github.com/filegator/filegator
Language: PHP
Files changed: backend/Services/Storage/Filesystem.php, tests/backend/Unit/FilesystemTest.php
Commit date: 2022-05-24 12:24:30 +0200
Commit message: path traversal fix

Diff: diff --git a/backend/Services/Storage/Filesystem.php b/backend/Services/Storage/Filesystem.php
index 26720ac..fe3807f 100644
--- a/backend/Services/Storage/Filesystem.php
+++ b/backend/Services/Storage/Filesystem.php
@@ -239,10 +239,11 @@ class Filesystem implements Service
             || strpos($path, '..'.$this->separator) !== false
             || strpos($path, $this->separator.'..') !== false
         ) {
             $path = $this->separator;
         }
+
         return $this->joinPaths($this->getPathPrefix(), $path);
     }
 
     private function stripPathPrefix(string $path): string
     {
@@ -264,10 +265,13 @@ class Filesystem implements Service
         return $this->separator.trim($dir, $this->separator).$this->separator;
     }
 
     private function joinPaths(string $path1, string $path2): string
     {
+        $path1 = $this->escapeDots($path1);
+        $path2 = $this->escapeDots($path2);
+
         if (! $path2 || ! trim($path2, $this->separator)) {
             return $this->addSeparators($path1);
         }
 
         return $this->addSeparators($path1).ltrim($path2, $this->separator);
@@ -293,6 +297,16 @@ class Filesystem implements Service
 
         $tmp = explode($this->separator, trim($path, $this->separator));
 
         return  (string) array_pop($tmp);
     }
+
+    private function escapeDots(string $path): string
+    {
+        $path = preg_replace('/\\\+\.{2,}/', '', $path);
+        $path = preg_replace('/\.{2,}\\\+/', '', $path);
+        $path = preg_replace('/\/+\.{2,}/', '', $path);
+        $path = preg_replace('/\.{2,}\/+/', '', $path);
+
+        return $path;
+    }
 }
diff --git a/tests/backend/Unit/FilesystemTest.php b/tests/backend/Unit/FilesystemTest.php
index 97a1568..02ff4b2 100644
--- a/tests/backend/Unit/FilesystemTest.php
+++ b/tests/backend/Unit/FilesystemTest.php
@@ -459,10 +459,22 @@ class FilesystemTest extends TestCase
         // no escaping path to upper dir
         $this->assertEquals('/john/', $this->invokeMethod($this->storage, 'applyPathPrefix', ['/..']));
         $this->assertEquals('/john/', $this->invokeMethod($this->storage, 'applyPathPrefix', ['..']));
         $this->assertEquals('/john/', $this->invokeMethod($this->storage, 'applyPathPrefix', ['../']));
         $this->assertEquals('/john/', $this->invokeMethod($this->storage, 'applyPathPrefix', ['/sub/../../']));
+        $this->assertEquals('/john/', $this->invokeMethod($this->storage, 'applyPathPrefix', ['..\\']));
+        $this->assertEquals('/john/', $this->invokeMethod($this->storage, 'applyPathPrefix', ['..\\\\']));
+        $this->assertEquals('/john/', $this->invokeMethod($this->storage, 'applyPathPrefix', ['..\\..\\']));
+        $this->assertEquals('/john/', $this->invokeMethod($this->storage, 'applyPathPrefix', ['\\\\..']));
+        $this->assertEquals('/john/', $this->invokeMethod($this->storage, 'applyPathPrefix', ['\\..\\..']));
+        $this->assertEquals('/john/\\.', $this->invokeMethod($this->storage, 'applyPathPrefix', ['\\.\\...']));
+        $this->assertEquals('/john/\\.', $this->invokeMethod($this->storage, 'applyPathPrefix', ['\\.\\....']));
+        $this->assertEquals('/john/.\\.', $this->invokeMethod($this->storage, 'applyPathPrefix', ['.\\.\\...']));
+        $this->assertEquals('/john/.', $this->invokeMethod($this->storage, 'applyPathPrefix', ['..\\.\\...']));
+        $this->assertEquals('/john/.', $this->invokeMethod($this->storage, 'applyPathPrefix', ['..\\.\\...']));
+        $this->assertEquals('/john/.', $this->invokeMethod($this->storage, 'applyPathPrefix', ['..\\.\\......']));
+        $this->assertEquals('/john/.\\', $this->invokeMethod($this->storage, 'applyPathPrefix', ['...\\.\\......\\']));
     }
 
     public function testStripPathPrefix()
     {
         $this->storage->setPathPrefix('/john/');

Fixed code: 
        $path1 = $this->escapeDots($path1);
        $path2 = $this->escapeDots($path2);


    private function escapeDots(string $path): string
    {
        $path = preg_replace('/\\\+\.{2,}/', '', $path);
        $path = preg_replace('/\.{2,}\\\+/', '', $path);
        $path = preg_replace('/\/+\.{2,}/', '', $path);
        $path = preg_replace('/\.{2,}\/+/', '', $path);

        return $path;
    }
        $this->assertEquals('/john/', $this->invokeMethod($this->storage, 'applyPathPrefix', ['..\\']));
        $this->assertEquals('/john/', $this->invokeMethod($this->storage, 'applyPathPrefix', ['..\\\\']));
        $this->assertEquals('/john/', $this->invokeMethod($this->storage, 'applyPathPrefix', ['..\\..\\']));
        $this->assertEquals('/john/', $this->invokeMethod($this->storage, 'applyPathPrefix', ['\\\\..']));
        $this->assertEquals('/john/', $this->invokeMethod($this->storage, 'applyPathPrefix', ['\\..\\..']));
        $this->assertEquals('/john/\\.', $this->invokeMethod($this->storage, 'applyPathPrefix', ['\\.\\...']));
        $this->assertEquals('/john/\\.', $this->invokeMethod($this->storage, 'applyPathPrefix', ['\\.\\....']));
        $this->assertEquals('/john/.\\.', $this->invokeMethod($this->storage, 'applyPathPrefix', ['.\\.\\...']));
        $this->assertEquals('/john/.', $this->invokeMethod($this->storage, 'applyPathPrefix', ['..\\.\\...']));
        $this->assertEquals('/john/.', $this->invokeMethod($this->storage, 'applyPathPrefix', ['..\\.\\...']));
        $this->assertEquals('/john/.', $this->invokeMethod($this->storage, 'applyPathPrefix', ['..\\.\\......']));
        $this->assertEquals('/john/.\\', $this->invokeMethod($this->storage, 'applyPathPrefix', ['...\\.\\......\\']));