Практическое руководство по вызову 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 'Команда залогирована';

Примечание: подобное логирование помогает выявить атаки, но не предотвращает их. Лучше вести логи до любого выполнения.

Использование system с GET в PHP - comments

En
System get php (php)