Перейти к содержимому

RISH 2.8.0: изоляция PHP, защита CRON и SFTP

22 июля 2026

К нам обратились владельцы сайта, который был взломан через уязвимость в установленной версии шаблона на базе Helix. Мы очистили сайт, но не ограничились удалением вредоносных файлов: главным этапом стал аудит действий злоумышленника и способов, которыми он пытался сохранить доступ к серверу.

Аудит показал две попытки закрепления. Злоумышленник изменил пользовательский CRON, чтобы вредоносные команды запускались повторно, а также добавил собственный публичный ключ в настройки SFTP. Даже после очистки сайта такие изменения могли вернуть ему доступ.

Именно этот случай стал отправной точкой для RISH 2.8.0. Мы пересмотрели границу между PHP-процессом сайта, системным пользователем и административными механизмами сервера. Теперь PHP-FPM получает только необходимые ему возможности, а CRON, ключи доступа и служебные учетные данные переходят под контроль root.

С чего начались изменения

Точкой входа стала уязвимость в коде сайта. Но после получения возможности выполнять PHP-код злоумышленник начал исследовать не только файлы CMS. Его интересовали механизмы, которые продолжают работать независимо от посещений сайта и позволяют вернуться после очистки.

Пользовательский CRON подходит для этого почти идеально: один раз созданное задание может регулярно загружать или запускать вредоносный код. Файл authorized_keys в домашнем каталоге решает другую задачу — добавляет постоянный ключ для удаленного доступа.

Обе возможности были связаны с одной архитектурной особенностью. В RISH каждый сайт изолирован отдельным пользователем Linux, и от имени этого же пользователя работает PHP-FPM pool. Такая схема разделяет сайты между собой, однако PHP-процесс наследовал больше возможностей системной учетной записи, чем требовалось для обработки веб-запросов.

После аудита мы решили не ограничиваться очисткой одного сервера. В RISH 2.8.0 административные данные и механизмы доступа выводятся из-под контроля пользователя сайта. Главный принцип релиза: PHP должен обслуживать сайт, но не управлять способом входа на сервер и не создавать себе постоянные задания.

Что меняется в общей модели

ОбластьРаньшеВ RISH 2.8.0
PHP-FPMПроцесс использовал обычное окружение системного пользователяСервис получает ограничения systemd
Учетные данные/home/<user>/.pass.txt/root/rish/credentials/<user>
CRONПользователь мог изменять собственный crontabУправление разрешено только root
Ключи SFTP/home/<user>/.ssh/authorized_keys/etc/ssh/authorized_keys/<user>, владелец root
SFTP-сессияБазовый блок ограниченийФайловый доступ без терминала, forwarding и пользовательских команд
Пользователи сервераСоздание и удаление внутри общего менюОтдельный сценарий с проверкой зависимых данных и откатом

Изоляция PHP-FPM через systemd

PHP должен читать код сайта, работать с файлами в /var/www/<пользователь>/www, использовать временные каталоги pool и подключаться к базе данных. Для этой работы ему не нужен доступ к домашним каталогам, обычным устройствам, параметрам ядра или механизмам повышения привилегий.

Для каждой установленной версии PHP-FPM RISH создаёт systemd drop-in:

/etc/systemd/system/phpXX-php-fpm.service.d/local.conf

Полная конфигурация выглядит так:

[Service]
Restart=on-failure
RestartSec=180
ProtectHome=yes
PrivateTmp=yes
PrivateDevices=yes
NoNewPrivileges=yes
RestrictSUIDSGID=yes
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectKernelLogs=yes
ProtectControlGroups=yes
LockPersonality=yes
RestrictRealtime=yes

RestrictSUIDSGID появился в systemd 242, а ProtectKernelLogs — в systemd 244. RISH консервативно включает их начиная с systemd 245, где поддержка обоих параметров гарантирована. Поэтому на AlmaLinux 9 и 10 применяются все перечисленные настройки защиты. AlmaLinux 8 использует systemd 239: на ней RISH пропускает только RestrictSUIDSGID=yes и ProtectKernelLogs=yes, а остальные ограничения применяет в полном объёме.

ProtectHome=yes делает каталоги /home, /root и /run/user недоступными для процессов PHP-FPM. Что закрываем: чтение и изменение домашних файлов, пользовательских настроек, старых файлов .pass.txt, каталогов .ssh и других данных, которые не относятся к сайту. Почему: сайты RISH находятся в /var/www, поэтому для обработки запросов PHP доступ к домашним каталогам не требуется.

PrivateTmp=yes создаёт для PHP-FPM отдельные /tmp и /var/tmp. Что закрываем: просмотр, подмену и использование временных файлов других сервисов и обычных процессов системы. Почему: PHP-приложениям нужны временные файлы, но им не нужно общее временное пространство со всем сервером.

PrivateDevices=yes выдаёт сервису изолированный минимальный /dev с необходимыми псевдоустройствами и собственной подсистемой псевдотерминалов. Что закрываем: доступ к физическим дискам, системной памяти через /dev/mem, портам через /dev/port, низкоуровневым операциям ввода-вывода и созданию новых device nodes. Почему: веб-приложение работает с файлами и сетью и не должно обращаться к физическим устройствам сервера.

NoNewPrivileges=yes запрещает процессам PHP-FPM и всем запущенным ими программам получать привилегии, которых не было у исходного процесса. Что закрываем: повышение прав при запуске SUID/SGID-программ или файлов с дополнительными capabilities. Почему: дочерняя команда, запущенная из PHP, должна оставаться в тех же границах прав, что и PHP-процесс.

RestrictSUIDSGID=yes запрещает процессам сервиса создавать файлы с битами SUID или SGID. Что закрываем: подготовку исполняемых файлов, которые в дальнейшем могли бы запускаться с правами владельца или группы файла. Почему: коду сайта не требуется создавать SUID/SGID-файлы. Этот параметр дополняет NoNewPrivileges: один запрещает получение новых привилегий, другой — создание таких файлов.

ProtectKernelTunables=yes закрывает изменение параметров ядра через /proc/sys, /sys и связанные интерфейсы. Что закрываем: изменение сетевых, файловых и других системных настроек ядра из процесса сайта. Почему: конфигурация ядра относится к администрированию сервера и не участвует в работе PHP-приложения.

ProtectKernelModules=yes запрещает явную загрузку и выгрузку модулей ядра, удаляет соответствующую capability, блокирует системные вызовы управления модулями и закрывает каталог /usr/lib/modules. Ограниченная автоматическая загрузка модулей самим ядром при этом всё ещё возможна. Что закрываем: прямое вмешательство процесса PHP-FPM в состав работающего ядра и доступ к файлам установленных модулей. Почему: сайт не должен управлять драйверами и расширениями ядра.

ProtectKernelLogs=yes запрещает доступ к журналу ядра, включая /dev/kmsg и /proc/kmsg. Что закрываем: чтение служебных сообщений ядра, адресов, ошибок устройств и другой низкоуровневой информации о системе. Почему: PHP использует собственные журналы и журналы веб-сервера; сообщения ядра ему не нужны.

ProtectControlGroups=yes делает иерархию control groups недоступной для изменения. Что закрываем: попытки менять распределение ресурсов, лимиты и размещение системных процессов. Почему: управление cgroups — задача systemd и администратора, а не веб-приложения.

LockPersonality=yes запрещает процессу менять execution domain через системный вызов personality. Что закрываем: переключение в альтернативные режимы совместимости ядра, которые могут использоваться для подготовки эксплуатации уязвимостей. Почему: PHP-FPM работает в штатном режиме операционной системы и смена personality ему не требуется.

RestrictRealtime=yes запрещает процессам PHP-FPM использовать realtime-планирование. Что закрываем: возможность получить приоритет, при котором вредоносный или ошибочный процесс вытесняет обычные задачи и ухудшает работу сервера. Почему: обработка веб-запросов не требует realtime-приоритета.

Эти ограничения применяет операционная система ко всему PHP-FPM и ко всем программам, которые он запускает. В результате защита не зависит от списка запрещённых PHP-функций и продолжает действовать для дочерних процессов.

Как защита PHP-FPM применяется при обновлении

RISH обрабатывает установленные версии PHP-FPM последовательно. Для каждой версии сначала подготавливается возможность отката и записывается systemd drop-in. Затем выполняется systemctl daemon-reload и проверяется, что systemd действительно загрузил ожидаемые параметры. После этого RISH запускает php-fpm -t и только при успешной проверке перезапускает активный сервис.

Если новая настройка не применяется или PHP-FPM не запускается, предыдущий local.conf восстанавливается и выполняется попытка вернуть сервис в рабочее состояние. Если администратор ранее создал собственный local.conf, RISH сохраняет отдельную резервную копию и показывает её расположение.

Административные CLI-сценарии при этом остаются рабочими. Например, команды Joomla CLI и WP-CLI запускаются RISH отдельно от веб-сервиса, с нужной версией PHP и от имени пользователя сайта.

Учетные данные переходят в каталог root

Раньше пароль системного пользователя, пароль MariaDB и данные администратора сайта сохранялись в файле:

/home/<пользователь>/.pass.txt

Старый .pass.txt имел служебный и неочевидный формат без секций и пояснений:

Database: ...
siteuser: ...
defaultsiteaccount ... ...

Названия параметров были оформлены по-разному, а в последней строке рядом находились логин и пароль администратора сайта. Чтобы понять назначение каждого значения или безопасно изменить его вручную, нужно было знать, как этот файл разбирают скрипты RISH.

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

Теперь они хранятся здесь:

/root/rish/credentials/<пользователь>

Каталог принадлежит root:root и имеет права 700, файлы пользователей — root:root 600. Новый файл получил понятную структуру в стиле INI: отдельные секции для SFTP, MariaDB и администратора сайта.

В начале файла находятся комментарии, которые объясняют его назначение, рекомендуемый способ подключения по SFTP, расположение публичных ключей и обязательные права доступа. Теперь файл понятен не только скриптам RISH, но и администратору сервера:

[SFTP]
Login = siteuser
Password = ...

[MariaDB]
Login = siteuser
Password = ...

[Default site administrator]
Login = ...
Password = ...

Создание пользователей, установка Joomla, WordPress и OpenCart, работа с базами данных и восстановление конфигурации Joomla переведены на новое хранилище.

При обновлении старые .pass.txt переносятся автоматически. Новый файл создаётся и проверяется до удаления исходного. Если формат повреждён, значения не совпали или целевой путь имеет небезопасный тип, исходный файл сохраняется, а шаг миграции будет предложен снова.

Управление пользовательским CRON остаётся у root

CRON полезен для резервного копирования, Joomla CLI и других регламентных задач. Одновременно это простой способ закрепить выполнение команды на сервере: если код сайта может вызвать crontab, он способен назначить себе повторный запуск.

RISH 2.8.0 создаёт файл /etc/cron.allow со следующим содержимым:

root

Файл должен быть обычным файлом с владельцем root:root и правами 644. Существующая конфигурация при первом изменении сохраняется в резервную копию. После этого пользователь сайта больше не может самостоятельно изменять свой crontab.

Сами задания продолжают работать. Администратор по-прежнему создаёт и редактирует CRON любого пользователя через меню RISH от root. При обновлении существующие задания не удаляются.

Меню CRON теперь сообщает ошибки чтения вместо скрытия проблемы, не использует предсказуемые временные файлы и показывает правильный путь /bin/php82 в примере команды.

Ключи SFTP теперь контролирует администратор

Раньше публичные ключи находились в стандартном файле:

/home/<пользователь>/.ssh/authorized_keys

Домашний каталог принадлежал пользователю сайта. Следовательно, процесс с теми же правами мог добавить в authorized_keys новый ключ.

Теперь OpenSSH читает ключи из:

/etc/ssh/authorized_keys/<имя пользователя>

Каталог имеет владельца root:root и права 755, файлы ключей — root:root 644. Публичный ключ не является секретом, поэтому чтение допустимо; критично то, что записывать и заменять ключи может только root.

Если в новом файле /etc/ssh/authorized_keys/<пользователь> уже находятся ключи, RISH добавляет к ним ключи из старого /home/<пользователь>/.ssh/authorized_keys. Одинаковые строки не дублируются. Старый файл удаляется только после успешной проверки и загрузки новой конфигурации SSH.

Администратору показываются отпечаток, тип и комментарий каждого перенесённого ключа. Строки без комментария выделяются отдельно, потому что их назначение сложнее установить.

Новые ключи нужно добавлять в /etc/ssh/authorized_keys/<пользователь>. Для быстрого перехода каталог включён в список быстрого доступа Midnight Commander.

SFTP становится строго файловым доступом

Перенос ключей дополняется новым файлом конфигурации:

/etc/ssh/sshd_config.d/00-rish.conf

Для группы sftp применяются обязательные параметры:

Match Group sftp
    AuthorizedKeysFile /etc/ssh/authorized_keys/%u
    PasswordAuthentication no
    KbdInteractiveAuthentication no
    DisableForwarding yes
    PermitTTY no
    PermitUserRC no
    ForceCommand internal-sftp -u 022
    ChrootDirectory /var/www/%u

Для SFTP-пользователей всегда запрещён вход по паролю и keyboard-interactive, даже если администратор разрешил парольную авторизацию для обычного SSH. Соединение нельзя использовать для forwarding, оно не выдаёт терминал, не запускает пользовательский SSH-сценарий и принудительно остаётся внутри internal-sftp.

RISH проверяет не только содержимое файла. Перед применением выполняется sshd -t, а затем через sshd -T проверяются фактически действующие параметры для каждого SFTP-пользователя. Если другой конфигурационный файл имеет более высокий приоритет, изменение не считается завершённым, а прежние настройки восстанавливаются.

Отдельное управление пользователями сервера

Создание и удаление пользователей вынесено из общего экрана настройки сервера в отдельный пункт Midnight Commander — «Пользователи сервера». Название точнее отражает роль учетной записи: с ней связаны не только SFTP, но также файлы сайтов, PHP-FPM pool и MariaDB.

При создании RISH проверяет имя, создаёт системную учетную запись, каталоги, защищённый файл SFTP-ключей, пароли и запись MariaDB. Если обязательный этап завершается ошибкой, сценарий удаляет уже созданные объекты и сообщает о результате отката.

Удаление теперь начинается с аудита зависимостей. RISH проверяет сайты и файлы пользователя, базы и права MariaDB, конфигурации Apache, запущенные процессы, CRON и PHP-FPM pools. Администратор видит точный перечень данных до подтверждения операции.

PHP-pool удаляется только после создания резервной копии и проверки новой конфигурации; затронутая версия PHP-FPM перезапускается. Если этап завершается ошибкой, pool и CRON восстанавливаются. Последнего пользователя сервера удалить нельзя.

Проверка обновлений расширений Joomla

В RISH уже был аудит расширений Joomla: он показывает компоненты, модули, плагины и шаблоны, которые не входят в стандартную поставку, а также выделяет расширения из списка нежелательных. В версии 2.8.0 аудит получил следующий важный шаг — проверку доступных обновлений.

Для Joomla 4, 5 и 6 в меню аудита появился пункт «Проверить обновления расширений». RISH запускает штатную команду:

cli/joomla.php update:extensions:check

Команда выполняется с версией PHP конкретного сайта и от имени его системного пользователя. Это важно для корректного окружения Joomla и одновременно соответствует новой модели изоляции: административный сценарий запускается явно, а не из веб-процесса PHP-FPM.

Во время проверки RISH показывает количество обработанных серверов обновлений и их названия. После завершения выводится таблица сторонних расширений с установленной и доступной версиями. Языковые пакеты, компоненты ядра Joomla и отключённые серверы обновлений в итоговый список не попадают.

Проверка только обновляет сведения Joomla и показывает результат — установка расширений автоматически не запускается. Для одного сайта нельзя одновременно запустить несколько проверок: используется блокировка. Также предусмотрены обработка сигналов, диагностический вывод и лимит 120 секунд, чтобы зависший сервер обновлений не оставил процесс работать бесконечно.

Эта функция помогает быстрее увидеть устаревший сторонний код — именно расширения и шаблоны часто становятся точкой входа при атаке.

Одинаковые правила при установке, обновлении и проверке

Новая модель применяется не только к свежим серверам. При чистой установке ri.sh сразу создаёт защищённый CRON, конфигурацию PHP-FPM, каталог ключей и строгие правила SFTP. Для существующих серверов postupdate.sh выполняет последовательную миграцию и не помечает шаг завершённым, пока проверка не прошла.

rish check контролирует:

  • наличие и фактическое применение systemd-защиты каждой версии PHP-FPM;
  • владельца, права и содержимое /etc/cron.allow;
  • каталог /etc/ssh/authorized_keys и файлы пользователей;
  • конфигурацию 00-rish.conf и эффективные параметры SFTP;
  • актуальность списка быстрого доступа Midnight Commander;
  • наличие устаревшего вызова backup.sh в CRON root.

Изменения PHP-FPM и SSH проверяются до окончательного применения. При ошибке выполняется откат либо шаг остаётся незавершённым и будет предложен при следующем обновлении.

Другие изменения RISH 2.8.0

В установщике переработан ранний этап перезагрузки. Если системные обновления требуют нового ядра и одновременно нужно отключить SELinux, причины объединяются в один запрос. Выполненное обновление отслеживается между повторными запусками установщика через временный маркер, а команда /root/rish/ri.sh заранее сохраняется в истории shell.

Изменение локали больше не вызывает ненужную перезагрузку: UTF-8 используется в новых сеансах. Состояние SELinux определяется безопаснее, включая ситуации, когда он уже отключён или штатная команда проверки недоступна.

rish check предупреждает, если в CRON root всё ещё вызывается старый /root/rish/backup.sh. Такое задание нужно вручную заменить на /root/rish/backup2.sh auto.

В список быстрого доступа Midnight Commander добавлены быстрые переходы в /root/rish, /root/rish/credentials и /etc/ssh/authorized_keys. Пункт управления пользователями получил отдельное место в главном меню, а горячие клавиши административных разделов приведены к единому стилю.

Установщик показывает версию RISH в начале работы, при чистой установке корректно инициализируется шаблон заглушки Apache, а уже ненужные миграционные проверки не задают лишних вопросов.

Что проверить после обновления

Обновление сопровождает администратора по каждому миграционному шагу. После завершения рекомендуем обратить внимание на четыре пункта:

  • проверьте отпечатки и комментарии перенесённых SFTP-ключей, особенно ключи без комментария;
  • убедитесь, что активные версии PHP-FPM успешно перезапустились с новой systemd-конфигурацией; для неактивных сервисов защита применится при следующем запуске;
  • если используется старый backup.sh, замените его в CRON на backup2.sh auto;
  • запустите rish check и исправьте найденные отклонения.

Новые пути также доступны через список быстрого доступа Midnight Commander: нажмите Ctrl+\, чтобы быстро перейти к учетным данным RISH или ключам SFTP.

Итог

RISH 2.8.0 сохраняет привычную архитектуру с отдельными Linux-пользователями и PHP-FPM pools, но яснее разделяет полномочия. PHP обслуживает сайт в /var/www, SFTP передаёт файлы внутри chroot, а CRON, ключи доступа и административные учетные данные остаются под контролем root.

Толчком к этим изменениям стал реальный аудит взломанного сайта. Попытки злоумышленника закрепиться через CRON и SFTP показали, какие возможности следует убрать из зоны контроля PHP-процесса. Результатом стала не одна точечная настройка, а согласованная модель, которая применяется при установке, обновлении и проверке сервера.

Дополняет релиз проверка обновлений расширений Joomla: теперь из интерфейса RISH можно не только увидеть сторонние расширения, но и запросить актуальные версии на их серверах обновлений.

Основатель проекта RISH