- 网络安全
- 漏洞扫描
- 渗透测试
- 应用安全
- CLI
【免费下载链接】wpscan
WPScan WordPress security scanner. Written for security professionals and blog maintainers to test the security of their WordPress websites. Contact us via contact@wpscan.com
WPScan 是面向 WordPress 站点的安全扫描器,其核心能力之一是在不知道插件版本号的情况下,仅凭远程可访问的文件内容推断出插件版本。本篇文章以wysiwyg-widgets插件的CHANGELOG.md测试固件为切入点,完整讲解 WPScan Dynamic Finder(动态发现器)体系中BodyPattern与 ChangeLog 组合的工作原理、配置语法、被动/主动扫描差异以及对应的测试验证方法。读完本文,你将掌握如何阅读 WPScan 的dynamic_finders.yml配置、理解版本正则的书写规则,并能够独立验证某个插件版本发现规则是否按预期工作。
引言:为什么 CHANGELOG 会成为版本指纹
WordPress 插件作者通常会在插件目录下放置一个CHANGELOG.md或changelog.txt文件,逐条记录每个版本的变更内容。这类文件天然包含"版本号 + 日期"的结构化文本,且绝大多数站点不会对插件目录下的静态文件做访问限制——这意味着攻击者或安全扫描器可以远程读取它。
WPScan 敏锐地利用了这一点:只要在响应正文中匹配到版本号模式,就能确定插件当前安装的版本,进而结合漏洞库判断该版本是否存在已知安全风险。wysiwyg-widgets插件的CHANGELOG.md就是这样一个典型样本,它被完整保存在仓库的 spec/fixtures/dynamic_finders/plugin_version/wysiwyg-widgets/change_log/CHANGELOG.md 中,作为 WPScan 测试"ChangeLog 类版本发现器"的固件数据。
从该固件内容可以看到,其版本条目采用= 版本号 - 日期 =的格式,例如:
= 2.3.8 - October 25, 2017 =Misc. textual improvements.
这种固定格式正是 WPScan 版本正则的匹配目标。 ## WPScan 动态发现器(Dynamic Finder)体系概览 在深入 ChangeLog 之前,有必要先了解 WPScan 把"插件版本发现"抽象成了什么。从源码结构看,WPScan 的版本发现分为两大类:静态类(如 Readme 文件名枚举)和动态类(Dynamic Finder)。动态发现器通过 [lib/wpscan/db/dynamic_finders/base.rb](https://link.gitcode.com/i/7167833d13813fa85bc06bde3c9f3f85) 统一管理,其中明确列出了六种被允许的发现器类: ```ruby @allowed_classes ||= %i[Comment Xpath HeaderPattern BodyPattern JavascriptVar QueryParameter ConfigParser]也就是说,WPScan 可以从插件目录中的注释(Comment)、XPath 节点、HTTP 响应头(HeaderPattern)、响应正文(BodyPattern)、JavaScript 变量(JavascriptVar)、URL 查询参数(QueryParameter)以及配置文件解析(ConfigParser)七个维度(其中 Readme 走独立逻辑)来定位版本信息。CHANGELOG 文件属于"响应正文中匹配正则"这一类,因此落到BodyPattern上。
每个插件的动态发现器配置都存放在数据库文件dynamic_finders.yml中(运行时为DB_DIR/dynamic_finders.yml,测试时使用 spec/fixtures/db/dynamic_finders.yml)。插件相关的加载逻辑在 lib/wpscan/db/dynamic_finders/plugin.rb 中实现,它会把 YAML 配置实例化为具体的 Ruby 类,并区分被动(passive)与主动(aggressive)两类配置。
ChangeLog 配置逐项拆解:以 wysiwyg-widgets 为例
在 spec/fixtures/db/dynamic_finders.yml 中,wysiwyg-widgets插件的配置如下:
wysiwyg-widgets: ChangeLog: class: BodyPattern path: CHANGELOG.md pattern: !ruby/regexp /^= (?<v>\d+\.[\.\d]+)/i version: true Readme: path: readme.txt逐项解读:
| 配置键 | 值 | 含义 |
|---|---|---|
class | BodyPattern | 指定该发现器使用的实现类,决定版本号如何从响应中提取 |
path | CHANGELOG.md | 相对插件目录的文件路径,仅在主动(aggressive)模式下请求;值为空则走被动模式 |
pattern | /^= (?<v>\d+\.[\.\d]+)/i | 应用于响应正文的正则,命名捕获组v用于提取版本号 |
version | true | 标记该配置是"版本发现器",会被纳入versions_finders_configs |
Readme条目没有class和version字段,说明它只是借用动态发现器系统来获取候选文件名列表(该细节在 base.rb 的注释中有明确说明),并非真正的版本指纹。
版本正则的匹配逻辑
配置中的正则^= (?<v>\d+\.[\.\d]+)需要与 CHANGELOG 的条目格式精确配合:
^=要求行首是等号和空格——这正是 CHANGELOG 版本条目的书写习惯;(?<v>...)命名捕获组把版本号单独提取出来;\d+\.[\.\d]+允许形如2.3.8、2.0.1这类点分数字序列;/i忽略大小写,增强对不同作者书写习惯的兼容性。
对照固件内容,= 2.3.8 - October 25, 2017 =这行会命中正则,捕获组v得到2.3.8——与 expected.yml 中期望的结果完全一致:
wysiwyg-widgets: ChangeLog: number: 2.3.8 found_by: Change Log (Aggressive Detection) interesting_entries: - 'http://wp.lab/wp-content/plugins/wysiwyg-widgets/CHANGELOG.md, Match: ''= 2.3.8'''found_by中的 "Change Log" 字样来源于配置键名ChangeLog(即 finder_name),"Aggressive Detection" 则表明该发现方式被归入主动扫描类别。
BodyPattern 实现原理与扫描流程
类的常量体系
lib/wpscan/finders/dynamic_finder/version/body_pattern.rb 定义了BodyPattern基类,其核心是默认常量的声明:
def self.child_class_constants @child_class_constants ||= super.merge(PATTERN: nil, CONFIDENCE: 60) end每个具体插件对应的BodyPattern子类由 finder.rb 中的create_child_class动态生成——它遍历父类常量(PATH、PATTERN、CONFIDENCE),并用 YAML 配置中同名的小写键覆盖默认值。因此wysiwyg-widgets的 ChangeLog 子类运行时等价于:
PATTERN = /^= (?<v>\d+\.[\.\d]+)/i PATH = 'CHANGELOG.md' CONFIDENCE = 60其中CONFIDENCE: 60是 BodyPattern 类的默认置信度,也可通过 YAML 里的confidence键覆盖(测试见 body_pattern_spec.rb)。
find 方法:匹配即取证
body_pattern.rb 中的find方法完成实际匹配:
def find(response, _opts = {}) return unless response.code != 404 && response.body =~ self.class::PATTERN create_version( Regexp.last_match[:v], interesting_entries: ["#{response.effective_url}, Match: '#{Regexp.last_match}'"] ) end关键点有三:
- 404 排除:响应码为 404 时不尝试匹配,避免把"文件不存在"误判为版本证据;
- 命名捕获组取值:
Regexp.last_match[:v]取出正则中的v组,交给 version/finder.rb 的create_version包装成WPScan::Model::Version对象,并写入found_by与confidence; - 取证条目:
interesting_entries记录"哪个 URL 上匹配到了什么",这会在扫描报告的--interesting-findings或详细输出中呈现,方便审计者回溯。
被动与主动两种触发路径
finder.rb 中的基类方法界定了两种模式:
def passive(opts = {}) return if self.class::PATH # 先查首页响应,再查 404 页响应 ... end def aggressive(opts = {}) return unless self.class::PATH find(Browser.get(target.url(self.class::PATH)), opts) end- 被动(Passive):仅当配置没有
path时可用,直接对已抓取到的首页与 404 页面响应做正文匹配,不发起额外请求; - 主动(Aggressive):仅当配置存在
path时可用,显式请求插件目录 + path(这里是/wp-content/plugins/wysiwyg-widgets/CHANGELOG.md)后再匹配。
wysiwyg-widgets的 ChangeLog 配置带path,因此它是典型的主动发现器:只有当扫描命令启用了 Aggressive Detection(如--plugins-detection aggressive)时,WPScan 才会去请求该 CHANGELOG 文件。这也解释了 expected.yml 中found_by为何标注 "Aggressive Detection"。
固件与测试:验证规则是否正确工作
WPScan 为每个动态发现器都准备了"期望结果 + 响应固件"的双重验证机制,确保数据库更新不会破坏扫描器。相关测试入口是 spec/lib/finders/dynamic_finder/plugin_version_spec.rb,文件头注释说明了新增一条动态发现器配置时需要同步维护的文件:
spec/fixtures/dynamic_finders/expected.yml:登记期望的版本号、found_by 与 interesting_entries;spec/fixtures/dynamic_finders/plugin_version/<slug>/<finder_name>/:存放模拟的远程响应文件(即本篇文章讨论的CHANGELOG.md固件所在目录)。
该 spec 会遍历versions_finders_configs中所有插件的所有版本发现器(见 plugin.rb),动态生成#passive与#aggressive两组用例。针对wysiwyg-widgets的主动用例,会先stub_request(:get, plugin.url(config['path'])),再把固件内容作为响应体喂给df_stubbed_response,最后断言:
- 返回对象是
WPScan::Model::Version实例; version.number等于expected.yml中的2.3.8;interesting_entries与期望的http://wp.lab/wp-content/plugins/wysiwyg-widgets/CHANGELOG.md, Match: '= 2.3.8'一致;found_by为 "Change Log (Aggressive Detection)"。
值得注意的是 plugin_version_spec.rb 中描述的优化策略:这类生成式 spec 数量高达数万条,绝大多数被标记为slow,只在主分支全量测试中运行;PR 的覆盖率统计(--tag ~slow)则保证"父类/路径/版本键"每个组合至少有一个未标记的用例,让底层代码路径在 PR 阶段至少被执行一次。
版本正则的实战编写建议
参考wysiwyg-widgets的配置与固件,可以总结出编写高质量 ChangeLog 版本指纹的要点:
- 观察真实格式再定正则:先确认目标插件的 CHANGELOG 用
= x.y.z - date =、## Version x.y.z、v1.2.3还是纯文本列表,正则必须与之一一对应; - 使用命名捕获组
v:BodyPattern 的find只认Regexp.last_match[:v],不写命名组将导致版本号提取失败; - 行首锚定防止误报:
^=能避免匹配到正文中偶然出现的版本号字符串,降低假阳性; - 善用
/i与宽松的数字模式:不同作者的大小写、分隔符习惯差异较大,\d+\.[\.\d]+比\d+\.\d+\.\d+更能容忍2.0.1与2.3.8这类变长结构; - 考虑 404 与重定向:
find方法显式排除了 404 响应,配置时应确认目标文件确实可公开访问。
对 CHANGELOG 格式多样性的兼容还体现在文件名上:仓库中大量插件使用change_log.txt、changelog.txt等不同命名(可参见 spec/fixtures/db/dynamic_finders.yml 中大量path: change_log.txt条目),因此path字段必须精确到目标插件实际使用的文件名。
小结
从wysiwyg-widgets插件的CHANGELOG.md固件出发,本文完整还原了 WPScan 利用 ChangeLog 文件做版本指纹的链路:YAML 配置(spec/fixtures/db/dynamic_finders.yml)→ 动态子类生成(finder.rb的create_child_class)→ 主动请求path指定文件(finder.rb#aggressive)→ 正则匹配与版本包装(body_pattern.rb#find+version/finder.rb#create_version)→ 测试验证(plugin_version_spec.rb+expected.yml)。
这套机制的价值在于:安全人员无需访问 WordPress 后台或数据库,仅凭一个公开可读的变更日志文件就能确认插件版本,进而评估其是否处于已知漏洞影响范围。理解 BodyPattern 与 ChangeLog 的组合,是读懂 WPScan 版本发现体系、甚至为 WPScan 数据库贡献新指纹的第一步。
- 网络安全
- 漏洞扫描
- 渗透测试
- 应用安全
- CLI
【免费下载链接】wpscan
WPScan WordPress security scanner. Written for security professionals and blog maintainers to test the security of their WordPress websites. Contact us via contact@wpscan.com
相关推荐
从 CHANGELOG.md 到插件版本指纹:WPScan 动态查找器如何用 BodyPattern 识别 array-partition 插件版本
从 CHANGELOG.md 到插件版本指纹:WPScan 动态查找器如何用 BodyPattern 识别 array partition 插件版本 导读 本文
网络安全漏洞扫描渗透测试应用安全CLI利用 CHANGELOG 指纹识别 WordPress 插件版本:WPScan BodyPattern 动态查找器与 Document Gallery 实例全解析
利用 CHANGELOG 指纹识别 WordPress 插件版本:WPScan BodyPattern 动态查找器与 Document Gallery 实例全解
网络安全漏洞扫描渗透测试应用安全CLI从 CHANGELOG.md 反推版本:WPScan Dynamic Finder 如何用 ChangeLog 指纹识别 WordPress 插件版本
从 CHANGELOG.md 反推版本:WPScan Dynamic Finder 如何用 ChangeLog 指纹识别 WordPress 插件版本 导读 本
网络安全漏洞扫描渗透测试应用安全CLI
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考