วัน: 22 ตุลาคม 2026

กลไกที่ผู้โจมตีใช้ trigger Out-of-memory บน PHP-FPM เชิงลึกกลไกที่ผู้โจมตีใช้ trigger Out-of-memory บน PHP-FPM เชิงลึก

ในการทำ Exploit ช่องโหว่ CVE-2026-109618 เป้าหมายของผู้โจมตีไม่ใช่แค่การยิง DoS ให้ระบบล่ม แต่คือการบังคับให้เกิดภาวะ Out-Of-Memory (OOM) ในเสี้ยววินาทีและตำแหน่งที่เฉพาะเจาะจง (Surgically-timed Allocation) เพื่อขัดจังหวะการทำงานของ Zend Engine ขณะที่โครงสร้างข้อมูลภายใน (Internal Data Structure) ยังสร้างไม่เสร็จสมบูรณ์


กลไกการจัดการ Memory และ Exception ของ Zend Engine

ทำความเข้าใจ 2 กลไกสำคัญของ PHP Core ก่อนพิจารณาวิธีโจมตี

  1. Zend Memory Manager (ZMM)
    PHP จัดการหน่วยความจำผ่าน Heap ของตัวเอง (emalloc(), efree()) โดยจะคอยนับยอดรวมเพื่อเทียบกับค่า memory_limit ใน php.ini
  2. Bailout ผ่าน setjmp() / longjmp()
    เมื่อ emalloc() ขอ RAM เกินโควตา ตัว Engine จะเรียกฟังก์ชัน zend_error_noreturn(E_ERROR, "Allowed memory size...") ซึ่งภายในจะยิงคำสั่ง longjmp(EG(bailout), 1) ทันที
    • ผลคือ Execution Stack จะถูกตัดทิ้ง (Unwind) ย้อนกลับไปยังจุด setjmp() นอกสุดของ Request Loop ทันที โดยไม่มีการรันโค้ดส่วนที่เหลือของฟังก์ชันที่กำลังทำงานอยู่

ขั้นตอนการ Trigger OOM เชิงลึก (The Exploitation Mechanics)

กลไกที่ผู้โจมตีใช้บีบให้เกิด OOM ประกอบด้วย 3 ขั้นตอนหลัก

ขั้นที่ 1: Memory Grooming (Heap Shaping)

ผู้โจมตีต้องดันการใช้งานหน่วยความจำของ PHP Worker ตัวนั้นให้เฉียดขีดจำกัด memory_limit (เช่น ตั้งไว้ 128MB ต้องดันให้แตะ ~127.8MB) เพื่อให้คำสั่งจอง RAM ถัดไปชนเพดานทันที

  • เทคนิคที่ใช้
    • ส่ง Multipart Form-Data ที่มีขนาดใหญ่พอดี
    • ส่ง Input ที่บีบให้ PHP สร้างอาเรย์ขนาดใหญ่ เช่น Nested JSON, XML Entity Expansion, หรือ Query String ซ้ำซ้อนหลายพันตัวแปร (foo[]=1&foo[]=2&...)

ขั้นที่ 2: The Critical Allocation Window (จุดเปราะบาง)

ในระดับซอร์สโค้ดภาษา C การสร้าง Object หรือ Data Structure ที่ซับซ้อนมักต้องใช้การ Allocation หลายจังหวะต่อเนื่องกัน (Sequential Allocations)

C

/* ตัวอย่างโค้ดโครงสร้างที่มีช่องโหว่ */
struct zend_my_context *ctx = emalloc(sizeof(struct zend_my_context));
// [จังหวะที่ 1] ctx ถูกสร้าง แต่สมาชิกข้างใน (ctx->destructor_fn, ctx->data_ptr) ยังเป็น Uninitialized / ค่าขยะใน RAM

ctx->data_ptr = emalloc(user_controlled_size); 
// [จังหวะที่ 2: The Trap!] ถ้า OOM เกิดขึ้นที่คำสั่งนี้!
// longjmp() จะกระโดดหนีทันที บรรทัดข้างล่างจะไม่ถูกรัน:

ctx->destructor_fn = my_cleanup_handler; // <--- โค้ดนี้ไม่ถูกรัน!
register_shutdown_context(ctx);

ขั้นที่ 3: บังคับให้ OOM ระเบิดตรงตำแหน่งจองลูก

เมื่อผู้โจมตีจัด Heap ให้เหลือช่องว่างขนาดแน่นอน (เช่น เหลือ 4KB ก่อนชน Limit) ผู้โจมตีจะยิง Request ที่บังคับให้

  1. การจองตัวแม่ (ctx) ขนาดเล็ก (เช่น 64 bytes) ผ่านฉลุย
  2. การจองตัวลูก (ctx->data_ptr) ขอขนาด 8KB ซึ่งเกิน Limit ทันที
  3. emalloc() สั่ง longjmp() ตัดการทำงานย้อนกลับไปที่ Shutdown Handler ทันที ทิ้งให้ ctx มีสมาชิกข้างในเป็น Garbage Data จากหน่วยความจำเดิม

จาก OOM สู่ Arbitrary Code Execution (RCE)

เมื่อ longjmp() กระโดดกลับมาที่ Loop ทำความสะอาด Request (เช่น php_request_shutdown())

  1. Uninitialized Pointer Traversal
    รูทีน Cleanup จะวนลูปตรวจหา Object ที่ค้างอยู่ใน Request เพื่อสั่ง Free หน่วยความจำ
  2. Corrupted Branching
    เมื่อถึงคิวของ ctx ฟังก์ชัน Cleanup จะพยายามเรียก Destructor Function Pointer หรืออ่าน Pointer ตำแหน่งถัดไป:Cif (ctx->destructor_fn) { ctx->destructor_fn(ctx->data_ptr); // กระโดดไปรัน Address ตามฟังก์ชันพอยน์เตอร์ }
  3. Control Flow Hijacking
    เนื่องจาก ctx->destructor_fn ไม่เคยถูกกำหนดค่าให้ถูกต้อง มันจึงมีค่าเป็นข้อมูลขยะที่ค้างอยู่ใน Heap
  4. ผู้โจมตีที่ทำ Heap Spraying สำเร็จตั้งแต่ ขั้นที่ 1 จะสามารถกำหนดให้ค่าขยะใน RAM ตรงตำแหน่งนั้นตรงกับคำสั่งใน Memory (เช่น ชี้ไปที่ฟังก์ชัน system() หรือ ROP Gadget) ทำให้ CPU หักเหทิศทางไปรันคำสั่งอันตรายของผู้โจมตีทันที

สรุปจุดตายของสถาปัตยกรรม

ช่องโหว่นี้ไม่ได้เกิดจาก Memory Leak ทั่วไป แต่เกิดจาก Structural Asymmetry

  • โค้ดส่วนหนึ่งเชื่อว่าถ้าเกิด OOM ตัว Engine จะพาลำดับงานออกไปคลีนอย่างปลอดภัย
  • แต่ระบบ Cleanup กลับเชื่อว่าทุก Pointer ที่ถูกสร้างขึ้นมา จะต้องได้รับการ Initialize โครงสร้างครบถ้วนแล้วเสมอ

เมื่อการสั่ง longjmp() เกิดขึ้นคั่นกลางระหว่างความเชื่อทั้งสองฝั่ง จึงเกิดสถานะ Use of Uninitialized Pointer / Type Confusion ที่นำไปสู่ RCE ในที่สุด