404 a null byte or a directory on the showcase's asset route
Plan step 38 review: a %00 in the asset path reached realpath(), which throws on a null byte, so the route answered 500 instead of 404; a directory whose name ended in a served extension would have reached response()->file(). Both 404 now. The tests add what the review probed: encoded dot segments and slashes, backslashes, absolute paths, a directory, a very long path and a symbolic link pointing out of a served folder, all 404. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
This commit is contained in:
co-authored by
Claude Opus 5
parent
2f649983fa
commit
3027f85cef
@@ -69,12 +69,16 @@ class ShowcaseAssetController
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A font or an SVG the stylesheet's `url()`s point at, MIME-typed by extension: `..` and
|
* A font or an SVG the stylesheet's `url()`s point at, MIME-typed by extension. The path is
|
||||||
* anything the two folders above do not contain resolves to nothing and 404s, the same as an
|
* resolved on disk first, so `..` (however it was encoded), a symbolic link and anything else
|
||||||
* unlisted extension (the font's own `OFL.txt`, sitting right beside it, included).
|
* that lands outside the two folders above 404s, as does a directory, a null byte (which
|
||||||
|
* `realpath()` would throw on) and an unlisted extension (the font's own `OFL.txt`, sitting
|
||||||
|
* right beside it, included).
|
||||||
*/
|
*/
|
||||||
public function file(string $path): BinaryFileResponse
|
public function file(string $path): BinaryFileResponse
|
||||||
{
|
{
|
||||||
|
abort_if(str_contains($path, "\0"), 404);
|
||||||
|
|
||||||
$segments = explode('/', $path, 2);
|
$segments = explode('/', $path, 2);
|
||||||
$folder = $segments[0] ?? null;
|
$folder = $segments[0] ?? null;
|
||||||
$rest = $segments[1] ?? null;
|
$rest = $segments[1] ?? null;
|
||||||
@@ -84,7 +88,7 @@ class ShowcaseAssetController
|
|||||||
$root = realpath(dirname(__DIR__, 3)."/resources/{$folder}");
|
$root = realpath(dirname(__DIR__, 3)."/resources/{$folder}");
|
||||||
$real = $root === false ? false : realpath("{$root}/{$rest}");
|
$real = $root === false ? false : realpath("{$root}/{$rest}");
|
||||||
|
|
||||||
abort_unless($real !== false && str_starts_with($real, $root.DIRECTORY_SEPARATOR), 404);
|
abort_unless($real !== false && str_starts_with($real, $root.DIRECTORY_SEPARATOR) && is_file($real), 404);
|
||||||
|
|
||||||
$mime = self::MIME_TYPES[strtolower(pathinfo($real, PATHINFO_EXTENSION))] ?? null;
|
$mime = self::MIME_TYPES[strtolower(pathinfo($real, PATHINFO_EXTENSION))] ?? null;
|
||||||
|
|
||||||
|
|||||||
@@ -54,6 +54,31 @@ it('404s a path that climbs out of the two served folders', function () {
|
|||||||
$this->get('/material/assets/fonts/../../composer.json')->assertNotFound();
|
$this->get('/material/assets/fonts/../../composer.json')->assertNotFound();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('404s every other way out of the two served folders', function (string $path) {
|
||||||
|
$this->get('/material/assets/'.$path)->assertNotFound();
|
||||||
|
})->with([
|
||||||
|
'encoded dot segments' => 'fonts/%2e%2e/%2e%2e/composer.json',
|
||||||
|
'encoded slashes' => 'fonts/..%2f..%2fcomposer.json',
|
||||||
|
'backslashes' => 'fonts/google-sans-flex/..%5c..%5c..%5ccomposer.json',
|
||||||
|
'an absolute path' => '/etc/hosts',
|
||||||
|
'an absolute path inside a folder' => 'fonts//etc/hosts',
|
||||||
|
'a null byte' => 'fonts/google-sans-flex/GoogleSansFlex-Latin.woff2%00.txt',
|
||||||
|
'a directory' => 'svg/symbols',
|
||||||
|
'a folder itself' => 'fonts/',
|
||||||
|
'a very long path' => 'fonts/'.str_repeat('a/', 3000).'x.woff2',
|
||||||
|
]);
|
||||||
|
|
||||||
|
it('404s a symbolic link inside a served folder that points out of it', function () {
|
||||||
|
$link = __DIR__.'/../../resources/svg/showcase-assets-test-link.svg';
|
||||||
|
symlink(realpath(__DIR__.'/../../composer.json'), $link);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$this->get('/material/assets/svg/showcase-assets-test-link.svg')->assertNotFound();
|
||||||
|
} finally {
|
||||||
|
unlink($link);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
it('404s a folder the showcase does not serve', function () {
|
it('404s a folder the showcase does not serve', function () {
|
||||||
$this->get('/material/assets/css/all.css')->assertNotFound();
|
$this->get('/material/assets/css/all.css')->assertNotFound();
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user