开发者

Is ASCII "../" the only byte sequence that indicates a directory traversal in PHP?

开发者 https://www.devze.com 2022-12-09 07:42 出处:网络
I have a PHP app that uses a $_GET parameter to select JS/CSS files on the filesystem. If I deny all requests in which the input strin开发者_运维百科g contains ./, \\ or a byte outside the visible 7-

I have a PHP app that uses a $_GET parameter to select JS/CSS files on the filesystem.

If I deny all requests in which the input strin开发者_运维百科g contains ./, \ or a byte outside the visible 7-bit ASCII range, is this sufficient to prevent parent directory traversals when the path is passed to PHP's underlying (C-based) file functions?

I'm aware of null-byte vulnerabilities, but are there any other alternative/malformed character encodings tricks that might squeak by these checks?

Here's the basic idea (not production code):

$f = $_GET['f']; // e.g. "path/to/file.js"

// goal: select only unhidden CSS/JS files within DOC_ROOT
if (! preg_match('@^[\x20-\x7E]+$@', $f)     // outside visible ASCII
   || false !== strpos($f, "./")             // has ./
   || false !== strpos($f, "\\")             // has \
   || 0 === strpos(basename($f), ".")        // .isHiddenFile
   || ! preg_match('@\\.(css|js)$i@', $f)    // not JS/CSS
   || ! is_file($_SERVER['DOCUMENT_ROOT'] . '/' . $f)) {
    die();
}
$content = file_get_contents($_SERVER['DOCUMENT_ROOT'] . '/' . $f);

Update: My question is really about how the C filesystem functions interpret arbitrary ASCII sequences (e.g. if there are undocumented escape sequences), but I realize this is likely system-dependent and perhaps unanswerable in practice.

My active validation additionally requires that realpath($fullPath) start with realpath($_SERVER['DOCUMENT_ROOT']), ensuring that the file is within the DOC_ROOT, but a goal of this posting was to ditch realpath() (it's proven unreliable in various environments) while still allowing unusual, but valid URIs like /~user/[my files]/file.plugin.js.


When filtering input for security, always use whitelists, not backlists.

You should reject all paths that don't match /^([A-Za-z0-9_-]+\/?)*[A-Za-z0-9_-]+\.(js)|(css)?$/.

This will only allow normal segmented paths where each segment has letters, numbers, or _-.


You mention it yourself, but comparing realpath of the input to a known root is the best solution I can think of. Realpath will resolve any hidden features of the path/filesystem, including symlinks.


Might require a little rearchitecting, but even if you are passed ../../passwd, basename() will insulate it. Then, you could place all of the files you want to serve in one folder.

Given ../../././././a/b/c/d.txt, basename($f) will be d.txt; this approach seems wiser to me, instead of trying to outsmart the user and forgetting a hole.

0

精彩评论

暂无评论...
验证码 换一张
取 消