ข้ามไปยังเนื้อหา
เซิร์ฟเวอร์และ .htaccess

กฎความปลอดภัย .htaccess ของ WordPress ที่ช่วยได้จริง

สิ่งที่ถูกขายในนามความปลอดภัย .htaccess ของ WordPress ส่วนใหญ่เป็นแค่ของประดับ กฎที่เปลี่ยนพื้นผิวการโจมตีของคุณได้จริงนั้นสั้น บทความนี้จะบอกว่าอันไหนเป็นอันไหน

เผยแพร่แล้ว

สิ่งที่ถูกขายในนาม “ความปลอดภัย .htaccess ของ WordPress” ส่วนใหญ่เป็นแค่ของประดับ กฎที่เปลี่ยนพื้นผิวการโจมตีของคุณได้จริงนั้นสั้น คือ บล็อกการรัน PHP ภายใน wp-content/uploads, ปฏิเสธการเข้าถึง wp-config.php โดยตรง และปิดการทำ directory indexing ส่วนกฎยอดฮิต เช่น การบล็อก xmlrpc.php “เพื่อหยุด brute force”, การซ่อนเวอร์ชัน WordPress, การก๊อปวาง user-agent list ของบอทเลว 200 บรรทัด นั้นมีตั้งแต่ช่วยได้เล็กน้อยไปจนถึงเป็นละครล้วนๆ ด้านล่างนี้คือว่าอันไหนเป็นอันไหน และเพราะอะไร

มีเรื่องหนึ่งที่ต้องเคลียร์ก่อน .htaccess ทำงานได้เฉพาะบน Apache เท่านั้น (และ LiteSpeed ซึ่งอ่านมันด้วย) บน nginx มันจะ ถูกละเลยทั้งหมด ไฟล์นั้นแค่วางอยู่เฉยๆ ในขณะที่คุณเชื่อว่าตัวเองได้รับการปกป้อง ถ้าโฮสต์ของคุณรัน nginx เรื่องพวกนี้ใช้ไม่ได้เลย และคุณต้องใช้บล็อก server/location แทน ตรวจสอบด้วย curl -I https://yoursite.com แล้วดูที่ header Server: ก่อนที่คุณจะเสียเวลาชั่วโมงหนึ่งไปกับการแก้ไฟล์ที่เซิร์ฟเวอร์ไม่เคยอ่าน

กฎที่สำคัญจริง: ห้าม PHP ใน /uploads

นี่คือกฎที่คุ้มค่าที่จะทำ wp-content/uploads ถูกออกแบบมาให้เขียนได้จากทุกที่ (world-writable) ทุกการอัพโหลดสื่อ ทุกปลั๊กอิน wordpress ที่บันทึกไฟล์ จะเขียนลงที่นั่น ถ้าผู้โจมตีเอาไฟล์ .php เข้าไปในไดเรกทอรีนั้นได้ (ผ่าน upload handler ที่มีช่องโหว่, ฟิลด์รูปภาพที่ไม่ตรวจสอบ MIME type, หรือปลั๊กอิน wordpress ที่ถูกเจาะ) ความต่างระหว่างแค่ความรำคาญกับการถูกยึดเครื่องแบบ remote-code-execution เต็มรูปแบบ อยู่ที่ว่าเซิร์ฟเวอร์จะ รัน ไฟล์นั้นเมื่อมีการร้องขอหรือไม่ ปฏิเสธการรันเสีย แล้ว payload ที่ถูกอัพโหลดก็เป็นแค่ไฟล์เฉื่อยๆ ที่นั่งอยู่บนดิสก์

วางโค้ดนี้ลงใน wp-content/uploads/.htaccess (สร้างไฟล์ขึ้นมาถ้ายังไม่มี)

<FilesMatch "\.php$">
    Require all denied
</FilesMatch>

นั่นคือ syntax ของ Apache 2.4 บนเวอร์ชันเก่า 2.2 ตัวที่เทียบเท่ากันคือ Order Deny,Allow / Deny from all การผสมสอง dialect นี้ในไฟล์เดียวคือสาเหตุที่พบบ่อยที่สุดของ 500 Internal Server Error ที่จู่ๆ ก็เกิดขึ้นหลังจาก “hardening” ถ้าทั้งเว็บล่มทันทีที่คุณเซฟ นั่นแทบจะเป็นเหตุผลนั้นเสมอ ตรวจสอบเวอร์ชันของคุณด้วย apachectl -v

ปกป้อง wp-config.php

wp-config.php เก็บ credential ของฐานข้อมูลและ auth salt ของคุณ ในขณะที่ PHP กำลังทำงาน การร้องขอไฟล์นี้โดยตรงจะได้หน้าเปล่ากลับมา เพราะ PHP รันไฟล์แทนที่จะพิมพ์มันออกมา ความเสี่ยงอยู่ที่กรณีที่มันพัง ถ้า PHP ล่ม, ถูกตั้งค่าผิดระหว่างการย้ายเซิร์ฟเวอร์ (migration), หรือ handler ถูกปิดใช้งาน Apache จะเสิร์ฟไฟล์นั้นเป็น plaintext และเทรหัสผ่านฐานข้อมูลของคุณให้ใครก็ตามที่ถาม การปฏิเสธการเข้าถึงคือประกันราคาถูกกันช่วงห้านาทีที่โชคร้าย

<Files wp-config.php>
    Require all denied
</Files>

ปิด directory indexing

ถ้ามีใครเข้ามาที่โฟลเดอร์ที่ไม่มี index.php และ Apache เปิด Options +Indexes อยู่ มันจะแสดงรายการเนื้อหาข้างในออกมา ทั้งไฟล์ backup ทุกไฟล์ ทั้ง SQL dump ที่หลงเหลือที่คุณลืมไปแล้ว ปิดมันทั้งเว็บเลย

Options -Indexes

ความรุนแรงต่ำ แต่มีจริง และไม่ต้องแลกอะไรเลย

นั่นคือลิสต์แกนหลักตามความจริง คุณสามารถประกอบสิ่งเหล่านี้เข้าด้วยกัน พร้อม syntax ที่ถูกต้องระหว่าง 2.4 กับ 2.2 เพื่อไม่ให้เว็บของคุณ 500 ได้ด้วย ตัวสร้าง .htaccess ของ WordPress แทนที่จะก๊อปวางจากโพสต์ในฟอรัมที่เขียนไว้สำหรับ Apache คนละเวอร์ชัน

สิ่งที่ไม่ควรทำ

การบล็อก xmlrpc.php “เพื่อป้องกัน brute-force” นี่คืออันใหญ่ที่ทุกคนพูดต่อๆ กัน และมันผิดอย่างที่พูด ใช่ ในอดีตเมธอด system.multicall ของ XML-RPC เคยเปิดทางให้ผู้โจมตีมัดการเดารหัสผ่านหลายครั้งรวมเป็น request เดียวได้ ซึ่งเป็นการขยายผลจริง แต่ช่องทาง brute-force ที่พบบ่อยที่สุดอย่างท่วมท้นคือ POST request ธรรมดาๆ ไปยัง wp-login.php และการบล็อก xmlrpc.php ไม่ช่วยอะไรเลย กับเรื่องนั้น Brute force ถูกเอาชนะด้วย rate limiting, รหัสผ่านที่แข็งแรง และ 2FA ไม่ใช่ด้วยการฆ่า endpoint หนึ่งตัว มี เหตุผลที่ชอบธรรมในการปิด XML-RPC อยู่จริง คือฟีเจอร์ pingback ของมันสามารถถูกนำไปใช้ในทางที่ผิดเพื่อทำ DDoS reflection ได้ ดังนั้นถ้าคุณไม่ได้ใช้ Jetpack, แอปมือถือ, หรือ pingback การปิดมันจะช่วยลดพื้นผิวการโจมตี แค่อย่าหลอกตัวเองว่ามันคือเกราะป้องกัน brute-force ของคุณ เพราะมันไม่ใช่

การซ่อนเวอร์ชัน WordPress / เอา generator tag ออก การถอด readme.html และ tag <meta name="generator"> ออกให้ความรู้สึกเหมือนได้ hardening ผู้โจมตีสามารถ fingerprint เวอร์ชันของคุณได้จาก query string ของ asset ที่ถูก enqueue, markup ของ block-editor และร่องรอยอีกนับสิบภายในไม่กี่วินาที คุณไม่ได้ซ่อนอะไรเลย คุณแค่ทำให้ตัวเองรู้สึกว่ายุ่ง

บล็อกลิสต์ user-agent และ referrer ของบอทเลวยักษ์ใหญ่ User agent เป็น HTTP header ตัวเดียวที่ปลอมได้ ลิสต์พวกนี้ล้าสมัยตั้งแต่วันที่คุณก๊อปวาง มันบล็อกอะไรที่มีฝีมือจริงไม่ได้เลย และ Apache จะประมวลผลทุก regex ใน ทุก request คุณกำลังจ่ายภาษีด้านประสิทธิภาพจริงๆ เพื่อความปลอดภัยที่เป็นศูนย์ ข้ามมันไปเถอะ

การล็อก IP ให้ wp-login.php ดีมาก จนกระทั่ง ISP ของคุณหมุนเปลี่ยน address แล้วคุณก็ล็อกตัวเองออกจาก admin ของตัวเอง ใช้ได้เฉพาะเมื่อมี static IP จริงๆ เท่านั้น

การ redirect กัน author-enumeration (?author=1) โค้ด rewrite ใน .htaccess ที่คนก๊อปวางมานั้นไม่สมบูรณ์ในตัวเอง เพราะ REST endpoint /wp-json/wp/v2/users ยังคงแสดง username อยู่ การบล็อกทางหนึ่งในขณะที่อีกทางเปิดอยู่คือละคร

ยังติดอยู่ใช่ไหม?

ถ้ากฎทำให้เว็บ 500 นั่นคือปัญหา syntax ให้ดึงบล็อกสุดท้ายที่คุณเพิ่งเพิ่มเข้าไปออกแล้ว reload นั่นจะแยกปัญหาออกมาได้ทันที ถ้ากฎดูเหมือนไม่ทำอะไรเลย ให้ยืนยันว่าคุณอยู่บน Apache จริงๆ และ AllowOverride ถูกเปิดใช้งานสำหรับไดเรกทอรีนั้น (โฮสต์แบบ managed จำนวนมากจำกัดมันไว้) สร้างไฟล์จากเทมเพลตที่รู้ว่าใช้ได้ด้วย ตัวสร้าง .htaccess เก็บสามกฎที่สำคัญไว้ แล้วทิ้งที่เหลือ