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 →

Isolér PHP-FPM-pools pr. website

En egen pool pr. website er begyndelsen, ikke enden. Sådan kontrollerer I eksisterende pools og hærder sockets, endelser, statussider, ressourcer og logs.

Status 5 min. læsetidHærdning og konfiguration

Kort sagt: Hvert website på en server bør have sin egen PHP-FPM-pool med egen systembruger og egen socket. Adskillelsen bliver dog først virksom med detaljerne: socket-rettigheder kun for webserveren, security.limit_extensions på .php, ingen offentligt tilgængelige statussider, egne grænser for processer og køretid samt adskilte logs. Mod PHP2Shell selv beskytter kun opdateringen; pools begrænser, hvor langt et angreb rækker.

En server, mange websites, en fælles risiko

På mange servere er der over årene vokset noget frem, som ingen har planlagt sådan. Firmawebsitet, shoppen, en kampagneside fra 2019 og et testsystem kører side om side, ofte under den samme bruger og i den samme pool. Kompromitteres den glemte kampagneside, ligger shoppens kundedata også åbne.

PHP-FPM, PHP's FastCGI-procesmanager, løser det med pools: grupper af worker-processer med egne indstillinger. Hvordan grundprincippet fungerer, forklarer PHP-FPM forklaret: Procesmanager, pools og workers. Opbygningen af adskilte pools med egen bruger, egen temp-mappe og open_basedir beskriver vejledningen open_basedir og isolation pr. website. Dette indlæg går et skridt videre: Hvordan kontrollerer I eksisterende pools, og hvilke indstillinger gør adskillelsen holdbar?

Vigtigt for indordningen: Ved PHP2Shell ligger sårbarhederne CVE-2026-109618 og CVE-2026-109619 i PHP-fortolkeren, som alle pools i en PHP-version deler. En egen pool forhindrer ikke en udnyttelse. Men den afgør, om en angriber bagefter når ét website eller alle.

PHP-FPM-pools med egen bruger: Kontrollér beholdningen

Før I ændrer noget, skaffer I jer overblik. De følgende kommandoer læser kun. Stier og versionsnumre afviger fra distribution til distribution:

# Alle pools med bruger og socket
grep -HE '^\s*(\[|user|group|listen)\s*' /etc/php/*/fpm/pool.d/*.conf

# Kørende pools og deres brugere
ps -eo user,pid,cmd | grep '[p]hp-fpm: pool'

# Sockets og deres rettigheder
ls -l /run/php/

# Kontrollér konfigurationen for fejl (ændrer intet)
sudo php-fpm8.3 -t

Vær opmærksom på tre mønstre: flere websites i den samme pool, pools, der kører som www-data eller endda som root, og sockets, der kan skrives af alle brugere. Hvert af dem underminerer adskillelsen.

Kører der flere PHP-versioner på serveren, har hver sin egen FPM-tjeneste med egne pool-mapper. Kontrollér dem alle, ikke kun den version, I senest satte op. Netop gamle versioner med glemte pools er en risiko, fordi de ofte hverken vedligeholdes eller genstartes efter opdateringer. Oplysninger herom står under Flere PHP-versioner på en server. Notér resultatet i en lille tabel: website, pool, bruger, socket, PHP-version. Det overblik er samtidig grundlaget for enhver senere ændring.

De vigtigste indstillinger pr. pool

Oversigten nedenfor bygger på FPM-konfigurationen i PHP-manualen. Værdier for processer og tider retter sig efter jeres belastning og applikation:

Indstilling Formål Anbefaling
user, group workernes bruger egen bruger pr. website, aldrig root
listen.owner, listen.group, listen.mode rettigheder på socketten kun webserverens gruppe, tilstand 0660
listen.acl_users adgang via ACL i stedet for ejer indtast kun webserverbrugeren
security.limit_extensions eksekverbare endelser begræns til .php (standard .php .phar)
clear_env tøm miljøvariabler lad stå på standarden yes
pm.max_children maksimalt antal workers begræns, så et website ikke belaster serveren fuldt ud
request_terminate_timeout afbrydelse af hængende forespørgsler sæt passende til den længste legitime forespørgsel
pm.status_path, ping.path status- og ping-side sæt kun, hvis det er nødvendigt, og afskærm internt
slowlog, request_slowlog_timeout protokol over langsomme forespørgsler pr. pool i egen fil

Et uddrag for en hærdet pool ser således ud:

; /etc/php/8.3/fpm/pool.d/shop.conf (uddrag)
[shop]
user = shop
group = shop
listen = /run/php/shop.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660

pm = dynamic
pm.max_children = 20
pm.start_servers = 4
pm.min_spare_servers = 2
pm.max_spare_servers = 6
request_terminate_timeout = 60s

security.limit_extensions = .php
clear_env = yes

slowlog = /var/log/php/shop.slow.log
request_slowlog_timeout = 10s
php_admin_value[error_log] = /var/log/php/shop.error.log

Indstillinger som memory_limit eller session.upload_progress.enabled sætter I pr. pool med php_admin_value og php_admin_flag. Ifølge manualen kan disse værdier ikke overskrives med ini_set(). Mere herom under php_admin_value: Lås indstillinger mod overskrivning.

Placér statussider og worker-skift rigtigt

pm.status_path og ping.path er kun aktive, hvis de er sat. Statussiden viser kørende forespørgsler, processer og belastning. Til overvågning er det nyttigt, men offentligt tilgængeligt er det en unødvendig informationskilde for angribere. Send disse stier videre i webserveren kun for interne adresser. Hvordan Nginx og PHP-FPM arbejder sikkert sammen, beskriver Afsikr Nginx med PHP-FPM.

pm.max_requests fastlægger, efter hvor mange forespørgsler en worker genstarter; standarden er 0, altså aldrig. En værdi over nul hjælper mod et langsomt voksende hukommelsesforbrug. Som beskyttelse mod PHP2Shell er den ikke tænkt: php.net nævner den ikke blandt overgangsforanstaltningerne og understreger, at kun opdateringen beskytter fuldstændigt. Efter opdateringen gælder desuden: Alle workers i alle pools skal genstartes, fordi gamle processer ellers fortsætter med den sårbare kode.

Tip

Vedligehold en skabelon til nye pools, og opret hvert nyt website med den. Så opstår adskillelsen automatisk i stedet for at blive eftermonteret år senere.

Typiske fejl ved omstillingen

Den hyppigste fejl er en ny pool, der ganske vist kører under egen bruger, men hvis filer fortsat tilhører www-data. Så må poolen ikke skrive, hvor den skal, og nogen sætter rettighederne for rundhåndet. Afklar på forhånd, hvilke mapper der skal kunne skrives i. Den anden fejl er planlagte opgaver, der fortsat kører under den gamle bruger og opretter filer med forkert ejer. Den tredje er en fælles session-mappe for alle pools, som ophæver adskillelsen igen.

Test efter hver omstilling websitets login, uploads, caches og planlagte opgaver, og hold poolens fejllog under opsyn.

Hvordan hjælper PHP SOS konkret?

  • Vi registrerer alle pools, brugere, sockets og indstillinger og viser, hvor websites i dag deler processer eller rettigheder.
  • Vi opsætter en hærdet pool pr. website, med socket-rettigheder, endelsesgrænse, ressourcegrænser og egne logs.
  • Vi afskærmer statussider, genstarter alle workers kontrolleret og tester hvert website.
  • Inden for rammerne af serverhærdningen dokumenterer vi adskillelsen som skabelon til fremtidige projekter.

Ofte stillede spørgsmål

Hvorfor skal hvert website have sin egen PHP-FPM-pool?

En egen pool med egen systembruger adskiller websites på niveauet af processer og filrettigheder. Kompromitteres et website, kan koden ikke uden videre læse de andres filer. Desuden kan grænser og indstillinger fastlægges særskilt for hvert website.

Beskytter en egen pool mod PHP2Shell?

Nej. Sårbarhederne ligger i selve PHP-fortolkeren, som alle pools deler. Beskyttelse giver kun opdateringen til en rettet version med genstart af alle workers. Adskilte pools begrænser dog, hvor langt et vellykket angreb rækker.

Hvad gør security.limit_extensions?

Ifølge PHP-manualen begrænser indstillingen, hvilke filendelser PHP-FPM udfører som hovedscript. Standarden er .php og .phar. Den, der begrænser til .php, forhindrer, at f.eks. uploadede billedfiler udføres som PHP, hvis webserveren er konfigureret forkert.

Er PHP-FPM's statusside farlig?

Den afslører detaljer om kørende processer og forespørgsler. Er den offentligt tilgængelig uden beskyttelse, letter den angribernes orientering. Ifølge manualen er den kun aktiv, hvis pm.status_path er sat. Så bør webserveren begrænse stien til interne adresser.

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