В этой небольшой статье я постарался рассмотреть вопрос безопасности при использовании PHP Compiler. Разрабатывая движок, я уделял много внимания его функциональности и быстродействию, но все же главный вопрос, который стоял передо мной − это его надежность и защищенность от взлома. Итак:
Бэкдоры − это способы несанкционированного доступа к сайту, которые недобросовестные создатели движков вставляют в свои творения еще во время разработки. Например, пароль типа "золотого ключика", который подходит к админской панели любого сайта, или определенная страница, зайдя на которую, можно получить доступ к вашим данным.
Никаких бэкдоров в PHPC нет. Даю слово. Это система с открытым исходным кодом, и при желании вы можете убедиться в этом самостоятельно. Я также готов держать ответ перед любой экспертной комиссией по данному поводу. Попасть в админскую панель может лишь тот, кто знает пароль, указанный при установке системы, и никто другой. Иных способов проникнуть "внутрь" сайта в движке нет.
Практически все данные, необходимые для работы сайта, движок хранит не в виде файлов, а в базе данных MySQL. С точки зрения безопасности такой вариант является наиболее предпочтительным, т.к. получить доступ к данным MySQL через протокол HTTP невозможно; кроме того, базы данных всегда защищены паролем.
На всякий случай в системе также предусмотрена возможность файлового кеширования различных данных. Пользоваться этой возможностью рекомендуется только в том случае, если скорость работы проекта совсем уж критична. Для работы с файловым кешем предназначена функция processFileCache, которая хранит файлы в каталоге cache. Для того, чтобы к этим файлам нельзя было получить доступ напрямую, каталог защищен файлом .htaccess с директивой "Deny from all".
Наконец, конфигурационные данные системы хранятся в файлах phpc/config.php и phpc/constant.php. Получить доступ к этим данным "снаружи" также невозможно, т.к. попытки вызова этих файлов напрямую приведут к их выполнению, а не отображению.
Все скрипты в PHP делятся на 2 категории: основные (те, которые вызываются пользователем напрямую) и вспомогательные (те, которые используются в основных скриптах и подключаются к ним с помощью директив include или require). Попытка взлома может состоять в том, чтобы вызвать вспомогательный скрипт напрямую, и по результатам его работы сделать какие-либо выводы.
Для того, чтобы предотвратить это, все вспомогательные скрипты в PHPC делятся на 2 группы: те, которые лишь определяют новые классы и функции, но ничего не делают, и те, которые лишь определяют новые данные, не используя при этом уже существующие и не выполняя никаких операций, кроме присваивания или определения новых констант (phpc/config.php, phpc/constant.php и файлы локализации). Единственное исключение из этого правила − файл phpc/language.php, но там поставлена защита (первая строка функции getPhpcLocale). Все эти файлы можно смело вызывать напрямую − отобразится пустая страница.
Другими словами, попытки прямого вызова вспомогательных скриптов, в том числе с произвольными параметрами, ничего не дадут потенциальному взломщику.
Если у вас динамический сайт, и некоторые из его страниц отображают те или иные данные в зависимости от переданных им параметров, злоумышленник может воспользоваться этим и попытаться взломать сайт, передав такой странице заведомо неверные данные. Например, страница gallery вашего сайта должна отображать галерею, причем номер этой галереи передается через параметр id. Взломщик, полюбовавшись на ссылки типа gallery?id=1 или gallery?id=2, может затем попробовать открыть ссылку gallery?id=1000 (неверное значение параметра), gallery?id= или просто gallery? (пустой или отсутствующий параметр), и даже ссылку gallery?id=hehe (любая ерунда вместо значения). Если система не готова к приему подобных данных, это может закончиться весьма плачевно для сайта.
Чтобы избежать этого, в PHP Compiler есть три важные функции: acceptIntParameter, acceptFloatParameter и acceptStringParameter. Все эти функции предназначены для получения переданных странице параметров. Они не зависят от значения настройки register_globals, не зависят от значения настроек magic_quotes_gpc и magic_quotes_sybase (что очень важно − отныне вам больше не нужно беспокоиться о проблеме лишних слешей в параметрах), и позволяют принимать значения как из обычных параметров, так и из параметров-массивов. Всегда пользуйтесь этими функциями, чтобы получить значения параметров, переданных из "внешнего мира". Рассмотрим их подробнее.
acceptIntParameter: функции передается параметр name (название принимаемого параметра). Функция всегда возвращает целое число, независимо от того, что было фактически указано в качестве значения параметра. Отсутствующий параметр, пустой параметр, произвольная строка вместо числа − все будет тихо сведено к значению 0. Также можно передать функции еще два параметра − min и max, в этом случае возвращаемое значение всегда будет находиться в диапазоне [min, max].
acceptFloatParameter: функция работает точно так же, как и предыдущая, с тем лишь отличием, что возвращает вещественное значение (float) вместо целочисленного.
acceptStringParameter: функции передается параметр name (название принимаемого параметра). Функция всегда возвращает строку, даже если параметр не был указан вообще (в этом случае возвращается пустая строка). Если вы хотите, чтобы возвращаемое значение было не длиннее определенной величины, просто передайте функции второй параметр limit. Важно помнить, что по умолчанию функция всегда выполняет оптимизацию переданных данных − удаляет все пробелы из начала и конца строки, а также удаляет из строки все повторяющиеся пробелы. Если вы хотите отключить оптимизацию, передайте функции false в качестве третьего параметра optimize.
Таким образом, для вышеприведенного примера (отображение галереи) достаточно написать:
$id=acceptIntParameter("id",1,2);и никакие взломщики вам уже не страшны.
Еще одной весьма распространенной уязвимостью является ситуация, когда программист перед использованием переменной в скрипте забывает ее проинициализировать. Поначалу такая ошибка незаметна − PHP сводит все отсутствующие переменные к пустой строке, но если на сервере включена настройка register_globals (как оно обычно и бывает), ошибка превращается в уязвимость. Злоумышленник может передать странице параметр, который автоматически превратится в переменную и может затем повлиять на работу скрипта.
Единственный способ борьбы с этим − правильно писать скрипты, то есть всегда инициализировать переменные перед их использованием. Именно по этому принципу и создан движок. В нем нет мест, где использовалась бы переменная, которая не была определена заранее, тем более в глобальном блоке (global scope). Единственное исключение из этого правила − файлы локализации, но они являются вспомогательными и также безопасны, исходя из пункта 3.
Еще одно замечание: весь программный код, который не относится напрямую к ядру движка, а именно код, полученный при компиляции шаблонов и пакетов, выполняется не в глобальном блоке, а в локальном блоке класса Compiler (версия движка до 2.4.1) или класса Optimizer (версия 2.4.1 или выше). Поэтому он также устойчив к данной уязвимости, даже если был написан без соблюдения этого принципа.
На мой взгляд, при создании функции addslashes разработчиками PHP была допущена серьезная ошибка. Эта функция лишь экранирует специальные символы внутри строки, но не добавляет кавычки в начале и в конце строки. В результате эти кавычки приходится добавлять самостоятельно.
Необходимость вручную добавлять кавычки в начале и в конце строки привела к распространенной ошибке среди программистов − человек добавляет кавычки вокруг строки, но при этом забывает вызвать функцию addslashes для обработки ее содержимого. Это привело в появлению понятия SQL-инъекции (SQL-injection), когда злоумышленник, используя специальные символы, управляет поведением SQL-запроса.
Пример уязвимого запроса:
"SELECT * FROM users WHERE username='$username'"
В PHP Compiler предлагается несколько иной способ экранирования строк в SQL-запросах. Вместо использования кавычек в запросах настоятельно рекомендуется использовать функцию slashes. Данная функция делает то же самое, что и addslashes, но дополнительно к этому добавляет одинарные кавычки в начале и конце строки. Помня это нехитрое правило (никогда не использовать кавычки напрямую), вы всегда будете писать код, устойчивый к SQL-инъекциям.
Пример правильного запроса:
"SELECT * FROM users WHERE username=".slashes($username)
Межсайтовый (или кросс-сайтовый) скриптинг − довольно хитрая уязвимость, которая в принципе опасна только для сложных динамических сайтов с поддержкой аккаунтов пользователей. В результате этой уязвимости злоумышленник может получить список cookies, который хранится на вашем компьютере (причем даже не все cookies, а лишь те, которые относятся к уязвимому сайту). На форуме, например, это может привести к краже вашего пароля. Все, что необходимо сделать злоумышленнику − это внедрить в текст страницы определенный HTML-код (обычно написанный на Javascript).
Для того, чтобы помешать этому, следует всегда фильтровать данные, полученные от посетителя, перед тем, как выводить их на страницу. Например, в гостевой книге нельзя просто так взять и вывести комментарий неизвестного гостя − это уязвимость:
echo $comment;
Вместо этого перед выводом необходимо отфильтровать комментарий:
$comment=htmlspecialchars($comment);
echo $comment;
Для защиты от данной уязвимости весь вывод данных в PHPC фильтруется автоматически. Каждый раз, когда вы пишете в тексте шаблона вывод переменной при помощи тега <var:...>, эта переменная фильтруется перед выводом. Кроме того, при выводе форматированного текста, когда указанные посетителем данные могут попасть в значения параметров HTML-тегов, к этим данным применяется еще более строгая проверка.
Вообще межсайтовый скриптинг − это такая уязвимость, от которой сложнее всего защитить проект. Я должен признаться, что не уверен в стопроцентной устойчивости движка к ней. Тем не менее, начиная с версии 2.4.1, система стала неуязвимой ко всем методам межсайтового скриптинга, которые были мне известны.
Защита админпанели от несанкционированного доступа − последний вопрос, который хотелось бы рассмотреть здесь. Как и в остальной части движка, скрипты в каталоге admin делятся на основные и вспомогательные. Ко вспомогательным относятся файлы controls.php и function.php, к основным − все остальное. Для того, чтобы предотвратить доступ злоумышленника к панели, каждый скрипт в самом начале обязательно должен подключать файл global.php:
require "global.php";
Файл global.php создает все необходимые переменные и классы, а также проверяет авторизацию. Если кто-то попытается запросить защищенный файл, не авторизовавшись, работа скрипта будет немедленно прекращена. После авторизации зашифрованный пароль администратора записывается во временный cookie под именем phpcpassword. Чтобы выйти из админпанели и удалить этот cookie с компьютера, достаточно закрыть браузер.