Защита файлов предназначенных для includeРечь пойдет о том, как сделать php файл недоступным для посетителей сайта, но при этом доступным для того, чтобы подключать его (по средствам функции include и т.п.) к другим php файлам на сервере. В первую очередь я задался вопросом о том, каким образом можно внутри php файла (средствами php) узнать - вызван/исполняется ли файл непосредственно или через какой-то другой скрипт.

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

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

Вот маленький пример подобной организации файловой структуры некоего закрытого сайта (требующего авторизации).

Допустим, у нас есть один главный файл “index.php”, через который для посетителей происходит вся работа с сайтом путем передачи ему разных GET параметров. В нем в свою очередь анализируется, к примеру, параметр $_GET['page'] и в зависимости от того, какое слово в нем указано, в “index.php” подключается соответствующий файл (передали, к примеру, слово “inbox”, тогда через функцию include подключается файл “p_inbox.php”), в котором описаны дальнейшие инструкции. Еще в файле “index.php” реализован механизм авторизации (через сессии), то есть в нем каждый раз проверяется, имеет ли зашедший пользователь права для просмотра сайта.

Проблема, на мой взгляд, начинается тогда, когда понимаешь, что есть возможность того, что человек может вызвать напрямую файл “p_inbox.php”. Как минимум, такой скрипт может некорректно отработать и вывести какие-то ошибки, у него, к примеру, может не быть в распоряжении каких-то данных, которые заранее подготавливаются в файле “index.php” в расчете на то, что далее их подхватит “p_inbox.php”.

Так же появляется необходимость в самом начале файла “p_inbox.php” проверять данные сессии, чтобы не дать возможность сделать что-то с помощью этого скрипта неавторизованному пользователю (по сути, если такой проверки не будет, то это может быть серьезной дырой в безопасности). И такая проверка (в каждом подобном файле) может быть не единственной. Если хочется избежать какой-либо некорректной работы скрипта при его непосредственном вызове в браузере, то приходится добавлять так же проверки на существование тех самых подготовленных заранее данных в index.php и т.п.

Все это выглядит как-то печально… Происходит дублирование проверок и излишние вычисления, а в некоторых случаях даже дополнительные запросы в базу данных. Именно это сподвигло меня к тому, чтобы задуматься о том, как определять достаточно легко и универсально, вызывается ли файл “p_inbox.php” в “чистом” виде или же в контексте файла “index.php”. Ведь если найти такое решение, то можно избавиться от всего избыточного кода в каждом подключаемом файле. Так же это можно использовать чтобы “вставляемые” скрипты могли выдавать информацию по-разному (или в разных форматах), в зависимости от того, вызван файл самостоятельно или же в контексте какого-то другого файла.

Испытательный стенд

Предположим, что у нас есть два файла, лежащих в одном и том же каталоге, “index.php” и “addon.php”.


//Файл index.php
print '<p>This index.php</p>';

include('addon.php');

//Файл addon.php
print '<p>This addon.php</p>';

Задача сделать так, чтобы при непосредственном вызове файла “addon.php” не выводилось надписи “This addon.php”, но при вызове файла “index.php” появлялись сразу две надписи: “This index.php” и “This addon.php” (вторая будет сигнализировать об успешной вставке файла “addon.php”).

Варианты решения проблемы

Варианты перечислены в порядке поступления мыслей, которые приходили в ходе решения задачи :-) Тот способ, к которому я в итоге пришел, описан самым последним (четвертый).

Первый

Самое первое, что мне пришло в голову, это давать какое-нибудь более сложное (секретное) имя подключаемому файлу, к примеру “40dfg_9n2xo_addon.php”. Но вариант можно сразу же браковать из-за того, что если человек имеет информацию о структуре каталогов и именах файлов сайта (что не так уж и сложно может быть в некоторых случаях), то такая “защита” сразу же с треском провалится.

Второй

В самом начале главного файла (”index.php”) можно создать переменную - “secret”, которая будет равна какой-то секретной строке.

$secret = '85mb_*4ce56Z-e';

После чего в начале файла “addon.php” проверять, существует ли такая переменная и равна ли она секретной фразе “85mb_*4ce56Z-e”. Саму фразу можно, конечно, разместить в каком-то одном файле типа “config.php”, подключая его во всех скриптах через функцию “require_once”, чтобы в последствии можно было легко изменять эту фразу, но в примере я хочу показать саму суть без примесей лишних отвлекающих маневров :-)

В этом способе так же строится предположение, что исходный код и структуру сайта никто больше не увидит, что, так или иначе, является заблуждением. Если посторонний человек каким-то образом узнает эту секретную фразу и директива register_globals в php.ini будет установлена в “on”, он имеет возможность вызвать скрипт с соответствующим параметром (”addon.php?secret=85mb_*4ce56Z-e”) и получить добро на исполнение файла.

Да, чтобы избежать опасности, связанной с “register_globals”, достаточно в начале файла “addon.php” сделать проверку секретной фразы правильным способом. Не так:

If(!isset($secret) || $secret != '85mb_*4ce56Z-e') exit();

А так:

If(!isset($GLOBALS['secret']) || $GLOBALS['secret'] != '85mb_*4ce56Z-e') exit();

(обращаясь к переменной через $GLOBALS['secret'] php будет знать, что имеется ввиду именно переменная, определенная ранее в скрипте, а не та, что может быть передана через GET или POST запрос)

Но мне этот способ все равно не очень нравится, какой-то он уж слишком мудреный…

Третий

Идея в том, чтобы средствами самого сервера не разрешать доступ “извне” к файлам на подобие “addon.php”, но при этом оставить доступ для их внутреннего использования (для вставки в другие файлы). Можно все вставляемые файлы положить в одну директорию и закрыть к ней внешний доступ паролем (.htpasswd). Или вообще закрыть доступ к папке с помощью файла .htaccess, добавив, к примеру, в него строку “Deny from all”. Вполне возможно, что есть еще способы закрыть доступ извне к файлам или директориями средствами сервера.

Способ достаточно хороший и можно сказать “железный”. Только есть шанс, что, к примеру, при переезде сайта файлы .htaccess ненароком не перенесутся, и этого можно не заметить. А паранойя должна быть паранойной :-) поэтому вполне можно совмещать этот способ защиты файлов с их внутренней защитой, реализованной средствами php.

Четвертый

Этот тот способ, на котором я остановился и который в целом мне нравится :-) Идея в том, чтобы в файле “addon.php” узнавать имя файла, в котором содержится конкретный кусок кода (__FILE__), и имя файла, в котором выполняется текущий код ($_SERVER['PHP_SELF']), после чего можно сравнить оба эти значения. И если они отличаются, значит, “addon.php” выполняется внутри “index.php”, если значения одинаковы, значит, выполняется непосредственно “addon.php”.

Собственно, вот модифицированный код файла “addon.php”.


//Файл addon.php
if($_SERVER['DOCUMENT_ROOT'].$_SERVER['PHP_SELF'] == __FILE__) exit();

print '<p>This addon.php</p>';

Определение __FILE__ возвращает абсолютный путь к файлу, в котором оно вызвано. Используется для отображения информации о скрипте, если в нем произошла ошибка (при обработке исключений).

$_SERVER['PHP_SELF'] – хранит в себе полный путь относительно корня сайта к скрипту, который в данный момент исполняется (который запросил пользователь в своем браузере, если посмотреть значение переменной $_SERVER['PHP_SELF'] из подключаемого файла, будет все равно возращен путь к “главному” файлу).

$_SERVER['DOCUMENT_ROOT'] – возвращает абсолютный путь к корню сайта. Из-за того, что в __FILE__ абсолютный путь, а в $_SERVER['PHP_SELF'] полный путь именно от корня сайта, приходится PHP_SELF совмещать с DOCUMENT_ROOT, чтобы получился абсолютный путь, который уже можно сравнивать с тем путем, который возвращает __FILE__.

Если обнаруживается, что файл “addon.php” вызван без каких-либо посредников, скрипт просто останавливает свое исполнение. Хотя можно еще по-разному реагировать на такие попытки вторжения - генерировать ошибку 404, прикидываясь, что файла не существует, или перенаправлять посетителя на первую страницу сайта и т.д.

Внимание: под виндовым сервером проверка не будет срабатывать, в этом случае нужно у пути, который берется через PHP_SELF, заменять все слеши (”/”) на обратные слеши (”\”):

if($_SERVER['DOCUMENT_ROOT']. str_replace('/', '\', $_SERVER['PHP_SELF']) == __FILE__) exit();

Все, буквы кончились…

Было бы интересно услышать конструктивную критику или какие-то дополнения (может, есть какой-то еще более простой и эффективный способ?).

1 звезда2 звезд3 звезд4 звезд5 звезд (6 голосов, средний: 3.67 из 5)
Загрузка ... Загрузка ...