周末给自己的站做了一次只读巡检。所谓只读,是全程只用 GET,外加一条空的 POST:不装扫描器、不跑目录字典、不改任何配置,就是把 curl 能看见的东西从头到尾看一遍。看之前我以为顶多翻出几条缺的安全头,看完发现站点主动交出去的东西比这多——版本、账号、作者页地址、一个能塞进几百次尝试的接口,还有最近十篇文章的全文。

响应头里先藏着两笔账
第一条 curl 只取了响应头,看出来的事就够写一段。好的部分先说:HSTS 在,max-age 是一年;Server 只回一个 nginx,不带版本号;Alt-Svc 挂着 h3,说明前面那一层认 QUIC。这三样是很多人第一次配服务器就会忘的,这里都有。
缺的是四个常见的加固头:X-Content-Type-Options、Referrer-Policy、X-Frame-Options、Permissions-Policy。前两个管内容嗅探和来源泄露,后两个管被第三方页面嵌框。它们不是漏洞,是浏览器给你的一次表态机会,不表态就按最宽松的默认来。
另一笔账在 Set-Cookie 里。首页响应带了一条 server_session_…,Max-Age 是 864000 秒,也就是十天,httponly 打了勾,但 Secure 和 SameSite 都没有。更麻烦的是我用 http:// 再敲了一次:站点确实 301 跳到 HTTPS,可这个 301 的响应头上同样挂着这条 cookie——也就是说它在明文连接上就下发了一次,而客户端手里拿到的这个标识,之后会在所有请求里回传。
还有一处不属于安全、属于成本:整条响应里没有 Cache-Control,也没有任何 X-Cache 类头。意思是首页那段 HTML 每次请求都现生成,缓存层没接住。访客多一个,PHP 就多跑一次。
先把这条 session cookie 的 Secure 与 SameSite 补上,再把那四个安全头一起加进去。
站点会自己报出三个名字
/readme.html 返回 200。这是 WordPress 自带的说明页,每次升级跟着改,等于在门口立了块牌子写「我是什么版本」。版本号本身不是洞,但它是挑洞的第一步——绝大多数批量扫描脚本第一件事就是拿它比对已知漏洞列表。
/wp-json/wp/v2/users 返回 200。里面是 id、显示名、slug、作者页地址,还有 avatar_urls 里那串 Gravatar 哈希,而 Gravatar 用的哈希就是邮箱的 MD5。拿到它,等于拿到一个可以离线慢慢撞的起点。
/?author=1 返回 301,跳到 /author/<slug>。这一条最容易被漏掉:不少人把 REST 的 users 端点关掉就以为收工了,可作者归档的参数还在,把数字从 1 换上去,有文章的账号一个不落地数得出来。前两条里你没写出来的 slug,这里补齐了。
先把 users 端点对未登录访客关掉,再把 author 参数的跳转掐掉——两条一起做才有意义,只做一条等于没做。
XML-RPC:一个请求里塞进几百次尝试
/xmlrpc.php 用 GET 访问是 405,看着像被拦了。换成 POST 发一个 system.listMethods,200,方法表整整齐齐列出来,system.multicall 在列。
这个方法是给批量调用设计的,用在这里就是一次 HTTP 打包上百组用户名密码,服务器的访问日志里只留一行 200。配合 wp-login.php 那个正常的 200 首页(没有任何频率限制的迹象),暴力破解的成本被压得很低——攻击者不需要发一百个请求,只需要发一个。
不用远程发布就整个关掉;确实要用,就限流并按 IP 白名单放行。
全文 RSS:最容易被忘掉的那条出口
/feed 返回 200,十条 item,每条 content:encoded 里是整篇正文,最长的那条一万四千六百多个字符,正文图、脚注、小标题全在。这不是插件的额外功能,是 WordPress 装好就这样的默认值——后台「设置 → 阅读」里那个「对于 feed 中的每篇文章,显示」默认选的是全文。
从一个运维的角度看,它占的是出口带宽和抓取配额;从内容的角度看,抓取方不加载你的统计脚本,也不经过你的广告位,读者在阅读器里读完就走。要不要防这一手是取舍,但至少该知道自己开着它。
把 feed 输出改成摘要,需要全文的读者会点进来。
本来就对的那几件,别顺手改坏
这次巡检也有几条是好消息:/wp-content/uploads/ 返回 403(目录列表是关的)、/wp-content/debug.log 404、根目录下的 .env 与 wp-config.php.bak 都是 404、随手猜的几个备份包名也是 404。robots.txt 只 Disallow 了 /wp-admin/,还留了 admin-ajax.php 的 Allow,站点地图指向 wp-sitemap.xml 且能正常打开。
这几条值得单独拎出来,是因为很多加固清单会让人继续往下关:关 REST 根路径、关站点地图、拦 admin-ajax.php。关掉的其实是功能,不是风险——REST 根路径没了,区块编辑器和站点健康检查先哑;admin-ajax 拦了,前台一半交互跟着挂;站点地图关了,搜索引擎只能靠猜。
改配置之前先想清楚:这一条关掉之后,哪个功能会跟着哑。

把这几条落到配置文件里
Nginx 的 server 段里,四个头加三条拦截。注意 add_header 在 location 里会覆盖 server 级的同名配置,别在别处重写时漏掉这几行:
add_header X-Content-Type-Options nosniff always;
add_header Referrer-Policy strict-origin-when-cross-origin always;
add_header X-Frame-Options SAMEORIGIN always;
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
location = /readme.html { return 404; }
location = /license.txt { return 404; }
location = /xmlrpc.php { deny all; }
那条 session cookie 如果来自 PHP,就补三行 ini;如果不是,得去它真正的主人那里改——面板或者主机层的会话配置:
session.cookie_httponly = 1
session.cookie_secure = 1
session.cookie_samesite = "Lax"
两个枚举入口放在子主题的 functions.php 里,动作小、影响面清楚:
// 未登录访客看不到用户列表
add_filter('rest_endpoints', function ($routes) {
if (!is_user_logged_in()) {
unset($routes['/wp/v2/users'], $routes['/wp/v2/users/(?P<id>[d]+)']);
}
return $routes;
});
// ?author=N 直接回首页
add_action('template_redirect', function () {
if (!is_admin() && isset($_GET['author']) && !is_user_logged_in()) {
wp_safe_redirect(home_url('/'), 301);
exit;
}
});
改完再跑一遍同样八条:readme.html 应该 404,users 应该 401 或空数组,?author=1 应该回首页,/xmlrpc.php 的 POST 应该 403,响应头里该有的四个都在,Set-Cookie 后面跟着 Secure 和 SameSite=Lax。
这套动作加起来只是让站少说几句话。它不会让站变安全,但会让挑软柿子的人多花几分钟——多数自动化扫描在这几分钟里就换目标了。巡检也不必天天做:配置改完一次、插件更新完一次、换服务器之后一次,再加每季度一次,足够了。
一、一手信息
- WordPress 官方文档:Hardening WordPress(默认暴露面与逐条加固的官方口径)
- WordPress 官方文档:Security(高级管理下的安全章节总览)
- WordPress 官方手册:REST API › Users(
/wp/v2/users的字段与权限说明,未登录时的可见范围) - WordPress 官方参考:rest_endpoints 钩子(用于摘除单个端点的过滤器签名)
- Nginx 官方文档:ngx_http_headers_module(
add_header的生效范围与always参数) - PHP 官方手册:Session 配置项(
session.cookie_secure、session.cookie_samesite、session.cookie_httponly) - WordPress 官方文档:WordPress Feeds(后台「摘要 / 全文」这一项的实际作用)
二、媒体报道
- MDN Web Docs:X-Content-Type-Options(
nosniff与内容嗅探的关系) - MDN Web Docs:Set-Cookie(
Secure、SameSite的取值与浏览器行为)
本文的探测数据来自本站 2026 年 10 月 19 日的实际响应,命令只有 curl -I、curl -s 与一条空参数的 system.listMethods POST;文中出现的状态码与字段均为当日实测,未使用任何扫描工具。
相关阅读
原创声明:本文由 618A 情报站整理撰写,基于公开信息与实测记录,转载请注明出处并保留原文链接。