Sanitize путь к файлу в PHP без realpath ()

Есть ли способ безопасно realpath() ввод пути, не используя realpath() ?

Цель заключается в предотвращении вредоносных входов, таких как ../../../../../path/to/file

  $handle = fopen($path . '/' . $filename, 'r'); 

Solutions Collecting From Web of "Sanitize путь к файлу в PHP без realpath ()"

Не уверен, почему вы не захотите использовать realpath но realpath имени пути – это очень простая концепция, а именно:

  • Если путь относительный (не начинается с / ), префикс его с текущим рабочим каталогом и / , что делает его абсолютным путем.
  • Замените все последовательности более одного / одним (a) .
  • Замените все вхождения /./ с / .
  • Удалите /. если в конце.
  • Замените /anything/../ с помощью / .
  • Удалите /anything/.. если в конце.

Текст в этом случае означает самую длинную последовательность символов, которые не являются / .

Обратите внимание, что эти правила должны применяться непрерывно до тех пор, пока ни одно из них не приведет к изменению. Другими словами, сделайте все шесть (один проход). Если строка изменилась, вернитесь назад и повторите все шесть (еще один проход). Продолжайте делать это до тех пор, пока строка не будет такой же, как до только что выполненного прохода.

Как только эти шаги будут выполнены, у вас есть имя канонического пути, которое можно проверить на допустимый шаблон. Скорее всего, это будет что-то, что не начинается с ../ (другими словами, он не пытается двигаться выше начальной точки. Могут быть другие правила, которые вы хотите применить, но это выходит за рамки этого вопроса.


(a) Если вы работаете над системой, которая рассматривает // в начале пути как особый, убедитесь, что вы заменили несколько / символов в начале двумя из них. Это единственное место, где POSIX разрешает (но не гарантирует) специальную обработку для мультипликаторов, во всех остальных случаях несколько / символов эквивалентны одному.

Существует алгоритм Remove Dot Sequence, описанный в RFC 3986, который используется для интерпретации и удаления специального . и .. полный путь сегменты из ссылочного пути в процессе относительного опорного разрешения URI.

Вы также можете использовать эти алгоритмы для путей файловой системы:

 // as per RFC 3986 // @see http://tools.ietf.org/html/rfc3986#section-5.2.4 function remove_dot_segments($input) { // 1. The input buffer is initialized with the now-appended path // components and the output buffer is initialized to the empty // string. $output = ''; // 2. While the input buffer is not empty, loop as follows: while ($input !== '') { // A. If the input buffer begins with a prefix of "`../`" or "`./`", // then remove that prefix from the input buffer; otherwise, if ( ($prefix = substr($input, 0, 3)) == '../' || ($prefix = substr($input, 0, 2)) == './' ) { $input = substr($input, strlen($prefix)); } else // B. if the input buffer begins with a prefix of "`/./`" or "`/.`", // where "`.`" is a complete path segment, then replace that // prefix with "`/`" in the input buffer; otherwise, if ( ($prefix = substr($input, 0, 3)) == '/./' || ($prefix = $input) == '/.' ) { $input = '/' . substr($input, strlen($prefix)); } else // C. if the input buffer begins with a prefix of "/../" or "/..", // where "`..`" is a complete path segment, then replace that // prefix with "`/`" in the input buffer and remove the last // segment and its preceding "/" (if any) from the output // buffer; otherwise, if ( ($prefix = substr($input, 0, 4)) == '/../' || ($prefix = $input) == '/..' ) { $input = '/' . substr($input, strlen($prefix)); $output = substr($output, 0, strrpos($output, '/')); } else // D. if the input buffer consists only of "." or "..", then remove // that from the input buffer; otherwise, if ($input == '.' || $input == '..') { $input = ''; } else // E. move the first path segment in the input buffer to the end of // the output buffer, including the initial "/" character (if // any) and any subsequent characters up to, but not including, // the next "/" character or the end of the input buffer. { $pos = strpos($input, '/'); if ($pos === 0) $pos = strpos($input, '/', $pos+1); if ($pos === false) $pos = strlen($input); $output .= substr($input, 0, $pos); $input = (string) substr($input, $pos); } } // 3. Finally, the output buffer is returned as the result of remove_dot_segments. return $output; } 

Следующая функция канонизирует пути файловой системы и компоненты пути URI. Это быстрее, чем реализация RFC Gumbo .

 function canonicalizePath($path) { $path = explode('/', $path); $stack = array(); foreach ($path as $seg) { if ($seg == '..') { // Ignore this segment, remove last segment from stack array_pop($stack); continue; } if ($seg == '.') { // Ignore this segment continue; } $stack[] = $seg; } return implode('/', $stack); } 

Заметки

  • Он не разбивает последовательности из нескольких / поскольку это не соответствует RFC 3986 .
  • Очевидно, что это не работает с ..\backslash\paths .
  • Я не уверен, что эта функция на 100% безопасна, но я не смог найти вход, который компрометирует его вывод.

Поскольку вы только просили о дезинфекции, возможно, вам нужно всего лишь «провалиться по сложным путям». Если обычно не будет никаких ../../stuff/../like/this в вашем пути, вам нужно только проверить это:

 function isTricky($p) { if(strpos("/$p/","/../")===false) return false; return true; } 

или просто

 function isTricky($p) {return strpos("-/$p/","/../");} 

Этот быстрый и грязный способ блокировать любые обратные движения, и в большинстве случаев этого достаточно. (Вторая версия возвращает ненулевое значение вместо true, но эй, почему бы и нет! … Тире – это взлом для индекса 0 строки.)

Боковое примечание: также помните косые черты против обратной косой черты – я бы рекомендовал сначала конвертировать спины в простые косые черты. Но это зависит от платформы.

Поскольку вышеупомянутые функции не работали для меня одним или другим способом (или были довольно длинными), я попробовал свой собственный код:

 function clean_path( $A_path="", $A_echo=false ) { // IF YOU WANT TO LEAN CODE, KILL ALL "if" LINES and $A_echo in ARGS $_p = func_get_args(); // HOW IT WORKS: // REMOVING EMPTY ELEMENTS AT THE END ALLOWS FOR "BUFFERS" AND HANDELLING START & END SPEC. SEQUENCES // BLANK ELEMENTS AT START & END MAKE SURE WE COVER SPECIALS AT BEGIN & END // REPLACING ":" AGAINST "://" MAKES AN EMPTY ELEMENT TO ALLOW FOR CORRECT x:/../<path> USE (which, in principle is faulty) // 1.) "normalize" TO "slashed" AND MAKE SOME SPECIALS, ALSO DUMMY ELEMENTS AT BEGIN & END $_s = array( "\\", ":", ":./", ":../"); $_r = array( "/", "://", ":/", ":/" ); $_p['sr'] = "/" . str_replace( $_s, $_r, $_p[0] ) . "/"; $_p['arr'] = explode('/', $_p['sr'] ); if ( $A_echo ) $_p['arr1'] = $_p['arr']; // 2.) GET KEYS OF ".." ELEMENTS, REMOVE THEM AND THE ONE BEFORE (!) AS THAT MEANS "UP" AND THAT DISABLES STEP BEFORE $_p['pp'] = array_keys( $_p['arr'], '..' ); foreach($_p['pp'] as $_pos ) { $_p['arr'][ $_pos-1 ] = $_p['arr'][ $_pos ] =""; } if ( $A_echo ) $_p['arr2'] = $_p['arr']; // 3.) REMOVE ALL "/./" PARTS AS THEY ARE SIMPLY OVERFLUENT $_p['p'] = array_keys( $_p['arr'], '.' ); foreach($_p['p'] as $_pos ) { unset( $_p['arr'][ $_pos ] ); } if ( $A_echo ) $_p['arr3'] = $_p['arr']; // 4.) CLEAN OUT EMPTY ONES INCLUDING OUR DUMMIES $_p['arr'] = array_filter( $_p['arr'] ); // 5) MAKE FINAL STRING $_p['clean'] = implode( DIRECTORY_SEPARATOR, $_p['arr'] ); if ($A_echo){ echo "arr=="; print_R( $_p ); }; return $_p['clean']; } 

Ле простая форма:

 $filename = str_replace('..', '', $filename); if (file_exists($path . '/' . $filename)) { $handle = fopen($path . '/' . $filename, 'r'); } 

Le комплексная форма ( отсюда ):

 function canonicalize($address) { $address = explode('/', $address); $keys = array_keys($address, '..'); foreach($keys AS $keypos => $key) { array_splice($address, $key - ($keypos * 2 + 1), 2); } $address = implode('/', $address); $address = str_replace('./', '', $address); return $address; } echo canonicalize('/dir1/../dir2/'); // returning /dir2/