Статьи Новости Контакты

25.06.2008
Виталий Гавришенко

Безопасное PHP-программирование

основные принципы написания безопасного PHP-кода

Но как только случаются непредвиденные обстоятельства, вы понимаете, что нет ничего важнее, чем ваша информация и ее целостность и сохранность. В лучшем случае вы теряете дни продуктивной работы, пытаясь разобраться и закрыть уязвимость. Худший сценарий, когда вся ваша информация стерта и вы не имеете понятия, как ее восстановить. Не говоря о том, что вы подарили свои имена и пароли целому миру. Вы раскрыли информацию по своей кредитной карте, вашими деньгами уже, возможно, воспользовались... Звучит невероятно, это с вами никогда не случится? В Интернете есть множество ресурсов, где вы можете посмотреть статистику взломов — например, http://zone-h.org/ (eng) и сами чучела взломанных сайтов — http://taxidermia.void.ru/. К примеру, за последнюю неделю на сайте http://zone-h.org/, где хакеры сами заявляют о своих взломах, было извещено о 1361 взломе. За 2007 год по статистике этого же сайта http://www.zone-h.org/content/view/14928/1/ было совершено 480 905 дефейсов. А сколько взломов остаются без извещений? Без использования безопасного программирования никто не может гарантировать безопасную и стабильную работу своего сервиса. Лучше практиковать безопасное программирование, чтобы впоследствии не задаваться вопросами "кто виноват и что делать".

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

Риски на уровне данных

Риски хранения данных — это те риски, которые включают в себя размещение данных в базе данных или файловой системе. Наиболее широко известны атаки на этот класс — атаки SQL injection. Этот тип атак подразумевает собой внедрение атакующим в SQL-запрос произвольных данных, что может позволить получить практически любую информацию из базы данных вплоть до авторизационной информации и кредитных карт. Самый простой способ избежать этих атак — это защитить каждую использующуюся переменную, которая используется для SQL-запроса. К счастью, PHP имеет несколько встроенных функций для этого, например mysql_escape_string(). Функция экранирует все спецсимволы в unescaped_string , вследствие чего ее можно безопасно использовать в SQL-запросе.

Пример экранирования переменной

$item = "Zak's Laptop";

$escaped_item = mysql_escape_string($item);
printf ("Escaped string: %s\n", $escaped_item);

Вышеописанный пример выдаст следующий результат:

Escaped string: Zak\'s Laptop

Когда нужно экранировать данные вводимые пользователем? Это зависит от вашего стиля программирования. Некоторые программисты предпочитают экранировать данные, как только они поступают в приложение, другие предпочитают это делать непосредственно перед запросом в базу данных. Хорошим стилем считается ставить экранирование перед запросом, т. к. можно всегда посмотреть код, операции с базой данных и данные, которые переходят в экранированный запрос, и не нужно искать по всему исходному коду место экранирования.

Второй риск, о котором мы поговорим, — это размещение паролей в текстовом файле (имеются в виду в незашифрованном виде). Некоторые так и делают — есть много приложений с открытым исходным кодом, где пароли находятся в открытом виде. Необходимо взять на заметку, что нет ни одной весомой причины оставлять пароли в открытом виде. Не имеет значения, где вы храните пароли — в текстовом файле или в базе данных, всегда размещайте пароли в виде хеша. Трансформацию паролей достаточно просто сделать при помощи PHP-функции md5(), следует зашифровать их перед сохранением. Так как md5 повторяемая функция, вы можете проверять пароли обычным сравнением. MD5-хеш строки str вычисляется, используя алгоритм MD5 RSA Data Security, Inc. Хеш представляет собой 32-значное шестнадцатеричное число. Если необязательный аргумент raw_output имеет значение TRUE, то возвращается бинарная строка из 16 символов. (Замечание: необязательный аргумент raw_output был добавлен в PHP 5.0.0 и по умолчанию равен FALSE.)

Когда нужно шифровать пароли в хеш? Как можно быстрее. Не позволяйте переменной с паролем "плавать" по приложению. Как только пароль был введен, сразу конвертируйте его. Желательно сразу поместить хеш пароля в переменную и использовать только ее.

Давайте теперь поговорим об именах пользователей и паролях, которые нужны вашей программе для взаимодействия с другими приложениями (к примеру, серверы базы данных). Следует всегда разделять их в разные РНР-файлы с кодом и ссылаться на них как на константы или переменные. Это не только упростит программирование, но и, к примеру, если вы решите изменить пароль, вы будете точно знать где его искать.

Пример 1. Пример использования md5()

$str = 'apple';

if (md5($str) === '1f3870be274f6c49b3e31a0c6728957f') {
echo "Would you like a green or red apple?";
exit;
}

Здесь результат md5($str) представляет собой зашифрованную строку apple

Также хотелось бы коснуться концепции разделения прав. Все пользователи базы данных должны иметь наименьшие права доступа, необходимые только для корректного выполнения функций. Если приложению нужно только чтение из базы данных, оно должно иметь права на выполнение SELECT-запроса и никаких прав доступа к другим базам данных.

Для придерживания этой концепции рекомендуется сделать несколько аккаунтов в базе данных. Один аккаунт будет иметь только права на запись в нужные таблицы — INSERT, и полностью отделенный аккаунт будет иметь права на чтение — SELECT. Это позволит быть уверенным, что никакие INSERT-запросы не будут случайно выполнены, и смягчит возможное повреждение, сделанное инъекциями SQL.

Конечно, несколько аккаунтов работают лучше, когда есть четкое разделение между теми, кто может писать в базу данных, и теми, кто может ее читать (это используется в некоторых CMS — Content Management Systems). В теории вы можете использовать несколько аккаунтов в любом приложении, но могут возникнуть проблемы с многочисленными подключениями к базе данных, которые должны быть решены на стадии дизайна архитектуры ПО.

Программируя большие приложения, удобно разбивать код на множество логических файлов, но многие из РНР-программистов имеют привычку давать файлам расширение, отличное от .php, например .inc или .config. Это очень плохая идея, т. к. сервер может быть не настроен на парсинг этих файлов как РНР, и абсолютно любой может загрузить эти файлы как исходный код (потенциально содержащий пароли, имена пользователей и другую приватную информацию). Как вариант для наглядности называть такие файлы inc_ или class_ при необходимости, но расширение все-же ставить .php.

Говоря об инклудинге (включении, подгружении) файлов, хотелось бы также заметить особенности безопасной работы. Если у вас есть PHP-файл, который вы хотите использовать только как часть большого приложения, вставьте эти строки в начало файла (__FILE__, $_SERVER['PHP_SELF']).

Это предотвратит файл от прямого запуска, другими словами, остановит его работу, если кто-то обратится к нему напрямую. Хорошо написанный класс или инклуд-файл не должен делать ничего самостоятельно, но особо по этому поводу можно не беспокоиться — единожды вставленная строка вверху файла снимает все проблеммы.

Другая особенность инклудинга файлов — разница между функциями include() и readfile(). Include говорит серверу парсить файл как РНР, в то время как readfile() интерпретирует файл как обычный текст. Никогда не следует использовать include с файлами, у которых есть публичный доступ на запись, — например, когда у вас есть приложение, которое добавляет принятые от пользователя данные в конец файла (такие как гостевая книга), или с файлами, которые вы не контролируете (файлы на другом сервере или с возможностью редактирования другими пользователями) — произвольный пользователь сможет с легкостью внедрить его собственный РНР-код в вашу систему. В то же время никогда не следует выполнять readfile с файлами, которые оканчиваются на .php. При неправильном конфигурировании системы возникает риск раскрытия всем вашего исходного кода. Подведя итоги, можно сказать, что выполнять readfile() можно с html-, текстовыми и удаленными файлами. Include() стоит выполнять с локальными php-файлами, которые вы хотите выполнить.

Риски на уровне системы

Теперь давайте поговорим о рисках на уровне системы. Системный риск зависит от того, как мы выполняем код. Основной риск системы в любом приложении — неверные данные. Трудно абсолютно и достоверно проверить данные. Как только пользователь вводит данные в систему, следует сразу эти данные проверять и фильтровать. При проверке данных нужно активировать самый тщательный фильтр. К примеру, если программа подразумевает проценты, не нужно проверять, было ли введено что-либо, а проверять строгое соответствие числам между 0 и 100.

Также стоит делать проверки на всех уровня кода. Каждый раз как функция получает данные, проверяйте данные и, если данные неверные/подозрительные, реагируйте соответственно. Это также позволит избежать логических ошибок в вашем приложении.

Риски на уровне исполнения

Опасные функциии eval(), exec() и их подобия (shell_exec(),system(), passthru() и pcntl-exec()).

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

Риски раскрытия информации — стоит убирать сообщения об ошибках и отладочной информации, т. к. они могут дать хакерам информацию о потенциальных ошибках в системе. В работающих системах всегда выключайте сообщения об ошибках и используйте вместо этого функцию PHP errorlog().

Последний риск, о котором мы поговорим, — это использование сессионных идентификаторов. Проще говоря, не пробуйте когда-либо посылать id-сессии пользователю. Сессии не безопасны, но если вы передаете сессионный id, вы подвергаетесь риску того, что кто-то другой, отличный от ожидаемого пользователя, так называемый man in the middle, завладеет сессией пользователя. Пример — перехват сессии интернет-магазина. Злоумышленник может получить данные кредитной карты, изменить адрес доставки или сделать кое-что еще более злонамеренное, зависящее от системы.

Мы обговорили множество рисков безопасности при программировании в РНР, но все они вписываются в несколько простых концепций:

  • никогда не доверяйте пользователю, не позволяйте ему запускать код на вашем сервере и всегда проверяйте входящие данные;

  • не давайте пользователю или программному обеспечению уровень доступа более абсолютного минимума, необходимого для успешного выполнения своих задач;

  • не раскрывайте пользователю больше информации, чем ему необходимо знать, не позволяйте видеть исходный код, сессионные ID и любые сообщения об ошибках, за исключением тех, которые вы создали специально для него.





Скоро на сайте

  • Wordpress

    Серия статей о плагинах к движку WordPrress
  • AJAX

    Проекты и продукты, ориентированные на AJAX
  • Новые сервисы Google

    Обзор новых сервисов Google
 

Copyright © 2003—2017 Все права защищены

При использовании материалов сайта ссылка на hostinfo.ru обязательна

  • хостинг от .masterhost
  • Rambler's Top100