# Command Injection PHP prevencija

> Command Injection PHP prevencija: venkite shell, naudokite proc_open su masyvu, teisingai taikykite escapeshellarg ir raskite pavojingus kreipinius kode.

Šaltinis: https://phpsos.de/lt/blog/command-injection-php/  
Būsena: 2026-10-25  
Teikėjas: PHP SOS, PHP avarinė pagalba ir serverio sauga. Skubi pagalba per „WhatsApp“: https://wa.me/491736494914?text=Sveiki%2C%20PHP%20SOS%2C%20mums%20reikia%20pagalbos%20su%20m%C5%ABs%C5%B3%20PHP%20serveriu.%20%C4%AEmon%C4%97%3A%20%E2%80%A6%20Svetain%C4%97%3A%20%E2%80%A6

**Trumpai:** Command Injection PHP saugiausiai užkertate kelią visai nekviesdami shell: daugelį užduočių PHP funkcijos ar bibliotekos atlieka tiesiogiai. Kur reikalinga išorinė programa, nuo PHP 7.4 komandą perduodate kaip masyvą į `proc_open()`, ir tada PHP paleidžia programą be shell. Reikšmes tikrinate pagal leidžiamųjų sąrašą, parinktis užbaigiate su `--` ir procesą paleidžiate su minimaliomis teisėmis.

## Kodėl sisteminis kreipinys turi tiek galios

Vaizdų apdorojimas, PDF kūrimas, archyvavimas, virusų patikra, DNS įrašo užklausa: tokioms užduotims PHP programos mielai kviečia išorines programas. Techniškai tai patogu, pakanka vienos eilutės su `exec()` ar `shell_exec()`. Saugumo požiūriu tai viena pavojingiausių kodo vietų.

Jei komanda sudedama kaip tekstas ir vykdoma shell, shell sprendžia, kas yra komanda, o kas duomenys. Jei reikšmė iš išorės patenka netikrinta, ji gali pakeisti komandos prasmę. Blogiausiu atveju pasekmė ta pati kaip PHP2Shell: savavališkų komandų vykdymas serveryje su PHP proceso teisėmis.

Skirtumas: PHP2Shell pašalinate PHP atnaujinimu. Command Injection savame kode išlieka po kiekvieno atnaujinimo. Be to, tokios vietos dažnai slypi pagalbiniuose scenarijuose, skirtuose tik administratoriams, pavyzdžiui, priežiūrai, diagnostikai ar importams, ir vėliau lieka pasiekiamos be apsaugos.

## Kur Command Injection PHP atsiranda

Pavojinga kiekviena vieta, kurioje PHP paleidžia shell ar programą. Tam skirtos funkcijos:

| Funkcija | Ar naudoja shell? | Pastaba |
|---|---|---|
| `exec()`, `system()`, `passthru()`, `shell_exec()` | taip | komanda kaip tekstas, shell ją interpretuoja |
| Atvirkštinių kabučių operatorius | taip | atitinka `shell_exec()` |
| `popen()` | taip | komanda kaip tekstas |
| `proc_open()` su eilute | taip | komanda kaip tekstas |
| `proc_open()` su masyvu | ne | nuo PHP 7.4, argumentai perduodami tiesiogiai |
| `mail()` su penktu parametru | netiesiogiai | papildomi parametrai pašto programai |

Tipiškas nesaugus šablonas atrodo taip. Failo vardas ateina iš užklausos ir patenka tiesiai į komandų eilutę:

```php
<?php
// NESAUGU: reikšmė iš užklausos tampa shell komandos dalimi
$datei = $_POST['datei'];
exec('convert uploads/' . $datei . ' -resize 800x800 vorschau/' . $datei);
```

## Pirmas pasirinkimas: visai jokio shell

Veiksmingiausia apsauga yra pakeisti sisteminį kreipinį. OWASP tai aiškiai nurodo kaip pirmą gynybos liniją. Daugeliui užduočių PHP turi savas funkcijas ar plėtinius:

- Katalogai ir failai: `mkdir()`, `copy()`, `rename()`, `unlink()`
- Archyvai: `ZipArchive` arba `PharData`
- Paveikslėliai: GD arba Imagick kaip PHP plėtinys
- Tinklo užklausos: `dns_get_record()`, `gethostbyname()`, cURL
- Kontrolinės sumos: `hash_file()`
 
Tada visiškai išnyksta klausimas, kuriuos simbolius shell interpretuoja. Be to, sistemos komandų funkcijas tada galima užrakinti konfigūracijoje, kaip aprašo vadovas [disable_functions teisingas naudojimas](https://phpsos.de/lt/ratgeber/disable-functions/).

## Kai reikalinga išorinė programa: proc_open su masyvu

Nuo PHP 7.4 `proc_open()` priima komandą kaip masyvą. Pasak PHP vadovo, procesas tada paleidžiamas tiesiogiai, apeinant shell, o argumentų maskavimą perima PHP. Simboliai kaip kabliataškis ar vertikali linija tuomet nebeturi ypatingos reikšmės.

```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);
```

Pavyzdį tvirtą daro trys dalykai: programa su pilnu keliu nustatyta kode, failo vardas tikrinamas pagal siaurą šabloną, o kiekvienas kintamasis argumentas prasideda absoliučiu keliu, taigi negali būti perskaitytas kaip parinktis. Kur reikšmė stovi viena, parinktis užbaikite su `--`, jei programa pagal dokumentaciją tai palaiko, arba atmeskite reikšmes, prasidedančias brūkšneliu.

Užduotims, kurioms reguliariai reikia išorinių programų, pavyzdžiui, didesnės apimties paveikslėlių ar PDF apdorojimui, verta atsieti: žiniatinklio programa tik įdeda užduotį į eilę, o atskira foninė tarnyba su savu naudotoju ir savomis teisėmis ją įvykdo. Interneto užklausas atsakantis PHP procesas tada sistemos komandų funkcijų nebeprivalo turėti, ir jas jam galima užrakinti. Išorinės programos taip pat turėtų turėti laiko ir atminties ribas, kad viena užduotis neužblokuotų serverio.

Ypatingas atvejis yra `mail()`. Penktas parametras perduoda papildomas parinktis sistemos pašto programai. Reikšmės iš formų, pavyzdžiui, siuntėjo adresas, ten niekada neturi patekti netikrintos. Tvirtesnė yra pašto biblioteka, siunčianti per SMTP ir nereikalaujanti sisteminio kreipinio.

## escapeshellarg: teisingai taikyti ir žinoti ribas

Kur esamas kodas dar dirba su komandų eilutėmis, `escapeshellarg()` kiekvienai atskirai reikšmei yra minimali priemonė. Funkcija įdeda reikšmę į kabutes, kad shell ją traktuotų kaip lygiai vieną argumentą. `escapeshellcmd()` naudotojo įvesčiai netinka, nes leidžia papildomus parametrus, kaip OWASP parodo pavyzdžiu.

Svarbios `escapeshellarg()` ribos:

1. **Argumento injekcija lieka įmanoma:** užmaskuota reikšmė, prasidedanti brūkšneliu, tikslinės programos vis tiek perskaitoma kaip parinktis.
2. **Windows elgiasi kitaip:** ten funkcija, pasak vadovo, tam tikrus simbolius, kaip `%` ir `"`, pakeičia tarpais; reikšmės atkeliauja pakeistos.
3. **Simbolių rinkinys:** priklausomai nuo serverio locale nustatymų ne ASCII simboliai gali dingti.

## Radinių paieška esamame kode

Sisteminių kreipinių paieška atliekama greitai ir duoda apžvelgiamą sąrašą:

```bash
grep -rnE "\b(exec|system|passthru|shell_exec|popen|proc_open)\s*\(" src/ --include="*.php"
grep -rn '`' src/ --include="*.php" | grep -v '^\s*//'
```

Kiekvienas radinys reikalauja atsakymo į tris klausimus: ar kreipinį galima pakeisti PHP funkcijomis? Ar reikšmės iš išorės patenka į jį? Kokiomis teisėmis veikia procesas? Paskutinis klausimas lemia žalą blogiausiu atveju. PHP procesas su savu naudotoju ir be rašymo teisių į kodą pridaro mažiau, žr. [Least Privilege: žiniatinklio serverio naudotojo teisės](https://phpsos.de/lt/blog/rechte-des-webserver-benutzers/).

> **Svarbu:**
> Jei PHP-FPM ar žiniatinklio serverio procesas paleidžia shell, tai stiprus įsilaužimo perspėjamasis ženklas. Kaip tai atpažinti, aprašo [Perspėjimo ženklas: žiniatinklio serveris paleidžia shell](https://phpsos.de/lt/blog/webserver-startet-shell/).

## Kaip PHP SOS padeda konkrečiai?

- Ieškome visų sisteminių kreipinių jūsų programoje, papildiniuose ir pagalbiniuose scenarijuose.
- Kur įmanoma, kreipinius keičiame PHP funkcijomis, o likusius perjungiame į kreipinius be shell.
- Nereikalingas funkcijas užrakiname PHP konfigūracijoje ir patikriname PHP naudotojo teises.
- Pakeitimus išbandome ir dokumentuojame, kurias programas programa dar gali kviesti.

Savos programinės įrangos patikrą siūlome kaip [saugumo auditą ir įsiskverbimo testą](https://phpsos.de/lt/leistungen/sicherheitsaudit-penetrationstest/).


## Dažni klausimai

**Ar escapeshellarg pakanka nuo Command Injection?**  
Jis užkerta kelią tam, kad reikšmė būtų vykdoma kaip kita komanda. Jis neužkerta kelio tam, kad, jei reikšmė prasideda brūkšneliu, ją kviečiama programa perskaitytų kaip parinktį. Todėl papildomai tikrinkite reikšmes ir, kur programa tai palaiko, parinktis užbaikite su --.

**Kuo skiriasi escapeshellarg ir escapeshellcmd?**  
escapeshellarg vieną reikšmę traktuoja kaip lygiai vieną argumentą. escapeshellcmd tik pakeičia metasimbolius visoje komandų eilutėje ir leidžia papildomus parametrus. Naudotojo įvesčiai tinkamas pasirinkimas yra escapeshellarg, dar geriau kreipinys be shell.

**Ar exec ir system turėčiau užrakinti per disable_functions?**  
Jei jūsų programai jų nereikia, taip. Tai apriboja žalą po įsilaužimo. Tačiau saugaus kodo tai nepakeičia, nes programos, kurioms komandų reikia, funkcijų ir toliau reikalauja.

**Kaip atpažinti, ar Command Injection buvo išnaudota?**  
Stiprus perspėjamasis ženklas yra PHP ar žiniatinklio serverio procesai, paleidžiantys shell, pavyzdžiui, sh ar bash. Be to, nežinomi procesai, nauji failai įkėlimų aplankuose ir neįprasti išeinantys ryšiai.

## Šaltiniai

- [OWASP OS Command Injection Defense Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/OS_Command_Injection_Defense_Cheat_Sheet.html)
- [PHP vadovas: proc_open](https://www.php.net/manual/de/function.proc-open.php)
- [PHP vadovas: escapeshellarg](https://www.php.net/manual/de/function.escapeshellarg.php)
