Aktuel situation: PHP2Shell. To kritiske PHP-sårbarheder (CVSS 9,2) tillader skadelig kode uden login. Rettelser siden 21.10.2026.Hvad der skal gøres nu →

Forhindr command injection i PHP

Hvordan udviklere undgår command injection i PHP: erstat systemkommandoer med PHP-funktioner, brug proc_open uden shell, kontrollér argumenter og find farlige steder i eksisterende kode.

Status 5 min. læsetidSikker udvikling

Kort sagt: Command injection i PHP forhindrer I sikrest ved slet ikke at kalde en shell: Mange opgaver klarer PHP-funktioner eller biblioteker direkte. Hvor et eksternt program er nødvendigt, overgiver I siden PHP 7.4 kommandoen som array til proc_open(), så starter PHP programmet uden shell. Værdier kontrollerer I mod en tilladelsesliste, afslutter indstillinger med -- og lader processen køre med minimale rettigheder.

Hvorfor et systemkald har så meget magt

Billedbehandling, PDF-generering, arkivering, virusscanning, forespørgsel på en DNS-post: Til sådanne opgaver kalder PHP-applikationer gerne eksterne programmer. Teknisk er det bekvemt, en linje med exec() eller shell_exec() er nok. Sikkerhedsmæssigt er det et af de farligste steder i koden.

Sættes en kommando sammen som tekst og udføres af en shell, afgør shellen, hvad der er kommando, og hvad der er data. Når en værdi udefra kommer ukontrolleret ind, kan den ændre kommandoens betydning. Følgen er i værste fald den samme som ved PHP2Shell: udførelse af vilkårlige kommandoer på serveren med PHP-processens rettigheder.

Forskellen: PHP2Shell afhjælper I med en PHP-opdatering. En command injection i egen kode består efter enhver opdatering. Dertil kommer, at sådanne steder ofte sidder i hjælpescripts, der kun var tænkt til administratorer, f.eks. til vedligeholdelse, diagnose eller importer, og senere forbliver nåelige uden beskyttelse.

Hvor command injection i PHP opstår

Udsat er ethvert sted, hvor PHP starter en shell eller et program. Funktionerne hertil er:

Funktion Bruger en shell? Bemærkning
exec(), system(), passthru(), shell_exec() ja Kommando som tekst, shellen fortolker den
Backtick-operator ja svarer til shell_exec()
popen() ja Kommando som tekst
proc_open() med streng ja Kommando som tekst
proc_open() med array nej siden PHP 7.4, argumenter overgives direkte
mail() med femte parameter indirekte yderligere parametre til mailprogrammet

Et typisk usikkert mønster ser sådan ud. Filnavnet kommer fra forespørgslen og havner direkte i kommandolinjen:

<?php
// USIKKER: Værdi fra forespørgslen bliver del af en shell-kommando
$datei = $_POST['datei'];
exec('convert uploads/' . $datei . ' -resize 800x800 vorschau/' . $datei);

Første valg: slet ingen shell

Den mest virkningsfulde beskyttelse er at erstatte systemkaldet. OWASP nævner det udtrykkeligt som første forsvarslinje. Til mange opgaver findes der i PHP egne funktioner eller udvidelser:

  • Mapper og filer: mkdir(), copy(), rename(), unlink()
  • Arkiver: ZipArchive eller PharData
  • Billeder: GD eller Imagick som PHP-udvidelse
  • Netværksforespørgsler: dns_get_record(), gethostbyname(), cURL
  • Kontrolsummer: hash_file()

Dermed forsvinder spørgsmålet om, hvilke tegn en shell fortolker, fuldstændigt. Desuden kan funktionerne til systemkommandoer så spærres i konfigurationen, som vejledningen Brug disable_functions rigtigt beskriver.

Når et eksternt program er nødvendigt: proc_open med array

Siden PHP 7.4 accepterer proc_open() kommandoen som array. Ifølge PHP-manualen startes processen så direkte, uden omvej via shellen, og PHP overtager den nødvendige maskering af argumenterne. Tegn som semikolon eller pipe har så ikke længere nogen særbetydning.

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

Tre ting gør eksemplet robust: Programmet står med fuld sti fast i koden, filnavnet kontrolleres mod et snævert mønster, og hvert variabelt argument begynder med en absolut sti og kan altså ikke læses som indstilling. Hvor en værdi står alene, afslutter I indstillingerne med --, hvis programmet ifølge dokumentationen understøtter det, eller afviser værdier, der begynder med en bindestreg.

Ved opgaver, der jævnligt har brug for eksterne programmer, f.eks. billed- eller PDF-behandling i større omfang, er en afkobling værd: Webapplikationen lægger blot en opgave i en kø, en separat baggrundstjeneste med egen bruger og egne rettigheder udfører den. PHP-processen, der besvarer forespørgsler fra internettet, har så ikke længere brug for funktioner til systemkommandoer, og de kan spærres for den. Eksterne programmer bør desuden have tids- og hukommelsesgrænser, så en enkelt opgave ikke blokerer serveren.

Et særtilfælde er mail(). Den femte parameter overgiver yderligere indstillinger til systemets mailprogram. Værdier fra formularer, f.eks. en afsenderadresse, må aldrig havne dér ukontrolleret. Mere robust er et mailbibliotek, der sender via SMTP og ikke har brug for noget systemkald.

escapeshellarg: brug det rigtigt og kend grænserne

Hvor eksisterende kode stadig arbejder med kommandolinjer, er escapeshellarg() for hver enkelt værdi minimumsforanstaltningen. Funktionen sætter værdien i anførselstegn, så shellen behandler den som netop ét argument. escapeshellcmd() er uegnet til brugerinput, fordi den tillader yderligere parametre, som OWASP viser med et eksempel.

Vigtige grænser for escapeshellarg():

  1. Argument-injection er fortsat mulig: En maskeret værdi, der begynder med en bindestreg, læses alligevel af målprogrammet som en indstilling.
  2. Windows opfører sig anderledes: Dér erstatter funktionen ifølge manualen bestemte tegn som % og " med mellemrum; værdier ankommer altså ændrede.
  3. Tegnsæt: Afhængigt af serverens locale-indstilling kan tegn uden for ASCII gå tabt.

Find fundsteder i eksisterende kode

En søgning efter systemkald er hurtigt gjort og giver en overskuelig liste:

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

Hvert fund kræver et svar på tre spørgsmål: Kan kaldet erstattes af PHP-funktioner? Flyder der værdier udefra ind? Med hvilke rettigheder kører processen? Det sidste spørgsmål er afgørende for skaden i alvorlige tilfælde. En PHP-proces med egen bruger og uden skriverettigheder til koden anretter mindre, se Least Privilege: Webserver-brugerens rettigheder.

Vigtigt

Starter en PHP-FPM- eller webserverproces en shell, er det et stærkt advarselstegn på et indbrud. Hvordan I opdager det, beskriver Advarselstegn: Webserveren starter en shell.

Hvordan hjælper PHP SOS konkret?

  • Vi søger alle systemkald i jeres applikation, i plugins og i hjælpescripts.
  • Vi erstatter kald, hvor det er muligt, med PHP-funktioner og omstiller de øvrige til kald uden shell.
  • Vi spærrer unødvendige funktioner i PHP-konfigurationen og kontrollerer PHP-brugerens rettigheder.
  • Vi tester ændringerne og dokumenterer, hvilke programmer applikationen fortsat må kalde.

Kontrol af egen software tilbyder vi som sikkerhedsaudit og penetrationstest.

Ofte stillede spørgsmål

Er escapeshellarg nok mod command injection?

Det forhindrer, at en værdi udføres som en yderligere kommando. Det forhindrer ikke, at værdien læses som en indstilling til det kaldte program, hvis den begynder med en bindestreg. Kontrollér derfor desuden værdier, og afslut indstillinger med --, hvor programmet understøtter det.

Hvad er forskellen mellem escapeshellarg og escapeshellcmd?

escapeshellarg behandler en enkelt værdi som netop ét argument. escapeshellcmd maskerer kun metategn i en hel kommandolinje og tillader yderligere parametre. Til brugerinput er escapeshellarg det rigtige valg, et kald uden shell er endnu bedre.

Bør jeg spærre exec og system via disable_functions?

Hvis jeres applikation ikke har brug for dem, ja. Det begrænser skaden efter et indbrud. Det erstatter dog ikke sikker kode, for applikationer, der har brug for kommandoer, har fortsat brug for funktionerne.

Hvordan kan jeg se, om en command injection er blevet udnyttet?

Et stærkt advarselstegn er PHP- eller webserverprocesser, der starter en shell som sh eller bash. Dertil kommer ukendte processer, nye filer i upload-mapper og usædvanlige udgående forbindelser.

Kilder

Akut kontakt

Hver time tæller. Skriv til os nu.

Beskriv kort system, PHP-version og observation. Vi vender tilbage, afklarer situationen og arbejder på afstand, indtil hændelsen er afsluttet og dokumenteret.

Akut hjælp