scan-symlinks: relative targets escaping the archive or the homedir are DANGEROUS (N2-1)
A relative symlink was skipped as safe (`if ($target[0] !== '/') continue`),
so `cpmove-u/sds -> ../../../../root/.my.cnf` survived extraction and WHP
parsed the host file as the subdomain list. Resolve relative targets
lexically from the link's own directory: reaching or climbing above the
archive root, or a link under <top>/homedir/ resolving outside that homedir,
is DANGEROUS (stripped by extract.sh). Same rule as WHP's
CpanelBackupImporter::relativeSymlinkEscape().
The clean fixture's about-shortcut (../public_html/about.txt resolved
outside homedir/) now points at public_html/about.txt as its comment meant.
New cpmove-relesc fixture + checks (5 fail on 4a43d26).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
4a43d26de7
commit
a226b496a2
3 files changed
+79
-3
No files matched your search
@@ -90,6 +90,32 @@ $dangerousPrefixes = [
|
||||
'/dev',
|
||||
];
|
||||
|
||||
/**
|
||||
* Lexical resolution of a relative link from its own directory in the
|
||||
* archive: a reason when it reaches/climbs above the archive root, or when a
|
||||
* link under <top>/homedir/ resolves outside that homedir; NULL otherwise.
|
||||
*/
|
||||
function relative_symlink_escape(string $archivePath, string $target): ?string {
|
||||
$ap = preg_replace('#^(\./)+#', '', $archivePath);
|
||||
$dir = array_values(array_filter(explode('/', (string) $ap), fn($x) => $x !== '' && $x !== '.'));
|
||||
array_pop($dir);
|
||||
$home = (count($dir) >= 2 && $dir[1] === 'homedir') ? [$dir[0], 'homedir'] : null;
|
||||
$stack = $dir;
|
||||
foreach (explode('/', $target) as $seg) {
|
||||
if ($seg === '' || $seg === '.') continue;
|
||||
if ($seg === '..') {
|
||||
if (count($stack) <= 1) return 'escapes the archive root';
|
||||
array_pop($stack);
|
||||
continue;
|
||||
}
|
||||
$stack[] = $seg;
|
||||
}
|
||||
if ($home !== null && (count($stack) < 2 || $stack[0] !== $home[0] || $stack[1] !== 'homedir')) {
|
||||
return 'escapes the account homedir';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
$findings = [];
|
||||
$cpanelUsername = null;
|
||||
|
||||
@@ -111,7 +137,23 @@ while (($line = fgets($fh)) !== false) {
|
||||
$archivePath = $parts[5];
|
||||
$target = $right;
|
||||
|
||||
if ($target === '' || $target[0] !== '/') continue;
|
||||
// N2-1: a RELATIVE target that climbs out of the archive (or out of
|
||||
// the account's homedir/) is DANGEROUS (stripped), not safe: GNU tar
|
||||
// extracts `cpmove-u/sds -> ../../../../root/.my.cnf` as-is. Same rule
|
||||
// as WHP's CpanelBackupImporter::relativeSymlinkEscape().
|
||||
if ($target !== '' && $target[0] !== '/') {
|
||||
$escape = relative_symlink_escape($archivePath, $target);
|
||||
if ($escape !== null) {
|
||||
$findings[] = [
|
||||
'type' => 'DANGEROUS',
|
||||
'archive_path' => $archivePath,
|
||||
'target' => $target,
|
||||
'reason' => "relative target $escape",
|
||||
];
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if ($target === '') continue;
|
||||
|
||||
if ($cpanelUsername === null) {
|
||||
if (preg_match('#^cpmove-([^/]+)/#', $archivePath, $m)) {
|
||||
|
||||
Reference in new issue
Block a user