2026年11月12日
2026年11月12日
ซ่อนเวอร์ชัน WordPress: generator tag, ?ver= และ readme.html
บทความนี้เป็นฉบับปรับให้เหมาะกับท้องถิ่นจากต้นฉบับภาษาญี่ปุ่น อ่านต้นฉบับภาษาญี่ปุ่น
โดยค่าเริ่มต้น WordPress จะเขียนหมายเลขเวอร์ชันของตัวเองลงใน HTML ของหน้าเว็บและในฟีด ผู้โจมตีอาจใช้ข้อมูลนี้เป็นเบาะแสว่าเป็นเวอร์ชันที่มีช่องโหว่ที่รู้จักแล้วหรือไม่ บทความนี้อธิบายวิธีตรวจว่าเวอร์ชันแสดงอยู่ที่ใดบ้าง และขั้นตอนการซ่อน เนื้อหาเป็นของเดือนตุลาคม 2026
สิ่งที่อยากให้รู้ก่อนคือ การซ่อนเวอร์ชันเป็นเพียงมาตรการเสริม เพราะยังมีวิธีเดาเวอร์ชันจากเนื้อหาของไฟล์ CSS หรือ JavaScript ได้ จึงไม่ได้หมายความว่าซ่อนแล้วจะป้องกันการระบุเวอร์ชันได้เสมอไป สิ่งที่ได้ผลที่สุดคือการอัปเดตแกนหลัก ปลั๊กอิน และธีมของ WordPress ให้เป็นเวอร์ชันล่าสุดอยู่เสมอ
จุดที่แสดงเวอร์ชัน
| ตำแหน่ง | สิ่งที่แสดง | วิธีจัดการ |
|---|---|---|
<head> ของ HTML |
แท็กอย่าง <meta name="generator" content="WordPress 6.9.4"> |
ฟิลเตอร์ the_generator |
| ฟีด RSS และ Atom | บรรทัดอย่าง <generator>https://wordpress.org/?v=6.9.4</generator> |
ใช้ฟิลเตอร์ the_generator เช่นเดียวกัน |
| URL ของ CSS และ JavaScript | ?ver= อย่าง style.min.css?ver=6.9.4 |
ฟิลเตอร์ style_loader_src และ script_loader_src |
| เฮดเดอร์ HTTP | X-Powered-By: PHP/8.3.x (เป็นเวอร์ชันของ PHP ไม่ใช่ของ WordPress) |
expose_php ใน php.ini |
readme.html |
บอกว่าใช้ WordPress อยู่ (readme.html ในปัจจุบันไม่มีหมายเลขเวอร์ชัน) | ปฏิเสธการเข้าถึง |
ข้อมูลไซต์ที่ /wp-json/ ของ REST API ส่งกลับมา (ชื่อ คำอธิบาย URL เขตเวลา namespace เป็นต้น) ไม่มีเวอร์ชันของ WordPress รวมอยู่ จึงไม่จำเป็นต้องตัดผลลัพธ์ของ REST API เพื่อจุดประสงค์ในการซ่อนเวอร์ชัน
ขั้นตอนที่ 1: ตรวจสถานะการแสดงผลปัจจุบัน
ให้แทนที่ด้วย URL ของเว็บไซต์ตนเอง แล้วรันบนคอมพิวเตอร์หรือเซิร์ฟเวอร์ของตนเอง หากใช้ปลั๊กอินแคชหรือ CDN ให้ล้างแคชก่อนหลังเปลี่ยนแปลงแล้วจึงตรวจสอบ
# generator tag ใน HTML
curl -s https://example.com/ | grep -i 'name="generator"'
# generator ในฟีด
curl -s https://example.com/feed/ | grep -i '<generator>'
# รายการค่า ver= ที่ติดอยู่กับ CSS และ JS
curl -s https://example.com/ | grep -oE 'ver=[0-9][0-9.]*' | sort | uniq -c
# X-Powered-By ในเฮดเดอร์ HTTP
curl -sI https://example.com/ | grep -i 'x-powered-by'
# ตรวจว่าอ่าน readme.html ได้หรือไม่ (ถ้าได้ 200 แสดงว่าอ่านได้)
curl -s -o /dev/null -w '%{http_code}\n' https://example.com/readme.html
หากในรายการ ver= มีค่าเดียวกับเวอร์ชันของ WordPress ที่แสดงใน "แดชบอร์ด (Dashboard) → อัปเดต (Updates)" ของหน้าผู้ดูแล แสดงว่าสามารถรู้เวอร์ชันของ WordPress ได้จาก CSS หรือ JS นั้น เมื่อแกนหลักของ WordPress หรือธีมโหลด CSS และ JS โดยไม่ได้ระบุเวอร์ชัน เวอร์ชันของ WordPress จะถูกใส่ลงใน ver=
ขั้นตอนที่ 2: เลือกที่วางโค้ด
โค้ดในบทความนี้ให้วางไว้ที่ใดที่หนึ่งต่อไปนี้
- mu-plugin (แนะนำ): หากวางไฟล์ PHP ไว้ในโฟลเดอร์
wp-content/mu-plugins/(สร้างขึ้นหากยังไม่มี) ไฟล์จะถูกโหลดโดยไม่ต้องกดเปิดใช้งาน และผลยังคงอยู่แม้เปลี่ยนธีม - functions.php ของ child theme: หากเขียนใน functions.php ของธีมหลัก โค้ดจะหายเมื่อธีมอัปเดต
หากทำเป็น mu-plugin ให้รวมโค้ดของขั้นตอนถัดไปไว้ในไฟล์เดียว แล้วบันทึกด้วยชื่ออย่าง wp-content/mu-plugins/hide-wp-version.php
ขั้นตอนที่ 3: ซ่อนเวอร์ชันใน generator tag และ ver=
<?php
/**
* Plugin Name: Hide WordPress Version
* Description: ซ่อนเวอร์ชันของ WordPress ที่แสดงใน generator tag และใน ver= ของ CSS และ JS
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
// ทำให้ <meta name="generator"> ใน HTML และ <generator> ในฟีดว่างเปล่า
add_filter( 'the_generator', '__return_empty_string' );
/**
* เปลี่ยนเป็นค่าอื่นเฉพาะเมื่อ ver= ของ CSS และ JS ตรงกับเวอร์ชันของ WordPress
* เพราะเป็นการเปลี่ยนค่าแทนการลบ ver= แคชของเบราว์เซอร์จึงอัปเดตแม้หลังอัปเดต WordPress
*/
function wpdr_replace_wp_version_query( $src ) {
if ( ! is_string( $src ) || '' === $src ) {
return $src;
}
$query = wp_parse_url( $src, PHP_URL_QUERY );
if ( empty( $query ) ) {
return $src;
}
parse_str( $query, $args );
$wp_version = get_bloginfo( 'version' );
if ( isset( $args['ver'] ) && $args['ver'] === $wp_version ) {
$src = add_query_arg( 'ver', substr( wp_hash( $wp_version ), 0, 10 ), $src );
}
return $src;
}
add_filter( 'style_loader_src', 'wpdr_replace_wp_version_query', 20 );
add_filter( 'script_loader_src', 'wpdr_replace_wp_version_query', 20 );
ประเด็นสำคัญมีดังนี้
- ฟิลเตอร์
the_generatorมีผลทั้งกับ generator tag ใน HTML และ generator ในฟีด ส่วนremove_action( 'wp_head', 'wp_generator' );ลบเฉพาะแท็กใน HTML จึงไม่มีผลกับฟีด - หากลบ
?ver=ทั้งหมด เมื่ออัปเดตปลั๊กอินหรือธีม เบราว์เซอร์อาจใช้ CSS และ JS เก่าต่อไป โค้ดข้างต้นจะเปลี่ยนเป็นสตริงอื่นที่สร้างจากเวอร์ชันเฉพาะเมื่อค่าตรงกับเวอร์ชันของ WordPress เมื่ออัปเดต WordPress ค่าก็เปลี่ยนตาม แคชจึงถูกอัปเดต ver=ที่ปลั๊กอินหรือธีมระบุหมายเลขเวอร์ชันของตัวเองไว้ จะไม่ถูกเปลี่ยน
เมื่อบันทึกแล้ว ให้ใช้คำสั่งของขั้นตอนที่ 1 ตรวจว่า generator ไม่แสดงออกมา และในรายการ ver= ไม่เหลือเวอร์ชันของ WordPress หากใช้ปลั๊กอินแคช ให้ล้างแคชก่อนแล้วจึงตรวจสอบ
ขั้นตอนที่ 4: หยุดเฮดเดอร์ X-Powered-By
X-Powered-By เป็นเฮดเดอร์ที่ PHP เพิ่มเอง ไม่ใช่ WordPress และทำให้รู้เวอร์ชันของ PHP หากต้องการหยุด ให้ตั้ง expose_php เป็น Off ในไฟล์ตั้งค่า PHP (php.ini) ค่านี้เปลี่ยนได้ใน php.ini เท่านั้น
expose_php = Off
ตัวอย่างสำหรับ VPS ที่ใช้ Ubuntu และ PHP-FPM (ให้ปรับ 8.3 ให้ตรงกับเวอร์ชัน PHP ที่ใช้)
# ตรวจการตั้งค่าปัจจุบัน
grep -n '^expose_php' /etc/php/8.3/fpm/php.ini
# เมื่อแก้ไขแล้วให้รีสตาร์ต PHP-FPM
sudo systemctl restart php8.3-fpm
php.ini ของแพ็กเกจ Ubuntu ตั้งค่าเริ่มต้นเป็น expose_php = Off อยู่แล้ว หากเฮดเดอร์ยังแสดงอยู่ ให้ตรวจว่ามีการใช้ php.ini ไฟล์อื่นหรือไม่ บนเซิร์ฟเวอร์เช่า บางแห่งเปลี่ยนได้จากการตั้งค่า PHP ในหน้าจัดการ แต่บางแห่งเปลี่ยนไม่ได้
หากเปลี่ยน php.ini ไม่ได้ ก็มีวิธีถอดเฮดเดอร์ด้วยโค้ดของ WordPress อย่างไรก็ตาม send_headers เป็นฮุกที่ทำงานเมื่อ WordPress แสดงหน้าฝั่งหน้าบ้าน จึงไม่มีผลกับหน้าผู้ดูแลและหน้าล็อกอิน
add_action( 'send_headers', function () {
header_remove( 'X-Powered-By' );
} );
ขั้นตอนที่ 5: ปฏิเสธการเข้าถึง readme.html และ license.txt
readme.html และ license.txt เป็นไฟล์ของแกนหลัก WordPress readme.html ในปัจจุบันไม่มีหมายเลขเวอร์ชัน แต่ก็รู้ได้ว่าใช้ WordPress อยู่
ต่อให้ลบไป เมื่ออัปเดตแกนหลัก WordPress ไฟล์ก็จะกลับมาอีก การปฏิเสธการเข้าถึงที่เว็บเซิร์ฟเวอร์จึงทำได้ง่ายกว่าการลบ
กรณี Apache (.htaccess)
เพิ่มต่อท้ายใน .htaccess ที่อยู่ในโฟลเดอร์ของ WordPress ส่วนระหว่าง # BEGIN WordPress และ # END WordPress WordPress จะเขียนทับเอง จึงให้เขียนไว้นอกส่วนนั้น เป็นรูปแบบของ Apache 2.4 (Order และ Deny from all เป็นรูปแบบเก่าของ Apache 2.2)
<FilesMatch "^(readme\.html|license\.txt)$">
Require all denied
</FilesMatch>
กรณี Nginx
เพิ่มลงในบล็อก server ของไซต์ ตรวจสอบการตั้งค่า แล้วโหลดใหม่
location ~* ^/(readme\.html|license\.txt)$ {
deny all;
}
sudo nginx -t && sudo systemctl reload nginx
หลังเพิ่มแล้ว ให้รัน curl -s -o /dev/null -w '%{http_code}\n' https://example.com/readme.html และตรวจว่าได้ 403
สิ่งที่ซ่อนแล้วก็ยังป้องกันไม่ได้
- เนื้อหาของไฟล์ CSS และ JS ของ WordPress ต่างกันเล็กน้อยในแต่ละเวอร์ชัน จึงเดาเวอร์ชันได้หากดึงไฟล์มาเปรียบเทียบ
- บางครั้งรู้เวอร์ชันของปลั๊กอินได้จาก
readme.txtหรือver=ของปลั๊กอินและธีม สิ่งที่ถูกเล็งง่ายกว่าคือช่องโหว่ของปลั๊กอิน มากกว่าแกนหลัก - ผู้ที่ล็อกอินหน้าผู้ดูแลได้ยังเห็นเวอร์ชันเหมือนเดิม
ด้วยเหตุนี้ นอกจากการซ่อนเวอร์ชัน ควรทำควบคู่กับการอัปเดตแกนหลัก ปลั๊กอิน และธีม การลบปลั๊กอินที่ไม่ได้ใช้ และการปิดการแก้ไขไฟล์ในหน้าผู้ดูแล
สรุป
- จุดหลักที่แสดงเวอร์ชันคือ generator ใน HTML และฟีด และ
?ver=ของ CSS และ JS ส่วน/wp-json/ของ REST API ไม่มีเวอร์ชันของ WordPress รวมอยู่ add_filter( 'the_generator', '__return_empty_string' );ลบ generator ได้ทั้งใน HTML และฟีด- อย่าลบ
?ver=แต่ให้เปลี่ยนเฉพาะค่าที่ตรงกับเวอร์ชันของ WordPress จะไม่ทำให้การอัปเดตแคชหยุดชะงัก X-Powered-Byหยุดได้ด้วยexpose_php = Offใน php.ini- ต่อให้ลบ
readme.htmlก็จะกลับมาเมื่ออัปเดต จึงให้ปฏิเสธการเข้าถึงที่เว็บเซิร์ฟเวอร์ - การซ่อนเป็นเพียงมาตรการเสริม สิ่งสำคัญที่สุดคืออัปเดตต่อไปเรื่อย ๆ
บทความที่เกี่ยวข้อง:
- วิธีตั้งค่า security header ของ WordPress | แยกตาม .htaccess, Nginx และ functions.php
- วิธีจำกัดการเข้าถึง REST API ของ WordPress
- ปิดตัวแก้ไขไฟล์ธีมและปลั๊กอินของ WordPress | DISALLOW_FILE_EDIT
มีคำถามเกี่ยวกับ WordPress ไหม ผู้เผยแพร่เว็บไซต์นี้อยู่ที่ญี่ปุ่นและตอบเป็นภาษาญี่ปุ่น ติดต่อ (ภาษาญี่ปุ่น)