Skip to content

Commit c263033

Browse files
AngelFQCclaude
authored andcommitted
Security: Contain backup-import document source paths within the extraction root
see advisory GHSA-g5xh-m3w7-jmcq Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 7d24b4c commit c263033

1 file changed

Lines changed: 26 additions & 4 deletions

File tree

src/CourseBundle/Component/CourseCopy/CourseRestorer.php

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1014,15 +1014,37 @@ public function restore_documents($session_id = 0, $respect_base_content = false
10141014
return $this->resourceFileAbsPathFromDocument($srcDoc) ?: null;
10151015
}
10161016

1017-
$p = $srcRoot.(string) $item->path;
1018-
if (is_file($p) && is_readable($p)) {
1017+
// Defense in depth against path traversal: $item->path comes from the
1018+
// deserialized backup metadata, so an authenticated user importing a tampered
1019+
// backup fully controls it. Only accept files that resolve *inside* the
1020+
// extraction root, so a forged path such as "../../../../etc/passwd" cannot
1021+
// read files outside the backup directory.
1022+
$containedRealPath = static function (string $root, string $relPath): ?string {
1023+
$rootReal = realpath(rtrim($root, '/'));
1024+
if (false === $rootReal) {
1025+
return null;
1026+
}
1027+
$candidate = realpath($root.$relPath);
1028+
if (false === $candidate || !is_file($candidate) || !is_readable($candidate)) {
1029+
return null;
1030+
}
1031+
$rootReal = rtrim($rootReal, '/').'/';
1032+
if (!str_starts_with($candidate, $rootReal)) {
1033+
return null;
1034+
}
1035+
1036+
return $candidate;
1037+
};
1038+
1039+
$p = $containedRealPath((string) $srcRoot, (string) $item->path);
1040+
if (null !== $p) {
10191041
return $p;
10201042
}
10211043

10221044
$altRoot = rtrim((string) ($this->course->resources['__meta']['archiver_root'] ?? ''), '/').'/';
10231045
if ($altRoot && $altRoot !== $srcRoot) {
1024-
$p2 = $altRoot.(string) $item->path;
1025-
if (is_file($p2) && is_readable($p2)) {
1046+
$p2 = $containedRealPath($altRoot, (string) $item->path);
1047+
if (null !== $p2) {
10261048
return $p2;
10271049
}
10281050
}

0 commit comments

Comments
 (0)