# Ako zabrániť Command Injection v PHP

> Zabráňte Command Injection v PHP: vyhnúť sa shellu, proc_open s poľom, správne escapeshellarg a nájdenie nebezpečných volaní v kóde.

Zdroj: https://phpsos.de/sk/blog/command-injection-php/  
Stav: 25.10.2026  
Poskytovateľ: PHP SOS, Núdzová pomoc pri PHP a bezpečnosť serverov. Okamžitá pomoc cez WhatsApp: https://wa.me/491736494914?text=Dobr%C3%BD%20de%C5%88%2C%20PHP%20SOS%2C%20potrebujeme%20pomoc%20s%20na%C5%A1%C3%ADm%20serverom%20s%20PHP.%20Spolo%C4%8Dnos%C5%A5%3A%20%E2%80%A6%20Web%3A%20%E2%80%A6

**Stručne:** Command Injection v PHP najbezpečnejšie zabránite tak, že nevoláte vôbec žiadny shell: mnohé úlohy vybavia priamo funkcie PHP alebo knižnice. Kde je externý program nutný, odovzdajte príkaz od PHP 7.4 ako pole do `proc_open()`, vtedy PHP spustí program bez shellu. Hodnoty kontrolujte voči zoznamu povolených, možnosti ukončujte pomocou `--` a proces nechajte bežať s minimálnymi právami.

## Prečo má systémové volanie takú veľkú moc

Úprava obrázkov, tvorba PDF, archivácia, antivírusová kontrola, dotaz na záznam DNS: na takéto úlohy aplikácie PHP rady volajú externé programy. Technicky je to pohodlné, stačí jeden riadok s `exec()` alebo `shell_exec()`. Z hľadiska bezpečnosti je to jedno z najnebezpečnejších miest v kóde.

Ak sa príkaz zloží ako text a vykoná ho shell, o tom, čo je príkaz a čo údaje, rozhoduje shell. Ak sa dovnútra dostane hodnota zvonka bez kontroly, môže zmeniť význam príkazu. Následok je v najhoršom prípade rovnaký ako pri PHP2Shell: vykonanie ľubovoľných príkazov na serveri s právami procesu PHP.

Rozdiel: PHP2Shell odstránite aktualizáciou PHP. Command Injection vo vlastnom kóde zostane po každej aktualizácii. K tomu pristupuje to, že takéto miesta často sedia v pomocných skriptoch určených len pre administrátorov, napríklad na údržbu, diagnostiku alebo importy, ktoré neskôr zostanú dostupné bez ochrany.

## Kde vzniká Command Injection v PHP

Ohrozené je každé miesto, kde PHP spúšťa shell alebo program. Funkcie na to sú:

| Funkcia | Používa shell? | Poznámka |
|---|---|---|
| `exec()`, `system()`, `passthru()`, `shell_exec()` | áno | príkaz ako text, shell ho interpretuje |
| operátor Backtick | áno | zodpovedá `shell_exec()` |
| `popen()` | áno | príkaz ako text |
| `proc_open()` s reťazcom | áno | príkaz ako text |
| `proc_open()` s poľom | nie | od PHP 7.4, argumenty sa odovzdávajú priamo |
| `mail()` s piatym parametrom | nepriamo | dodatočné parametre pre poštový program |

Typický nebezpečný vzor vyzerá takto. Názov súboru pochádza z požiadavky a skončí priamo v príkazovom riadku:

```php
<?php
// NEBEZPEČNÉ: hodnota z požiadavky sa stane súčasťou príkazu shellu
$datei = $_POST['datei'];
exec('convert uploads/' . $datei . ' -resize 800x800 vorschau/' . $datei);
```

## Prvá voľba: vôbec žiadny shell

Najúčinnejšou ochranou je nahradiť systémové volanie. OWASP to výslovne uvádza ako prvú obrannú líniu. Pre mnohé úlohy existujú v PHP vlastné funkcie alebo rozšírenia:

- Priečinky a súbory: `mkdir()`, `copy()`, `rename()`, `unlink()`
- Archívy: `ZipArchive` alebo `PharData`
- Obrázky: GD alebo Imagick ako rozšírenie PHP
- Sieťové dotazy: `dns_get_record()`, `gethostbyname()`, cURL
- Kontrolné súčty: `hash_file()`

Tým úplne zmizne otázka, ktoré znaky shell interpretuje. Funkcie pre systémové príkazy sa potom dajú v konfigurácii aj zablokovať, ako opisuje sprievodca [Správne použitie disable_functions](https://phpsos.de/sk/ratgeber/disable-functions/).

## Keď je externý program nutný: proc_open s poľom

Od PHP 7.4 prijíma `proc_open()` príkaz ako pole. Podľa príručky PHP sa proces potom spustí priamo, bez okľuky cez shell, a PHP preberá potrebné maskovanie argumentov. Znaky ako bodkočiarka alebo pipe už nemajú osobitný význam.

```php
<?php
declare(strict_types=1);

$name = basename((string) ($_POST['datei'] ?? ''));
if (!preg_match('/^[a-z0-9_-]{1,64}\.(jpg|png)$/', $name)) {
    throw new InvalidArgumentException('Ungültiger Dateiname.');
}

$befehl = ['/usr/bin/convert', '/srv/app/uploads/' . $name, '-resize', '800x800',
           '/srv/app/vorschau/' . $name];

$prozess = proc_open($befehl, [1 => ['pipe', 'w'], 2 => ['pipe', 'w']], $pipes);
if (!is_resource($prozess)) {
    throw new RuntimeException('Programm konnte nicht gestartet werden.');
}
$fehlerausgabe = stream_get_contents($pipes[2]);
$status = proc_close($prozess);
```

Príklad robustným robia tri veci: program je v kóde pevne uvedený s plnou cestou, názov súboru sa kontroluje voči úzkemu vzoru a každý premenlivý argument začína absolútnou cestou, takže sa nemôže čítať ako možnosť. Tam, kde hodnota stojí sama, ukončite možnosti pomocou `--`, ak to program podľa dokumentácie podporuje, alebo odmietnite hodnoty, ktoré začínajú pomlčkou.

Pri úlohách, ktoré pravidelne potrebujú externé programy, napríklad spracovanie obrázkov alebo PDF vo väčšom rozsahu, sa oplatí oddelenie: webová aplikácia uloží len zákazku do fronty a oddelená služba na pozadí s vlastným používateľom a vlastnými právami ju vykoná. Proces PHP, ktorý odpovedá na požiadavky z internetu, potom už nepotrebuje funkcie pre systémové príkazy a dajú sa preňho zablokovať. Externé programy by mali mať navyše limity času a pamäte, aby jedna zákazka neblokovala server.

Zvláštnym prípadom je `mail()`. Piaty parameter odovzdáva dodatočné možnosti poštovému programu systému. Hodnoty z formulárov, napríklad adresa odosielateľa, sa tam nikdy nesmú dostať bez kontroly. Robustnejšia je poštová knižnica, ktorá odosiela cez SMTP a nepotrebuje systémové volanie.

## escapeshellarg: správne použitie a znalosť hraníc

Kde existujúci kód ešte pracuje s príkazovými riadkami, je `escapeshellarg()` pre každú jednotlivú hodnotu minimálnym opatrením. Funkcia vloží hodnotu do úvodzoviek, takže ju shell spracuje ako práve jeden argument. `escapeshellcmd()` je pre vstupy od používateľa nevhodné, pretože pripúšťa dodatočné parametre, ako ukazuje OWASP na príklade.

Dôležité hranice `escapeshellarg()`:

1. **Argument injection zostáva možná:** maskovaná hodnota, ktorá začína pomlčkou, sa cieľovým programom napriek tomu číta ako možnosť.
2. **Windows sa správa inak:** tam funkcia podľa príručky nahrádza určité znaky ako `%` a `"` medzerami; hodnoty teda prídu zmenené.
3. **Znaková sada:** v závislosti od nastavenia locale servera sa môžu stratiť znaky mimo ASCII.

## Nájdenie miest v existujúcom kóde

Hľadanie systémových volaní je rýchle a dá prehľadný zoznam:

```bash
grep -rnE "\b(exec|system|passthru|shell_exec|popen|proc_open)\s*\(" src/ --include="*.php"
grep -rn '`' src/ --include="*.php" | grep -v '^\s*//'
```

Každé nájdené miesto potrebuje odpoveď na tri otázky: dá sa volanie nahradiť funkciami PHP? Prúdia dovnútra hodnoty zvonka? S akými právami proces beží? Posledná otázka je rozhodujúca pre škodu v kritickom prípade. Proces PHP s vlastným používateľom a bez práv zápisu na kód napácha menej, pozri [Least Privilege: práva používateľa webservera](https://phpsos.de/sk/blog/rechte-des-webserver-benutzers/).

> **Dôležité:**
> Ak proces PHP-FPM alebo webservera spustí shell, je to silný varovný signál vlámania. Ako to rozpoznáte, opisuje [Varovný signál: webserver spúšťa shell](https://phpsos.de/sk/blog/webserver-startet-shell/).

## Ako konkrétne pomáha PHP SOS?

- Vyhľadáme všetky systémové volania vo vašej aplikácii, v pluginoch a v pomocných skriptoch.
- Volania tam, kde je to možné, nahradíme funkciami PHP a ostatné prestavíme na volania bez shellu.
- Nepotrebné funkcie zablokujeme v konfigurácii PHP a skontrolujeme práva používateľa PHP.
- Zmeny otestujeme a zdokumentujeme, ktoré programy smie aplikácia ešte volať.

Kontrolu vlastného softvéru ponúkame ako [bezpečnostný audit a penetračný test](https://phpsos.de/sk/leistungen/sicherheitsaudit-penetrationstest/).


## Časté otázky

**Stačí escapeshellarg proti Command Injection?**  
Zabráni tomu, aby sa hodnota vykonala ako ďalší príkaz. Nezabráni však tomu, aby ju volaný program čítal ako možnosť, ak začína pomlčkou. Preto hodnoty navyše kontrolujte a tam, kde to program podporuje, ukončujte možnosti pomocou --.

**Aký je rozdiel medzi escapeshellarg a escapeshellcmd?**  
escapeshellarg spracuje jednu hodnotu ako práve jeden argument. escapeshellcmd maskuje len metaznaky v celom príkazovom riadku a pripúšťa ďalšie parametre. Pre vstupy od používateľa je správnou voľbou escapeshellarg, ešte lepšie je volanie bez shellu.

**Mám exec a system zablokovať cez disable_functions?**  
Ak ich vaša aplikácia nepotrebuje, áno. Obmedzí to škodu po vlámaní. Nenahrádza to však bezpečný kód, pretože aplikácie, ktoré príkazy potrebujú, tieto funkcie naďalej potrebujú.

**Ako rozpoznám, či bol Command Injection zneužitý?**  
Silným varovným signálom sú procesy PHP alebo webservera, ktoré spúšťajú shell ako sh alebo bash. K tomu pristupujú neznáme procesy, nové súbory v priečinkoch s uploadmi a nezvyčajné odchádzajúce spojenia.

## Zdroje

- [OWASP OS Command Injection Defense Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/OS_Command_Injection_Defense_Cheat_Sheet.html)
- [Príručka PHP: proc_open](https://www.php.net/manual/de/function.proc-open.php)
- [Príručka PHP: escapeshellarg](https://www.php.net/manual/de/function.escapeshellarg.php)
