Moderate severityNVD Advisory· Published Mar 4, 2021· Updated Feb 25, 2026
[20210308] - Core - Path Traversal within joomla/archive zip class
CVE-2021-26028
Description
An issue was discovered in Joomla! 3.0.0 through 3.9.24. Extracting an specifilcy crafted zip package could write files outside of the intended path.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
joomla/archivePackagist | < 1.1.10 | 1.1.10 |
Affected products
1- Range: 3.0.0-3.9.24
Patches
132c9009a1020Security - Ensure unpacked files stay below destination path
2 files changed · +43 −17
composer.json+1 −1 modified@@ -7,7 +7,7 @@ "license": "GPL-2.0-or-later", "require": { "php": "^5.3.10|~7.0", - "joomla/filesystem": "~1.3|~2.0@beta" + "joomla/filesystem": "~1.6|~2.0@beta" }, "require-dev": { "joomla/coding-standards": "~2.0@alpha",
src/Zip.php+42 −16 modified@@ -204,7 +204,7 @@ public static function hasNativeSupport() * * @since 1.0 */ - public function checkZipData(&$data) + public function checkZipData($data) { return strpos($data, $this->fileHeader) !== false; } @@ -237,24 +237,29 @@ protected function extractCustom($archive, $destination) throw new \RuntimeException('Get ZIP Information failed'); } - for ($i = 0, $n = \count($this->metadata); $i < $n; $i++) + foreach ($this->metadata as $i => $metadata) { - $lastPathCharacter = substr($this->metadata[$i]['name'], -1, 1); + $lastPathCharacter = substr($metadata['name'], -1, 1); if ($lastPathCharacter !== '/' && $lastPathCharacter !== '\\') { $buffer = $this->getFileData($i); - $path = Path::clean($destination . '/' . $this->metadata[$i]['name']); + $path = Path::clean($destination . '/' . $metadata['name']); + + if (!$this->isBelow($destination, $destination . '/' . $metadata['name'])) + { + throw new \RuntimeException('Unable to write outside of destination path', 100); + } // Make sure the destination folder exists if (!Folder::create(\dirname($path))) { - throw new \RuntimeException('Unable to create destination folder ' . \dirname($path)); + throw new \RuntimeException('Unable to create destination folder'); } if (!File::write($path, $buffer)) { - throw new \RuntimeException('Unable to write entry to file ' . $path); + throw new \RuntimeException('Unable to write file'); } } } @@ -305,6 +310,11 @@ protected function extractNative($archive, $destination) throw new \RuntimeException('Unable to read ZIP entry'); } + if (!$this->isBelow($destination, $destination . '/' . $file)) + { + throw new \RuntimeException('Unable to write outside of destination path', 100); + } + if (File::write($destination . '/' . $file, $buffer) === false) { throw new \RuntimeException('Unable to write ZIP entry to file ' . $destination . '/' . $file); @@ -322,13 +332,13 @@ protected function extractNative($archive, $destination) * <pre> * KEY: Position in zipfile * VALUES: 'attr' -- File attributes - * 'crc' -- CRC checksum - * 'csize' -- Compressed file size - * 'date' -- File modification time - * 'name' -- Filename - * 'method'-- Compression method - * 'size' -- Original file size - * 'type' -- File type + * 'crc' -- CRC checksum + * 'csize' -- Compressed file size + * 'date' -- File modification time + * 'name' -- Filename + * 'method'-- Compression method + * 'size' -- Original file size + * 'type' -- File type * </pre> * * @param string $data The ZIP archive buffer. @@ -338,7 +348,7 @@ protected function extractNative($archive, $destination) * @since 1.0 * @throws \RuntimeException */ - private function readZipInfo(&$data) + private function readZipInfo($data) { $entries = array(); @@ -546,7 +556,7 @@ private function addToZipFile(array &$file, array &$contents, array &$ctrldir) $uncLen = \strlen($data); $crc = crc32($data); $zdata = gzcompress($data); - $zdata = substr(substr($zdata, 0, \strlen($zdata) - 4), 2); + $zdata = substr(substr($zdata, 0, -4), 2); $cLen = \strlen($zdata); // CRC 32 information. @@ -643,7 +653,7 @@ private function addToZipFile(array &$file, array &$contents, array &$ctrldir) * @since 1.0 * @todo Review and finish implementation */ - private function createZipFile(array &$contents, array &$ctrlDir, $path) + private function createZipFile(array $contents, array $ctrlDir, $path) { $data = implode('', $contents); $dir = implode('', $ctrlDir); @@ -665,4 +675,20 @@ private function createZipFile(array &$contents, array &$ctrlDir, $path) return File::write($path, $buffer); } + + /** + * Check if a path is below a given destination path + * + * @param string $destination + * @param string $path + * + * @return boolean + */ + private function isBelow($destination, $path) + { + $absoluteRoot = Path::clean(Path::resolve($destination)); + $absolutePath = Path::clean(Path::resolve($path)); + + return strpos($absolutePath, $absoluteRoot) === 0; + } }
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- developer.joomla.org/security-centre/848-20210308-core-path-traversal-within-joomla-archive-zip-class.htmlghsax_refsource_MISCvendor-advisoryWEB
- github.com/advisories/GHSA-vgwr-773q-7j3cghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2021-26028ghsaADVISORY
- github.com/FriendsOfPHP/security-advisories/blob/master/joomla/archive/CVE-2021-26028.yamlghsaWEB
- github.com/joomla-framework/archive/commit/32c9009a1020d16bc1060c0d06339898b697cf2cghsaWEB
News mentions
0No linked articles in our index yet.