php shell php скрипты
shell_exec
(PHP 4, PHP 5, PHP 7, PHP 8)
shell_exec — Выполнить команду через оболочку и вернуть вывод в виде строки
Описание
Функция идентична оператору с обратным апострофом.
Список параметров
Команда, которая будет выполнена.
Возвращаемые значения
Ошибки
Примеры
Пример #1 Пример использования shell_exec()
Смотрите также
User Contributed Notes 33 notes
In the above example, a line break at the beginning of the gunzip output seemed to prevent shell_exec printing anything else. Hope this saves someone else an hour or two.
To run a command in background, the output must be redirected to /dev/null. This is written in exec() manual page. There are cases where you need the output to be logged somewhere else though. Redirecting the output to a file like this didn’t work for me:
# this doesn’t work!
shell_exec ( «my_script.sh 2>&1 >> /tmp/mylog &» );
?>
Using the above command still hangs web browser request.
Seems like you have to add exactly «/dev/null» to the command line. For instance, this worked:
# works, but output is lost
shell_exec ( «my_script.sh 2>/dev/null >/dev/null &» );
?>
But I wanted the output, so I used this:
proc_open is probably a better solution for most use cases as of PHP 7.4. There is better control and platform independence. If you still want to use shell_exec(), I like to wrap it with a function that allows better control.
Something like below solves some problems with background process issues on apache/php. It also
A simple way to handle the problem of capturing stderr output when using shell-exec under windows is to call ob_start() before the command and ob_end_clean() afterwards, like this:
‘The system cannot find the path specified’.
I’m not sure what shell you are going to get with this function, but you can find out like this:
= ‘set’ ;
echo «» ;
?>
On my FreeBSD 6.1 box I get this:
USER=root
LD_LIBRARY_PATH=/usr/local/lib/apache2:
HOME=/root
PS1=’$ ‘
OPTIND=1
PS2=’> ‘
LOGNAME=root
PPID=88057
PATH=/etc:/bin:/sbin:/usr/bin:/usr/sbin
SHELL=/bin/sh
IFS=’
‘
Very interesting. Note that the PATH may not be as complete as you need. I wanted to run Ghostscript via ImageMagik’s «convert» and ended up having to add my path before running the command:
I had trouble with accented caracters and shell_exec.
Executing this command from shell :
Vidéos D 0 Tue Jun 12 14:41:21 2007
Desktop DH 0 Mon Jun 18 17:41:36 2007
Using php like that :
Vid Desktop DH 0 Mon Jun 18 17:41:36 2007
The two lines were concatenated from the place where the accent was.
I found the solution : php execute by default the command with LOCALE=C.
I just added the following lines before shell_exec and the problem was solved :
Just adapt it to your language locale.
Here is a easy way to grab STDERR and discard STDOUT:
add ‘2>&1 1> /dev/null’ to the end of your shell command
For example:
= shell_exec ( ‘ls file_not_exist 2>&1 1> /dev/null’ );
?>
Here is my gist to all:
On Windows, if shell_exec does NOT return the result you expected and the PC is on an enterprise network, set the Apache service (or wampapache) to run under your account instead of the ‘Local system account’. Your account must have admin privileges.
To change the account go to console services, right click on the Apache service, choose properties, and select the connection tab.
How to get the volume label of a drive on Windows
print GetVolumeLabel ( «c» );
?>
Note: The regular expression assumes a english version of Windows is in use. modify it accordingly for a different localized copy of Windows.
I have PHP (CGI) and Apache. I also shell_exec() shell scripts which use PHP CLI. This combination destroys the string value returned from the call. I get binary garbage. Shell scripts that start with #!/usr/bin/bash return their output properly.
A solution is to force a clean environment. PHP CLI no longer had the CGI environment variables to choke on.
// Binary garbage.
$ExhibitA = shell_exec ( ‘/home/www/myscript’ );
?>
— start /home/www/myscript
#!/usr/local/bin/phpcli
echo( «Output.\n» );
Be careful as to how you elevate privileges to your php script. It’s a good idea to use caution and planing. It is easy to open up huge security holes. Here are a couple of helpful hints I’ve gathered from experimentation and Unix documentation.
Things to think about:
1. If you are running php as an Apache module in Unix then every system command you run is run as user apache. This just makes sense.. Unix won’t allow privileges to be elevated in this manner. If you need to run a system command with elevated privileges think through the problem carefully!
2. You are absolutely insane if you decide to run apache as root. You may as well kick yourself in the face. There is always a better way to do it.
3. If you decide to use a SUID it is best not to SUID a script. SUID is disabled for scripts on many flavors of Unix. SUID scripts open up security holes, so you don’t always want to go this route even if it is an option. Write a simple binary and elevate the privileges of the binary as a SUID. In my own opinion it is a horrible idea to pass a system command through a SUID— ie have the SUID accept the name of a command as a parameter. You may as well run Apache as root!
As others have noted, shell_exec and the backtick operator (`) both return NULL if the executed command doesn’t output anything.
This can be worked around by doing anything like the following:
it took me a heck of a lot of head banging to finally solve this problem so I thought that I would mention it here.
If you are using Eclipse and you try to do something like
shell_exec is extremely useful as a substitute for the virtual() function where unavailable (Microsoft IIS for example). All you have to do is remove the content type string sent in the header:
This works fine for me as a substitute for SSI or the virtual() func.
Shell script с root правами из PHP?
с помощью приложения на PHP порождать некие процессы, работающие от конкретного (отличного от пользователя, от которого работает web-сервер) пользователя.
Сейчас, как вы можете догадаться, от apache отпочковываются процессы, работающие под его же пользователем, что естественно не есть гуд по нескольким причинам: 1) если понадобилось ребутнуть apache (или он не дай бог крэшнется сам), то упадут все процессы, порожденные от него 2) кривые права доступа для учетки www-data в связи с тем, что ей тоже нужны права на запуск экземпляров приложения, лежащего вне директории web-сервера. Хватит это терпеть, подумал я…
Был написан shell script, который умеет запускать приложение от конкретного пользователя. Он был протестирован от root-а через консольку — работет. Через PHP выполняем shell_exec(), который запускает наш скрипт с некоторыми параметрами (условно, для простоты понимания, с сетевым портом, который будет слушать экземпляр приложения). Осталось разобраться с правами www-data на запуск скрипта от root-а. Ну так же ж есть sudo, подумал я… Вот тут-то и возникли сложности.
Положим, что бинарник приложения лежит (условно) тут /home/app/bin. Пользователь username, от которого мы собираемся запускать экземпляры, имеет полный доступ (rwx) как к директории так и к бинарнику.
Файлы web-сервера как и положено валяются где-то в /var/www. Apache работает от пользователя www-data.
Shell script лежит (условно) в /var/www/script.sh. Полные права доступа (rwx) к нему имеет только root, все остальные делать с ним ни чего не могут. Он тщательно проверяет входные параметры (существование пути, существование бинарника и другие проверки) и запускает экземпляр приложения следующей командой:
или условно
Остается позволить www-data запускать script.sh от root-а. Ставим sudo,
идем в /etc/sudoers и добавляем:
www-data ALL=(root) NOPASSWD: /var/www/script.sh
По ощущениям этого достаточно и по тем же ощущениям подозрения — слишком просто.
Теперь из PHP вызываем наш скрипт:
Пробуем по другому (через консоль):
Появляется мало ожидаемое приглашение ввести пароль пользователя www-data:
[sudo] password for www-data:
А NOPASSWD на что? Да и вообще какого хрена пароль у www-data? 🙁
Собственно, что я делаю не так?
Понятное дело, что православность решения мягко говоря под сомнением, но пока хочется «добить» хотя бы его (силы-то и время уже потрачены). Другие решения придут (надеюсь) позднее с новым опытом.
Через какую дыру взломали сайт?
Если сайт взломан, мало удалить с него вирус и загруженный PHP Shell. Нужно еще найти причину, по которой произошел взлом, иначе через день-два на сайте снова будет под бодрую музыку развеваться красивый
турецкий иностранный флаг. Чаще всего причина — украденный пароль от FTP, устаревшая версия CMS или плагина к ней, но как найти, что именно было использовано для проникновения?
Имея некоторый опыт в этой сфере (в среднем наша техподдержка занимается поиском причины взлома сайта раз в неделю), мы систематизировали накопившуюся информацию.
Итак, зачем вообще взламывают сайты? И что делать, если сайт взломан, как найти причину и защититься от последующих атак?
Зачем взламывают сайты
Алгоритм поиска причины взлома
Как искать следы взлома
Помимо файлов сайта стоит также проверить общий /tmp сервера — в нем могут находиться временные файлы, использованные для взлома или для запуска ботов.
Иными словами, необходимо искать все необычное/непонятное и заглядывать внутрь всех подозрительных файлов. PHP Shell может выглядеть, например, так:
Файлы можно искать и вручную, но быстрее, если взлом произошел недавно, воспользоваться командой find:
Если ничего не помогает, можно просто поискать все файлы, содержащие закодированное в base64 содержимое, например, так:
Определение времени взлома
Когда файлы найдены, определить время взлома очень просто — достаточно посмотреть время изменения самого раннего файла.
Если подозрительных файлов не найдено, но сайт заражен вирусом, посмотрите дату изменения файлов index.php, index.html или тех, в которых обнаружите вирус. Скорее всего в этом случае сайт взломали, украв пароль от FTP.
Поиск журналов взлома сайта
Теперь самое главное — чтобы эти журналы были в наличии!
Если на сайте только произведен дефейс или добавлен вирус ко всем файлам index.html, скорее всего, сайт взломали через кражу пароля FTP (или, гораздо реже, SSH). Посмотрите журналы подключения по FTP и SSH во время взлома — присутствие в нем неизвестных IP-адресов или большого количества разных IP-адресов, осуществивших успешное подключение к сайту, означает, что пароль украден.
В этом случае достаточно проверить компьютеры, с которых осуществлялся доступ к сайту, на вирусы, сменить пароли FTP и никогда больше не сохранять пароли в клиентах FTP.
Если же на сайте присутствуют PHP Shell или вредоносные скрипты (например, для рассылки спама), скорее всего, сайт взломали через уязвимость в CMS или каком-либо её плагине. В этом случае потребуется проанализировать логи веб-сервера.
Если удалось определить IP-адрес
Определив IP-адрес взломщика, мы производим поиск этого IP-адреса по журналу веб-сервера и видим все действия, которые он совершал. Где-то близко к моменту обращения к PHP Shell будет успешное использование уязвимости сайта.
Если определить IP-адрес не удалось
Описанный выше способ работает, если известно конкретное имя файла, через которое производилась работа с сайтом после взлома. Однако это имя не всегда известно. В этом случае придется поискать момент взлома немного подольше, но найти его все равно можно.
Большинству, подавляющему большинству взломов свойственны запросы HTTP POST, так как через них происходит загрузка файлов на взламываемый сайт. Через POST злоумышленник может также пытаться взломать форму ввода пароля или просто зайти в раздел администрирования с украденным паролем.
Если известно время взлома сайта (а мы его уже знаем), необходимо поискать в журнале веб-сервера все запросы POST, находящиеся близко ко времени взлома. Здесь нет конкретных советов — выглядеть они могут совершенно по-разному, но выглядеть они будут в любом случае необычно. Например, это могут быть запросы, содержащие ‘../../../../’ или длинные запросы, содержащие имена файлов или запросы SQL.
Если ничего не удалось найти
Такое тоже может быть. В этом случае можно порекомендовать только чистую переустановку CMS и ее расширений и последующий импорт данных. Обязательно убедитесь, что версия CMS и ее расширений — последняя.
И ее расширений! Чаще всего взломы производятся не через ядро системы управления сайтом, а через какой-нибудь устаревший плагин, автор которого давно забросил его разработку.
Ну и, разумеется, смените все пароли, которые имеют какое-либо отношение к сайту.
Запускаем PHP-скриптики через php-fpm без web-сервера. Или свой FastCGI-клиент (под капотом)
Приветствую всех читателей «Хабра».
Дисклеймер
Статья получилась довольно длинная и тем кто не хочет читать предысторию, а хочет перейти сразу к сути прошу прямиком к главе «Решение».
Вступление
В данной статье хотелось бы рассказать о решении довольно нестандартной задачи, с которой пришлось столкнуться во время рабочего процесса. А именно, нам понадобилось запускать в цикле кучу php скриптов. О причинах и о спорности подобного архитектурного решения в данной статье распространяться не буду, т.к. собственно она и не про это вовсе, просто была задача, ее нужно было решить и решение показалось мне достаточно интересным чтобы им поделиться с Вами, тем более манов по данному вопросу в интернете я не нашел совсем (ну разумеется кроме официальных спецификаций). Спеки конечно это хорошо и в них конечно все есть, но думаю вы согласитесь, что если вы не особо знакомы с темой, да и еще и ограничены по времени то разбираться в них то еще удовольствие.
Для кого эта статья
Для всех кто работает с web-ом и о протоколе FastCgi знает лишь что это протокол в соответствии с котороым web-сервер запускает php скриптики, но хочет более детально его изучить и заглянуть под капот.
Обоснование (зачем эта статья)
Но при запуске каждого скрипта, будет создаваться окружение, запускаться отдельный процесс, в общем как то затратно по ресурсам нам показалось. Данную реализацию отвергли. Второе что пришло на ум это конечно же php-fpm, он ведь такой крутой, всего один раз запускает окружение, следит за памятью, все там логирует, корректно запускает и останавливает скрипты, в общем все делает круто, и нам конечно же этот путь понравился больше.
Но вот незадача, в теории то мы знали как это работает, в общих чертах (как оказалось в очень общих), но вот реализовать этот протокол на практике без участия web-сервера оказалось довольно трудно. Чтение спецификаций и пару часов безуспешных попыток показали что для реализации потребуется время, которого у нас на тот момент не было. Манов по реализации данной затеи, в которых было бы просто и понятно описано данное взаимодействие не нашлось, спеки наскоком взять тоже не удалось, из готовых решений нашли питоновский скрипт и пыховскую либу на гитхабе, которую в итоге не захотели тащить к себе в проект (может это и не правлиьно но не особо мы любим всякие сторонние библиотеки да еще и не очень то и популярные, а значит и не проверенные). В общем по итогу от этой идеи мы отказались и реализовали все это через старых добрый rabbitmq.
Хоть задачу в итоге и решили, но разобраться в FastCgi детально я все таки решил, и в добавок решил написать об этом статью, в которой будет просто и подробно описано как заставить php-fpm запустить php скрипт без web-сервера, а точнее в качестве web-сервера будет другой скрипт, далее его буду называть Fcgi клиент. В общем надеюсь что данная статья поможет тем кто столкнулся с такой же задачей как и мы и прочитав ее сможет быстро все написать как ему надо.
Творческий поиск (ложный путь)
Итак проблема обозначена, надо приступать к решению. Естественно как любой «нормальный» программист для решения задачи, про которую ни где не написано что делать и что вводить в консоль, я не стал читать и переводить спецификацию, а сразу же придумал свое «гениальное» решение. Суть его в следующем, я знаю что nginx (мы используем nginx и чтобы не писать далее дурацкое — web-сервер, буду писать nginx, так как то посимпатичнее) что то передает в php-fpm, это что то php-fpm обрабатывает и на основе него запускает скрипт, что ж вроде все просто, возьму да залогирую то что передает nginx и передам то же самое.
Тут поможет великолепный netcat (UNIX-утилита для работы с сетевым трафиком, которая по моему может практически все). Итак ставим netcat на прослушивание локального порта, а nginx настраиваем на работу с php файлами через сокет (естественно сокет на том же порту который слушает netcat)
Проверить что все ок, можно обратившись через браузер на адрес 127.0.0.1:9000 должна быть следующая картина
настраиваем nginx чтобы он php скрипты обрабатывал через сокет на 9000 порту (в настройках ‘/etc/nginx/sites-available/default’, конечно могут отличаться)
После этих манипуляций проверим что же получилось, обратившись к php скрипту через браузер
Видно что nginx отправил переменные окружения, а также непечатаемые символы, то есть данные были переданы в двоичной кодировке, а это значит что так просто их нельзя скопировать и послать в сокет php-fpm. Если сохранить их в файл например то они сохраняться в 16-ричной кодировке, выглядеть это будет примено так
Но это тоже мало что нам дает, наверное чисто теоретически их можно перевести в двоичную кодировку, каким то образом (даже не представляю каким) их отправить в сокет fpm, и даже есть вероятность что весь этот велосипед как то сработает, и даже запустит какой то скрипт, но уж как то все это страшненько и кривенько.
Стало ясно что данный путь совершенно неверный, сами видите насколько все это убого выглядит, и тем более все эти действия не позволят нам управлять соединением, и ни как не приблизят к пониманию взаимодействия между php-fpm и nginx.
Все пропало, изучения спецификации не миновать!
Решение (тут собственно начинается вся соль данной статьи)
Теоретическая подготовка
Давайте теперь рассмотрим как же все таки происходит соединение и обмен данными между nginx и php-fpm. Немного теории, все общение происходит как уже понятно через сокеты, далее будем рассматривать конкретно соединение через TCP сокет.
Единицей информации в протоколе FastCgi является cgi запись. Такие записи сервер отправляет приложению и точно такие же записи получает в ответ.
Немного теории (структуры)
Далее рассмотрим структуру записи. Для понимания из чего состоит запись нужно понимать что из себя представляют Си подобные структуры и понимать их обозначения. Для тех кто не знает далее это будет кратко (но достаточно для понимания) описано. Описать постараюсь как можно проще, в детали углубляться тут нет смысла, да и боюсь что в деталях запутаюсь, главное чтобы было общее понимание.
Структуры представляют собой просто напросто набор байтов, и нотацию к ним позволяющую их интерпретировать. То есть у вас есть просто последовательность нулей и единиц, и в этой последовательности зашифрованы какие то данные, но пока у вас к этой последовательности нет аннотации то эти данные для вас не представляют никакой ценности, т.к. интерпретировать их вы не можете.
Что тут видно, у нас есть некоторые биты, что это за биты мы понятия не имеем. Ну давайте попробуем например их разделить на байты и представить в десятичной системе
Отлично мы интерпретировали их и получили какие то результаты, допустим что эти данные отвечают за то сколько определенная квартира должна за электроэнергию. Получается что в доме 222 квартира номер 2 должна заплатить 88 рублей. А что еще за две цифры, что с ними делать просто отбросить? Конечно нет! дело в том что мы не имели нотации (формата) которая подсказала бы нам как интерпретировать данные, и интерпретировали их по своему, в связи с этим получили не только бесполезный, но и вредный результат. В итоге квартира 2 заплатила совершенно не то что должна была. (примеры конечно надуманные и служат лишь для того чтобы более понятно объяснить ситуацию)
Теперь посмотрим как же мы должны были интерпретировать правильно эти данные, имея нотацию (формат). Далее буду называть вещи своими именами, а именно нотация = формат (вот тут форматы).
Теперь все сходиться в доме №222 квартира 600 за электричество должна 1000 рублей Думаю теперь ясна важность формата, и теперь понятно как примерно выглядит условно Си подобная структура. (прошу обратить внимания, тут цель не детально объяснить что такое эти структуры, а дать общее понимание что это такое и как это работает)
Условное обозначение данной структуры будет такое
Еще немного теории (FastCgi записи)
Как я уже сказал выше единицей информации в протоколе FastCgi являются записи. Записи сервер отправляет приложению и такие же записи получает в ответ. Запись состоит из заголовка и тела с данными.
Далее идет само тело записи:
Вот пример самой простой FastCgi записи в двоичном виде с форматом
Практика
Скрипт клиент и передающий сокет
Для передачи данных будем использовать стандартное php расширение socket. И первое что нужно будет сделать — это настроить php-fpm на прослушивание порта на локальном хосте, например 9000. Это делается в большинстве случаем в файле ‘/etc/php/7.3/fpm/pool.d/www.conf’, путь конечно зависит от настроек вашей системы. Там нужно прописать примерно следующее (всю портянку привожу чтобы можно было сориентироваться, главная секция здесь listen)
После настройки fpm, следующим этапом будет подключение к сокету
Начало запроса FCGI_BEGIN_REQUEST
Для открытия соединения мы должны отправить запись с типом FCGI_BEGIN_REQUEST = 1 Заголовок записи будет такой (для приведения числовых значений к бинарной строке с заданным форматом будет использована php функция pack())
Тело записи для открытия соединения должно содержать роль записи и флаг управляющий соединением
Итак запись для открытия соединения успешно отправлена, php-fpm ее примет и далее будет ожидать от нас дальнейшей записи в которой нужно передать данные для разворачивания окружения и запуска скрипта.
Передача параметров окружения FCGI_PARAMS
В данной записи мы будем передавать все параметры которые нужны для разворачивания окружения, а так же имя скрипта который нам надо будет запустить.
Минимальные необходимые параметры окружения
Первое что нам тут нужно сделать — это подготовить необходимые переменные, то есть пары имя => значение, которые мы передадим приложению.
Структура пар имя значение будет такая
Идет сначала 1 байт — длинна имени, потом 1 байт значение
В нашем случае и имя и значения короткие и подходят под первый вариант, по этому его и будем рассматривать.
Закодируем наши переменные в соответствии форматом
Получение ответа FCGI_PARAMS
Собственно после того как все предыдущее проделано, и приложению отправлено все что оно ожидает, оно начинает работу и нам остается только забрать результат этой работы из сокета.
Помним что в ответ мы получаем такие же записи и нам их тоже нужно интерпретировать.
Получаем заголовок, он всегда равен 8 байт (получать данные будем по байту)
Теперь в соответствии с полученной длинной тела ответа сделаем еще одно чтение из сокета
Ура все сработало! Наконец то!
Что мы имеем в ответе, если например в этом файле
то в ответе получим в итоге
Итоги
Много тут не буду писать итак статья длинная получилась. Надеюсь она кому то поможет. И приведу сам итоговый скрипт, он получился совсем небольшой. Конечно он в таком виде довольно мало может, и в нем нет обработки ошибок и всего этого, но ему это и не надо, он нужен как пример, чтобы показать основы.