WordPress XML-RPC: 3 วิธีปิดใช้งาน & วิธีตรวจจับการโจมตี xmlrpc.php | ข้อยกเว้นของ Jetpack

ถ้าบันทึกการเข้าถึงของเว็บไซต์ WordPress มีคำขอ xmlrpc.php จาก IP เดียวกันต่อเนื่องหลายร้อยครั้ง นั่นคือการเดารหัสผ่านที่เล็งไปที่ XML-RPC ของ WordPress บทความนี้แยกประเภทบันทึกการเข้าถึง 8,624 รายการในหนึ่งวันของเซิร์ฟเวอร์ Nginx + Apache ที่รันเว็บไซต์ WordPress หลายเว็บ แล้วลองใช้ 3 วิธีปิดใช้งานจริงเพื่อตรวจรหัสตอบกลับ และสรุปว่าต้องบล็อกที่ชั้นไหน การรัน PHP จึงจะลดลงไปด้วย
XML-RPC คือโปรโตคอลที่รับส่งเอกสาร XML ผ่าน HTTP เพื่อเรียกใช้ฟังก์ชันของ WordPress จากระยะไกล ตั้งแต่ WordPress 3.5 เป็นต้นมา ตัวเลือกเปิดปิดในหน้าตั้งค่าถูกนำออกไป จึงเปิดใช้งานอยู่ตั้งแต่ติดตั้งเสร็จ
1️⃣ บทบาทของ xmlrpc.php และใครใช้งานบ้าง
ตั้งแต่มี REST API ก็ง่ายที่จะมองว่า [xmlrpc.php] เป็นไฟล์ที่ไม่มีใครใช้แล้ว แต่ Jetpack ยังเชื่อมเว็บไซต์กับ WordPress.com ผ่านไฟล์นี้อยู่จนถึงทุกวันนี้ เอกสารทางการของ Jetpack ระบุชัดว่าต้องพึ่งการเชื่อมต่อ XML-RPC และบนโฮสติ้งที่บล็อก XML-RPC เป็นค่าเริ่มต้น การเชื่อมต่อ Jetpack จะล้มเหลวไปเลย เหมือนกับที่ทุกวันนี้สำนักงานก็ยังเก็บเบอร์แฟกซ์ไว้สักเบอร์
ณ WordPress 7.1 ไฟล์นี้รับเมธอด 77 ตัว ประกอบด้วยกลุ่ม wp 50 ตัวที่จัดการโพสต์ สื่อ และความเห็น โปรโตคอลบล็อกรุ่นเก่า (MetaWeblog, MovableType, Blogger) 23 ตัว pingback 2 ตัว และ demo สำหรับทดสอบ 2 ตัว
กลุ่ม wp ล็อกอินด้วยชื่อผู้ใช้และรหัสผ่านที่อยู่ในเนื้อหาคำขอ จึงไม่ผ่านหน้าล็อกอิน การเขียนและแก้ไขโพสต์ การอัปโหลดสื่อ และการจัดการความเห็นล้วนอยู่ในกลุ่มนี้ ส่วนโปรโตคอลบล็อกรุ่นเก่า 23 ตัวเป็นเมธอดที่เก็บไว้เพื่อรับส่งโพสต์ด้วยวิธีเดียวกับเครื่องมือบล็อกอื่น
demo.sayHello คือการเรียกสำหรับทดสอบที่มีอยู่ใน XML-RPC ของ WordPress ส่งไปแล้วจะได้คำตอบกลับมาแค่คำเดียวคือ [Hello!] และเพราะไม่ต้องล็อกอิน จึงใช้ตรวจว่า XML-RPC ทำงานอยู่หรือไม่ คู่ของมันคือ demo.addTwoNumbers ที่บวกเลขสองตัวแล้วส่งผลกลับมา
ทุกเมธอดจะตอบกลับก็ต่อเมื่อส่งเนื้อหา XML ด้วย POST ถ้าเปิด URL ในเบราว์เซอร์ จะได้ 405 กลับมาพร้อมข้อความ [XML-RPC server accepts POST requests only.]
pingback ก็เป็นหน้าที่ของไฟล์นี้ เมื่อเผยแพร่โพสต์ ระบบจะส่ง pingback ไปยัง xmlrpc.php ของเว็บไซต์ที่ลิงก์ไว้ในเนื้อหา และเซิร์ฟเวอร์ฝั่งรับจะเปิด URL ที่ส่งมาเองเพื่อตรวจว่ามีลิงก์อยู่จริง
2️⃣ การโจมตี xmlrpc.php ที่เห็นในบันทึกการเข้าถึง
การเดารหัสผ่านเข้ามาทาง [xmlrpc.php] โดยไม่ผ่านหน้าล็อกอิน ในบันทึกการเข้าถึงของเซิร์ฟเวอร์ Nginx + Apache ที่รันเว็บไซต์ WordPress หลายเว็บ ตั้งแต่ 00:00 ถึง 20:00 น. ของวันที่ 14 ตุลาคม มีคำขอ POST /xmlrpc.php 8,624 ครั้งจาก 72 IP และในจำนวนนี้มี 19 IP ที่ส่งมา IP ละ 300 ครั้งพอดี
IP หนึ่งส่งมา 300 ครั้งภายใน 28 นาที ตั้งแต่ 14:09 ถึง 14:37 น. โดยเว้นช่วง 5~6 วินาทีสม่ำเสมอจนจบ คำขอ 8,524 ครั้งเข้ามาโดยใส่ Jetpack หรือ WordPress.com ไว้ใน User-Agent และ IP เดียวกันสลับใช้ค่าหลายแบบที่เลียนรูปแบบของ Jetpack จริง เช่น [Jetpack by WordPress.com (Jetpack 13.0; WordPress 6.3)]
ในวันเดียวกัน ก่อน 14:00 น. การปิด XML-RPC ของปลั๊กอินความปลอดภัยส่งคำขอเหล่านี้กลับไปหน้าแรกด้วย 302 และหลัง 14:00 น. เมื่อบล็อกที่ Nginx แล้ว คำขอก็จบด้วย 403 แม้เนื้อหาจะเป็น 0 ไบต์ แต่ 302 คือผลที่ PHP ถูกรันทุกคำขอ ส่วน 403 เป็นการตอบกลับที่ไม่ผ่าน PHP
3️⃣ system.multicall กับการเดารหัสผ่าน
คำอธิบายจำนวนมากบอกว่า [system.multicall] ใช้ลองรหัสผ่านหลายร้อยตัวได้ในคำขอเดียว แต่ถ้าล็อกอินล้มเหลวหนึ่งครั้งภายในคำขอเดียวกัน WordPress จะไม่ตรวจรหัสผ่านของการเรียกที่เหลืออีก มีเพียงการเรียกแรกที่ผ่าน wp_authenticate ส่วนการเรียกหลังจากนั้นจะตอบด้วยข้อผิดพลาดเดียวกัน [Incorrect username or password.] โดยไม่ตรวจ
การตอบกลับยังกลับมาครบตามจำนวนการเรียก คำขอเดียวที่มีรหัสผ่านผิด 100 ตัวจึงได้ข้อผิดพลาด 100 รายการกลับมาในการตอบกลับขนาด 23KB ภายใน 0.12 วินาที ถ้าวางรหัสผ่านที่ถูกไว้ข้างหลังในคำขอเดียวกัน จะถูกปฏิเสธไปด้วยเพราะการเรียกก่อนหน้าล้มเหลว แต่ถ้าวางไว้ข้างหน้า จะสำเร็จเฉพาะการเรียกนั้น การตรวจรหัสผ่านเกิดขึ้นครั้งเดียว แต่การแยกวิเคราะห์ XML และการโหลด WordPress เกิดซ้ำทุกคำขอ ภาระเซิร์ฟเวอร์จากการโจมตีนี้จึงมาจากจำนวนคำขอเอง ไม่ใช่จากการตรวจล็อกอิน
4️⃣ 3 วิธีปิดใช้งาน XML-RPC และรหัสตอบกลับ
แม้ปิด XML-RPC ในปลั๊กอินแล้ว คำขอ [xmlrpc.php] ก็ยังเข้ามาถึง PHP เมื่อส่งคำขอที่มี 100 การเรียกในสถานะที่ปิดด้วยฟิลเตอร์ ก็ยังได้ HTTP 200 พร้อมข้อผิดพลาด 100 รายการ (24KB) กลับมาเหมือนเดิม วิธีปิดแบ่งได้เป็น 3 แบบตามชั้นที่ปฏิเสธคำขอ และแต่ละแบบให้รหัสตอบกลับต่างกัน
1. ปิดด้วยโค้ดผ่านฟิลเตอร์ xmlrpc_enabled
แค่ใส่ add_filter หนึ่งบรรทัดที่ให้ฟิลเตอร์ [xmlrpc_enabled] คืนค่า false ลงใน functions.php ของธีม ในสถานะนี้ การเรียกที่ต้องล็อกอินจะถูกปฏิเสธด้วยข้อผิดพลาด [XML-RPC services are disabled on this site.] และ HTTP 405 แต่ demo.sayHello ซึ่งเป็นการเรียกทดสอบที่ไม่ต้องล็อกอินยังตอบกลับด้วย 200 อยู่
2. ปิดจากการตั้งค่าปลั๊กอิน: Perfmatters·Hide My WP
เป็นวิธีปิดด้วยการตั้งค่า Disable XML-RPC ในปลั๊กอินอย่าง Perfmatters และ Hide My WP โดย Hide My WP ใช้วิธีฟิลเตอร์แบบเดียวกับด้านบน การตอบกลับจึงเหมือนกัน แต่ Perfmatters นอกจากฟิลเตอร์เดียวกันแล้ว ยังจบคำขอที่ส่งตรงไปที่ [xmlrpc.php] ด้วย 403 และลบทั้งเฮดเดอร์ pingback และลิงก์ pingback เมื่อปิดแล้วทั้ง GET และ POST จึงตอบกลับด้วย 403 แต่การจะส่ง 403 ได้ WordPress ต้องรันไปจนถึงขั้น init จึงใช้เวลาตอบกลับ 0.13~0.15 วินาที เท่ากับตอนที่ยังเปิดอยู่ และภาระ PHP ก็ยังอยู่
3. บล็อกที่เว็บเซิร์ฟเวอร์: Nginx·Apache
ไม่ว่าจะเป็น Nginx หรือ Apache ก็ปฏิเสธคำขอก่อนถึง PHP ต่างจากวิธีปลั๊กอินตรงที่ WordPress ไม่ถูกรันเลย
# Nginx — ภายในบล็อก server เหนือ location ที่ประมวลผล PHP
location = /xmlrpc.php {
deny all;
}วางบล็อก location นี้ไว้เหนือ location ที่ประมวลผล PHP แล้ว Nginx จะปฏิเสธคำขอที่ตรงกับ [xmlrpc.php] ทุกตัวอักษรทันที หลังใส่แล้วต้องตรวจการตั้งค่าและโหลดใหม่จึงจะมีผล
nginx -tnginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successfulsystemctl reload nginxหลังโหลดใหม่ GET และ POST จะจบด้วย 403 โดยไม่ไปถึง PHP เนื้อหาการตอบกลับคือหน้า 403 เริ่มต้นของ Nginx ขนาด 153 ไบต์ และใช้เวลาตอบกลับ 0.02 วินาที
# Apache — .htaccess ที่รูทของเว็บไซต์ (Apache 2.4)
<Files xmlrpc.php>
Require all denied
</Files>บนเซิร์ฟเวอร์ที่ใช้ Apache อย่างเดียว ใส่ไดเรกทีฟ Files ด้านบนลงใน .htaccess ที่รูทของเว็บไซต์ก็มีผลทันที และจะได้หน้า 403 เริ่มต้นของ Apache (199 ไบต์) กลับมาใน 0.02 วินาทีเท่ากัน
curl -s -o /dev/null -w '%{http_code}' -X POST https://example.com/xmlrpc.php -d '<methodCall><methodName>demo.sayHello</methodName></methodCall>'403คำสั่งด้านบนส่งการเรียกทดสอบ demo.sayHello ด้วย POST เพื่อตรวจรหัสตอบกลับ หลังบล็อกที่เว็บเซิร์ฟเวอร์แล้วจะได้ 403 กลับมา ถ้าส่งโดยตัดตัวเลือก [-o /dev/null] และ [-w] ออก จะเห็นเนื้อหา ถ้าได้ [Hello!] แสดงว่ายังเปิดอยู่ แต่เว็บไซต์ที่ปิดด้วยฟิลเตอร์อย่างเดียวก็ได้การตอบกลับแบบเดียวกัน จึงต้องส่งการเรียกที่ต้องล็อกอินไปด้วยจึงจะเห็น 405
5️⃣ กรณีที่ไม่ควรปิดและทางเลือกอื่น
การบล็อกทั้งหมดที่เว็บเซิร์ฟเวอร์ได้ผลมากที่สุด แต่ไม่เหมาะกับเว็บไซต์ที่เชื่อมกับ WordPress.com ผ่าน Jetpack เพราะ Jetpack พึ่งการเชื่อมต่อ XML-RPC และบนโฮสติ้งที่บล็อก XML-RPC เป็นค่าเริ่มต้นอย่าง WP Engine ต้องเปิดข้อยกเว้นตามคำแนะนำของ Jetpack จึงจะเชื่อมต่อได้ เว็บไซต์ที่ต้องการรับ pingback ก็เช่นกัน
เว็บไซต์แบบนี้ให้จำกัดขอบเขตลงด้วยการอนุญาตเฉพาะรายการ IP ที่ Jetpack เผยแพร่ (jetpack.com/ips-v4.txt) และปฏิเสธที่เหลือ แค่ใส่บรรทัด allow ตามจำนวนช่วง IP ไว้ในบล็อก location ของ Nginx แล้วปิดท้ายด้วย deny all
ถ้าบล็อกทั้งหมดไม่ได้ ยังเหลือวิธีจำกัดจำนวนครั้งต่อนาทีสำหรับคำขอที่ไม่ได้ล็อกอิน การตั้งค่าเหมือนกับหัวข้อการจำกัดอัตราของ Nginx ในบทความ ความปลอดภัย wp-admin และเพราะนับเฉพาะคำขอที่ไม่มีคุกกี้ล็อกอิน งานของผู้ดูแลที่ล็อกอินอยู่จึงไม่ได้รับผลกระทบ
🔢 FAQ & เนื้อหาแนะนำ










ℹ️ ประกาศเรื่องลิงก์แอฟฟิลิเอต
เนื้อหาของเว็บไซต์นี้มีลิงก์แอฟฟิลิเอต หากผู้เข้าชมซื้อสินค้าหรือบริการผ่านลิงก์เหล่านี้ เว็บไซต์นี้จะได้รับค่าคอมมิชชันจากผู้ขาย ในกระบวนการนี้ ราคาที่ผู้ซื้อจ่าย(ราคาจะลดลงเมื่อมีโปรโมชัน↓)จะไม่สูงขึ้นแต่อย่างใด ราคา ส่วนลด และข้อมูลสต็อกที่แสดงเป็นข้อมูล ณ เวลาที่เขียนและอาจต่างจากความเป็นจริง โปรดตรวจสอบกับผู้ขายอีกครั้งก่อนซื้อ การเลือกและการประเมินสินค้าเขียนตามเกณฑ์ของเราเอง และการได้รับค่าคอมมิชชันหรือไม่ไม่มีผลต่อลำดับการแนะนำหรือเนื้อหาการประเมิน