2012-04-08 7 views
8

Ich möchte einen Pfad von einer externen Ressource normalisieren, um Directory Traversal-Angriffe zu verhindern. Ich weiß über die realpath() Funktion, aber leider diese Funktion gibt nur den Pfad der vorhandenen Verzeichnisse zurück. Wenn also das Verzeichnis (noch) nicht existiert, schneidet die Funktion realpath() den gesamten Teil des Pfades ab, der nicht existiert.PHP: normalize Pfad von nicht existierenden Verzeichnissen, um Directory Traversals zu verhindern?

Also meine Frage ist: Kennen Sie eine PHP-Funktion, die nur den Pfad normalisiert?

PS: Ich weiß nicht auch alle möglichen Verzeichnisse im Voraus erstellt werden soll ;-)

Antwort

4

Es gibt keine PHP-Funktion eingebaute in dieser für. Verwenden Sie so etwas wie die folgenden statt:

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

ich auch, obwohl über eine solche Lösung, aber da gibt es mehrere Möglichkeiten, um die Punkte ([siehe wikipedia] (http://en.wikipedia.org/ zu kodieren wiki/Directory_traversal_attack # URI_encoded_directory_traversal)), das wäre nicht genug: -/ – JepZ

+2

Nun, das war die [MVP] [0] Implementierung. Sie können einen Aufruf von "rawurldecode()" und "regexp match" davor hinzufügen, um zu steuern, welche Zeichen in Ihren Pfaden zulässig sind. Auf der anderen Seite war die Frage, ob es eine eingebaute Funktion dafür gibt. Dieser Code war nur von dort aus möglich. [0]: http: //en.wikipedia.org/wiki/Minimum_viable_product –

2

Dank Benubird/Cragmonkey mich korrigiert, dass meine Antwort unter einer gewissen Situation nicht funktioniert hat. so mache ich einen neuen, für den ursprünglichen Zweck: Führen Sie gut, weniger Linien und mit reinem regulärem Ausdruck:

Dieses Mal, wenn ich, wie unten mit vielen strengen Testfall getestet.

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

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

Die richtige Antwort wäre/test/sein.

Nicht gemeint Wettbewerb zu tun, aber Performance-Test ist ein Muss:

Testfall: for-Schleife 100k mal auf einem Windows 7, i5-3470 Quad Core, 3,20 GHz.

mein: 1,746 Sekunden.

Tom Imrei: 4,548 Sekunden.

Benubird: 3,593 Sek.

Ursa: 4,334 Sekunden.

Es bedeutet nicht, dass meine Version immer besser ist. In einigen Situationen verhalten sie sich ähnlich.

+1

Das ist falsch. a/b /../ c normalisiert sich zu a/c, nicht a/b/c. – Benubird

+1

Danke für die Korrektur. Ich habe meinen Beitrag bearbeitet. – Val

+1

Dies funktioniert, wenn nicht mehrere Instanzen von '/../' vorhanden sind. Zum Beispiel sollte '/ a/b/c /../../../d/e/file.txt' in'/d/e/file.txt' aufgelöst werden, stattdessen geht es nur eine Ebene zurück ('/ a/b/d/e/Datei.txt'). Außerdem mag es keine geraden Zahlen von '/../' wie '/ a/b/c /../../d/e/file.txt', die in'/a/b aufgelöst werden/.d/e/file.txt' (extra Periode) – Cragmonkey

2

Ich denke Tamas Lösung wird funktionieren, aber es ist auch möglich, es mit Regex zu tun, die weniger effizient sein kann, aber sieht besser aus. Vals Lösung ist falsch; aber dieses funktioniert.

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

Ja, es nicht alle möglichen verschiedenen Codierungen von ./ \ usw. handhaben, dass es sein kann, aber das ist nicht der Zweck davon; Eine Funktion sollte nur eine Sache ausführen. Wenn Sie also auch %2e%2e%2f in ../ konvertieren möchten, führen Sie sie zuerst durch eine separate Funktion aus.

Realpath löst auch symbolische Links auf, was natürlich unmöglich ist, wenn der Pfad nicht existiert; aber wir können die zusätzlichen Zeichen '/./', '/../' und '/' entfernen.

+0

Dies funktioniert mit einigen Fällen, kann aber manchmal nicht korrekt ausgeführt werden. Beispiel: $ path = '/var/.////./user/././..///./////// //../././.././Prüfung/////'; $ path = '/var/user/.///////./.././.././././test/'; Die Ergebnisse von beiden sollten/test/sein, aber die erste gibt "/ var/test" zurück, die zweite return "/ var/user/test /". – Val

+0

@Val Du hast ganz recht, es war ein Fehler da - danke, dass du das herausgebracht hast! Obwohl Ihre Beispiele nicht ganz korrekt sind - die erste reduziert sich auf '/../../ test/', nicht auf '/ test/'. – Benubird

+0

@ Benubird Ich habe extra Arbeit, um die redundante /../../ zu entfernen, weil es nichts unter absoluten Pfad bedeutet, und sieht besser aus. Aber ich stimme Ihnen zu, lassen Sie es dort würde es flexibler machen mit relativen Pfad zu arbeiten. – Val

1

Strenge, aber sichere Implementierung. Wenn Sie nur ASCII für Dateinamen verwenden, wäre es geeignet:

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

Dies funktioniert mit einigen Fällen, kann aber manchmal nicht korrekt ausgeführt werden. Beispiel: $ path = '/var/.////./user/././..///./../// ///../././.././Prüfung/////'; $ path = '/var/user/./././.././../.././././test/'; Die Ergebnisse beider sollten sein/test /, aber leere Zeichenfolge zurückgegeben. – Val

0

Meine 2 Cent.Die regexp ist nur für leere Blöcke Pfad verwendet:

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