● 2026-10-21
PHP2Shell: เมื่อโค้ดยังไม่ทันรัน แต่โดนยึดเครื่องไปแล้ว (ฝันร้ายระดับ Core ของชาว PHP)
#php #php2shell #security #devops #rce
1. “Security Review ผ่านฉลุย… แต่ตัว Runtime บอก ‘ขอยืมเครื่องแป๊บ’”
ในฐานะ Software Engineer เราถูกพร่ำสอนให้ยึดมั่นในคัมภีร์ความปลอดภัย:
- ทำ Parameterized Query ทุกจุด ลาก่อน SQL Injection
- ใส่ Sanitize / Escape ทุก Output ชาตินี้ไม่มีทางเจอ XSS
- แปะ Middleware ตรวจสอบ Token / JWT แน่นหนาจนมดสักตัวก็ห้ามผ่าน
เราเชื่อมั่นเสมอว่า flow การทำงานของจักรวาลคือ:
flowchart LR
A[HTTP Request] --> B[Reverse Proxy]
B --> C[Auth Middleware]
C --> D[Controller]
แต่ในความเป็นจริงของโลก PHP มันมีด่านลับที่เกิดขึ้นก่อนหน้านั้นเสมอ:
flowchart TD
CLIENT["Untrusted HTTP Client (แฮกเกอร์)"] --> NGINX["Web Server / Nginx"]
NGINX --> SAPI["PHP SAPI Layer (PHP-FPM / mod_php)"]
SAPI --> PARSER["Native Multipart Parser & Session Logic (ภาษา C)"]
PARSER -->|"💥 PHP2Shell บึ้มตรงนี้! (RCE สำเร็จ)"| SHELL["ยึด Server / Reverse Shell"]
PARSER -.->|"ยังเดินทางมาไม่ถึงด้วยซ้ำ"| BOOTSTRAP["PHP Engine Bootstrap (index.php)"]
BOOTSTRAP -.-> AUTH["Middleware: auth()->check()"]
AUTH -.-> APP["Clean Architecture / Controllers"]
style PARSER fill:#ff4d4f,stroke:#333,stroke-width:2px,color:#fff
style SHELL fill:#d9363e,stroke:#333,stroke-width:2px,color:#fff
style AUTH fill:#52c41a,stroke:#333,stroke-width:2px,color:#fff
คุณยืนเฝ้าประตูห้องรับแขกด้วยชุดเกราะเหล็กกล้า ตรวจบัตรประชาชนทุกคนอย่างเข้มงวด… แต่แฮกเกอร์ระเบิดกำแพงห้องครัวทะลุเข้ามาเปิดตู้เซฟไปเรียบร้อยแล้วตั้งแต่ก่อนจะเดินเข้าประตูบ้าน!
เมื่อ HTTP Request วิ่งเข้ามาถึง PHP ตัวระบบจะต้องทำหน้าที่ Parse ข้อมูลดิบเพื่อเตรียมตัวแปร Superglobals ($_POST, $_FILES, $_SERVER) ช่องโหว่นี้เกิดขึ้นที่เลเยอร์ C Runtime ดังนั้นต่อให้คุณมี auth()->check() หรือเขียนครอบ try-catch คลุมจักรวาลไว้ โค้ดพวกนั้นก็ไม่เคยถูกเรียกเลยแม้แต่ไบต์เดียว
2. เกิดอะไรขึ้นข้างใน: เมื่อ Out of Memory กลายเป็นประตูสู่ RCE
ถ้าอธิบายแบบไม่ต้องกางโค้ด C 10,000 บรรทัดมานั่งปวดหัว ปัญหาหลักมาจาก 2 ช่องโหว่ที่จับมือกันแกล้ง Developer:
1) CVE-2026-109618: แรมเต็มปุ๊บ สติหลุดปั๊บ (OOM Memory Corruption)
เมื่อมีคนส่ง multipart/form-data ที่ออกแบบโครงสร้างมาแปลกประหลาดจนทำให้หน่วยความจำชนเพดาน memory_limit:
- แทนที่ PHP จะคืนหน่วยความจำอย่างสุภาพ มันดันสะดุดล้ม (
zend_bailout) แล้วกระโดดข้ามขั้นตอน Cleanup ขยะทิ้งไปดื้อ ๆ - ตัวชี้ในหน่วยความจำกลายเป็น Dangling Pointer (Use-After-Free) ค้างเติ่งอยู่ในระบบ
- ซ้ำร้ายกว่านั้น ระบบติดตามไฟล์อัปโหลด (
session.upload_progress) ดันนำเศษหน่วยความจำของ Worker ตัวเก่าส่งกลับไปใน CookiePHPSESSIDประหนึ่งเป็น Heartbleed เวอร์ชัน PHP!
2) CVE-2026-109619: ซากขยะค้างคืนใน OPcache (Stale Global State)
- บริการอย่าง PHP-FPM หรือ FrankenPHP ทำงานเร็วเพราะใช้ Worker ตัวเดิมซ้ำ ๆ รับคำขอของคนต่อไป
- ทว่าความผิดพลาดจาก Fatal Error ในรอบแรกทิ้ง “สถานะผี” ค้างไว้ในโครงสร้างส่วนกลางของ OPcache
- พอ Request ใหม่ถูกส่งเข้ามา PHP ดันสับสนชนิดข้อมูล (Type Confusion) เปิดโอกาสให้แฮกเกอร์ควบคุม Instruction Pointer ชี้เป้าไปรันคำสั่งอะไรก็ได้ตามใจชอบ
3. 🕵️♂️ ผ่าตัดโค้ด C: ดู Patch จริงของ PHP ที่ใช้หยุด RCE (Commit 01a40e89fb)
ถ้าคุณสงสัยว่าแฮกเกอร์เปลี่ยนแค่ “แรมเต็ม” ให้กลายเป็น “ยึดเซิร์ฟเวอร์” ได้ยังไง ลองมาดูโค้ดภาษา C ต้นฉบับจาก Zend Engine กันครับ
ในโครงสร้างเก่า (ก่อนแพตช์) PHP ใช้คำสั่ง emalloc() เพื่อจองหน่วยความจำ แต่ฟังก์ชันนี้มีพฤติกรรมโหดร้ายคือ ถ้าจอง RAM ไม่ได้ มันจะกระโดดข้ามโค้ด (longjmp) เพื่อตัดจบการทำงานทันที
❌ โค้ดก่อนแก้ (Vulnerable Flow)
/* main/rfc1867.c และ ext/session/session.c (จำลองพฤติกรรมก่อนแก้) */
// 1. ระบบพยายามจอง Memory ขนาดใหญ่เพื่อรับข้อมูลที่แฮกเกอร์ส่งมา
progress_array = emalloc(size);
// 🚨 จุดสลบ: ถ้า memory_limit เต็ม emalloc() จะเรียก zend_bailout()
// ซึ่งจะทำการกระโดดหนีออกจากฟังก์ชันนี้ไปเลย!
// ---------------------------------------------------------
// ❌ โค้ดโซนด้านล่างนี้ "ไม่เคย" ได้ทำงานเลยถ้าเกิด zend_bailout()
// ---------------------------------------------------------
if (PS(id)) {
zend_string_release(PS(id)); // คืนหน่วยความจำของ Session ID
PS(id) = NULL; // รีเซ็ต Pointer
}
เกิด RCE ได้อย่างไร?
เมื่อโค้ดบรรทัด PS(id) = NULL; ไม่ถูกรัน ตัวแปร PS(id) จะยังคงชี้ไปยังตำแหน่ง Memory เดิมที่กลายเป็นขยะไปแล้ว (Dangling Pointer) พอ PHP-FPM เอา Worker ตัวนี้ไปรับ Request ถัดไปของแฮกเกอร์ แฮกเกอร์ก็แค่ส่ง Payload เข้ามาเสียบในช่องว่างนั้น ตัว PHP Engine จะตีความข้อมูลนั้นผิดพลาด (Type Confusion) และถูกบิดเบือนทิศทางการรันคำสั่ง นำไปสู่การเกิด Remote Code Execution ทันที
✅ โค้ดที่แพตช์แล้ว (Commit 01a40e89fb)
ทีมพัฒนา PHP แก้ไขโดยรื้อระบบตัดจบนี้ใหม่ เปลี่ยนมาใช้ Fallible Allocation (ถ้าจองไม่ผ่าน ให้ส่งค่า NULL กลับมา ไม่ใช่กระโดดหนี) และเพิ่ม UnwindExit:
/* หลังแก้: เปลี่ยนมาจัดการ State ให้สมบูรณ์ก่อนตาย */
// 1. ใช้ฟังก์ชันที่ "ไม่" กระโดดหนีเมื่อ RAM เต็ม
void *tmp_ptr = zend_safe_femalloc(nmemb, size, offset);
if (UNEXPECTED(tmp_ptr == NULL)) {
// 2. ถ้าระบบจอง RAM ไม่ผ่าน จะวิ่งลงมาเคลียร์ขยะให้เรียบร้อย!
goto oom_cleanup;
}
progress_array = tmp_ptr;
// ... (ทำงานปกติต่อไป) ...
oom_cleanup:
// ✅ ทำการคืนค่า Pointer และ Reset Global State ปิดประตู UAF ถาวร
clean_partial_state();
reset_globals_properly(); // มั่นใจได้ว่า Worker ตัวนี้จะกลับมาคลีน 100%
return FAILURE;
บทเรียนราคาแพง: การ Error แล้วให้ระบบพังไปเลยแบบดื้อ ๆ ในภาษา C (การทำ Non-local exit ข้าม Scope) ทิ้งซากขยะใน Memory ที่กลายมาเป็นช่องโหว่ RCE ระดับพันล้านได้!
4. ใครโดนผลกระทบบ้าง? (SAPI Impact Matrix)
| สภาพแวดล้อม / SAPI | ระดับความเสี่ยง | ความรู้สึกของ Developer / SRE |
|---|---|---|
Apache (mod_php) | วิกฤตสูงสุด (Extreme) | โค้ด PHP กับ Web Server อยู่ห้องเดียวกัน แฮกเกอร์ยิง Request เดียวติด RCE ได้ง่ายดายที่สุด |
| PHP-FPM (Nginx) | วิกฤต (Critical) | มาตรฐานของเว็บ 90% ทั่วโลก Worker reuse ทำให้เกิดบั๊กสถานะค้างข้ามคำขอได้อย่างสมบูรณ์แบบ |
| FrankenPHP | วิกฤต (Critical) | เน้น Worker Mode โหลดโค้ดค้างในแรมเพื่อความแรง ยิ่งเปิดทางให้หน่วยความจำที่เสียหายวนกลับมาทำงาน |
| LiteSpeed (LSPHP) | วิกฤต (Critical) | ได้รับผลกระทบเช่นเดียวกันเมื่อประมวลผลคำขอ Multipart |
| CLI (Command Line) | ต่ำ (Low) | รันสคริปต์เดี่ยวใน Terminal รอดตัวไป เพราะไม่มีใครส่ง HTTP multipart เข้า Terminal ตรง ๆ ได้ |
5. แผนปฏิบัติการเร่งด่วน: แก้ไขให้ถูกจุด (ก่อนโดน On-Call)
สเต็ปที่ 1: ตรวจสอบและอัปเดต PHP Engine ทันที
อย่าดูแค่ php -v ในเครื่องแล้วสบายใจ เพราะบางที CLI อัปเดตแล้ว แต่ PHP-FPM ของ Web Server ยังรันไบนารีตัวเดิมอยู่! ต้องอัปเดตเป็นเวอร์ชันทางการเหล่านี้เท่านั้น:
| สาขาหลัก | เวอร์ชันที่ปลอดภัย (Fixed Versions) |
|---|---|
| PHP 8.5 | 8.5.12 |
| PHP 8.4 | 8.4.27 |
| PHP 8.3 | 8.3.36 |
| PHP 8.2 | 8.2.35 |
| PHP 7.4 / 8.0 / 8.1 (EOL) | ไม่มี official binary release ต้องใช้ source patch หรือ Extended Repo จาก Linux Distro เท่านั้น |
สเต็ปที่ 2: สั่ง Restart Service (อันนี้สำคัญมาก!)
แพตช์เสร็จแล้ว ต้องเตะ Worker เก่าทิ้งออกจาก RAM ทันที:
# ตัวอย่างสำหรับ Ubuntu / Debian
sudo apt update && sudo apt --only-upgrade install php8.3-fpm
sudo systemctl restart php8.3-fpm
sudo systemctl reload nginx
6. แผนซื้อเวลา: ถ้าเซิร์ฟเวอร์ยังอัปเดตทันทีไม่ได้ ทำอย่างไร?
หากบริษัทของคุณติดระเบียบ Change Request อันเข้มงวด หรือต้องรอ Maintenance Window ตอนตีสาม นี่คือมาตรการบรรเทาชั่วคราว:
-
บล็อก Multipart ที่ Nginx ก่อนส่งถึง PHP: ในเมื่อเวกเตอร์หลักมาจากการแกะฟอร์มอัปโหลดไฟล์ ให้ Nginx ปฏิเสธคำขอนั้นทิ้งไปก่อนเลย:
# วางใน block server ของ Nginx ก่อน fastcgi_pass if ($http_content_type ~* "multipart/form-data") { return 403; }(หมายเหตุ: ฟังก์ชันอัปโหลดไฟล์ในเว็บจะใช้ไม่ได้ชั่วคราว แต่ดีกว่าโดนขโมยเซิร์ฟเวอร์)
-
ปิด Upload Progress ใน
php.ini:session.upload_progress.enabled = 0 -
อย่าหลงเชื่อคำสั่ง
artisan downหรือโหมด Maintenance ของ Framework: การเปิด Maintenance Mode ในระดับแอปพลิเคชัน ไม่ช่วยอะไรทั้งสิ้น เพราะตัว Parser ภาษา C ของ PHP ได้รับคำขอและเกิด Memory Corruption พังไปก่อนที่ Framework จะได้โหลดไฟล์เช็กสถานะบำรุงรักษาด้วยซ้ำ! ถ้าจะปิดปรับปรุง ต้องปิดด้วย Static HTML ที่ระดับ Reverse Proxy / Cloudflare เท่านั้น
บทสรุปแด่นักพัฒนาทุกคน
- Clean Architecture — แยก Layer สวยจน Uncle Bob น้ำตาไหล
- Unit Test — เขียวทั้งแถบ Coverage 95% อีก 5% ฝากไว้กับสิ่งศักดิ์สิทธิ์
- Type Safety — TypeScript strict mode, any เป็นอาชญากรรม
- Security Audit — ผ่านหมด OWASP Top 10 ยืนปรบมือ
- CI/CD — 47 Checks Passed, GitHub Actions เขียวจนแสบตา
- Docker — Non-root, Read-only filesystem, Drop all capabilities
- Code Review — Senior 3 คน Approve พร้อมคอมเมนต์ LGTM 🚀
“คุณใช้เวลา 6 เดือนออกแบบ Architecture เพื่อไม่ให้ใครเข้าถึง Business Logic โดยไม่ได้รับอนุญาต
แต่ Hacker ไม่ได้พยายามเข้าผ่าน Business Logic ตั้งแต่แรก”
เพราะ Clean Architecture ช่วยจัดระเบียบโค้ดของคุณ
ไม่ได้รับประกันว่า Library ทุกตัวที่โค้ดคุณเรียกใช้จะปลอดภัย
และที่เจ็บที่สุดคือ…
คุณเขียน Unit Test ครอบคลุมโค้ดตัวเอง 95%
แต่ช่องโหว่อยู่ในโค้ดที่คุณไม่เคยเขียน และไม่เคยทดสอบเองด้วยซ้ำ 🗿
git commit -m "fix: apparently dependencies are part of the application"
Technical Debt ไม่น่ากลัวเท่า Technical Inheritance — มรดกโค้ด C อายุ 30 ปี ที่ยังมีสิทธิ์ทำ Production คุณพังได้ในปี 2026
นี่คือรายการแหล่งข้อมูลอ้างอิง (References) ทั้งหมดจากการวิจัยและวิเคราะห์ช่องโหว่ PHP2Shell (CVE-2026-109618 และ CVE-2026-109619) ที่ใช้ประกอบการเขียนบทความครับ คุณสามารถนำไปแนบไว้ท้ายบล็อกได้เลย:
แหล่งข้อมูลอ้างอิง (References):
-
Wikipedia: PHP2Shell
-
Cyber Security News: PHP Releases Emergency Patches
-
Sansec Research: PHP2Shell Vulnerability Analysis
-
The Next Web: PHP2Shell, OpenAI, and Wiz Research
-
Huntress: Leaked Critical PHP Vulnerabilities
-
Imperva: PHP2Shell Vulnerability Updates
-
Clarin: PHP Vulnerability 2026 Explained
-
Reddit (r/selfhosted): PHP2Shell in Self-Hosted Apps
-
The Record: PHP Script Vulnerabilities Warnings
-
Palo Alto Unit 42: CVE-2026-109618 & CVE-2026-109619 Analysis
-
OX Security: Two Vulnerabilities Allow Unauthenticated RCE in PHP
-
Google Cloud Blog: Responding to Core PHP Vulnerabilities
-
AlphaSignal: OpenAI’s PHP2Shell Exposes 70% of Websites
-
Help Net Security: PHP2Shell Vulnerabilities Patched
-
Brandefense: PHP RCE CVE-2026-109618 & 109619
-
Cloudflare Blog: PHP Vulnerabilities WAF Protection
-
Precursor Security: Critical PHP RCE Vulnerabilities
-
CERT-EU: Security Advisory 2026-018
-
The Hacker News: Two Unpatched Critical PHP Vulnerabilities
-
AlmaLinux Blog: PHP2Shell CVE-2026-109618 & CVE-2026-109619
-
Ubuntu Security: CVE-2026-109618
-
Wiz Research Blog: Wiz Blog
Retrieve the raw markdown file or structured JSON of this post directly in your command line:
curl -sL https://dekkapok.engineering/blogs/php2shell_rce_analysis.txt curl -sL https://dekkapok.engineering/blogs/php2shell_rce_analysis.json
สรุปผลการสืบสวน Binance Hack ตุลาคม 2026: ความจริง 6.3B USD ข้าม 10 เชน, ปมจุดบุกรุก และข้อเท็จจริง EIP-7702
เมื่อ AI เจาะโค้ด Bitcoin Core: ผ่า 3 ช่องโหว่ DoS และจุดเปลี่ยนของนโยบายเปิดเผยบั๊ก