Безопасность PHP скриптов

Файлы
SQL Injection
Eval
Mail Injection
XSS
CSRF, Cross-site Request Forging
HTTP Injection
Инициализация переменных
Заливка файлов на сервер
ОПС
Комментарии (8)

В принципе, я не большой специалист по безопасности. Да и тема настолько обширная и многогранная, что одной заметкой её охватить невозможно. Поэтому я сначала писать не собирался. Но со временем выяснилось, что даже очевидные вещи неизвестны многим программистам. Поэтому я решил собрать здесь несколько рекомендаций самого базового уровня.

Основной принцип написания безопасных программ - это контроль данных, приходящих от пользователя.
Есть несколько основных типов атак, которым может подвергнуться сайт, и мы их сейчас рассмотрим.

Файлы
В хорошем современном сайте уже редко можно встретить передачу имени файла в скрипт - данные хранятся в базе, а модули подключаются по более сложным алгоритмам, чем просто передача имени файла в адресной строке. Однако начинающие программисты просто без этого не могут. Доходит до анекдота - "хочу, чтобы было index.php?module=news потому что news.php - не солидно, сайт не выглядит крутым!". Ну, на что не пойдешь ради того, чтобы выглядеть круто. Надо только не забывать о безопасности. О том, что подсунуть для инклюда можно через адресную строку что угодно - от файла на другом сервере (с любым пхп-кодом!), до файла на своем - с настройками или паролями.

Для борьбы с такими подстановками есть несколько методов. Можно проверять имя файла регулярками, но мне ближе всего функция basename(), которая отрезает от имени файла все лишнее.
Если надо передавать файл вместе с путем к нему, к примеру, themes/green/balloons.php, то проверка будет сложнее. Поэтому я бы рекомендовал, все-таки, передавать только имя файла. Или не передавать ничего.

SQL Injection
Следующий тип атак довольно подробно рассмотрен в уже имеющейся статье на этом сайте, Защита от SQL-инъекций. Хочется лишь добавить, что защита от инъекций - не главное в соблюдении правильного синтаксиса, а побочный продукт, и синтаксис надо соблюдать всегда. Особенно - учитывая инъекции второго порядка, когда данные в запрос подставляются не от пользователя, а уже лежащие на сервере.

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

Mail Injection
Получившая распространение в последнее время атака так же уже рассмотрена на этом сайте, Mail injection. Работа с e-mail средствами PHP.. Коротко можно сказать, что нельзя допускать наличие переводов строки в данных, которые попадут в заголовки письма.

XSS
Тип атаки, так же получивший распространение сравнительно недавно. В отличие от всех предыдущих, которые атакуют непосредственно код скрипта, XSS атакует его функциональность: заставляет выполнять пользователя действия, нужные хакеру, или ворует его, пользователя, данные.
Методы весьма разнообразны и многочисленны, однако наиболее распространенный и опасный - это внедрение javascript инструкций в безобидные теги - <img>, <a> и так далее.
Наиболее действенным методом борьбы с этим является полный запрет на ввод пользователем какого бы то ни было HTML кода. Методов борьбы с этим достаточно много, но следует помнить, что функция strip_tags() не убирает опасные вставки из разрешенных тегов. поэтому лично я предпочитаю пользоваться функцией htmlspecialchars(), при выводе любых пользовательских данных.
Но главная проблема при защите от XSS - не сам метод, а последовательность его применения. практически все известные атаки были произведены в тех частях сайта, где никто и подумать не мог об обработке пользовательских данных. А зря. Обрабатывать их надо везде и всегда.

Так же к этому разделу можно отнести атаки на сессии. Связанные с кражей или подстановкой идентификатора. подробнее можно почитать в разделе Сессии. Подробное описание работы и объяснение механизма.

CSRF, Cross-site Request Forging
Формирование на сайте хакера запроса, который будет выполнен браузером пользователя (и, как следствие - со всеми правами пользователя!). То есть, просто авторизация от этой атаки не спасает. Для защиты от этого типа атак следвет взять за правило добавлять ко всем важным формам скрытое поле, содержащее уникальную строку (сгенерированную случайным образом только для этой формы), записывать её в сессию и сравнивать при обработке формы. Хакер добавить такую строку в свою форму не сможет, и атака не сработает.

HTTP Injection
XSS уязвимость, похожая на mail injection, только объектом атаки являются не заголовки письма, а HTTP-заголовки. Атака экзотическая, но знать про неё надо. Хакеру ничего не стоит дописать к урлу код с яваскриптом, который благополучно через переменную PHP_SELF или REQUEST_URI программист сам вставит в свою страницу.

Инициализация переменных
Со времени появления PHP в нем есть одна очень неприятная функциональность: получить пользовательские данные в своем скрипте можно совсем того не ожидая. Да-да, речь идет о register_globals. Если эта директива включена, пользователь может создать в скрипте любую переменную с любым содержимым, если только она не была создана программистом. А программисты часто это делать забывают, к примеру, если мы имеем код
if ($login=="login" AND $password=="password"$admin=1;
и в дальнейшем проверяем переменную $admin, то любой может получить права админа, просто дописав в адресной строке index.php?admin=1.
Лекарством от этого служит принудительная инициализация всех переменных перед использованием. В строгих языках, таких, как Паскаль, невозможно использовать переменную, предварительно не объявив. В PHP это возможно, но злоупотреблять этим не стоит - следует объявлять все переменные перед использованием. В качестве же временной заплатки можно порекомендовать register_globals=off.
то же самое можно почитать на офсайте PHP, http://ru.php.net/manual/ru/security.globals.php

Отдельной строкой следует упомянуть борьбу с register_globals. Попытки эмулировать её включение (если отключена) или нейтрализовать (если включена) приводят к обратным результатам. Многочисленные циклы с назначением или удалением переменных приводят к проблемам более худшим, если бы директива просто была установлена в желаемое значение. Единственный гарантированный способ избежать её влияния - инициализация переменных.

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

ОПС
Статья Сергея Круглова, http://www.captcha.ru/articles/antihack/