CVE-2020-36193: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal') (CWE-22)
value: Tar.php in Archive_Tar through 1.4.11 allows write operations with Directory Traversal due to inadequate checking of symbolic links, a related issue to CVE-2020-28948.
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: 7.5
Published: 2021-01-18T20:15Z
Repository: https://github.com/pear/Archive_Tar
Language: PHP
Files changed: Archive/Tar.php, tests/out_of_path_fnames.phpt
Commit date: 2020-12-14 14:15:41 +0100
Commit message: Disallow symlinks to out-of-path filenames

Diff: diff --git a/Archive/Tar.php b/Archive/Tar.php
index f461c1e..76771d5 100644
--- a/Archive/Tar.php
+++ b/Archive/Tar.php
@@ -2122,10 +2122,18 @@ class Archive_Tar extends PEAR
                                 );
                                 return false;
                             }
                         }
                     } elseif ($v_header['typeflag'] == "2") {
+                        if (strpos(realpath(dirname($v_header['link'])), realpath($p_path)) !== 0) {
+                            $this->_error(
+                                 'Out-of-path file extraction {'
+                                 . $v_header['filename'] . ' --> ' .
+                                 $v_header['link'] . '}'
+                            );
+                            return false;
+                        }
                         if (!$p_symlinks) {
                             $this->_warning('Symbolic links are not allowed. '
                                 . 'Unable to extract {'
                                 . $v_header['filename'] . '}'
                             );
diff --git a/tests/out_of_path_fnames.phpt b/tests/out_of_path_fnames.phpt
new file mode 100644
index 0000000..a26100c
--- /dev/null
+++ b/tests/out_of_path_fnames.phpt
@@ -0,0 +1,18 @@
+--TEST--
+tests writes to out-of-path filenames
+--SKIPIF--
+--FILE--
+<?php
+require_once dirname(__FILE__) . '/setup.php.inc';
+$tar = new Archive_Tar(dirname(__FILE__) . '/out_of_path_symlink.tar');
+$tar->extract();
+$phpunit->assertErrors(array(array('package' => 'PEAR_Error', 'message' => "Out-of-path file extraction {symlink --> /tmp/}")), 'after 1');
+$phpunit->assertFileNotExists('symlink/whatever-filename', 'Out-of-path filename should not have succeeded');
+echo 'tests done';
+?>
+--CLEAN--
+<?php
+@unlink("symlink");
+?>
+--EXPECT--
+tests done
diff --git a/tests/out_of_path_symlink.tar b/tests/out_of_path_symlink.tar
new file mode 100644
index 0000000..fc6854b
Binary files /dev/null and b/tests/out_of_path_symlink.tar differ

Fixed code:                         if (strpos(realpath(dirname($v_header['link'])), realpath($p_path)) !== 0) {
                            $this->_error(
                                 'Out-of-path file extraction {'
                                 . $v_header['filename'] . ' --> ' .
                                 $v_header['link'] . '}'
                            );
                            return false;
                        }
--TEST--
tests writes to out-of-path filenames
--SKIPIF--
--FILE--
<?php
require_once dirname(__FILE__) . '/setup.php.inc';
$tar = new Archive_Tar(dirname(__FILE__) . '/out_of_path_symlink.tar');
$tar->extract();
$phpunit->assertErrors(array(array('package' => 'PEAR_Error', 'message' => "Out-of-path file extraction {symlink --> /tmp/}")), 'after 1');
$phpunit->assertFileNotExists('symlink/whatever-filename', 'Out-of-path filename should not have succeeded');
echo 'tests done';
?>
--CLEAN--
<?php
@unlink("symlink");
?>
--EXPECT--
tests done