Vineri, 23 octombrie, ora 9:20 dimineața. Cu o zi înainte terminasem de actualizat PHP-ul pe toate serverele, după php2shell. Cafeaua abia începuse să-și facă efectul când am deschis un nou anunț de securitate. De data asta nu era despre PHP, ci despre Node.js.
Pe 21 octombrie 2026, în aceeași zi cu php2shell, proiectul Node.js a publicat actualizări care repară zece vulnerabilități. Cea mai gravă se află într-o piesă la care aproape nimeni nu se gândește: decodorul Brotli, componenta care despachetează datele comprimate. Echipa Node.js spune că poate permite execuție de cod de la distanță, adică preluarea controlului asupra aplicației.
Articolul ăsta e povestea acelei dimineți: cum am aflat ce aplicații Node.js rulează pe servere, cum am citit codul unei aplicații ca să vedem dacă e expusă, ce am blocat până la instalarea pachetelor reparate și ce ne-au spus logurile despre ce se întâmplase deja. Detaliile despre client sunt anonimizate. Lecțiile, nu.
Ce s-a întâmplat pe 21 octombrie, pe scurt
Node.js a lansat versiunile v22.23.4, v24.22.1 și v26.12.1. Ele repară zece vulnerabilități (CVE): patru notate ca grave (High), trei medii și trei minore. La momentul lansării nu fuseseră publicate scoruri CVSS.
Cele grave, pe înțelesul tuturor:
- CVE-2026-111497, decodorul Brotli. Când Node.js despachetează date comprimate cu Brotli, poate scrie în afara zonei de memorie pe care o are la dispoziție. Cu date construite special, un atacator poate folosi asta ca să ruleze cod.
- CVE-2026-105482, numele DNS. Un nume de domeniu care conține un caracter invizibil (caracterul NUL) poate fi tăiat în timpul rezolvării, așa că aplicația ajunge să se conecteze la alt server decât credea.
- CVE-2026-105483 și CVE-2026-106069, verificarea certificatelor TLS. În anumite cazuri, Node.js poate accepta un certificat pentru o adresă care nu-i aparține. Practic, verificarea „vorbesc chiar cu serverul corect?” poate fi păcălită.
Lista completă, cu toate cele zece vulnerabilități și liniile afectate, e în anunțul CloudLinux despre actualizările Node.js din octombrie 2026.
Un detaliu important din anunț: pentru vulnerabilitatea Brotli nu există nicio soluție temporară oficială. Singura reparație e pachetul actualizat. Iar actualizarea pachetului Brotli al sistemului de operare nu ajută, pentru că Node.js vine cu propria lui copie de Brotli, compilată înăuntru.
De ce te privește, chiar dacă ai „doar” un site WordPress
WordPress rulează pe PHP, nu pe Node.js. Așa că, dacă ai un site WordPress clasic, vulnerabilitatea asta nu-ți atinge direct site-ul. Doar că tot mai multe afaceri au, pe lângă site, și altceva:
- un magazin online sau o aplicație făcută la comandă, în Next.js, React sau Express;
- un site „headless”, unde WordPress ține conținutul, dar paginile le afișează o aplicație Node.js;
- un chatbot, un configurator de produse, un panou pentru clienți sau un mic API care leagă site-ul de alte servicii.
Toate acestea rulează, de obicei, pe Node.js. Am scris deja despre mitul site-urilor „imune la hackeri” pentru că nu sunt făcute în WordPress. Episodul ăsta e un exemplu perfect: tehnologia e alta, problemele sunt aceleași, iar actualizările sunt la fel de obligatorii.
Pe găzduirea partajată mai există o particularitate. Versiunea de PHP o alege, de regulă, firma de găzduire. Versiunea de Node.js, în schimb, o alege fiecare client pentru aplicația lui. Pe același server pot rula, în paralel, aplicații pe Node 16, 18, 20, 22 sau 24. Fiecare are nevoie de reparația ei, iar cele vechi, ieșite din suport, sunt de obicei cele uitate cel mai ușor.
Dimineața de 23 octombrie: cum am lucrat
Serverele noastre rulează cPanel pe CloudLinux, cu LiteSpeed ca server web. Pe astfel de servere, clienții își pornesc aplicațiile Node.js din panou, prin instrumentul „Setup Node.js App”. Fiecare versiune de Node vine ca pachet separat, iar fiecare aplicație alege una dintre ele.
Pasul 1: inventarul, înainte de orice
Prima întrebare nu a fost „ce versiune de Node avem?”, ci „cine folosește Node.js pe acest server?”. Pe un server cu zeci de conturi, răspunsul poate fi „nimeni”, „un singur client” sau „jumătate din ei”. Iar strategia depinde complet de răspuns.
Pe serverul la care ne uitam, răspunsul a fost: un singur cont, cu o singură aplicație. Un magazin online construit la comandă, pentru un client din Germania, care rula pe Node 24, una dintre liniile afectate.
Pe același server erau instalate șase versiuni de Node.js, de la 16 la 24. Cinci dintre ele nu erau folosite de nicio aplicație. Nu reprezentau un risc imediat, pentru că un pachet instalat, dar nefolosit, nu primește trafic. Pot însă deveni un risc oricând, din momentul în care un client își pornește o aplicație nouă pe o versiune veche.
Pasul 2: „nu rulează nimic” s-a dovedit fals
Prima verificare a proceselor a spus că pe server nu rulează nicio aplicație Node.js. Ar fi fost o veste foarte bună. Dacă nu rulează nimic, nu e nimic de atacat.
Doar că verificarea căuta procesele după numele lor obișnuit, iar pe serverele cu LiteSpeed aplicațiile Node.js pornesc cu un alt nume, lsnode. Abia a doua verificare, făcută după utilizator, nu după nume, a arătat realitatea: aplicația rula de trei zile, în două procese.
Lecția e aceeași pe care am învățat-o și cu php2shell, doar că din alt unghi: un răspuns gol nu înseamnă „totul e în regulă”. Uneori înseamnă doar că ai căutat în locul greșit.
Pasul 3: am citit codul aplicației
Vulnerabilitatea Brotli devine periculoasă doar dacă aplicația despachetează date comprimate primite din surse pe care nu le controlează. Așa că întrebarea corectă era: de unde primește aplicația asta date comprimate?
Există două căi:
- Datele care intră. Cineva trimite aplicației un formular sau o cerere cu conținutul comprimat. Browserele normale nu fac asta niciodată, dar un atacator poate.
- Datele pe care aplicația le cere de la alte servere. Când aplicația descarcă ceva de pe internet, funcția standard din Node.js acceptă și despachetează automat răspunsuri comprimate cu Brotli.
Am căutat în codul aplicației toate locurile în care descarcă ceva de pe alte servere. Au ieșit câteva zeci de rezultate, iar unul arăta, la prima vedere, foarte rău: o funcție care descarca o imagine de la o adresă construită în cod. Dacă adresa ar fi venit de la vizitator, oricine ar fi putut trimite aplicația să descarce date de pe un server controlat de atacator, exact scenariul în care vulnerabilitatea Brotli devine o ușă deschisă.
Am citit codul din jurul acelei linii înainte să trag concluzii. Adresa era construită întotdeauna către același serviciu extern, un generator de imagini cu inteligență artificială, pe HTTPS. Vizitatorul putea controla doar textul descrierii imaginii, nu și serverul. Deci nu era o ușă deschisă, ci o dependență de un serviciu extern de încredere. Restul rezultatelor erau cod din librării folosite de aplicație, care nu rulau efectiv.
Recunosc că prima mea reacție la acel rezultat a fost una de alarmă. Lecția: o căutare automată în cod îți spune unde să te uiți, nu și ce ai găsit. Diferența dintre „aplicația e vulnerabilă” și „aplicația folosește un serviciu extern” se vede doar dacă citești codul.
Pasul 4: am blocat calea principală de atac
Pe serverul respectiv, pachetele Node.js reparate nu erau încă disponibile în dimineața aceea. Așa că, până la actualizare, aveam nevoie de o măsură temporară care să închidă calea principală: cererile trimise de oricine de pe internet cu conținut comprimat Brotli.
Soluția a fost o regulă de cinci rânduri, pusă la începutul fișierului de configurare al site-ului: orice cerere care anunță că trimite conținut comprimat cu Brotli primește un refuz („403 Forbidden”) și nu mai ajunge deloc la aplicație.
Întrebarea firească a fost: stricăm ceva? Răspunsul e nu, iar motivul merită explicat, pentru că aici apare cea mai frecventă confuzie.
Brotli e folosit pe internet în fiecare zi, în miliarde de pagini. Dar aproape întotdeauna într-o singură direcție: serverul comprimă pagina, iar browserul o despachetează. Browserul anunță doar că știe să primească Brotli. Nu trimite niciodată el însuși conținut comprimat cu Brotli. Așa că regula noastră nu afectează niciun vizitator real, iar compresia paginilor către browsere funcționează exact ca înainte.
După aplicarea regulii am testat imediat ambele cazuri: o vizită normală (răspuns 200, site-ul merge) și o cerere cu conținut Brotli (răspuns 403, blocată). Cât despre ideea de a dezactiva compresia Brotli a paginilor, la care se gândesc mulți: nu ajută deloc. Acea compresie o face serverul web sau CDN-ul, nu Node.js, iar decodorul vulnerabil nu are niciun rol acolo.
Pasul 5: aceeași regulă și în Cloudflare, dar nu doar acolo
Site-ul era servit prin Cloudflare, așa că aceeași regulă se poate pune și acolo, ca regulă de firewall. E o barieră în plus, utilă, dar nu suficientă singură.
Cloudflare protejează doar traficul care trece prin el. Cine află adresa IP reală a serverului poate trimite cereri direct, ocolind complet Cloudflare. De aceea regula trebuie să existe și pe server, nu doar la marginea rețelei. Am verificat și asta: o cerere trimisă direct pe IP-ul serverului, fără Cloudflare, a fost refuzată la fel.
Am explicat mai pe larg ce face și ce nu face un firewall aplicativ (WAF). Principiul e același: un filtru pus în fața serverului e bun doar dacă nu poate fi ocolit.
Ce ne-au spus logurile
În aceeași dimineață a sosit și un email automat de la Cloudflare. Ne anunța că pe 21 octombrie, chiar în ziua lansării vulnerabilităților, site-ul clientului avusese o creștere bruscă a traficului și a erorilor de server: 28% dintre cereri se terminaseră cu erori, cu peste 226% mai multe decât de obicei.
Un astfel de email te pune pe gânduri, mai ales într-o zi în care tocmai ai aflat de o vulnerabilitate gravă. Așa că am deschis jurnalele serverului pentru 21 octombrie și am reconstituit ziua, oră cu oră. Povestea reală a fost alta decât sugera emailul.
La miezul nopții: un robot care căuta parole
Între miezul nopții și ora 1, o singură adresă IP a trimis aproximativ 3.300 de cereri. Căuta fișiere cu nume ca .env, .env.bak, .env.old sau .env.backup, adică fișierele în care aplicațiile își țin, de obicei, parolele bazei de date, cheile de API și datele de acces la email. Căuta și pagini de administrare: /admin, /panel, /portal.
Aproape toate cererile au primit refuz (403), pentru că protecția serverului l-a recunoscut și l-a oprit. Fișierul .env nici nu exista în aplicație. Iar fișierul de configurare al site-ului, cel cu setările sensibile, nu se poate descărca din exterior, nici prin Cloudflare, nici direct pe IP. Am verificat ambele căi.
Roboții ăștia nu țintesc pe cineva anume. Scanează automat tot internetul, adresă după adresă, și încearcă aceleași câteva sute de nume de fișiere peste tot. E motivul pentru care vârful de trafic din emailul Cloudflare arăta dramatic, deși, de fapt, nu se întâmplase nimic.
Seara: erorile adevărate
Erorile reale au apărut seara, între orele 18 și 20. Le-au primit toți vizitatorii din acel interval, iar unul dintre procesele aplicației pornise exact atunci. Aplicația fusese repornită sau căzuse și se repornise singură. Cât timp pornea, serverul răspundea cu „503 Service Unavailable”.
În aceeași fereastră, un alt robot a încercat să citească fișiere de sistem prin trucuri cunoscute, de tipul ../../../../proc/self/environ, cu care atacatorii încearcă să fure variabilele de mediu ale unei aplicații. A nimerit fereastra în care aplicația era oprită, așa că a primit și el 503.
Cine a intrat în panoul de administrare
Întrebarea care contează cel mai mult după orice scanare e simplă: a reușit cineva să intre? Am extras din jurnale toate accesările reușite ale zonei de administrare a aplicației. Toate veneau de la doar două adrese IP, ambele din Germania, folosite exact în intervalul în care cineva lucra activ la catalogul de produse. Am verificat cu clientul: erau oamenii lui.
Concluzia zilei de 21 octombrie: o scanare automată blocată, o repornire a aplicației și niciun semn de compromitere. Emailul de la Cloudflare era un indiciu bun, dar nu un diagnostic. Diagnosticul l-au dat jurnalele.
Ce n-a scris nimeni în anunțuri
Anunțurile oficiale spun „actualizați și reporniți aplicațiile”. Au dreptate. Iată ce am învățat în plus în dimineața aceea.
1. La versiunile vechi de Node, numărul versiunii nu se schimbă
Pentru liniile Node.js ieșite din suport, cum sunt 16, 18 sau 20, pachetele reparate de furnizori păstrează exact același număr de versiune. Un Node 20.20.2 poate fi vulnerabil sau reparat, în funcție de numărul intern al pachetului, nu de ce afișează node --version. E aceeași capcană pe care am întâlnit-o la PHP: verificarea trebuie făcută pe pachet, nu pe numărul afișat.
2. Actualizarea pachetului nu ajunge până nu repornești aplicația
O aplicație Node.js pornită acum trei zile rulează în memorie codul vechi, chiar dacă pe disc pachetul a fost actualizat între timp. Fără repornire, actualizarea nu protejează nimic. Pe găzduirea partajată, asta înseamnă repornirea fiecărei aplicații, a fiecărui client, după fiecare actualizare de Node.js.
3. Cea mai mare problemă nu era vulnerabilitatea
Cât timp ne uitam în configurația aplicației, am observat că parola zonei de administrare era foarte slabă, ușor de ghicit. Iar pagina de login era publică, accesată deja de roboți pe 21 octombrie.
O vulnerabilitate ca cea din Brotli are nevoie de un exploit sofisticat. O parolă slabă pe o pagină publică are nevoie doar de răbdare. Am recomandat clientului să schimbe imediat parola de administrare cu una puternică și, ca măsură de prevedere, și parola contului de email folosit de aplicație.
Ca și la php2shell, o urgență a scos la suprafață o problemă veche, care nu-și găsea niciodată rândul într-o zi obișnuită.
4. Izolarea conturilor contează mai mult decât pare
Pe serverele noastre, fiecare cont rulează într-un mediu izolat (pe CloudLinux se numește CageFS). Dacă o aplicație e compromisă, atacatorul rămâne închis în contul acelei aplicații și nu vede nici restul conturilor, nici sistemul. Am verificat că izolarea era activă pe contul cu aplicația Node.js înainte de orice altceva.
Izolarea nu oprește un atac. Limitează însă cât de mult poate strica. Diferența dintre „un site compromis” și „un server compromis, cu toate site-urile de pe el” ține adesea de o singură setare.
5. Riscul e al clientului, dar serverul e al tău
Când aplicația e a clientului, e tentant să spui „e riscul lui”. Pentru deciziile despre aplicație, chiar așa e. Doar că o aplicație compromisă rulează cod pe serverul tău: poate trimite spam de pe adresa ta IP, poate fi folosită pentru alte atacuri și poate încerca să iasă din contul ei. De aceea am aplicat măsura temporară pe server și am anunțat clientul în scris, cu ce am găsit și ce are de făcut.
Ce înseamnă asta pentru tine
Dacă ai doar un site WordPress
Site-ul tău nu e afectat direct. Rămâne valabil tot ce am scris despre php2shell, care te privește direct, pentru că WordPress rulează pe PHP.
Dacă ai o aplicație sau un site pe Node.js
Ai nevoie de trei lucruri: pachetul Node.js reparat pentru versiunea pe care rulează aplicația, repornirea aplicației după actualizare și o verificare că aplicația funcționează după repornire. Până la actualizare, blocarea cererilor cu conținut Brotli e o măsură temporară sigură și simplă.
Dacă aplicația ta rulează pe o versiune veche de Node.js
Proiectul Node.js nu mai repară oficial versiunile ieșite din suport. Unii furnizori, cum e CloudLinux, au adus totuși reparația și pentru ele. E un gest bun, dar nu un plan. Dacă aplicația ta rulează pe Node 16, 18 sau 20, episodul ăsta e un moment bun să planifici trecerea pe o versiune suportată.
Dacă nu știi dacă ai Node.js
Întreabă-l pe cel care ți-a făcut site-ul: „Folosim Node.js undeva, pe site sau pe server?”. Dacă site-ul a fost făcut „modern”, cu React, Next.js sau un panou de administrare scris la comandă, răspunsul e probabil da.
3 întrebări pentru firma de găzduire sau pentru omul tău de IT
1. „Aplicațiile noastre Node.js rulează pe versiuni care au primit reparația din 21 octombrie?” Pentru versiunile actuale, numerele de versiune reparate sunt 22.23.4, 24.22.1 și 26.12.1 sau mai noi. Pentru cele vechi, verificarea se face pe pachet.
2. „Ați repornit aplicațiile după actualizare?” Fără repornire, aplicația rulează în continuare codul vulnerabil.
3. „Aplicația noastră primește sau descarcă date comprimate din surse externe?” Răspunsul arată cât de expusă e aplicația până la actualizare și dacă e nevoie de o măsură temporară.
Pentru colegii administratori de servere
Dacă administrezi servere cPanel cu CloudLinux și Node.js Selector, iată pe scurt ce ne-a ajutat.
Inventarul: cine folosește Node.js și ce rulează acum. Pe LiteSpeed, procesele apar ca lsnode, nu ca node:
ls -d /home/*/nodevenv 2>/dev/null
grep -l PassengerNodejs /home/*/public_html/.htaccess 2>/dev/null
ps -eo user,pid,etime,cmd | grep -E '[l]snode|[n]odejs/bin/node'
Verificarea pachetelor după release, nu după versiune. Pragurile minime publicate de CloudLinux: alt-nodejs12 → 35, 14 → 37, 16 → 33, 18 → 26, 20 → 15, 22 → 2, 24 → 2, 26 → 1.
declare -A MIN=([12]=35 [14]=37 [16]=33 [18]=26 [20]=15 [22]=2 [24]=2 [26]=1)
rpm -qa --qf '%{NAME} %{VERSION} %{RELEASE}\n' 'alt-nodejs*-nodejs' | while read n v r; do
l=$(echo "$n" | sed -E 's/alt-nodejs([0-9]+)-nodejs/\1/'); rel=${r%%.*}; min=${MIN[$l]}
[ -z "$min" ] && echo "? $n $v-$r" && continue
[ "$rel" -ge "$min" ] && echo "OK $n $v-$r" || echo "!! $n $v-$r (minim $min)"
done
Repornirea aplicațiilor după actualizare, din Setup Node.js App sau din linia de comandă:
cloudlinux-selector restart --json --interpreter nodejs --user <user> --app-root <app_root>
Măsura temporară pe server, la începutul fișierului .htaccess al contului, înaintea blocurilor generate de CloudLinux:
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{HTTP:Content-Encoding} br [NC]
RewriteRule ^ - [F]
</IfModule>
Și testul, care trebuie să dea 200 pentru o cerere normală și 403 pentru cea cu Brotli:
curl -s -o /dev/null -w 'normal: %{http_code}\n' https://domeniu.ro/
curl -s -o /dev/null -w 'br: %{http_code}\n' -X POST -H 'Content-Encoding: br' --data x https://domeniu.ro/
Aceeași regulă în Cloudflare (Security → WAF → Custom rules, acțiunea Block), ca barieră în plus, nu ca înlocuitor:
any(http.request.headers["content-encoding"][*] contains "br")
Pentru dezvoltatori, aceeași protecție se poate pune direct în cod, la o aplicație Express: parserele de conținut refuză orice cerere comprimată dacă primesc opțiunea inflate: false.
app.use(express.json({ inflate: false }));
app.use(express.urlencoded({ extended: true, inflate: false }));
Și încă un lucru: verifică izolarea contului (cagefsctl --user-status <user>) înainte de orice altceva. Dacă o aplicație e deja compromisă, izolarea e cea care decide cât de rău poate fi.
Întrebări frecvente
Ce e, de fapt, Brotli?
Un format de compresie creat de Google, folosit pe tot internetul ca paginile să se încarce mai repede. Serverul comprimă, browserul despachetează. Formatul în sine e sigur. Problema era o greșeală în codul care îl despachetează, în interiorul Node.js.
Dacă dezactivez compresia Brotli pe site, sunt protejat?
Nu. Compresia paginilor o face serverul web sau CDN-ul, nu aplicația Node.js. Ce protejează temporar e blocarea cererilor care trimit conținut comprimat către aplicație.
Cloudflare mă protejează?
Parțial. O regulă în Cloudflare oprește atacurile care trec prin Cloudflare, dar nu și pe cele trimise direct pe adresa IP a serverului. Măsura trebuie pusă și pe server.
Ce înseamnă „execuție de cod de la distanță”?
Că atacatorul poate rula ce vrea în aplicație, de la distanță, fără cont și fără parolă: poate citi datele, poate modifica site-ul, poate trimite spam. Pe un server bine configurat, rămâne totuși închis în contul aplicației atacate.
De unde știu dacă am fost deja atacat?
Jurnalele serverului îți spun cine a accesat ce, când și cu ce rezultat. Semnele unei aplicații compromise sunt aceleași ca la orice site: fișiere noi și necunoscute, utilizatori pe care nu i-ai creat, redirecționări ciudate. Am adunat cele 7 semne că un site a fost spart. Iar dacă ai un backup funcțional, păstrat în afara serverului, ai și un drum sigur înapoi.
Am primit un email de la Cloudflare despre erori pe site. Am fost atacat?
Nu neapărat. La noi, emailul a fost declanșat de o scanare automată blocată și de o repornire a aplicației. E un semnal să te uiți în jurnale, nu o concluzie.
Ce facem noi pentru clienții WebFixer în astfel de situații
În două zile la rând au apărut două vulnerabilități grave, în două tehnologii diferite. Pentru clienții noștri, procedura a fost aceeași: aflăm în aceeași zi ce aplicații sunt afectate, citim codul și jurnalele ca să vedem cât de expuse sunt, aplicăm măsuri temporare acolo unde reparația nu a sosit încă și anunțăm clientul în scris, cu ce am găsit și ce are de făcut. Când sosesc pachetele reparate, actualizăm și repornim aplicațiile.
Nu e nimic spectaculos aici. Urmărim direct anunțurile de securitate de la Node.js, PHP, WordPress, cPanel și CloudLinux, avem acces administrativ la servere și o procedură pentru exact astfel de dimineți. Iar după fiecare episod, procedura devine puțin mai bună. Inventarul aplicațiilor Node.js, de exemplu, face acum parte din verificările noastre obișnuite.
Dacă nu știi pe ce rulează site-ul sau aplicația ta, dacă ai primit un email de la Cloudflare pe care nu-l înțelegi sau dacă vrei pur și simplu ca problemele astea să nu mai fie ale tale, începe cu un audit gratuit. Îți spunem ce putem verifica din exterior și ce ar trebui făcut. Iar dacă vrei ca data viitoare altcineva să citească logurile în locul tău, pachetele WebFixer pornesc de la 49 € pe lună.
Descoperă mai multe la WebFixer
Abonează-te ca să primești ultimele articole prin email.