Подтвердить что ты не робот

Уязвимость PHP regex

Сотрудник сегодня сделал мне ставку на то, что он знает способ подачи специально отформатированной строки, которая могла бы пройти следующую проверку регулярных выражений и все еще предоставлять имя файла с расширением .php или .jsp или .asp:

if (preg_match('/\.(jpeg|jpg|gif|png|bmp|jpe)$/i', $var) && preg_match('/\.(asp|jsp|php)$/i', $var) == false) 
{
    echo "No way you have extension .php or .jsp or .asp after this check.";
}

Как бы я ни старался и обыскал сеть, я не смог найти недостаток, который мог бы сделать такое. Могу ли я что-то забыть? Учитывая, что проблема с "нулевым байтом" устранена, что еще может быть проблемой?

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

EDIT - действительный код:

if (isset($_FILES["image"]) && $_FILES["image"]["name"] && preg_match('/\.(jpeg|jpg|gif|png|bmp|jpe)$/i', $_FILES["image"]["name"]) && preg_match('/\.(asp|jsp|php)$/i', $_FILES["image"]["name"]) == false) {
        $time = time();
        $imgname = $time . "_" . $_FILES["image"]["name"];
        $dest = "../uploads/images/";

        if (file_exists($dest) == false) {
            mkdir($dest);
        }

        copy($_FILES['image']['tmp_name'], $dest . $imgname);

    }else{
        echo "Invalid image file";
    }

Версия PHP: 5.3.29

EDIT: эпилог

Оказалось, что "уязвимость" присутствует только в Windows. Тем не менее, он сделал именно то, что сказал мне мой коллега, - проверил проверку регулярного выражения и сохранил файл с исполняемым расширением. Следующее было проверено на WampServer 2.2 с PHP 5.3.13:

Передача следующей строки в проверку регулярного выражения выше test.php:.jpg (обратите внимание на символ двоеточия ":" в конце желаемого расширения) проверит его, и функция copy(), кажется, опускает все после символа двоеточия, включая символ сам. Опять же, это верно только для окон. В linux файл будет написан точно с тем же именем, который передается функции.

4b9b3361

Ответ 1

Существует не один шаг или полный прямой способ использовать ваш код, но вот некоторые мысли.

Вы передаете его в copy() в этом примере, но вы упомянули, что используете этот метод для проверки файла ext awhile now, поэтому я предполагаю, что у вас были другие случаи, которые могли использовать эту процедуру для других функций тоже для разных PHP-версии.

Рассмотрим это как тестовую процедуру (Exploiting include, require):

$name = "test.php#.txt";
if (preg_match('/\.(xml|csv|txt)$/i', $name) && preg_match('/\.(asp|jsp|php)$/i', $name) == false) {
    echo "in!!!!";
    include $name;
} else {
    echo "Invalid data file";
}

Это закончится печатью "in!!!!" и выполнение "test.php", даже если оно будет загружено, оно будет включать его из папки tmp - конечно, что в этом случае вы уже являетесь владельцем злоумышленника, но также рассмотрите эти варианты. Это не общий сценарий для процедуры загрузки, но это концепция, которая может быть использована путем объединения нескольких методов:

Перемещение вперед - если вы выполните:

//$_FILES['image']['name'] === "test.php#.jpg";
$name = $_FILES['image']['name'];
if (preg_match('/\.(jpeg|jpg|gif|png|bmp|jpe)$/i', $name) && preg_match('/\.(asp|jsp|php)$/i', $name) == false) {
    echo "in!!!!";
    copy($_FILES['image']['tmp_name'], "../uploads/".$name);
} else {
    echo "Invalid image file";
}

Снова отлично. Файл скопирован в папку "uploads" - вы не можете получить к нему доступ напрямую (поскольку веб-сервер будет вырезать правую сторону #), но вы ввели файл, и злоумышленник может найти способ или другую слабую точку для вызова это позже.

Пример такого сценария выполнения распространен среди сайтов совместного доступа и хостинга, где файлы обслуживаются PHP script, который (в некоторых небезопасных случаях) может загрузить файл, включив его с неправильным типом функций, таких как require, include, file_get_contents, которые все уязвимы и могут выполнять файл.

NULL-байт Атаки с нулевым байтом были большой слабостью в php < 5.3, но был повторно введен регрессией в версиях 5.4+ в некоторых функциях, включая все связанные с файлом функции и многое другое в расширениях. Он был исправлен несколько раз, но он все еще там, и многие старые версии все еще используются. Если вы работаете со старой версией php, вы определенно Exposed:

//$_FILES['image']['name'] === "test.php\0.jpg";
$name = $_FILES['image']['name'];
if (preg_match('/\.(jpeg|jpg|gif|png|bmp|jpe)$/i', $name) && preg_match('/\.(asp|jsp|php)$/i', $name) == false) {
    echo "in!!!!";
    copy($_FILES['image']['tmp_name'], "../uploads/".$name);
} else {
    echo "Invalid image file";
}

Будет напечатан "in!!!!" и скопируйте файл с именем "test.php".

Как исправлено php, проверяя длину строки до и после передачи ее более глубокой процедуре C, которая создает фактический массив char, и тем самым, если строка усечена нулевым байтом (что указывает на конец строки в C) длина не будет соответствовать. читать больше

Как ни странно, даже в исправленных и современных версиях PHP он все еще там:

$input = "foo.php\0.gif";
include ($input); // Will load foo.php :)

Мой вывод: Ваш метод проверки расширений файлов может быть значительно улучшен - ваш код позволяет пропустить PHP файл с именем test.php#.jpg, пока он не должен. Успешные атаки в основном выполняются путем объединения нескольких уязвимостей даже младших - вы должны рассматривать любые неожиданные результаты и поведение как один.

Примечание: есть еще много проблем с именами файлов и картинками, потому что они много раз включены в страницы позже, и если они не фильтруются правильно и включены безопасно, вы подвергаете себя еще многим материалам XSS но это не по теме.

Ответ 2

Попробуйте этот код.

$allowedExtension = array('jpeg','png','bmp'); // make list of all allowed extension

if(isset($_FILES["image"]["name"])){
     $filenameArray = explode('.',$_FILES["image"]["name"]);
     $extension = end($filenameArray);
     if(in_array($extension,$allowedExtension)){
        echo "allowed extension";
     }else{
          echo "not allowed extension";
     }
}

Ответ 3

preg_match() возвращает 1, если шаблон соответствует заданному объекту, 0, если это не так, или FALSE, если произошла ошибка.

$var = "test.php";
if (preg_match('/\.(jpeg|jpg|gif|png|bmp|jpe)$/i', $var) === 1 
    && preg_match('/\.(asp|jsp|php)$/i', $var) !== 1) 
{
    echo "No way you have extension .php or .jsp or .asp after this check.";
} else{
    echo "Invalid file";
}

Итак, когда вы собираетесь проверить свой код, используйте === 1.

В идеале вы должны использовать.

function isImageFile($file) {
    $info = pathinfo($file);
    return in_array(strtolower($info['extension']), 
                    array("jpg", "jpeg", "gif", "png", "bmp"));
}

Ответ 4

Я помню, что в версии certains в PHP < 5.3.X, PHP позволяет содержать строки 0x00, этот char рассматривается как конец строки
Итак, например, если ваша строка содержит: myfile.exe\0.jpg, поэтому preg_match() будет соответствовать jpg, но другие PHP функции будут останавливаться в myfile.exe, как функции include() или copy()