2012-04-08 11 views
8

Vorrei normalizzare un percorso da una risorsa esterna per prevenire gli attacchi di attraversamento di directory. Conosco la funzione realpath(), ma purtroppo questa funzione restituisce solo il percorso delle directory esistenti. Quindi se la directory non esiste (ancora) la funzione realpath() taglia l'intera parte del percorso che non esiste.PHP: normalizza il percorso delle directory non esistenti per impedire gli attraversamenti di directory?

Quindi la mia domanda è: conosci una funzione PHP che normalizza solo il percorso?

PS: Anche io non voglio creare tutte le possibili directory in anticipo ;-)

risposta

4

Non c'è built-in funzione PHP per questo. Usa qualcosa come invece la seguente:

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

Ho anche se una simile soluzione, ma poiché ci sono diversi modi per codificare i puntini ([vedi Wikipedia] (http://en.wikipedia.org/ wiki/Directory_traversal_attack # URI_encoded_directory_traversal)), questo non sarebbe sufficiente: -/ – JepZ

+2

Bene, questa è stata l'implementazione [MVP] [0]. Puoi aggiungere una chiamata rawurldecode() e una corrispondenza regexp prima di essa per controllare quali caratteri permetti nei tuoi percorsi. D'altra parte, la domanda era se c'è una funzione integrata per questo. Questo codice era solo il modo possibile per andare da lì. [0]: http: //en.wikipedia.org/wiki/Minimum_viable_product –

2

Grazie alla Benubird/Cragmonkey mi corresse che in qualche situazione la mia risposta precedente non ha funzionato. quindi Faccio uno nuovo, per lo scopo originario: compiere buone, un minor numero di linee, e con espressioni regolari puro:

Questa volta ho provato con molto più severi test case, come di seguito.

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

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

La risposta corretta sarebbe/test /.

non vuole fare concorrenza, ma test delle prestazioni è un must:

banco di prova: ciclo for 100k volte, su un Windows 7, i5-3470 Quad Core, 3,20 GHz.

mina: 1.746 sec.

Tom Imrei: 4.548 sec.

Benubird: 3.593 sec.

Ursa: 4,334 secondi.

Non significa che la mia versione sia sempre migliore. In diverse situazioni eseguono simular.

+1

Questo non è corretto. a/b /../ c si normalizza a a/c, non a/b/c. – Benubird

+1

Grazie per la correzione. Ho modificato il mio post. – Val

+1

Questo funziona bene a meno che non ci siano più istanze di '/../'. Ad esempio, '/ a/b/c /../../../ d/e/file.txt' dovrebbe risolversi in'/d/e/file.txt', invece torna indietro di un livello ('/ a/b/d/e/file.txt'). Inoltre, non ama nemmeno i numeri di '/../', come '/ a/b/c /../../ d/e/file.txt', che risolve in'/a/b/.d/e/file.txt' (periodo extra) – Cragmonkey

2

Penso che la soluzione di Tamas funzionerà, ma è anche possibile farlo con regex, che può essere meno efficiente ma sembra più ordinato. La soluzione di Val non è corretta; ma questo funziona.

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

Sì, non gestisce tutti i possibili codifiche differenti di ./ \ ecc che ci possono essere, ma non è lo scopo di esso; una funzione dovrebbe fare solo una cosa, quindi se vuoi convertire anche %2e%2e%2f in ../, eseguirlo prima attraverso una funzione separata.

Realpath risolve anche collegamenti simbolici, che è ovviamente impossibile se il percorso non esiste; ma possiamo eliminare i caratteri extra '/./', '/../' e '/'.

+0

Funziona con alcuni casi, ma a volte non è possibile eseguire correttamente ad esempio: $ percorso = '/var/.////./user/./././..//.//../// //../././.././test/////'; $ path = '/var/user/.///////././.././../././test/'; I risultati di entrambi dovrebbero essere/test /, ma il primo restituisce "/ var/test", il secondo restituisce "/ var/user/test /". – Val

+0

@Val Hai ragione, c'è stato un errore lì - grazie per averlo fatto notare! Sebbene, gli esempi non siano del tutto corretti - il primo si riduce a '/../../ test /', non '/ test /'. – Benubird

+0

@ Benubird Ho fatto un lavoro extra per rimuovere il ridondante /../../ perché non significa nulla sotto il percorso assoluto e sembra migliore. Ma sono d'accordo con te, lascialo lì renderebbe più flessibile lavorare con il percorso relativo. – Val

1

Implementazione rigorosa ma sicura. Se si utilizza solo ASCII per i nomi di file, sarebbe adatto:

/** 
* 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

Funziona con alcuni casi, ma a volte non può funzionare correttamente ad esempio: $ percorso = '/var/.////./user/./././..//.//..// ///../././.././test/////'; $ path = '/var/user/./././.././../../././test/'; I risultati di entrambi dovrebbero essere/test /, ma la stringa vuota restituita. – Val

0

I miei 2 centesimi.L'espressione regolare viene utilizzato solo per i blocchi vuoti di percorso:

<?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); 
    } 
?> 
Problemi correlati