Практическое руководство по вызову system() с GET параметрами: от опасных решений к защищённым
Общие принципы вызова системных команд через GET параметры
Как обеспечить максимальную безопасность при использовании system() с GET?
Основное рекомендуемое решение
Безопасное выполнение требует строгого контроля входных данных. Лучший подход - использование белого списка разрешённых команд и экранирование аргументов с помощью escapeshellarg() или escapeshellcmd(). Ниже приведён пример, где GET параметр cmd проверяется по списку допустимых команд, а аргументы экранируются.
// index.php?action=ping&target=8.8.8.8
$allowed_actions = ['ping', 'whois', 'dig'];
$action = $_GET['action'] ?? '';
$target = $_GET['target'] ?? '';
if (in_array($action, $allowed_actions, true) && !empty($target)) {
$safe_target = escapeshellarg($target);
$command = "{$action} -c 1 {$safe_target}";
$output = system($command, $return_code);
// обработка результата
} else {
echo 'Некорректный запрос';
}
System php id (выполнение системной команды с php id)
Это решение предотвращает инъекции, так как $target экранируется, а $action берётся только из списка разрешённых.
Типичная ошибка:
Попытка передать команду целиком через GET, например ?cmd=ls -la, без экранирования. Это приводит к полной уязвимости RCE.
Как выполнить любую команду, переданную через GET, без ограничений?
Прямой вызов system() - небезопасный вариант
// ОПАСНО: Никогда не используйте в production
$cmd = $_GET['cmd'];
system($cmd);
System get php (использование system с get в php)
Такая конструкция позволяет выполнить произвольную команду на сервере. Злоумышленник может передать ?cmd=rm -rf /.
Проблема: нет экранирования, нет валидации. Решение - никогда не использовать этот подход.
Как экранировать аргументы команды, переданные через GET?
Использование escapeshellarg()
$arg = $_GET['arg'] ?? '';
$safe_arg = escapeshellarg($arg);
system('ls -l ' . $safe_arg);
Функция escapeshellarg() оборачивает строку в одинарные кавычки и экранирует существующие кавычки. Подходит для одного аргумента, но не защищает от инъекций через имя команды или флаги.
Ошибка: если $arg содержит пробел или спецсимвол, экранирование сработает, но если аргумент не нужен, можно случайно передать пустую строку. Важно проверять наличие аргумента перед вызовом.
Как экранировать всю командную строку, полученную из GET?
Использование escapeshellcmd()
$cmd = $_GET['cmd'] ?? '';
$safe_cmd = escapeshellcmd($cmd);
system($safe_cmd);
escapeshellcmd() экранирует метасимволы оболочки (;, |, &, > и др.), но не защищает от инъекций через кавычки или пробелы. Подходит, когда нужно выполнить предопределённую команду с экранированием всего аргумента.
Недостаток: позволяет передать небезопасные флаги (например, --option=value). Комбинировать с белым списком.
Как выполнить системный вызов через GET с помощью exec() и безопасной обработки вывода?
Использование exec() с массивами и ограничением по времени
// index.php?action=df
$allowed = ['df', 'uptime', 'whoami'];
$action = $_GET['action'] ?? '';
if (in_array($action, $allowed, true)) {
$output = [];
$return_var = null;
exec($action, $output, $return_var);
echo 'Результат: ' . implode('\n', $output);
}
Здесь команда не содержит аргументов из GET, что является самым безопасным. Для аргументов используйте белый список и экранирование.
Проблема: exec() не отображает вывод в реальном времени, только возвращает массив строк. Для потокового вывода используйте passthru() или system().
Как выполнить сложную команду с аргументами, используя proc_open() для большего контроля?
Пример с proc_open() и передачей GET параметров через массивы
$allowed_commands = [
'grep' => ['min_args' => 2],
];
$command = $_GET['cmd'] ?? '';
$args = $_GET['args'] ?? [];
if (array_key_exists($command, $allowed_commands)) {
// экранируем аргументы
$safe_args = array_map('escapeshellarg', (array)$args);
$full_cmd = $command . ' ' . implode(' ', $safe_args);
$descriptorspec = [
0 => ['pipe', 'r'],
1 => ['pipe', 'w'],
2 => ['pipe', 'w']
];
$process = proc_open($full_cmd, $descriptorspec, $pipes);
if (is_resource($process)) {
fclose($pipes[0]);
$stdout = stream_get_contents($pipes[1]);
fclose($pipes[1]);
$stderr = stream_get_contents($pipes[2]);
fclose($pipes[2]);
proc_close($process);
echo 'Stdout: ' . $stdout;
echo 'Stderr: ' . $stderr;
}
}
Такой подход даёт полный контроль над вводом/выводом, но усложняет код.
Частая ошибка: не проверять ресурс proc_open(), забывать закрывать каналы - приводит к утечке памяти.
Как получить результат команды в переменную без вывода на экран?
Использование shell_exec()
$output = shell_exec($_GET['cmd']);
echo 'Результат: ' . $output;
Функция возвращает вывод команды в виде строки, но также уязвима к инъекциям. Всегда комбинировать с экранированием.
Проблема: shell_exec() не передаёт код возврата. Для диагностики используйте exec() с третьим параметром.
Расширенные примеры использования system() с GET
Ниже приведены детальные примеры безопасной и небезопасной передачи GET параметров в system(), включая код, результат и рекомендации.
Пример 1: Проверка доступности узла через ping с белым списком
Передаём аргумент target только после проверки, а команда фиксирована.
// safe_ping.php?target=8.8.8.8
$target = $_GET['target'] ?? '';
// Валидация IP адреса
if (filter_var($target, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
$safe_target = escapeshellarg($target);
$cmd = "ping -c 2 {$safe_target}";
echo 'Выполняется: ' . $cmd . PHP_EOL;
system($cmd, $code);
echo 'Код возврата: ' . $code;
} else {
echo 'Передан некорректный IP';
}
Результат выполнения для IP 8.8.8.8:
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data. 64 bytes from 8.8.8.8: icmp_seq=1 ttl=114 time=9.41 ms 64 bytes from 8.8.8.8: icmp_seq=2 ttl=114 time=9.39 ms --- 8.8.8.8 ping statistics --- 2 packets transmitted, 2 received, 0% packet loss, time 1001ms rtt min/avg/max/mdev = 9.389/9.399/9.409/0.010 ms Код возврата: 0
Пример 2: Вывод списка файлов в директории с ограничением по пути
Передаём GET параметр dir, но разрешаем только конкретные директории из белого списка.
// safe_ls.php?dir=/var/log
$allowed_dirs = ['/var/log', '/etc/php', '/tmp'];
$dir = realpath($_GET['dir'] ?? ''); // реальный путь
if (in_array($dir, $allowed_dirs, true) && is_dir($dir)) {
$safe_dir = escapeshellarg($dir);
$cmd = "ls -la {$safe_dir}";
passthru($cmd);
} else {
echo 'Директория не разрешена или не существует.';
}
Результат для разрешённой директории /var/log:
итого 1024 drwxr-xr-x 2 root root 4096 Sep 15 10:00 . drwxr-xr-x 22 root root 4096 Sep 15 09:00 .. -rw-r--r-- 1 root root 10240 Sep 15 12:00 syslog ...
Пример 3: Использование proc_open() для чтения конфигурации (утилита grep)
Передаём GET параметры pattern и file после тщательной валидации.
// grep_filter.php?pattern=error&file=/var/log/syslog
$allowed_files = ['/var/log/syslog', '/var/log/auth.log'];
$file = $_GET['file'] ?? '';
$pattern = $_GET['pattern'] ?? '';
if (!in_array($file, $allowed_files, true) || empty($pattern)) {
exit('Недопустимые параметры');
}
$safe_file = escapeshellarg($file);
$safe_pattern = escapeshellarg($pattern);
$descriptors = [
0 => ['pipe', 'r'],
1 => ['pipe', 'w'],
2 => ['pipe', 'w']
];
$cmd = "grep -i {$safe_pattern} {$safe_file}";
$proc = proc_open($cmd, $descriptors, $pipes);
if (is_resource($proc)) {
fclose($pipes[0]); // закрываем stdin
$stdout = stream_get_contents($pipes[1]);
$stderr = stream_get_contents($pipes[2]);
fclose($pipes[1]);
fclose($pipes[2]);
proc_close($proc);
echo 'Найденные строки:' . PHP_EOL;
echo htmlspecialchars($stdout);
if ($stderr) {
echo '\nОшибки: ' . htmlspecialchars($stderr);
}
}
Результат для pattern=error, file=/var/log/syslog:
Найденные строки: Sep 15 12:00:00 server kernel: [1234.5678] ERROR: something happened Sep 15 12:05:00 server crond[1234]: error reading crontab Ошибки: (пусто)
Пример 4: Отключение выполнения shell_* функций через PHP-конфигурацию
Рассмотрим административный подход - отключение опасных функций в php.ini, чтобы даже при наличии уязвимости в коде команды не выполнялись.
; php.ini
disable_functions = exec, system, shell_exec, passthru, popen, proc_open, proc_close, proc_nice, proc_terminate, pclose, exec, escapeshellcmd, escapeshellarg
Важно: Это блокирует и безопасные использования. Если необходимы системные вызовы, лучше использовать белый список и разрешить только конкретные команды, либо использовать системную утилиту через запуск отдельного скрипта (например, скрипт на Python с серверным API).
Пример 5: Безопасная обработка GET параметров для whois с ограничением длины
Проверка длины и тип входных данных, чтобы избежать перегрузки системы.
// safe_whois.php?domain=example.com
$domain = $_GET['domain'] ?? '';
if (strlen($domain) > 100) {
exit('Слишком длинное имя');
}
// Проверка на недопустимые символы (только буквы, цифры, точки, дефисы)
if (preg_match('/^[a-zA-Z0-9.-]+$/', $domain)) {
$safe_domain = escapeshellarg($domain);
system("whois {$safe_domain}", $retcode);
} else {
exit('Недопустимые символы');
}
Результат для example.com: вывод whois (опущен для краткости).
Пример 6: Логирование всех вызовов system() через GET
Полезно для аудита. Записываем в файл все попытки выполнения, включая время, IP, команду.
$command = $_GET['cmd'] ?? '';
$ip = $_SERVER['REMOTE_ADDR'];
$timestamp = date('Y-m-d H:i:s');
$log_entry = "[$timestamp] IP $ip выполнил: $command\n";
file_put_contents('/var/log/system_get.log', $log_entry, FILE_APPEND);
// Далее выполнение после проверки
echo 'Команда залогирована';
Примечание: подобное логирование помогает выявить атаки, но не предотвращает их. Лучше вести логи до любого выполнения.