Trenutno stanje: PHP2Shell. Dvije kritične PHP ranjivosti (CVSS 9.2) omogućuju zlonamjerni kod bez prijave. Zakrpe od 21. 10. 2026.Što sada učiniti →

Spriječiti Command Injection u PHP-u

Kako razvojni inženjeri izbjegavaju Command Injection u PHP-u: sistemske naredbe zamijeniti PHP funkcijama, koristiti proc_open bez ljuske, provjeravati argumente i pronaći opasna mjesta u postojećem kodu.

Stanje 5 min čitanjaSiguran razvoj

Ukratko: Command Injection u PHP-u najsigurnije sprječavate tako da uopće ne pozivate ljusku: mnoge zadatke izravno obavljaju PHP funkcije ili biblioteke. Gdje je potreban vanjski program, naredbu od PHP-a 7.4 predajte kao polje (array) funkciji proc_open(), tada PHP pokreće program bez ljuske. Vrijednosti provjeravajte prema popisu dopuštenog, opcije završite s --, a proces pokrećite s minimalnim pravima.

Zašto sistemski poziv ima toliku moć

Obrada slika, izrada PDF-ova, arhiviranje, virusni sken, upit DNS zapisa: za takve zadatke PHP aplikacije rado pozivaju vanjske programe. Tehnički je to zgodno, dovoljan je jedan redak s exec() ili shell_exec(). Sigurnosno-tehnički to je jedno od najopasnijih mjesta u kodu.

Kad se naredba sastavlja kao tekst i izvršava je ljuska, ljuska odlučuje što je naredba, a što podatak. Ako vanjska vrijednost uđe nepromijenjena, može promijeniti značenje naredbe. Posljedica je u najgorem slučaju ista kao kod PHP2Shella: izvršavanje proizvoljnih naredbi na poslužitelju s pravima PHP procesa.

Razlika: PHP2Shell otklanjate PHP updateom. Command Injection u vlastitom kodu ostaje nakon svakog updatea. Tome se pridodaje da se takva mjesta često kriju u pomoćnim skriptama namijenjenima samo administratorima, primjerice za održavanje, dijagnostiku ili uvoze, a kasnije ostaju dostupna bez zaštite.

Gdje nastaje Command Injection u PHP-u

Ugroženo je svako mjesto na kojem PHP pokreće ljusku ili program. Funkcije za to su:

Funkcija Koristi li ljusku? Napomena
exec(), system(), passthru(), shell_exec() da naredba kao tekst, ljuska je interpretira
operator povratnog navodnika (backtick) da odgovara shell_exec()
popen() da naredba kao tekst
proc_open() sa stringom da naredba kao tekst
proc_open() s poljem ne od PHP-a 7.4, argumenti se predaju izravno
mail() s petim parametrom neizravno dodatni parametri za program za e-poštu

Tipičan nesiguran uzorak izgleda ovako. Naziv datoteke dolazi iz zahtjeva i izravno završava u naredbenoj liniji:

<?php
// NESIGURNO: vrijednost iz zahtjeva postaje dio naredbe ljuske
$datei = $_POST['datei'];
exec('convert uploads/' . $datei . ' -resize 800x800 vorschau/' . $datei);

Prvi izbor: uopće bez ljuske

Najučinkovitija zaštita je zamijeniti sistemski poziv. OWASP to izričito navodi kao prvu liniju obrane. Za mnoge zadatke u PHP-u postoje vlastite funkcije ili proširenja:

  • Direktoriji i datoteke: mkdir(), copy(), rename(), unlink()
  • Arhive: ZipArchive ili PharData
  • Slike: GD ili Imagick kao PHP proširenje
  • Mrežni upiti: dns_get_record(), gethostbyname(), cURL
  • Kontrolni zbrojevi: hash_file()

Time potpuno nestaje pitanje koje znakove ljuska interpretira. Osim toga funkcije za sistemske naredbe tada se mogu blokirati u konfiguraciji, kako opisuje vodič Ispravno koristiti disable_functions.

Kad je vanjski program potreban: proc_open s poljem

Od PHP-a 7.4 proc_open() prihvaća naredbu kao polje. Prema PHP priručniku proces se tada pokreće izravno, bez zaobilaznice preko ljuske, a PHP preuzima potrebno maskiranje argumenata. Znakovi poput točke sa zarezom ili pipea tada više nemaju posebno značenje.

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

Primjer čine robusnim tri stvari: program je s punom putanjom čvrsto upisan u kod, naziv datoteke provjerava se prema uskom uzorku, a svaki promjenjivi argument počinje apsolutnom putanjom, pa se ne može pročitati kao opcija. Gdje vrijednost stoji sama, opcije završite s --, ako to program prema dokumentaciji podržava, ili odbijte vrijednosti koje počinju crticom.

Kod zadataka koji redovito trebaju vanjske programe, primjerice obrada slika ili PDF-ova većeg opsega, vrijedi odvajanje: web-aplikacija samo stavlja zadatak u red čekanja, a zasebna pozadinska usluga s vlastitim korisnikom i vlastitim pravima ga izvršava. PHP proces koji odgovara na zahtjeve s interneta tada više ne treba funkcije za sistemske naredbe i one se za njega mogu blokirati. Vanjski programi trebali bi uz to imati vremenska i memorijska ograničenja, kako jedan zadatak ne bi blokirao poslužitelj.

Poseban je slučaj mail(). Peti parametar predaje dodatne opcije sistemskom programu za e-poštu. Vrijednosti iz obrazaca, primjerice adresa pošiljatelja, tamo nikad ne smiju završiti neprovjerene. Robusnija je biblioteka za e-poštu koja šalje preko SMTP-a i ne treba sistemski poziv.

escapeshellarg: ispravna primjena i poznavanje granica

Gdje postojeći kod još radi s naredbenim linijama, escapeshellarg() za svaku pojedinačnu vrijednost minimalna je mjera. Funkcija vrijednost stavlja u navodnike tako da je ljuska tretira kao točno jedan argument. escapeshellcmd() nije prikladan za unos korisnika jer dopušta dodatne parametre, kako OWASP pokazuje na primjeru.

Važne granice funkcije escapeshellarg():

  1. Argument Injection ostaje moguć: maskirana vrijednost koja počinje crticom ciljni program svejedno čita kao opciju.
  2. Windows se ponaša drukčije: tamo funkcija prema priručniku određene znakove poput % i " zamjenjuje razmacima; vrijednosti stoga stižu promijenjene.
  3. Skup znakova: ovisno o postavci locale poslužitelja, znakovi izvan ASCII-ja mogu se izgubiti.

Pronaći mjesta u postojećem kodu

Pretraga sistemskih poziva brzo je gotova i daje pregledan popis:

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

Svako nalazište traži odgovor na tri pitanja: može li se poziv zamijeniti PHP funkcijama? Utječu li vanjske vrijednosti? S kojim pravima proces radi? Posljednje pitanje presudno je za štetu u najgorem slučaju. PHP proces s vlastitim korisnikom i bez prava pisanja na kod čini manje štete, vidi Najmanje ovlasti: prava korisnika web-poslužitelja.

Važno

Ako PHP-FPM ili proces web-poslužitelja pokrene ljusku, to je snažan znak upozorenja na provalu. Kako to prepoznati, opisuje Znak upozorenja: web-poslužitelj pokreće ljusku.

Kako PHP SOS konkretno pomaže?

  • Tražimo sve sistemske pozive u Vašoj aplikaciji, dodacima i pomoćnim skriptama.
  • Pozive gdje je moguće zamjenjujemo PHP funkcijama, a ostale prebacujemo na pozive bez ljuske.
  • Nepotrebne funkcije blokiramo u PHP konfiguraciji i provjeravamo prava PHP korisnika.
  • Testiramo promjene i dokumentiramo koje programe aplikacija još smije pozivati.

Provjeru vlastitog softvera nudimo kao sigurnosni audit i penetracijski test.

Česta pitanja

Je li escapeshellarg dovoljan protiv Command Injectiona?

Sprječava da se vrijednost izvrši kao dodatna naredba. Ne sprječava da pozvani program vrijednost pročita kao opciju ako počinje crticom. Zato dodatno provjeravajte vrijednosti i, gdje program to podržava, opcije završite s --.

Koja je razlika između escapeshellarg i escapeshellcmd?

escapeshellarg vrijednost tretira kao točno jedan argument. escapeshellcmd samo maskira metaznakove u cijeloj naredbenoj liniji i dopušta dodatne parametre. Za unos korisnika escapeshellarg je pravi izbor, a poziv bez ljuske još bolji.

Trebam li exec i system blokirati pomoću disable_functions?

Ako ih Vaša aplikacija ne treba, da. To ograničava štetu nakon provale. No ne zamjenjuje siguran kod, jer aplikacije kojima su naredbe potrebne i dalje trebaju te funkcije.

Kako prepoznati je li Command Injection iskorišten?

Snažan znak upozorenja su PHP ili web-poslužiteljski procesi koji pokreću ljusku poput sh ili bash. Tome se pridružuju nepoznati procesi, nove datoteke u mapama za upload i neobične odlazne veze.

Izvori

Hitni kontakt

Svaki sat se računa. Pišite nam odmah.

Ukratko opišite sustav, verziju PHP-a i opažanje. Javit ćemo Vam se, razjasniti situaciju i raditi na daljinu dok incident nije zaključen i dokumentiran.

Hitna pomoć