WordPress 用的人多,插件和主题又基本决定了它的安全底线。同一个插件换个大版本,风险面就完全变了。所以要摸清一个 WordPress 站点,第一步永远是同一件事:它装了什么,是什么版本。WPScan 专门干这件事。
我在自己机器上用 Docker 搭了一个靶场,从装库文件到跑出 SUCCESS,前后十五分钟。下面这条路径就是我走过的,命令和输出都能照抄。前提是你只能在自己有权测试的站点上用,练手请自己搭靶场。
一、它到底在做什么
WPScan 站在普通访客的位置,从网页表面收集信息:WordPress 版本、装了哪些主题和插件、有哪些用户名、哪些入口开着。拿到这些之后再去对官方漏洞库,告诉你哪几样有已知 CVE。
| 探测点 | 它靠什么线索 |
|---|---|
| WordPress 核心版本 | 首页 meta 标签、/readme.html、RSS 输出 |
| 主题 | 主题目录名、style.css 文件头 |
| 插件 | 页面里露出的插件路径、插件目录探测 |
| 用户名 | /?author=1 的跳转、RSS 作者字段、登录报错文案 |
| 配置备份文件 | 常见备份文件名的字典探测 |
| 入口外露 | xmlrpc.php、wp-cron.php 是否直接可达 |
| 密码强度 | 走登录接口逐个试密码 |
它的输出分两层,新手容易混在一起。探测层的结果不用账号也不用 token,装完就有;漏洞层要把探测到的东西对到官方库里,得有一个免费 API token,在 wpscan.com 注册后每天 25 次查询。刚开始只用探测层就够了,一个用户名加一份插件清单,后面的事都从这里长出来。
二、先把库文件弄好
Kali 上两条命令装完:
sudo apt install -y wpscan
wpscan --version # 实测输出:Version 3.8.28
第一次用之前更新漏洞库,文件放在 ~/.wpscan/db/,更新完大概 26MB,八个文件:metadata.json 是版本和指纹总表,wp_fingerprints.json 是文件指纹清单,dynamic_finders.yml 管动态探测规则,另外还有 timthumb 字典和备份、导出文件的字典。
我在这台机器上卡住了。到 data.wpscan.org 直连只有每秒几 KB,还时不时 SSL 握手失败,wpscan --update 半小时下不完一个文件。官方在 GitHub 上说过手动放库文件是被支持的,于是换成从官方 Docker 镜像里把库拷出来,镜像里烘焙了发布时的完整库:
docker pull docker.1ms.run/wpscanteam/wpscan
docker create --name wpscantmp docker.1ms.run/wpscanteam/wpscan
docker cp wpscantmp:/wpscan/.cache/wpscan/db/. /root/.wpscan/db/
docker rm wpscantmp
ls /root/.wpscan/db # 上面那八个文件齐了
之后每次扫描加一个参数,告诉它库已经备好,别联网检查更新:
wpscan --url <目标> --no-update
网络正常的环境照常用 wpscan --update 就行。--no-update 只跳过启动时的联网检查,扫描功能不受影响。
三、八个参数够用了
完整列表看 wpscan -hh,新手先记住这八个:
| 参数 | 干什么用 | 怎么记 |
|---|---|---|
| --url URL | 目标网址 | 一切的起点,必填 |
| -e u | 枚举用户名 | u 就是 users,第一个该跑的 |
| -e ap / -e at | 列出全部插件 / 主题 | ap 就是 all plugins |
| -U admin -P 字典 | 指定用户名和字典试密码 | U 是 usernames,P 是 passwords |
| --password-attack xmlrpc|wp-login | 选从哪条门试密码 | 一般不用手动选,工具挑快的 |
| --detection-mode passive|mixed|aggressive | 探测力度三档 | aggressive 最细,请求也最多 |
| --no-update | 跳过启动时的联网检查 | 慢网的救命参数 |
| -t N / --throttle MS | 线程数 / 每请求间隔毫秒 | 对真站请主动降速 |
枚举选项 -e 可以连写,比如 -e ap,u。常用的值有 vp(有已知漏洞的插件)、ap(所有插件)、p(热门插件)、vt 和 at、t(主题相关)、tt(timthumb 脚本)、cb(配置备份)、dbe(数据库导出)、u(用户,默认查 1 到 10)、m(媒体文件)。
有件事我一开始想不通:新版 WPScan 默认只自动查主题,插件必须显式加 -e ap。第一次默认扫描里那句 No plugins Found,多半就是这个原因,跟人家站上有没有插件没关系。
四、搭靶场,跑第一份报告
四步,十五分钟:
① 用 Docker 搭一个带弱密码的 WordPress (约 5 分钟)
② 跑一次全景扫描,学会读输出 (约 70 秒)
③ 枚举用户名,拿到"爆破靶子" (约 30 秒)
④ 拿字典试密码,看到 [SUCCESS] (约 10 秒)
4.1 起数据库和 WordPress
先起数据库,再起 WordPress,8080 端口留给扫描:
docker run -d --name wpdb -e MYSQL_ROOT_PASSWORD=root123 \
-e MYSQL_DATABASE=wordpress mysql:5.7
sleep 25
docker run -d --name wp -p 8080:80 --link wpdb:mysqlhost \
-e WORDPRESS_DB_HOST=mysqlhost:3306 \
-e WORDPRESS_DB_USER=root \
-e WORDPRESS_DB_PASSWORD=root123 \
wordpress:latest
sleep 6
docker ps --format '{{.Names}} {{.Status}} {{.Ports}}'
然后用一次请求走完安装向导。这里得故意留一个弱密码 admin/admin123,给第四步的密码攻击当靶子:
curl -s 'http://127.0.0.1:8080/wp-admin/install.php?step=2' \
--data 'weblog_title=LabWP&language=&user_name=admin&admin_password=admin123&admin_password2=admin123&pw_weak=on&blog_public=1&admin_email=admin@lab.local'
返回页面里有 Success! 就说明装好了。打开 /wp-login.php 用 admin/admin123 能进后台,靶场就绪。
4.2 第一次全景扫描
wpscan --url http://127.0.0.1:8080 --no-update
[+] URL: http://127.0.0.1:8080/ [127.0.0.1]
Interesting Finding(s):
[+] Headers
| - Server: Apache/2.4.69 (Debian)
| - X-Powered-By: PHP/8.3.36
| Found By: Headers (Passive Detection)
[+] XML-RPC seems to be enabled: http://127.0.0.1:8080/xmlrpc.php
[+] WordPress readme found: http://127.0.0.1:8080/readme.html
[+] The external WP-Cron seems to be enabled: http://127.0.0.1:8080/wp-cron.php
[i] The WordPress version could not be detected.
[+] WordPress theme in use: twentytwentyfive
[+] Enumerating All Plugins (via Passive Methods)
[i] No plugins Found.
[+] Requests Done: 1441
[+] Elapsed time: 00:01:10
一个刚装好的干净站,报告已经交出六条信息:
| 发现 | 为什么值得看 |
|---|---|
| Server: Apache / PHP 8.3.36 | PHP 是 WordPress 的运行底座,版本决定了哪批已知漏洞存在 |
| XML-RPC enabled | 一次请求能打包多件事,试密码时最省流量 |
| readme.html found | 自带说明文件没删,版本和历史都对外露着 |
| WP-Cron enabled | 定时任务接口对外开放,常见的骚扰点 |
| version not detected | 反探测挡住了它,但版本未必真的未知 |
| No plugins Found | 默认的被动探测找不到不露面的插件 |
最后两行是这次体检的消耗:1441 个请求,70 秒。打自己的靶场无所谓,打真站之前先掂量一下这两个数字。
4.3 枚举用户名
wpscan --url http://127.0.0.1:8080 --no-update -e u
[i] User(s) Identified:
[+] admin
| Found By: Rss Generator (Passive Detection)
| Confirmed By:
| Author Id Brute Forcing - Author Pattern (Aggressive Detection)
| Login Error Messages (Aggressive Detection)
一条命令,三个不相干的角度都对上了同一个名字。这三种方法为什么都能读出名字?
- RSS Generator:不少站的 RSS 会把作者写进 feed,名字直接是明文。
- Author Id 枚举:访问 /?author=1,WordPress 自动跳到作者档案页,跳转地址里就藏着用户名。
- 登录报错文案:密码输错时系统会提示该用户存在但密码不对,这条报错本身就是探测器。
有了用户名,密码攻击才有靶子。只要任何一处露了名,就够了。
4.4 插件点名:找出隐身的 Akismet
上面说 No plugins Found,可 Akismet 明明在装着。默认的被动探测只看首页 HTML 有没有主动提到插件路径,Akismet 开着但不在前台露面,就隐身了。点名确认一下:
wpscan --url http://127.0.0.1:8080 --no-update \
--plugins-list akismet,hello-dolly --plugins-detection aggressive
[+] akismet
| Location: http://127.0.0.1:8080/wp-content/plugins/akismet/
| Latest Version: 5.7.2
| Found By: Known Locations (Aggressive Detection)
| - status: 403
原理很朴素:对着常见插件路径字典挨个发请求,看目录在不在。返回 403 表示目录存在但禁止访问,这就是它在这的硬证据;返回 404 就是没有。
三档探测的分工也就清楚了。passive 是站远了看一眼,快,对目标几乎没影响;aggressive 是挨家挨户敲门,准,慢,请求多。隐身插件只能用 aggressive 喊出来。
4.5 密码攻击:看到 SUCCESS
先备一本小字典,每行一个密码,然后开打:
wpscan --url http://127.0.0.1:8080 --no-update -U admin -P /tmp/wp/dict.txt
[+] Performing password attack on Xmlrpc against 1 user/s
[SUCCESS] - admin / admin123
[+] Valid Combinations Found:
| Username: admin, Password: admin123
这份输出怎么读:
- Performing password attack on Xmlrpc 说明工具选了 XMLRPC 通道,因为一个请求能打包大约 100 个候选密码,由 --multicall-max-passwords 控制,比网页登录框快得多。
- SUCCESS 那一行就是试出来的明文组合。
- 如果目标的 xmlrpc 被管理员关了,工具会自动换 wp-login 通道,一个密码一个请求,慢但通用。
四步串起来是一条链:全景扫描看站长什么样,用户枚举看谁有账号,插件清单看装了什么,密码攻击看能不能进。WPScan 的参数不是一堆孤立开关,都是围着这条链设计的。
五、几条能直接抄的命令
# ① 隐身扫描:随机 UA + 只做被动探测(被 WAF 盯着的站首选)
wpscan --url https://站 --stealthy
# ② 全枚举一次打满(靶场随意;真站请配 --throttle 降速)
wpscan --url https://站 --no-update -e ap,at,cb,dbe,u1-10 -t 10
# ③ 带上 API token,报告加漏洞层
wpscan --url https://站 --api-token YOUR_TOKEN
# ④ 点名精确探测,不做全字典
wpscan --url https://站 --no-update \
--plugins-list akismet,elementor --themes-list twentytwentyfive \
--detection-mode aggressive
# ⑤ 强制 XMLRPC 通道试密码;被关时改走 wp-login
wpscan --url https://站 --no-update -U admin -P dict.txt --password-attack xmlrpc
# ⑥ 规定时限,到点自动收工
wpscan --url https://站 --no-update --max-scan-duration 300
# ⑦ 缓存管理(参数变了记得清)
wpscan --url https://站 --clear-cache
两个开关各管一件事,别搞混:--update 管探测层数据的新鲜度,--api-token 管有没有漏洞报告层。
六、七个坑
- 坑一,更新卡死。data.wpscan.org 在部分网络极慢甚至握手失败,--update 永远下不完。用官方镜像 docker cp 取库,之后加 --no-update。
- 坑二,No WPScan API Token given 不是报错。它只是提醒你这次只有探测层,没有漏洞匹配。没 token 照样能完整扫描。
- 坑三,version not detected 不等于版本未知。人工核一遍首页源码(curl -s 站点 | grep generator)和 readme.html 是标准动作。
- 坑四,No plugins Found 不等于没插件。补救顺序是先 -e ap 跑全量字典,慢但全;再 --plugins-list 名字 --plugins-detection aggressive 点名,快且准。
- 坑五,请求量是肌肉秤。基础扫描 1441 个请求 70 秒,全枚举 7000 多个请求约 10 分钟。对生产站点等于做一次小压测,容易触发防护,也会在日志里留下痕迹,没有书面授权就是违规。真站必配 --throttle 200,最好再加 --max-scan-duration 兜底。
- 坑六,看清密码攻击走的是哪条通道。Xmlrpc 是一包多试的快通道,被关掉后自动转 wp-login 慢通道,同样的字典耗时差十倍。
- 坑七,新装的 WordPress 也会报一堆发现。XML-RPC、readme.html、wp-cron 都在标准加固清单上,不算误报。工具报得凶和站真的有漏洞,中间还隔着一层上下文。
七、小结
WPScan 大概可以记成三件事的合体:查 WordPress 版本,拿用户名名单,试密码。四步路线是:
--url 看全景 → -e u 找人 → -e ap 找插件 → -U -P 试密码
每一步的答案都明明白白出现在输出里。Requests Done 和 Elapsed time 是这次体检的子弹和体力,要不要打、打到什么程度,先看这两个数再决定。
最实在的一课还是自己动手:拿一台自己的机器,从 docker run 一路走到 SUCCESS - admin / admin123,十五分钟就能走通。工具用过一遍,再谈熟练。
本文所有操作都在自建靶场完成。扫描和爆破他人站点可能触犯法律,请只在有明确授权的范围内做安全测试。