Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

API Remove Path class in favour of Symfony's Path class #11380

Closed

Conversation

GuySartorelli
Copy link
Member

Comment on lines -849 to +846
public static function getAbsFile($file)
public static function getAbsFile(string $file): string
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strong typing added here because the symfony Path class has strong typing - so we can't allow other types to come in anymore.

I've added similar typing changes in other methods across the PRs.

Comment on lines +850 to 852
if (!Path::isBasePath(Director::baseFolder(), $path)) {
throw new InvalidArgumentException("'$file' must not be outside the project root");
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added traversal protection here because it seems likely for the return value of Director::getAbsFile() to be directly used to read files at the returned path.

Comment on lines -138 to -148
// Rewrite to _resources with public directory
if (Director::publicDir()) {
// All resources mapped directly to _resources/
$relativePath = Path::join(RESOURCES_DIR, $relativePath);
} elseif (stripos($relativePath ?? '', ManifestFileFinder::VENDOR_DIR . DIRECTORY_SEPARATOR) === 0) {
// If there is no public folder, map to _resources/ but trim leading vendor/ too (4.0 compat)
$relativePath = Path::join(
RESOURCES_DIR,
substr($relativePath ?? '', strlen(ManifestFileFinder::VENDOR_DIR ?? ''))
);
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed legacy logic for handling missing public folder. That folder was enforced in 5.0.0 so this is dead code.

);
}
// All resources mapped directly to public/_resources/
$relativePath = Path::join(RESOURCES_DIR, $relativePath);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No traversal protection needed here because is $relativepath comes from module resource, which we should be able to trust.

Comment on lines 196 to 198
if (!Path::isBasePath(RESOURCES_DIR, $privatePath)) {
throw new InvalidArgumentException("'$relativePath' must not be outside the resources root");
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added traversal protection here because it seems likely for the return value to be directly used to read files at the returned path, and we have no idea where the relative path is coming from.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't have any logic that would allow unsafe relative paths to be passed to this class, so no need for path traversal protection.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't have any logic that would allow unsafe relative paths to be passed to this class, so no need for path traversal protection.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for path traversal protection here because there's no way for an attacker to inject unsafe values here without already owning your server

Comment on lines -1419 to +1418
$substitute = FilesystemPath::canonicalize(FilesystemPath::join($fileUrlDir, $relativePath));
$substitute = Path::join($fileUrlDir, $relativePath);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

join() already does canonicalize() so removed the unnecessary call.

Copy link
Member Author

@GuySartorelli GuySartorelli Sep 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for path traversal protection here because the only call to the old Path::join() is for matching css relative paths which should explicitly allow path traversal (and couldn't be altered by an attacker without owning the server anyway)

Comment on lines +331 to +333
if (!Path::isBasePath($themePath, $relativePath) || !Path::isBasePath($this->base, $absolutePath)) {
continue;
}
Copy link
Member Author

@GuySartorelli GuySartorelli Sep 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Path traversal outside of the theme path or base dir means the resource doesn't exist.
Not throwing an exception because this is consistent with what happens if the file just doesn't exist anyway.

Comment on lines +59 to +61
if (!Path::isBasePath($langFolder, $langFile)) {
throw new InvalidArgumentException("Language file must be inside '$langFolder'");
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added protection here because we don't know where $locale is coming from

Comment on lines +1081 to +1083
if ($locale && str_contains($locale, '..')) {
throw new InvalidArgumentException('Locale must not contain ".."');
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No locale has .. in its name, so this is easy to do and protects against... well, probably not much because this can't really be run by arbitrary users. But it seems sensible anyway.

Copy link
Member

@emteknetnz emteknetnz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not keen on the fact that we could be inadvertently introducing security issues by removing directory traversal protection. This makes these PR's hard to peer review as there's a need to consider all the possible scenarios where we may have introduced a new attack vector.

This is also removing a layer of protection for projects that use the Silverstripe Path class path

I'm not sure that if (!Path::isBasePath(Director::baseFolder(), $path)) { is protection enough against directory traversal as you still access files that you shouldn't be able to

Seems like the main motivation for this work was that both the Silverstripe and Symfony classes were called Path. I think we should simply rename the Path to something like SecurePath and ditch probably ditch the normlize() method off it.

@GuySartorelli GuySartorelli deleted the pulls/6/symfony-path branch September 18, 2024 03:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants