Trenutno stanje: PHP2Shell. Dve kritični ranljivosti PHP (CVSS 9,2) omogočata izvedbo škodljive kode brez prijave. Popravki od 21. 10. 2026.Kaj je zdaj treba storiti →

Preprečevanje command injection v PHP

Kako razvijalci preprečujejo command injection v PHP: sistemske ukaze nadomestijo s funkcijami PHP, proc_open uporabijo brez lupine, preverjajo argumente in poiščejo nevarna mesta v obstoječi kodi.

Stanje 5 min branjaVaren razvoj

Na kratko: Command injection v PHP najvarneje preprečite tako, da sploh ne kličete lupine: številne naloge neposredno opravijo funkcije PHP ali knjižnice. Kjer je zunanji program potreben, ukaz od PHP 7.4 naprej posredujete kot polje v proc_open(), nato PHP program zažene brez lupine. Vrednosti preverite proti dovoljenemu seznamu, možnosti zaključite z -- in proces poganjate z minimalnimi pravicami.

Zakaj ima sistemski klic toliko moči

Obdelava slik, ustvarjanje PDF, arhiviranje, protivirusno skeniranje, poizvedba za vnos DNS: za take naloge aplikacije PHP rade kličejo zunanje programe. Tehnično je to udobno, dovolj je ena vrstica z exec() ali shell_exec(). Z vidika varnosti je to eno najnevarnejših mest v kodi.

Če se ukaz sestavi kot besedilo in izvede z lupino, lupina odloča, kaj je ukaz in kaj podatki. Če od zunaj nepreverjena vrednost pride vanj, lahko spremeni pomen ukaza. Posledica je v najslabšem primeru enaka kot pri PHP2Shell: izvajanje poljubnih ukazov na strežniku s pravicami procesa PHP.

Razlika: PHP2Shell odpravite s posodobitvijo PHP. Command injection v lastni kodi ostane po vsaki posodobitvi. Poleg tega so taka mesta pogosto v pomožnih skriptah, namenjenih samo skrbnikom, na primer za vzdrževanje, diagnostiko ali uvoze, in pozneje ostanejo dosegljiva brez zaščite.

Kje nastane command injection v PHP

Ogroženo je vsako mesto, kjer PHP zažene lupino ali program. Funkcije za to so:

Funkcija Uporablja lupino? Opomba
exec(), system(), passthru(), shell_exec() da ukaz kot besedilo, lupina ga interpretira
Operator z obrnjenimi opuščaji (backtick) da ustreza shell_exec()
popen() da ukaz kot besedilo
proc_open() z nizom da ukaz kot besedilo
proc_open() s poljem ne od PHP 7.4, argumenti se posredujejo neposredno
mail() s petim parametrom posredno dodatni parametri za poštni program

Tipičen nevaren vzorec izgleda takole. Ime datoteke pride iz zahteve in konča neposredno v ukazni vrstici:

<?php
// NEVARNO: vrednost iz zahteve postane del ukaza lupine
$datei = $_POST['datei'];
exec('convert uploads/' . $datei . ' -resize 800x800 vorschau/' . $datei);

Prva izbira: sploh nobene lupine

Najučinkovitejša zaščita je nadomestitev sistemskega klica. OWASP to izrecno navaja kot prvo obrambno linijo. Za številne naloge ima PHP lastne funkcije ali razširitve:

  • Imeniki in datoteke: mkdir(), copy(), rename(), unlink()
  • Arhivi: ZipArchive ali PharData
  • Slike: GD ali Imagick kot razširitev PHP
  • Omrežne poizvedbe: dns_get_record(), gethostbyname(), cURL
  • Kontrolne vsote: hash_file()

S tem vprašanje, katere znake lupina interpretira, v celoti odpade. Poleg tega je funkcije za sistemske ukaze potem mogoče zakleniti v konfiguraciji, kot opisuje vodnik Pravilna uporaba disable_functions.

Kadar je zunanji program potreben: proc_open s poljem

Od PHP 7.4 proc_open() sprejme ukaz kot polje. Po priročniku PHP se proces takrat zažene neposredno, brez ovinka prek lupine, nujno maskiranje argumentov pa prevzame PHP. Znaki, kot sta podpičje ali cevna črta, potem nimajo več posebnega pomena.

<?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);

Primer naredijo robustnega tri stvari: program je s polno potjo trdno v kodi, ime datoteke se preveri proti ozkemu vzorcu in vsak spremenljiv argument se začne z absolutno potjo, zato ga ni mogoče prebrati kot možnost. Kjer vrednost stoji sama, možnosti zaključite z --, če to program po dokumentaciji podpira, ali pa zavrnite vrednosti, ki se začnejo s pomišljajem.

Pri nalogah, ki redno potrebujejo zunanje programe, na primer obsežna obdelava slik ali PDF, se splača ločitev: spletna aplikacija v čakalno vrsto vpiše samo nalogo, ločena storitev v ozadju z lastnim uporabnikom in lastnimi pravicami pa jo izvede. Proces PHP, ki odgovarja na zahteve z interneta, potem ne potrebuje več funkcij za sistemske ukaze in jih je zanj mogoče zakleniti. Zunanji programi naj imajo tudi omejitve časa in pomnilnika, da posamezna naloga ne blokira strežnika.

Poseben primer je mail(). Peti parameter poštnemu programu sistema posreduje dodatne možnosti. Vrednosti iz obrazcev, na primer naslov pošiljatelja, tam nikoli ne smejo končati nepreverjene. Robustnejša je poštna knjižnica, ki pošilja prek SMTP in ne potrebuje sistemskega klica.

escapeshellarg: pravilna uporaba in meje

Kjer obstoječa koda še dela z ukaznimi vrsticami, je escapeshellarg() za vsako posamezno vrednost najmanjši ukrep. Funkcija vrednost postavi v narekovaje, tako da jo lupina obravnava kot natanko en argument. escapeshellcmd() je za uporabniške vnose neprimeren, ker dopušča dodatne parametre, kot OWASP pokaže na primeru.

Pomembne meje escapeshellarg():

  1. Vbrizgavanje argumentov ostane mogoče: maskirana vrednost, ki se začne s pomišljajem, ga ciljni program vseeno prebere kot možnost.
  2. Windows se obnaša drugače: tam funkcija po priročniku določene znake, kot sta % in ", nadomesti s presledki; vrednosti torej prispejo spremenjene.
  3. Nabor znakov: glede na nastavitev locale strežnika se lahko znaki, ki niso ASCII, izgubijo.

Iskanje najdb v obstoječi kodi

Iskanje sistemskih klicev je hitro in da pregleden seznam:

grep -rnE "\b(exec|system|passthru|shell_exec|popen|proc_open)\s*\(" src/ --include="*.php"
grep -rn '`' src/ --include="*.php" | grep -v '^\s*//'

Vsaka najdba potrebuje odgovor na tri vprašanja: ali je klic mogoče nadomestiti s funkcijami PHP? Ali vanj pritekajo vrednosti od zunaj? S katerimi pravicami teče proces? Zadnje vprašanje je odločilno za škodo v skrajnem primeru. Proces PHP z lastnim uporabnikom in brez pravic pisanja v kodo naredi manj škode, glejte Least Privilege: pravice uporabnika spletnega strežnika.

Pomembno

Če proces PHP-FPM ali spletnega strežnika zažene lupino, je to močan opozorilni znak za vdor. Kako to prepoznate, opisuje Opozorilni znak: spletni strežnik zažene lupino.

Kako konkretno pomaga PHP SOS?

  • Poiščemo vse sistemske klice v vaši aplikaciji, vtičnikih in pomožnih skriptah.
  • Klice, kjer je mogoče, nadomestimo s funkcijami PHP, preostale pa preusmerimo na klice brez lupine.
  • Nepotrebne funkcije zaklenemo v konfiguraciji PHP in preverimo pravice uporabnika PHP.
  • Spremembe preizkusimo in dokumentiramo, katere programe aplikacija še sme klicati.

Pregled lastne programske opreme ponujamo kot varnostni pregled in penetracijski test.

Pogosta vprašanja

Ali escapeshellarg zadošča proti command injection?

Prepreči, da bi se vrednost izvedla kot nadaljnji ukaz. Ne prepreči pa, da klicani program vrednost prebere kot možnost, če se začne s pomišljajem. Zato vrednosti dodatno preverite in, kjer program to podpira, možnosti zaključite z --.

Kakšna je razlika med escapeshellarg in escapeshellcmd?

escapeshellarg obravnava posamezno vrednost kot natanko en argument. escapeshellcmd samo maskira metaznake v celotni ukazni vrstici in dopušča dodatne parametre. Za uporabniške vnose je prava izbira escapeshellarg, klic brez lupine pa je še boljši.

Ali naj exec in system zaklenem z disable_functions?

Če ju vaša aplikacija ne potrebuje, da. To omeji škodo po vdoru. Varne kode pa ne nadomesti, saj aplikacije, ki ukaze potrebujejo, funkcije še naprej potrebujejo.

Kako prepoznam, ali je bil command injection izkoriščen?

Močan opozorilni znak so procesi PHP ali spletnega strežnika, ki zaženejo lupino, kot sta sh ali bash. K temu pridejo neznani procesi, nove datoteke v mapah za nalaganje in nenavadne odhodne povezave.

Viri

Takojšnji stik

Vsaka ura šteje. Pišite nam zdaj.

Na kratko opišite sistem, različico PHP in opažanje. Odzovemo se, razjasnimo stanje in delamo na daljavo, dokler incident ni zaključen in dokumentiran.

Takojšnja pomoč