Регулярное выражение, чтобы проверить, если путь идет только вниз - PullRequest
5 голосов
/ 25 сентября 2011

Я хочу проверить, идет ли путь, указанный пользователем, как:

my/down/path

напротив:

this/path/../../go/up

по соображениям безопасности.

Я уже сделал это:

return (bool)preg_match('#^([a-z0-9_-])+(\/[a-z0-9_-])*$#i', $fieldValue);

Но пользователю должно быть разрешено использовать '.' на своем пути (например: my/./path, это не полезно, но он может), и я не знаю, как это рассмотреть.

Затем я ищу безопасное регулярное выражение, чтобы проверить это.

Спасибо

edit: после просмотра ответов, да, было бы хорошо, если бы тест проверял, является ли реальный путь (удаляющий '.' и '..') нисходящим.

Ответы [ 5 ]

4 голосов
/ 25 сентября 2011

Вы можете просто проверить, начинается ли реальный путь указанного пользователем пути с разрешенного пути:

function isBelowAllowedPath($allowedPath, $pathToCheck)
{
    return strpos(
        realpath($allowedPath . DIRECTORY_SEPARATOR . $pathToCheck), 
        realpath($allowedPath)
    ) === 0;
}

Демонстрация на кодовой панели

Обратите внимание, что это также вернет false для каталогов, которые не существуют ниже $allowedPath.

2 голосов
/ 25 сентября 2011

Вы, вероятно, не хотите проверять, что путь не содержит .., но вместо этого хотите проверить, что, если он оценивается как целое, он не увеличивается Например. ./path/.. по-прежнему в ., даже если оно содержит ...

Вы можете найти реализацию проверки глубины пути в Twig :

$parts = preg_split('#[\\\\/]+#', $name);
$level = 0;
foreach ($parts as $part) {
    if ('..' === $part) {
        --$level;
    } elseif ('.' !== $part) {
        ++$level;
    }

    if ($level < 0) {
        return false;
    }
}

return true;

Twig не использует realpath для проверки, потому что realpath имеет проблемы с путями в архивах Phar. Кроме того, realpath работает, только если путь уже существует.

0 голосов
/ 25 сентября 2011

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

Например,

  • Вы заботитесь о прохождении через жесткийили программные ссылки под текущим рабочим каталогом?
  • Поддерживает ли система, в которой вы работаете (или потенциально можете развернуть), юникод?
  • Сколько вещей оценивает данную строку до или после вашейPHP код видит это?Веб-сервер?Оболочка?Что-то еще?

Предположим, я отправил вашему сценарию строку вроде ./..%2f../?Для вашего приложения важно, чтобы эта строка подняла меня на два уровня?Или что сценарии, представленные в других ответах, не поймут это, потому что оно не оценивается как ..?

А как насчет ./\.\./?Если путь анализируется путем разделения на \ и /, сценарий в принятом ответе не поймает его, потому что каждая часть будет выглядеть как ., который является просто текущим каталогом.Но типичная оболочка UNIX рассматривает \ как escape-символ, поэтому его передача ./\.\./ эквивалентна ./../, и поэтому злоумышленник может использовать тот факт, что сценарий объединяет тесты для путей в стиле UNIX и Windows.

Если под «безопасностью» вы действительно подразумеваете, что хотите обеспечить защиту от случайных ошибок и опечаток, тогда других ответов, вероятно, достаточно.Если вы программируете для враждебной среды и хотите предотвратить преднамеренные атаки, направленные на нарушения, тогда они едва ли выцарапывают поверхность, и вам будет рекомендовано ознакомиться с безопасным программированием в OWASP.Я бы начал с их статей о Path Traversal , а затем прочитал о других атаках, которые они описали, а также о том, как их избежать и, что более важно, как их проверить.

0 голосов
/ 25 сентября 2011

Если вы хотите ограничить только переход пользователя в иерархию путей, вы можете явно выполнить поиск '..':

if (1 === preg_match('/\.\./', $path)) {
    /* path contains .. */
}

, что также быстрее, чем разнесение и in_array.

Сравнительный анализ:

<?php

$attempts = 100000;
$path     = 'my/path/with/../invalid';

$t = microtime(true);
for ($i = 0; $i < $attempts; ++$i) {
  $folders = explode('/', $path);
  if (in_array('..', $folders)) {
    /* .. in path */ ;
  }
}
$end = microtime(true);
printf("in_array: %f\n", $end - $t);

$t = microtime(true);
for ($i = 0; $i < $attempts; ++$i) {
  if (1 === preg_match('/\.\./', $path)) {
    /* .. in path */ ;
  }
}
$end = microtime(true);
printf("preg_match: %f\n ", $end - $t);

in_array: 0,088750

preg_match: 0,071547

0 голосов
/ 25 сентября 2011
$folders = $explode('/', $path);

if (in_array('..', $folders)) {
    print('Error: path contains ..');
}
...