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:
ZipArchivetaiPharData - 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:
- Argumentti-injektio on yhä mahdollinen: Maskeerattu arvo, joka alkaa tavuviivalla, luetaan kohdeohjelmassa silti asetuksena.
- Windows käyttäytyy toisin: Siellä funktio korvaa käsikirjan mukaan tietyt merkit kuten
%ja"välilyönneillä; arvot saapuvat siis muuttuneina. - 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.
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.