Skip to content

Commit dda81f0

Browse files
committed
Fix bug #70019 - limit extracted files to given directory
1 parent 0e09009 commit dda81f0

File tree

3 files changed

+68
-4
lines changed

3 files changed

+68
-4
lines changed

ext/phar/phar_object.c

+46-4
Original file line numberDiff line numberDiff line change
@@ -4200,6 +4200,9 @@ static int phar_extract_file(zend_bool overwrite, phar_entry_info *entry, char *
42004200
char *fullpath;
42014201
const char *slash;
42024202
mode_t mode;
4203+
cwd_state new_state;
4204+
char *filename;
4205+
size_t filename_len;
42034206

42044207
if (entry->is_mounted) {
42054208
/* silently ignore mounted entries */
@@ -4209,8 +4212,39 @@ static int phar_extract_file(zend_bool overwrite, phar_entry_info *entry, char *
42094212
if (entry->filename_len >= sizeof(".phar")-1 && !memcmp(entry->filename, ".phar", sizeof(".phar")-1)) {
42104213
return SUCCESS;
42114214
}
4215+
/* strip .. from path and restrict it to be under dest directory */
4216+
new_state.cwd = (char*)malloc(2);
4217+
new_state.cwd[0] = DEFAULT_SLASH;
4218+
new_state.cwd[1] = '\0';
4219+
new_state.cwd_length = 1;
4220+
if (virtual_file_ex(&new_state, entry->filename, NULL, CWD_EXPAND TSRMLS_CC) != 0 ||
4221+
new_state.cwd_length <= 1) {
4222+
if (EINVAL == errno && entry->filename_len > 50) {
4223+
char *tmp = estrndup(entry->filename, 50);
4224+
spprintf(error, 4096, "Cannot extract \"%s...\" to \"%s...\", extracted filename is too long for filesystem", tmp, dest);
4225+
efree(tmp);
4226+
} else {
4227+
spprintf(error, 4096, "Cannot extract \"%s\", internal error", entry->filename);
4228+
}
4229+
free(new_state.cwd);
4230+
return FAILURE;
4231+
}
4232+
filename = new_state.cwd + 1;
4233+
filename_len = new_state.cwd_length - 1;
4234+
#ifdef PHP_WIN32
4235+
/* unixify the path back, otherwise non zip formats might be broken */
4236+
{
4237+
int cnt = filename_len;
4238+
4239+
do {
4240+
if ('\\' == filename[cnt]) {
4241+
filename[cnt] = '/';
4242+
}
4243+
} while (cnt-- >= 0);
4244+
}
4245+
#endif
42124246

4213-
len = spprintf(&fullpath, 0, "%s/%s", dest, entry->filename);
4247+
len = spprintf(&fullpath, 0, "%s/%s", dest, filename);
42144248

42154249
if (len >= MAXPATHLEN) {
42164250
char *tmp;
@@ -4224,33 +4258,37 @@ static int phar_extract_file(zend_bool overwrite, phar_entry_info *entry, char *
42244258
spprintf(error, 4096, "Cannot extract \"%s\" to \"%s...\", extracted filename is too long for filesystem", entry->filename, fullpath);
42254259
}
42264260
efree(fullpath);
4261+
free(new_state.cwd);
42274262
return FAILURE;
42284263
}
42294264

42304265
if (!len) {
42314266
spprintf(error, 4096, "Cannot extract \"%s\", internal error", entry->filename);
42324267
efree(fullpath);
4268+
free(new_state.cwd);
42334269
return FAILURE;
42344270
}
42354271

42364272
if (PHAR_OPENBASEDIR_CHECKPATH(fullpath)) {
42374273
spprintf(error, 4096, "Cannot extract \"%s\" to \"%s\", openbasedir/safe mode restrictions in effect", entry->filename, fullpath);
42384274
efree(fullpath);
4275+
free(new_state.cwd);
42394276
return FAILURE;
42404277
}
42414278

42424279
/* let see if the path already exists */
42434280
if (!overwrite && SUCCESS == php_stream_stat_path(fullpath, &ssb)) {
42444281
spprintf(error, 4096, "Cannot extract \"%s\" to \"%s\", path already exists", entry->filename, fullpath);
42454282
efree(fullpath);
4283+
free(new_state.cwd);
42464284
return FAILURE;
42474285
}
42484286

42494287
/* perform dirname */
4250-
slash = zend_memrchr(entry->filename, '/', entry->filename_len);
4288+
slash = zend_memrchr(filename, '/', filename_len);
42514289

42524290
if (slash) {
4253-
fullpath[dest_len + (slash - entry->filename) + 1] = '\0';
4291+
fullpath[dest_len + (slash - filename) + 1] = '\0';
42544292
} else {
42554293
fullpath[dest_len] = '\0';
42564294
}
@@ -4260,23 +4298,27 @@ static int phar_extract_file(zend_bool overwrite, phar_entry_info *entry, char *
42604298
if (!php_stream_mkdir(fullpath, entry->flags & PHAR_ENT_PERM_MASK, PHP_STREAM_MKDIR_RECURSIVE, NULL)) {
42614299
spprintf(error, 4096, "Cannot extract \"%s\", could not create directory \"%s\"", entry->filename, fullpath);
42624300
efree(fullpath);
4301+
free(new_state.cwd);
42634302
return FAILURE;
42644303
}
42654304
} else {
42664305
if (!php_stream_mkdir(fullpath, 0777, PHP_STREAM_MKDIR_RECURSIVE, NULL)) {
42674306
spprintf(error, 4096, "Cannot extract \"%s\", could not create directory \"%s\"", entry->filename, fullpath);
42684307
efree(fullpath);
4308+
free(new_state.cwd);
42694309
return FAILURE;
42704310
}
42714311
}
42724312
}
42734313

42744314
if (slash) {
4275-
fullpath[dest_len + (slash - entry->filename) + 1] = '/';
4315+
fullpath[dest_len + (slash - filename) + 1] = '/';
42764316
} else {
42774317
fullpath[dest_len] = '/';
42784318
}
42794319

4320+
filename = NULL;
4321+
free(new_state.cwd);
42804322
/* it is a standalone directory, job done */
42814323
if (entry->is_dir) {
42824324
efree(fullpath);

ext/phar/tests/bug70019.phpt

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
--TEST--
2+
Bug #70019 Files extracted from archive may be placed outside of destination directory
3+
--FILE--
4+
<?php
5+
$dir = __DIR__."/bug70019";
6+
$phar = new PharData(__DIR__."/bug70019.zip");
7+
if(!is_dir($dir)) {
8+
mkdir($dir);
9+
}
10+
$phar->extractTo($dir);
11+
var_dump(file_exists("$dir/ThisIsATestFile.txt"));
12+
?>
13+
===DONE===
14+
--CLEAN--
15+
<?php
16+
$dir = __DIR__."/bug70019";
17+
unlink("$dir/ThisIsATestFile.txt");
18+
rmdir($dir);
19+
?>
20+
--EXPECTF--
21+
bool(true)
22+
===DONE===

ext/phar/tests/bug70019.zip

184 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)