2012-04-08 16 views
8

Chciałbym znormalizować ścieżkę z zasobu zewnętrznego, aby zapobiec atakom przemierzania katalogów. Wiem o funkcji realpath(), ale niestety ta funkcja zwraca tylko ścieżkę do istniejących katalogów. Jeśli więc katalog nie istnieje (jeszcze), funkcja realpath() odcina całą część ścieżki, która nie istnieje.PHP: znormalizować ścieżkę nieistniejących katalogów, aby zapobiec przechodzeniu katalogów?

Moje pytanie brzmi: Czy znasz funkcję PHP, która normalizuje tylko ścieżkę?

PS: Ja też nie chcę, aby utworzyć wszystkie możliwe katalogi z góry ;-)

Odpowiedz

4

Nie ma wbudowanej funkcji PHP do tego. Użyj coś jak następujące zamiast:

function removeDots($path) { 
    $root = ($path[0] === '/') ? '/' : ''; 

    $segments = explode('/', trim($path, '/')); 
    $ret = array(); 
    foreach($segments as $segment){ 
     if (($segment == '.') || strlen($segment) === 0) { 
      continue; 
     } 
     if ($segment == '..') { 
      array_pop($ret); 
     } else { 
      array_push($ret, $segment); 
     } 
    } 
    return $root . implode('/', $ret); 
} 
+0

również jednak o takiego rozwiązania, ale ponieważ istnieje wiele sposobów w celu zakodowania punktów ([patrz Wikipedia] (http://en.wikipedia.org/ wiki/Directory_traversal_attack # URI_encoded_directory_traversal)), to nie wystarczy: -/ – JepZ

+2

Cóż, to była implementacja [MVP] [0]. Możesz dodać wywołanie rawurldecode() i dopasowanie regexp, aby kontrolować znaki dozwolone w ścieżkach. Z drugiej strony, pytanie dotyczyło wbudowanej funkcji. Ten kod był tylko możliwy do zrobienia. [0]: http: //en.wikipedia.org/wiki/Minimum_viable_product –

2

Dzięki Benubird/Cragmonkey poprawił mnie, że w pewnych sytuacji moja poprzednia odpowiedź nie działa. więc robię nowy, do pierwotnego celu: Wykonanie dobre, mniej linii, a czystą wyrażenia regularnego:

Tym razem testowałem ze znacznie bardziej ścisłego przypadku testowego, jak poniżej.

$path = '/var/.////./user/./././..//.//../////../././.././test/////'; 

function normalizePath($path) { 
    $patterns = array('~/{2,}~', '~/(\./)+~', '~([^/\.]+/(?R)*\.{2,}/)~', '~\.\./~'); 
    $replacements = array('/', '/', '', ''); 
    return preg_replace($patterns, $replacements, $path); 
} 

Prawidłowa odpowiedź byłaby/test /.

Nie oznaczało to zrobić konkurencję, ale test wydajności jest koniecznością:

Test case: pętli 100k razy, na Windows 7, i5-3470 Quad Core 3,20 GHz.

kopalnia: 1,746 secs.

Tom Imrei: 4,548 secs.

Benubird: 3,593 secs.

Ursa: 4,334 secs.

Nie oznacza to, że moja wersja jest zawsze lepsza. W kilku sytuacjach wykonują symulację.

+1

To jest nieprawidłowe. a/b /../ c normalizuje się do a/c, a nie a/b/c. – Benubird

+1

Dzięki za poprawione. Zmieniłem mój post. – Val

+1

Działa to dobrze, chyba że istnieje wiele instancji '/../'. Na przykład '/ a/b/c /../../../ d/e/file.txt' powinno zostać przetłumaczone na'/d/e/file.txt', zamiast tego wraca tylko o jeden poziom ('/ a/b/d/e/file.txt'). Ponadto, nie podoba się parzystym liczbom '/../', takim jak '/ a/b/c /../../ d/e/file.txt', które są tłumaczone na'/a/b/.d/e/file.txt' (dodatkowy okres) – Cragmonkey

2

Myślę, że rozwiązanie Tamas będzie działało, ale można to również zrobić za pomocą regex, które może być mniej wydajne, ale wygląda lepiej. Rozwiązanie Val jest niepoprawne; ale ten działa.

function normalizePath($path) { 
    do { 
     $path = preg_replace(
      array('#//|/\./#', '#/([^/.]+)/\.\./#'), 
      '/', $path, -1, $count 
     ); 
    } while($count > 0); 
    return $path; 
} 

Tak, to nie obsługuje wszystkie możliwe różne kodowanie z ./ \ itd., Że nie może być, ale nie jest to celem tego; jedna funkcja powinna wykonywać tylko jedną rzecz, więc jeśli chcesz przekonwertować także %2e%2e%2f na ../, najpierw uruchom ją za pomocą oddzielnej funkcji.

Realpath również rozwiązuje dowiązania symboliczne, co jest oczywiście niemożliwe, jeśli ścieżka nie istnieje; ale możemy usunąć dodatkowe znaki "/ ./", "/../" i "/".

+0

Działa to w niektórych przypadkach, ale czasami nie może działać poprawnie, na przykład: $ path = '/var/.////./user/./././..//.//../// //../././.././test/////'; $ path = '/var/user/ ./////// ././.././.././././test/ "; Wyniki obu powinny być/test /, ale pierwszy zwraca "/ var/test", drugi zwrot "/ var/user/test /". – Val

+0

@Val Masz całkowitą rację, tam był błąd - dzięki za wskazanie tego! Chociaż twoje przykłady nie są całkowicie poprawne - pierwsza redukuje się do '/../../ test /', a nie '/ test /'. – Benubird

+0

@ Benubird Zrobiłem dodatkową pracę, aby usunąć zbędne /../../, ponieważ nie oznacza to nic pod absolutną ścieżką i wygląda lepiej. Ale zgadzam się z tobą, zostaw to, co sprawi, że będzie bardziej elastyczny do pracy z relatywną ścieżką. – Val

1

Ścisła, ale bezpieczna implementacja. Jeśli używasz tylko ASCII dla nazw plików, byłoby odpowiednie:

/** 
* Normalise a file path string so that it can be checked safely. 
* 
* @param $path string 
*  The path to normalise. 
* @return string 
* Normalised path or FALSE, if $path cannot be normalized (invalid). 
*/ 
function normalisePath($path) { 
    // Skip invalid input. 
    if (!isset($path)) { 
    return FALSE; 
    } 
    if ($path === '') { 
    return ''; 
    } 

    // Attempt to avoid path encoding problems. 
    $path = preg_replace("/[^\x20-\x7E]/", '', $path); 
    $path = str_replace('\\', '/', $path); 

    // Remember path root. 
    $prefix = substr($path, 0, 1) === '/' ? '/' : ''; 

    // Process path components 
    $stack = array(); 
    $parts = explode('/', $path); 
    foreach ($parts as $part) { 
    if ($part === '' || $part === '.') { 
     // No-op: skip empty part. 
    } elseif ($part !== '..') { 
     array_push($stack, $part); 
    } elseif (!empty($stack)) { 
     array_pop($stack); 
    } else { 
     return FALSE; // Out of the root. 
    } 
    } 

    // Return the "clean" path 
    $path = $prefix . implode('/', $stack); 
    return $path; 
} 
+0

Działa to w niektórych przypadkach, ale czasami nie może działać poprawnie, na przykład: $ path = '/var/ .////./user/./././..//.//..// ///../././.././test/////'; $ path = '/var/user/ ././ ./.././../.././././test/ "; Wyniki obu powinny być/test /, ale zwracany jest pusty ciąg. – Val

0

Moje 2 centy.Tego wyrażenia są stosowane tylko w przypadku pustych pakietów ścieżce:

<?php 
echo path_normalize('/a/b/c/../../../d/e/file.txt'); 

echo path_normalize('a/b/../c'); 

echo path_normalize('./../../etc/passwd'); 

echo path_normalize('/var/user/.///////././.././.././././test/'); 

function path_normalize($path){ 
    $path = str_replace('\\','/',$path); 
    $blocks = preg_split('#/#',$path,null,PREG_SPLIT_NO_EMPTY); 
    $res = array(); 

    while(list($k,$block) = each($blocks)){ 
     switch($block){ 
      case '.': 
       if($k == 0) 
        $res = explode('/',path_normalize(getcwd())); 
      break; 
      case '..'; 
       if(!$res) return false; 
       array_pop($res); 
      break; 
      default: 
       $res[]=$block; 
      break; 
      } 
     } 
    return implode('/',$res); 
    } 
?>