2026年11月12日

2026年11月12日

隐藏 WordPress 版本信息的方法|generator 标签、ver=、readme.html

本文是日语原文的本地化版本。 阅读日语原文

WordPress 在初始状态下,会把自己的版本号写进页面的 HTML 和订阅源(Feed)中。攻击者可能以此为线索,查看是不是存在已知漏洞的版本。本文说明版本号会出现在哪些位置,以及隐藏它们的步骤。内容截至 2026 年 10 月。

首先要知道的是,隐藏版本号只是辅助性的措施。由于还可以从 CSS 和 JavaScript 文件的内容等推测出版本,所以即使隐藏了,也不一定能防止被识别出来。最有效的做法,是让 WordPress 核心、插件和主题保持最新。

版本号会出现的位置

位置 显示的内容 处理方式
HTML 的 <head> 形如 <meta name="generator" content="WordPress 6.9.4"> 的标签 the_generator 过滤器
RSS、Atom 订阅源 形如 <generator>https://wordpress.org/?v=6.9.4</generator> 的行 同样用 the_generator 过滤器
CSS、JavaScript 的 URL 形如 style.min.css?ver=6.9.4 的 ?ver= style_loader_src、script_loader_src 过滤器
HTTP 头 X-Powered-By: PHP/8.3.x(不是 WordPress 而是 PHP 的版本) php.ini 的 expose_php
readme.html 表明正在使用 WordPress(现在的 readme.html 中没有写版本号) 拒绝访问

REST API 的 /wp-json/ 返回的站点信息(名称、描述、URL、时区、命名空间等)中,不包含 WordPress 的版本。为了隐藏版本,没有必要删减 REST API 的输出。

步骤 1:确认当前的显示状态

请换成你自己站点的 URL,在本地电脑或服务器上执行。如果使用了缓存类插件或 CDN,修改之后请先清除缓存再确认。

# HTML 的 generator 标签
curl -s https://example.com/ | grep -i 'name="generator"'

# 订阅源的 generator
curl -s https://example.com/feed/ | grep -i '<generator>'

# 列出 CSS、JS 上附带的 ver= 的值
curl -s https://example.com/ | grep -oE 'ver=[0-9][0-9.]*' | sort | uniq -c

# HTTP 头的 X-Powered-By
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= 的列表中,有与后台“仪表盘 (Dashboard)”→“更新 (Updates)”中显示的 WordPress 版本相同的值,就说明从这些 CSS 或 JS 就能看出 WordPress 的版本。WordPress 核心或主题在加载 CSS、JS 时如果不指定版本,WordPress 的版本就会写进 ver=。

步骤 2:决定放置代码的位置

本文的代码,放在下面两处之一。

  • mu-plugin(推荐):把 PHP 文件放进 wp-content/mu-plugins/ 文件夹(没有的话就创建),无需启用操作就会被加载。更换主题后效果依然持续
  • 子主题的 functions.php:如果写进父主题的 functions.php,会在主题更新时消失

做成 mu-plugin 时,请用 wp-content/mu-plugins/hide-wp-version.php 这样的名称,把下一步的代码汇总到一个文件中保存。

步骤 3:隐藏 generator 标签和 ver= 中的版本号

<?php
/**
 * Plugin Name: Hide WordPress Version
 * Description: 隐藏 generator 标签,以及 CSS、JS 的 ver= 中出现的 WordPress 版本号。
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

// 把 HTML 的 <meta name="generator"> 和订阅源的 <generator> 清空。
add_filter( 'the_generator', '__return_empty_string' );

/**
 * 只有当 CSS、JS 的 ver= 与 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 过滤器对 HTML 的 generator 标签和订阅源的 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 不是 WordPress,而是 PHP 添加的头,可以看出 PHP 的版本。要去掉它,请在 PHP 的配置文件(php.ini)中把 expose_php 设为 Off。这个设置只能在 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

Ubuntu 软件包的 php.ini,初始状态下就是 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 核心之后它们也会恢复。与其删除,不如在 Web 服务器中拒绝访问,更省事。

Apache(.htaccess)的情况

追加到 WordPress 文件夹中的 .htaccess。# 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。

隐藏了也无法防住的事

  • WordPress 的 CSS、JS 文件的内容会因版本而略有不同,所以获取文件进行比较,就能推测出版本
  • 有时可以从插件或主题的 readme.txt 或 ver= 看出插件的版本。比起核心,更容易被盯上的是插件的漏洞
  • 能够登录后台的人,依然可以像以前一样看到版本

因此,在隐藏版本号的同时,请一并做好更新核心、插件和主题,删除不用的插件,停用后台的文件编辑等工作。

总结

  • 版本号主要出现在 HTML 和订阅源的 generator,以及 CSS、JS 的 ?ver=。REST API 的 /wp-json/ 中不包含 WordPress 的版本
  • 用 add_filter( 'the_generator', '__return_empty_string' ); 可以同时去掉 HTML 和订阅源的 generator
  • ?ver= 不要删除,只替换与 WordPress 版本相同的值,就不会妨碍缓存的更新
  • X-Powered-By 用 php.ini 的 expose_php = Off 去掉
  • readme.html 即使删除也会在更新后恢复,所以在 Web 服务器中拒绝访问
  • 隐藏只是辅助性的措施,持续更新才是最重要的

相关文章:

关于 WordPress 有疑问?本站运营方位于日本,仅以日语回复。 联系我们(日语)

お気軽にご相談ください

お見積りへ お問い合わせへ
▲