# Command Injectionin estäminen PHP:ssä

> Command Injectionin esto PHP:ssä: vältä shelliä, käytä proc_openia taulukolla, escapeshellargia oikein ja löydä vaaralliset kutsut koodista.

Lähde: https://phpsos.de/fi/blog/command-injection-php/  
Tilanne: 25.10.2026  
Palveluntarjoaja: PHP SOS, PHP-hätäapu ja palvelimen tietoturva. Pikaapu WhatsAppilla: https://wa.me/491736494914?text=Hei%20PHP%20SOS%2C%20tarvitsemme%20apua%20PHP-palvelimemme%20kanssa.%20Yritys%3A%20%E2%80%A6%20Verkkosivusto%3A%20%E2%80%A6

**Lyhyesti:** Command Injectionin estätte PHP:ssä varmimmin olemalla kutsumatta shelliä lainkaan: monet tehtävät hoitavat PHP-funktiot tai kirjastot suoraan. Missä ulkoinen ohjelma on tarpeen, välitätte komennon PHP 7.4:stä alkaen taulukkona funktiolle `proc_open()`, jolloin PHP käynnistää ohjelman ilman shelliä. Arvot tarkistatte sallittujen luetteloa vasten, päätätte asetukset merkinnällä `--` ja ajatte prosessin vähimmillä oikeuksilla.

## Miksi järjestelmäkutsulla on niin paljon valtaa

Kuvankäsittely, PDF-tiedostojen luonti, pakkaus, virustarkistus, DNS-merkinnän kysely: tällaisiin tehtäviin PHP-sovellukset kutsuvat mielellään ulkoisia ohjelmia. Teknisesti se on kätevää, yksi rivi funktiolla `exec()` tai `shell_exec()` riittää. Tietoturvan kannalta se on yksi koodin vaarallisimmista kohdista.

Kun komento kootaan tekstinä ja shell suorittaa sen, shell päättää, mikä on komentoa ja mikä dataa. Jos arvo tulee ulkopuolelta tarkistamatta mukaan, se voi muuttaa komennon merkityksen. Seuraus on pahimmillaan sama kuin PHP2Shellissä: mielivaltaisten komentojen suoritus palvelimella PHP-prosessin oikeuksilla.

Ero: PHP2Shellin korjaatte PHP-päivityksellä. Oman koodin Command Injection säilyy jokaisen päivityksen jälkeen. Lisäksi tällaiset kohdat piilevät usein apuskripteissä, jotka oli tarkoitettu vain ylläpitäjille, esimerkiksi huoltoon, diagnostiikkaan tai tuonteihin, ja jäävät myöhemmin saavutettaviksi ilman suojaa.

## Missä Command Injection PHP:ssä syntyy

Vaarassa on jokainen kohta, jossa PHP käynnistää shellin tai ohjelman. Tätä varten ovat seuraavat funktiot:

| Funktio | Käyttääkö shelliä? | Huomautus |
|---|---|---|
| `exec()`, `system()`, `passthru()`, `shell_exec()` | kyllä | komento tekstinä, shell tulkitsee sen |
| Backtick-operaattori | kyllä | vastaa funktiota `shell_exec()` |
| `popen()` | kyllä | komento tekstinä |
| `proc_open()` merkkijonolla | kyllä | komento tekstinä |
| `proc_open()` taulukolla | ei | PHP 7.4:stä alkaen, argumentit välitetään suoraan |
| `mail()` viidennellä parametrilla | välillisesti | lisäparametrit sähköpostiohjelmalle |

Tyypillinen turvaton malli näyttää tältä. Tiedostonimi tulee pyynnöstä ja päätyy suoraan komentoriville:

```php
<?php
// TURVATON: pyynnön arvosta tulee osa shell-komentoa
$datei = $_POST['datei'];
exec('convert uploads/' . $datei . ' -resize 800x800 vorschau/' . $datei);
```

## Ensisijainen valinta: ei shelliä lainkaan

Tehokkain suoja on korvata järjestelmäkutsu. OWASP mainitsee tämän nimenomaisesti ensimmäisenä puolustuslinjana. Moniin tehtäviin PHP:ssä on omat funktiot tai laajennukset:

- Hakemistot ja tiedostot: `mkdir()`, `copy()`, `rename()`, `unlink()`
- Arkistot: `ZipArchive` tai `PharData`
- Kuvat: GD tai Imagick PHP-laajennuksena
- Verkkokyselyt: `dns_get_record()`, `gethostbyname()`, cURL
- Tarkistussummat: `hash_file()`

Silloin koko kysymys siitä, mitä merkkejä shell tulkitsee, poistuu kokonaan. Lisäksi järjestelmäkomentojen funktiot voidaan silloin estää asetuksissa, kuten opas [disable_functions oikein käytettynä](https://phpsos.de/fi/ratgeber/disable-functions/) kuvaa.

## Kun ulkoinen ohjelma on tarpeen: proc_open taulukolla

PHP 7.4:stä alkaen `proc_open()` hyväksyy komennon taulukkona. PHP-käsikirjan mukaan prosessi käynnistetään silloin suoraan ilman shellin kiertotietä, ja PHP hoitaa argumenttien tarvittavan maskeerauksen. Merkeillä kuten puolipiste tai putki ei ole silloin enää erityismerkitystä.

```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);
```

Kolme asiaa tekee esimerkistä kestävän: ohjelma on koodissa kiinteästi täydellä polulla, tiedostonimi tarkistetaan kapeaa kuviota vasten, ja jokainen muuttuva argumentti alkaa absoluuttisella polulla, joten sitä ei voi lukea asetuksena. Missä arvo on yksin, päättäkää asetukset merkinnällä `--`, jos ohjelma dokumentaation mukaan tukee sitä, tai hylätkää arvot, jotka alkavat tavuviivalla.

Tehtävissä, jotka tarvitsevat säännöllisesti ulkoisia ohjelmia, esimerkiksi laajamittaisessa kuva- tai PDF-käsittelyssä, kannattaa erottaa toiminnot toisistaan: verkkosovellus jättää vain toimeksiannon jonoon, ja erillinen taustapalvelu omalla käyttäjällään ja omilla oikeuksillaan suorittaa sen. Internetistä tulevia pyyntöjä vastaava PHP-prosessi ei silloin tarvitse enää järjestelmäkomentojen funktioita, ja ne voidaan estää siltä. Ulkoisilla ohjelmilla pitäisi lisäksi olla aika- ja muistirajat, jottei yksittäinen toimeksianto tuki palvelinta.

Erikoistapaus on `mail()`. Viides parametri välittää lisäasetuksia järjestelmän sähköpostiohjelmalle. Lomakkeiden arvot, esimerkiksi lähettäjän osoite, eivät saa koskaan päätyä sinne tarkistamattomina. Kestävämpi on sähköpostikirjasto, joka lähettää SMTP:llä eikä tarvitse järjestelmäkutsua.

## escapeshellarg: oikea käyttö ja rajat

Missä vanha koodi toimii yhä komentoriveillä, `escapeshellarg()` jokaiselle yksittäiselle arvolle on vähimmäistoimenpide. Funktio asettaa arvon lainausmerkkeihin, jolloin shell käsittelee sen täsmälleen yhtenä argumenttina. `escapeshellcmd()` ei sovellu käyttäjän syötteille, koska se sallii lisäparametreja, kuten OWASP näyttää esimerkillä.

`escapeshellarg()`-funktion tärkeät rajat:

1. **Argumentti-injektio on yhä mahdollinen:** Maskeerattu arvo, joka alkaa tavuviivalla, luetaan kohdeohjelmassa silti asetuksena.
2. **Windows käyttäytyy toisin:** Siellä funktio korvaa käsikirjan mukaan tietyt merkit kuten `%` ja `"` välilyönneillä; arvot saapuvat siis muuttuneina.
3. **Merkistö:** Palvelimen Locale-asetuksesta riippuen ei-ASCII-merkit voivat kadota.

## Löydökset vanhasta koodista

Järjestelmäkutsujen haku käy nopeasti ja tuottaa hallittavan luettelon:

```bash
grep -rnE "\b(exec|system|passthru|shell_exec|popen|proc_open)\s*\(" src/ --include="*.php"
grep -rn '`' src/ --include="*.php" | grep -v '^\s*//'
```

Jokainen löytö tarvitsee vastauksen kolmeen kysymykseen: Voiko kutsun korvata PHP-funktioilla? Virtaako siihen arvoja ulkopuolelta? Millä oikeuksilla prosessi toimii? Viimeinen kysymys on ratkaiseva vahingon suuruuden kannalta pahimmassa tapauksessa. PHP-prosessi omalla käyttäjällään ja ilman kirjoitusoikeuksia koodiin aiheuttaa vähemmän tuhoa, katso [Vähimmän oikeuden periaate: verkkopalvelinkäyttäjän oikeudet](https://phpsos.de/fi/blog/rechte-des-webserver-benutzers/).

> **Tärkeää:**
> Jos PHP-FPM- tai verkkopalvelinprosessi käynnistää shellin, se on vahva varoitusmerkki tunkeutumisesta. Miten sen tunnistatte, kuvaa [Varoitusmerkki: verkkopalvelin käynnistää shellin](https://phpsos.de/fi/blog/webserver-startet-shell/).

## Miten PHP SOS auttaa konkreettisesti?

- Etsimme kaikki järjestelmäkutsut sovelluksestanne, laajennuksista ja apuskripteistä.
- Korvaamme kutsut mahdollisuuksien mukaan PHP-funktioilla ja siirrämme loput kutsuihin ilman shelliä.
- Estämme tarpeettomat funktiot PHP-konfiguraatiossa ja tarkistamme PHP-käyttäjän oikeudet.
- Testaamme muutokset ja dokumentoimme, mitä ohjelmia sovellus saa vielä kutsua.

Oman ohjelmiston tarkistuksen tarjoamme [tietoturva-auditointina ja tunkeutumistestinä](https://phpsos.de/fi/leistungen/sicherheitsaudit-penetrationstest/).


## Usein kysytyt kysymykset

**Riittääkö escapeshellarg Command Injectionia vastaan?**  
Se estää arvon suorittamisen lisäkomentona. Se ei estä sitä, että kutsuttu ohjelma lukee arvon asetuksena, jos se alkaa tavuviivalla. Siksi arvot kannattaa lisäksi tarkistaa ja, jos ohjelma tukee sitä, päättää asetukset merkinnällä --.

**Mitä eroa on escapeshellargilla ja escapeshellcmd:llä?**  
escapeshellarg käsittelee yksittäisen arvon täsmälleen yhtenä argumenttina. escapeshellcmd maskeeraa vain metamerkit kokonaisessa komentorivissä ja sallii lisäparametrit. Käyttäjän syötteille escapeshellarg on oikea valinta, kutsu ilman shelliä vielä parempi.

**Pitäisikö exec ja system estää asetuksella disable_functions?**  
Jos sovelluksenne ei tarvitse niitä, kyllä. Se rajoittaa vahinkoa tunkeutumisen jälkeen. Se ei kuitenkaan korvaa turvallista koodia, sillä komentoja tarvitsevat sovellukset tarvitsevat funktioita edelleen.

**Mistä tiedän, onko Command Injectionia hyödynnetty?**  
Vahva varoitusmerkki ovat PHP- tai verkkopalvelinprosessit, jotka käynnistävät shellin kuten sh tai bash. Lisäksi tuntemattomat prosessit, uudet tiedostot upload-kansioissa ja epätavalliset lähtevät yhteydet.

## Lähteet

- [OWASP OS Command Injection Defense Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/OS_Command_Injection_Defense_Cheat_Sheet.html)
- [PHP-käsikirja: proc_open](https://www.php.net/manual/de/function.proc-open.php)
- [PHP-käsikirja: escapeshellarg](https://www.php.net/manual/de/function.escapeshellarg.php)
