PHP2Shell ispmanageri ja CloudLinuxiga: mis ei läinud juhendi järgi

ISPmanager, Turvalisus, Veebimajutus

21. oktoobril 2026 avaldas PHP versioonid 8.5.12, 8.4.27, 8.3.36 ja 8.2.35, mis parandavad kaks kriitilist turvaviga, CVE-2026-109618 ja CVE-2026-109619 (CVSS v4.0 9.2). Mõlemad võimaldavad serveris koodi käivitada ilma autentimiseta, ja haavatav pole mõni plugin, vaid interpretaator ise: iga PHP-l töötav veebileht, alates WordPressist kuni eraldi arendatud koodini.

Tavalises serveris on lugu lühike: uuenda pakett ja taaskäivita PHP. ispmanageri ja CloudLinuxiga majutusserverites osutus see huvitavamaks. Ühe öö ja hommikuga parandasime kõik oma serverid ning astusime teel peaaegu kõigile rehadele, mida ei maini ei PHP turvateade ega paneeli dokumentatsioon.

See ei ole turvateate ümberjutustus, vaid nimekiri sellest, mis ei läinud juhendi järgi, ja kuidas me selle lahendasime. Kui haldate ispmanageriga serverit, eriti CloudLinuxil, kontrollige iga punkti oma süsteemis: osa probleeme jääb nähtamatuks, kuni õigesse kohta ei vaata.

Ajutised meetmed enne parandusi

Kuni pakette pole, ei saa turvavigu täielikult sulgeda, saab ainult ära lõigata teadaolevad ründeteed. Avalikud eksploidid, mis ilmusid juba enne väljalaset, kasutasid failide üleslaadimise edenemise käsitlejat, mis käivitub iga multipart/form-data päringu korral, isegi kui veebilehel pole ühtegi üleslaadimisvormi.

Tegime kaks asja:

  1. Lülitasime välja session.upload_progress.enabled kõigis PHP versioonides. See on PHP enda soovitus, seda funktsiooni ei kasuta peaaegu keegi ja veebilehed selle väljalülitamisest ei kannata. Oluline: väärtus peab olema määratud failis php.ini või basseini (pool) seadistuses, mitte ini_set() ega .user.ini kaudu, muidu rakendub see liiga hilja.
  2. Blokeerisime multipart päringud nginxi tasemel kõigil veebilehtedel. ispmanageris kaasab iga vhost faili /etc/nginx/vhosts-includes/*.conf, nii et ühest failist piisab kogu serveri jaoks:
if ($http_content_type ~* "multipart") {
    return 403;
}

Teise meetme hind: lakkavad töötamast failide üleslaadimine vormide kaudu, manused veebipostis ja import phpMyAdminis. Seega on see meede tundideks, mitte päevadeks.

Kaks kohta, mida on lihtne märkamata jätta:

  • Vaikimisi server failis /etc/nginx/nginx.conf. See püüab kinni päringud IP-aadressile ja tundmatu Host päisega ning suunab need ispmanageris Apache’ile, kuid vhosts-includes faile ei kaasa. Ilma eraldi include reata läheb rünnak IP-aadressi kaudu blokeeringust mööda.
  • Apache avalikul aadressil. Kui Apache kuulab mitte 127.0.0.1, vaid avalikku IP-aadressi, saab päringu saata otse sellele, nginxist mööda. Kontroll: ss -ltnp | grep httpd.

CageFS-i lõks: paneeli seadistus ei jõua veebilehtedeni

Päeval lülitasime session.upload_progress.enabled välja tavapärasel viisil: „Veebiserveri seaded → PHP → versioon → seaded“. Paneel näitas, et väärtus on salvestatud. Õhtul näitas kontroll CageFS-i puuri seest, et veebilehtede jaoks oli see endiselt sisse lülitatud.

Põhjus peitub selles, kuidas alt-php CloudLinuxil töötab:

  • ispmanager kirjutas väärtuse faili /opt/alt/phpXX/etc/php.d.all/session.ini, st session-laienduse ini-faili;
  • alt-php-s on session-laiendus sisse ehitatud ja seda ei laadita PHP Selectori kaudu, nii et puuri sees seda ini-faili keegi ei loe;
  • puuri sees loeb PHP põhifaili /opt/alt/phpXX/etc/php.ini ja kasutaja alt_php.ini faili kataloogist /opt/alt/phpXX/link/conf, mis on hoopis teine kataloog kui see, mida näeb root.

Siit peamine õppetund: PHP seadeid tuleb kontrollida kasutaja nimel puuri seest, mitte root’ina:

su -s /bin/bash -c "/opt/alt/php83/usr/bin/php -r 'var_dump(ini_get(\"session.upload_progress.enabled\"));'" KLIENDI_KASUTAJANIMI

Mis toimis:

  1. Lisada direktiiv iga versiooni tegelikult laaditava php.ini faili lõppu (tee näitab php --ini, rida Loaded Configuration File).
  2. Käivitada cagefsctl --rebuild-alt-php-ini, et kasutajate alt_php.ini failid uuesti koostada.
  3. Käivitada cagefsctl --update: puuri sees on süsteemi enda PHP-l CageFS-i skeletis oma koopia failist /etc/php.ini ja ilma skeleti uuendamiseta muudatus veebilehtedeni ei jõua.
  4. Mitte unustada CGI-režiimis veebilehtede isiklikke php.ini faile: ispmanager hoiab neid kataloogis /var/www/KASUTAJA/data/php-bin*/php.ini.
  5. Taaskäivitada httpd ja FPM.

Veel üks detail: samal ööl kell 03:37 koostas miski kõigi alt-php versioonide php.ini failid mallist uuesti. Kui muudatus on ainult failis php.ini, tasub seda kindlustada cron-ülesandega, mis kontrollib ja taastab rea, kuni parandused välja tulevad. Nii me tegimegi ja cron taastas rea tõepoolest mitu korda, näiteks pärast alt-php pakettide uuendamist.

Multipart-blokeering lõhub sisselogimise paneeli

Paar tundi pärast blokeeringut kirjutas klient, et ei saa paneeli sisse logida: sisselogimispäring sai vastuseks 403 ja liides näitas lõputult laadimisindikaatorit. Kliendi IP-aadressi ei olnud keegi blokeerinud.

ispmanageri paneel töötab oma veebiserveril ihttpd pordil 1500 ja otse sellesse porti sisse logides ei ole nginx tõesti asjasse puutuv. Kuid kui moodul nginx_proxy on sisse lülitatud, on paneel kättesaadav ka pordi 443 kaudu aadressil https://HOST/ispmgr ja sisselogimisvorm saadetakse kui multipart/form-data. Meie blokeering vastas 403 juba nginxis ja paneeli kasutajaliides ei oska sellist viga näidata.

Moodul on kaasatud ainult paneeli hosti vhostis (/etc/nginx/modules-includes/nginx_proxy/) ja kõik selle teed lähevad ihttpd-le, mitte PHP-le. Seetõttu on erandit turvaline teha hosti ja tee kombinatsiooni järgi:

set $cve_mp 0;
if ($http_content_type ~* "multipart") { set $cve_mp 1; }
set $cve_hu "$host:$uri";
if ($cve_hu ~* "^panel\.example\.com:/(ispmgr|mancgi|manimg|long|shell|notify)(/|$)") { set $cve_mp 0; }
if ($cve_mp = 1) { return 403; }

Erand ainult tee järgi, ilma hostita, oleks viga: päring aadressile kliendi-leht/ispmgr läheks Apache’ile, WordPress suunaks selle ümber failile index.php ja PHP töötleks multipart-sisu. Paneeli hostil läheb see tee garanteeritult ihttpd-le.

Teede nimekiri tasub võtta moodulist endast (grep location /etc/nginx/modules-includes/nginx_proxy/*.conf), mitte mälu järgi. Ja kohe pärast nginxi uuesti laadimist ei tasu kontrollida: esimesi päringuid võib veel teenindada vana protsess vana seadistusega.

Unustatud Roundcube ja phpMyAdmin

Kui alt-php parandused välja tulid, uuendasime paketid ja eemaldasime blokeeringu. Alles siis meenus meile, et veebipost ja phpMyAdmin töötavad ispmanageris mitte sellel PHP versioonil, mille valisite veebilehtedele. alt-php uuendamine neid ei puuduta.

Mõlemad rakendused on kaasatud vhosts-includes kaudu, st kättesaadavad serveri igal domeenil (/roundcube, /webmail, /phpmyadmin või juhusliku eesliitega tee). Millist PHP-d need kasutavad, näitab nendes failides rida fastcgi_pass:

grep -E 'location|fastcgi_pass' /etc/nginx/vhosts-includes/*roundcube*.conf /etc/nginx/vhosts-includes/*phpmyadmin*.conf

Meie serverites leidus korraga mitu varianti:

Rakendusnginxi kauduApache kaudu
Roundcube /roundcubesüsteemi PHP-FPM (php-fpm.apache.sock) või isp-php74isp-php74 oma sokli kaudu
Roundcube /webmailsüsteemi PHP-FPMmod_php
phpMyAdminsüsteemi PHP-FPMmod_php

Teine veerg on sama oluline kui esimene. Apache saab need päringud kahel juhul:

  • päring IP-aadressile või tundmatu Host päisega läheb nginxi vaikimisi serveri kaudu otse Apache’ile, kus Alias /webmail ja /phpmyadmin kehtivad globaalselt;
  • nginxi seadistuses on /webmail ja /phpmyadmin jaoks read error_page 404 @apache ja error_page 502 = @apache. PATH_INFO-ga aadress, näiteks /webmail/index.php/x, ei vasta mustrile location ~ \.php$, saab vastuseks 404 ja läheb Apache’ile, kus selle käivitab mod_php.

Ühes serveris oli süsteemi PHP versioon 5.4 CentOS 7-st, millele parandusi ei tule kunagi. Kuni viisime need rakendused alt-php peale, jäi multipart-blokeering ainult nende teedele, veebilehed töötasid juba piiranguteta:

if ($uri !~* "^/(roundcube|webmail|phpmyadmin)(/|$)") { set $cve_mp 0; }

Sel ajal ei saanud kliendid veebipostis manuseid lisada ja märkasid seda peaaegu kohe.

Miks CloudLinuxil versiooninumber ei muutu

CloudLinux avaldas parandused kõigile alt-php harudele korraga, 5.4-st kuni 8.5-ni, kuid mitte versioonidena 8.3.36 või 8.4.27, vaid olemasolevate versioonide uute ehitustena: alt-php83-8.3.35-6, alt-php84-8.4.26-5, alt-php85-8.5.11-5 ja nii edasi. Parandus lisatakse paigaldatud versioonile, kasvab ainult paketi väljalaske number.

Seetõttu näitavad kontrollskriptid, mis võrdlevad PHP_VERSION väärtust versiooniga 8.3.36, ka pärast uuendamist „haavatav“. Ja kliendid, kes paneeli vaatavad, näevad samu numbreid ning küsivad, miks midagi pole uuendatud. Usaldusväärseid tunnuseid on kaks.

Paketi muudatuste logi (changelog). Vanemate harude puhul eemaldas CloudLinux lisaks üleslaadimise edenemise käsitleja täielikult:

for p in $(rpm -qa --qf '%{NAME}\n' 'alt-php[0-9]*' | grep -E '^alt-php(5[4-9]|[78][0-9])$' | sort -V); do
  printf '%-10s %-16s ' $p "$(rpm -q --qf '%{VERSION}-%{RELEASE}' $p)"
  rpm -q --changelog $p | head -10 | grep -c 'CVE-2026-10961[89]'
done

Build Date phpinfo() väljundis. Parandatud ehitustel on seal 21. oktoober 2026, eelmistel varasemad kuupäevad. Seda saab klientidele öelda: kontrollida saab igaüks, ilma serveri ligipääsuta.

Süsteemi enda PHP-ga on sama lugu teisest küljest. RHEL 9 ja selle ümberehituste puhul on parandatud 8.0 versioon php-8.0.30-8.el9_8.1 (Red Hati turvateade RHSA-2026:87151), kuid muudatuste logis olid CVE numbrid kleepunud loendi viimase punkti külge, millel on hoopis teine kirjeldus. Ilma turvateateta on seda lihtne pidada tavaliseks uuenduseks.

ispmanageri pakettidel (isp-phpXX) on muudatuste logi üldse mallipõhine, ühe 2019. aasta kirjega. Paranduse tunnused on seal kaudsed: ehituse number (meil 1781 versioonidel 8.2-8.5, 1782 versioonil 7.4, 1786 versioonil 7.1) ja Build Date 22. oktoober.

Uuendamise enda ohukohad

dnf --best --allowerasing ispmanageriga serveris. CloudLinux 9-l väljastas dnf update pika konfliktide nimekirja: repositooriumis on süsteemi PHP 8.3.36 moodulivoost ja dnf peab seda paigaldatud 8.0 parimaks kandidaadiks. Kuid ispmanageri Apache moodul (isp-httpd-mod_php) nõuab rangelt PHP 8.0.x versiooni ning sellest sõltuvad coremanager-pkg-php ja ispmanager-pkg-php, st paneel ise. dnf-i vihje lisada --best --allowerasing on siin ohtlik: see eemaldaks paneeli paketid. Konflikt on kahjutu, lõpus kirjutab dnf Nothing to do. Müra eemaldamiseks kuni 8.0 ehituse ilmumiseni: dnf update --exclude='php,php-*'.

ispmanageril on oma Apache PHP moodul. See laaditakse failist modules/isp-libphp8.so ja kuulub paketile isp-httpd-mod_php, mitte süsteemi paketile php. Nende CVE-de parandused asuvad PHP tuumas, st mooduli enda sees, ja paketi php uuendamine seda ei muuda. Kontroll:

grep -rhE 'LoadModule\s+php' /etc/httpd/conf.modules.d/ /etc/httpd/conf.d/
rpm -qf /etc/httpd/modules/isp-libphp8.so
strings /etc/httpd/modules/isp-libphp8.so | grep -m1 -E '[A-Z][a-z]{2} [ 0-9][0-9] 20[0-9]{2}'

Kui moodul on ehitatud enne 21. oktoobrit, jäävad kõik „Apache mooduli“ režiimis veebilehed haavatavaks. Nende leidmiseks: grep -lE 'SetHandler\s+application/x-httpd-php\s*$' /etc/httpd/conf/vhosts/*/*. Viisime sellised veebilehed alt-php peale ja seal, kus see polnud võimalik (liides, mis vajab ligipääsu väljaspool CageFS-i), piirasime ligipääsu IP-aadressi järgi.

Väljalülitatud PHP on halvem kui sisselülitatud. Osa endistest mod_php veebilehtedest osutus staatiliseks ja lülitasime neil PHP välja. Ühel neist oli kohatäitelehe kõrval 61 vana veebilehe faili ja väljalülitatud PHP korral anti nende lähtekood, sealhulgas andmebaasi parooliga seadistusfail, välja tavalise tekstina. Enne PHP väljalülitamist kontrollige: find JUURKATALOOG -name '*.php*' | wc -l.

SSH-ühenduse katkemine keset tehingut. 160 alt-php paketi uuendamine kestis mitu minutit ja ühendus katkes. Tehing õnneks lõpetati, kuid seda tuleb kontrollida: dnf history list, dnf check (CentOS 7-l: package-cleanup --problems, package-cleanup --dupes). Pikad uuendused tasub käivitada tmux-is.

ispmanageri vaiksed ümberehitused. Parandatud isp-php paketid tulid välja ööpäeva jooksul ükshaaval: kõigepealt 8.2-8.5, siis 7.4, siis 7.1. CentOS 7 jaoks ei olnud 7.4 ehitust selle kirjutamise ajaks ilmunud. Mõistlik on mitu korda päevas kontrollida: dnf check-update 'isp-php*'.

Kuidas viia Roundcube ja phpMyAdmin alt-php peale

Süsteemi PHP 5.4-ga serveris viisime teenindava FPM-basseini apache alt-php56 peale, säilitades sama sokli. Nii ei pea nginxi seadistust muutma ja tagasipööramine tähendab lihtsalt vana teenuse käivitamist. Valik 5.6 kasuks 7.4 asemel oli lihtne: Roundcube 1.4 ja vana phpMyAdmin töötasid versioonil 5.4, 5.6 on sellele kõige lähemal ja CloudLinux avaldas sellele tagasipordi.

Sammud:

  1. Oma ini-kataloog. Väljaspool CageFS-i laadib alt-php root’i all ainult minimaalse laienduste komplekti, ülejäänud asuvad kataloogis php.d.all. Koostasime ühe faili extensions.ini vajalikest moodulitest (session, json, dom, xml*, mbstring, intl, gd, mcrypt, zip, pdo, nd_mysql*). CloudLinuxi ini-failid laadivad ise oma sõltuvused, seega tuleb topelt extension= read eemaldada.
  2. FPM-i seadistus basseiniga apache soklil /var/run/php-fpm.apache.sock, samade user, group ja listen.owner väärtustega ning selgesõnalise session.save_path väärtusega, sest süsteemi php.ini failis polnud see määratud.
  3. Kontroll enne ümberlülitamist: PHP_INI_SCAN_DIR=… php -m ilma vigadeta ja php-fpm -t -y … tulemusega test is successful.
  4. systemd-üksus ridadega Environment=PHP_INI_SCAN_DIR=… ja ExecStart=… php-fpm --nodaemonize -y ….
  5. Ümberlülitamine: nimetada ümber /etc/php-fpm.d/apache.conf, taaskäivitada süsteemi FPM (sellele jääb bassein www) ja käivitada uus. Katkestus mõni sekund.
  6. Ka Apache samale soklile. Failides roundcube-webmail.conf ja phpmyadmin-httpd.conf asendada SetHandler application/x-httpd-php reaga SetHandler "proxy:unix:/var/run/php-fpm.apache.sock|fcgi://localhost/", failis roundcube-isp.conf asendada isp-php74 sokkel. Veenduda, et SetHandler asub ploki <FilesMatch "\.php$"> sees, muidu läheb FPM-i ka staatiline sisu.

Topeltlaienduste eemaldamine sammust 1:

awk '/^[[:space:]]*(zend_)?extension[[:space:]]*=/ { k=$0; gsub(/[[:space:]]/,"",k); if (seen[k]++) next } { print }' $FILES > conf.d/extensions.ini

Kontroll, milline PHP igal teel tegelikult töötab, sealhulgas otsepäring Apache’ile ja PATH_INFO:

echo '<?php echo PHP_VERSION, " ", php_sapi_name();' > /usr/share/roundcubemail/_v.php
curl -s -H 'Host: nonexistent.invalid' http://127.0.0.1:8080/webmail/_v.php
curl -sk https://DOMEEN/webmail/_v.php/x
rm -f /usr/share/roundcubemail/_v.php

Igal pool peab olema 5.6.40 fpm-fcgi. Pärast seda võib nginxi blokeeringu eemaldada.

Lahendusel on kaks miinust: Apache kaudu kaovad mod_php seadistustes olnud open_basedir piirangud ja roundcube-isp.conf on ispmanageri poolt märgitud paketi uuendamisel ülekirjutatavaks. Pärast paneeli uuendusi tasub kontrollida, et soklid pole tagasi muutunud ja et /etc/php-fpm.d/apache.conf pole uuesti tekkinud.

Kontrollnimekiri ispmanageriga serverile

Kui teil on ispmanageriga server ja te pole veel kindel, et PHP2Shell on täielikult suletud, käige läbi see nimekiri:

  • Kõik kasutusel olevad alt-php harud on uuendatud, muudatuste logis on CVE-2026-109618/109619 või Build Date on 21. oktoober 2026 või hilisem.
  • Paketid isp-php* on uuendatud ja teenused php-fpm* taaskäivitatud (pärast üksuse faili muutumist esmalt systemctl daemon-reload).
  • Operatsioonisüsteemi enda PHP on uuendatud või ei teeninda midagi (kontrollige basseine kataloogis /etc/php-fpm.d/ ja seda, kes nende soklitega ühendub).
  • Apache moodul isp-libphp8.so on ehitatud pärast 21. oktoobrit või ükski veebileht ei tööta „Apache mooduli“ režiimis.
  • Roundcube ja phpMyAdmin töötavad parandatud PHP-l nii nginxi kui ka Apache kaudu.
  • PHP seaded on kontrollitud kasutaja nimel CageFS-i seest, mitte root’ina.
  • CageFS-i skelett on uuendatud (cagefsctl --update).
  • Ajutised nginxi blokeeringud on eemaldatud, sealhulgas rida vaikimisi server plokis failis nginx.conf.
  • Väljalülitatud PHP-ga veebilehtedel pole .php faile.
  • Klientidele on selgitatud, miks versiooninumber paneelis ei muutunud.

Selle öö peamine järeldus on lihtne: juhtpaneeliga majutusserveris ei tähenda „PHP uuendamine“ ühte paketti, vaid viit-kuut erinevat PHP ehitust kolmelt tarnijalt (algversioon CloudLinuxi kaudu, ispmanager, distributsioon), ja osa neist ei teeninda veebilehti, vaid paneeli enda rakendusi. Tasub ette teada, milline PHP millise tee taga asub, enne kui seda öösel vaja läheb.

Allikad