Ajankohtainen tilanne: PHP2Shell. Kaksi kriittistä PHP-haavoittuvuutta (CVSS 9.2) sallii haittakoodin suorittamisen ilman kirjautumista. Korjaukset 21.10.2026 alkaen.Mitä nyt on tehtävä →

Command Injectionin estäminen PHP:ssä

Miten kehittäjät välttävät Command Injectionin PHP:ssä: järjestelmäkomentojen korvaaminen PHP-funktioilla, proc_openin käyttö ilman shelliä, argumenttien tarkistus ja vaarallisten kohtien löytäminen vanhasta koodista.

Tilanne Lukuaika 4 minTurvallinen kehitys

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
// 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ä 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
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:

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.

Tärkeää

Jos PHP-FPM- tai verkkopalvelinprosessi käynnistää shellin, se on vahva varoitusmerkki tunkeutumisesta. Miten sen tunnistatte, kuvaa Varoitusmerkki: verkkopalvelin käynnistää shellin.

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ä.

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

Pikayhteys

Jokainen tunti on tärkeä. Kirjoittakaa meille nyt.

Kuvatkaa lyhyesti järjestelmä, PHP-versio ja havainnot. Otamme yhteyttä, selvitämme tilanteen ja työskentelemme etänä, kunnes tapaus on käsitelty loppuun ja dokumentoitu.

Pikaapu