1. 项目概述为什么我们需要自定义WhatWeb插件在渗透测试或者安全评估的初期信息收集的广度和精度直接决定了后续攻击面的宽度。我们手里有Nmap、Dirb、Subfinder等一大堆工具但说到快速识别一个Web应用的技术栈——比如它用的是WordPress 6.4.2还是Laravel 10服务器是Nginx 1.18还是IIS 10有没有用上特定的JavaScript库或者暴露了某些管理接口——WhatWeb往往是那个被第一时间想起的“老伙计”。它快准而且有一个庞大的插件库支撑。但问题来了当你面对一个公司内部自研的OA系统或者某个小众的、版本迭代极快的SaaS应用时你会发现WhatWeb的默认插件库“哑火”了。它返回的结果可能是泛泛的“Apache”、“PHP”甚至干脆识别不出来。这时候通用的指纹匹配就失去了意义你需要的是针对特定目标、特定版本的精确检测。这就是掌握WhatWeb插件开发的核心价值将你的经验和对目标的理解固化成可自动化执行的检测规则让你的侦察环节从“可能有用”变成“必然生效”。简单来说WhatWeb插件就是一个用Ruby写的、包含一系列匹配规则正则表达式、MD5哈希、文本匹配等的脚本。它的工作逻辑是向目标发送HTTP请求然后分析返回的响应头、响应体、URL结构甚至Cookie等信息寻找能表明其身份特征的“指纹”。自己写插件意味着你可以为任何你想追踪的目标定制专属的探测器。2. 核心概念与插件结构拆解在动手写代码之前必须理解WhatWeb插件的基本构成。一个标准的插件文件例如my-plugin.rb就像一份结构清晰的“体检表”告诉WhatWeb检查什么、怎么判断、以及判断后输出什么结果。2.1 插件的基本骨架每个WhatWeb插件都是一个Ruby类继承自WhatWeb::Plugin。其核心结构如下Plugin.define “插件内部名称” do author “你的名字” # 作者信息 version “0.1” # 插件版本 description “这里描述你的插件是用来检测什么的例如检测自研XX管理系统” # 匹配规则将在这里定义 end这个骨架定义了插件的元信息。author、version、description这些字段在多人协作或插件管理时很有用。最重要的是Plugin.define后面的字符串这是插件的唯一内部标识符不能与其他插件重复。2.2 匹配规则Matches的多种武器插件的灵魂在于matches方法里定义的匹配规则数组。WhatWeb提供了多种“武器”来从HTTP响应中寻找指纹文本匹配:text在响应HTML中搜索特定字符串。这是最直接、最常用的方法但可能误报。matches [ { :text ‘title欢迎登录XX后台管理系统/title‘ } ]正则表达式匹配:regexp功能最强大的武器可以匹配复杂的模式如版本号。matches [ { :regexp /Powered by a href[^]MyCMS ([\d\.])/i } # 提取版本号 ]URL路径匹配:url检查特定的路径或文件是否存在。matches [ { :url ‘/admin/js/common.js‘, :text ‘var ADMIN_TOKEN‘ } # 访问特定路径并检查内容 ]状态码匹配:status利用某些路径访问会返回特定HTTP状态码如403、404来作为特征。matches [ { :url ‘/wp-config.php.bak‘, :status 200 } # 如果备份文件可访问极可能是WordPress且配置不当 ]MD5哈希匹配:md5计算特定资源如图片、JS、CSS文件的MD5值进行精确匹配。这对于识别特定版本的文件极其准确。matches [ { :url ‘/favicon.ico‘, :md5 ‘a1b2c3d4e5f678901234567890123456‘ } ]GHDB匹配:ghdb使用Google Hacking Database风格的搜索语法进行匹配本质上也是文本匹配的变体。matches [ { :ghdb ‘intitle:“Dashboard“ “Laravel“‘ } ]一个关键技巧matches数组里的多个规则是“或OR”关系只要其中任意一个匹配成功插件就会判定目标命中。如果你需要“与AND”关系比如必须同时满足A和B特征你需要在单个匹配规则里通过更复杂的正则表达式来实现或者编写更严谨的逻辑。2.3 被动匹配与主动匹配被动匹配仅分析WhatWeb最初请求目标根目录/所获得的响应。上述大多数例子都是被动匹配。它速度快对目标影响小。主动匹配通过:url参数指定额外的路径进行探测。这需要WhatWeb发起新的HTTP请求。主动匹配能获取更多信息但会增加扫描时间和网络流量也可能触发目标的安全警报。在实际开发中优先考虑被动匹配尽可能从首页中挖掘指纹。只有当被动信息不足时才谨慎地添加一两个关键的主动匹配规则例如检查特定的API端点、图标文件或配置文件。3. 从零开始编写你的第一个插件理论说得再多不如动手来一遍。假设我们要为一个虚构的“SimpleCMS”内容管理系统编写检测插件。我们通过浏览器访问其首页查看源代码发现了以下特征页面底部有!-- Powered by SimpleCMS --注释。有一个特有的CSS文件路径/assets/css/simplecms-styles-v2.css。登录页面/admin/login的标题是SimpleCMS Admin Panel。3.1 环境准备与插件存放WhatWeb插件是纯Ruby文件不需要特殊的编译环境。你只需要知道插件该放在哪里。对于系统级安装的WhatWeb插件通常位于/usr/share/whatweb/plugins/或/usr/local/share/whatweb/plugins/。需要sudo权限才能写入。对于通过Git克隆的WhatWeb插件位于克隆目录下的plugins/文件夹。个人插件目录更推荐的做法是在你的家目录下创建一个自定义插件文件夹例如~/.whatweb/plugins/然后通过WhatWeb的-p或--plugins参数指定这个路径。这样既不会污染系统目录也便于管理自己的插件。mkdir -p ~/.whatweb/plugins3.2 插件代码编写实战接下来我们基于发现的三个特征来编写插件。我们将它保存为~/.whatweb/plugins/simplecms.rb。Plugin.define “SimpleCMS” do author “YourName” version “0.1” description “Detects the SimpleCMS content management system.” website “http://example.com/simplecms” # 可选官方网址 # 匹配规则1通过HTML注释进行被动匹配高可靠性 matches [ { :name “HTML Comment”, :regexp /!--[^]*Powered by SimpleCMS[^]*--/ }, # 匹配规则2通过CSS路径进行被动匹配 { :name “CSS Path”, :regexp %r{link[^]*href[][^]*/assets/css/simplecms-styles[^]*\.css[’]} }, # 匹配规则3通过主动探测登录页标题进行匹配 { :name “Admin Login Title”, :url “/admin/login”, :text “titleSimpleCMS Admin Panel/title” } ] # 进阶提取版本号假设我们在注释中发现了版本 # 假设注释为 !-- Powered by SimpleCMS v2.5.1 -- passive do m [] if body ~ /!--[^]*Powered by SimpleCMS v?([\d\.])[^]*--/ version $1 m { :name “Version”, :version version } end m end end代码解读与注意事项:name参数给每个匹配规则起一个描述性的名字非常有用。当WhatWeb以详细模式-v运行时它会显示是哪个规则命中了目标这对于调试和确认指纹有效性至关重要。正则表达式转义注意在正则表达式中对特殊字符如引号、点号进行转义。使用%r{}语法可以避免对路径中的斜杠进行转义让正则更清晰。passive do块这是一个更灵活但稍复杂的匹配方式。它用于处理那些需要一些逻辑判断的被动匹配比如从字符串中提取版本号。body变量代表了HTTP响应体。这里我们使用正则表达式捕获组([\d\.])来提取版本号并将其赋值给:version字段。WhatWeb会漂亮地输出这个版本信息。可靠性权衡规则1注释和规则3登录页标题的可靠性很高。规则2CSS路径如果路径不是唯一的可能存在误报。好的插件应该组合使用高可靠性和中等可靠性的规则以提高整体识别的准确率。3.3 测试与调试你的插件插件写好了不测试就等于没写。WhatWeb提供了强大的测试功能。基础测试# 使用你的自定义插件目录扫描一个测试目标 whatweb -p ~/.whatweb/plugins/ http://your-test-site.com如果插件生效你会在输出中看到SimpleCMS被识别出来。详细模式调试必备whatweb -p ~/.whatweb/plugins/ -v http://your-test-site.com-v参数会显示详细的匹配过程。你会看到类似下面的输出[] SimpleCMS | * Detected by: HTML Comment | * Version: 2.5.1 (from passive match)这能让你确认是哪个规则起了作用提取的版本号是否正确。模拟测试无网络请求有时你需要针对一个保存的HTML文件进行测试或者不想发出真实请求。# 将目标网页保存为文件 curl -s http://your-test-site.com test_page.html # 使用WhatWeb的模拟输入功能 whatweb -p ~/.whatweb/plugins/ --input-file test_page.html这种方式非常适合在插件开发初期进行快速迭代和调试。实操心得调试插件时我习惯先用curl -i把目标的HTTP头和完整响应体保存下来然后在一个独立的Ruby脚本或irb交互环境里测试我的正则表达式确认无误后再放入插件文件。这比反复运行完整的WhatWeb扫描要快得多。4. 高级技巧与实战场景剖析掌握了基础之后我们来探讨一些能让你插件更强大、更精准的高级技巧。4.1 精准版本号提取策略版本号是资产梳理中极具价值的信息。提取版本号有多种方法从注释/文本中正则提取如上例所示最常用。从特定文件内容中提取例如许多PHP应用在version.php或CHANGELOG.md文件中明确定义了版本。matches [ { :url “/includes/version.inc.php”, :regexp /define\(APP_VERSION, ([\d\.])\)/ } ]通过文件MD5匹配版本库这是最准确的方法。你需要事先收集目标应用各个版本关键文件如/readme.txt,/favicon.ico, 核心JS文件的MD5值建立一个小型哈希数据库。matches [ # WordPress 6.4.2 的 readme.html { :url “/readme.html”, :md5 “a1b2…”, :version “6.4.2” }, # WordPress 6.4.1 的 readme.html { :url “/readme.html”, :md5 “c3d4…”, :version “6.4.1” }, ]WhatWeb内置的WordPress插件就大量使用了这种技术实现了极其精准的版本识别。4.2 处理动态内容与避免误报现代Web应用大量使用JavaScript动态生成内容这给基于静态HTML匹配的插件带来了挑战。关注“硬编码”特征寻找那些即使在前端框架如React、Vue下也不太会变的内容例如meta标签中的生成器信息meta namegenerator contentNext.js 14.2.3 /。静态资源路径中的特征字符串/_next/static/chunks/…是Next.js的典型特征。HTTP响应头X-Powered-By: Express,Server: cloudflare。利用HTTP头headers方法可以专门匹配响应头这通常非常可靠。def headers m [] # 检查 Server 头 m { :name “Server Header”, :string server } if server ~ /^MyCustomServer\// # 检查 X-Generated-By 头 m { :name “X-Generated-By Header”, :regexp /^Django/ } if headers[“x-generated-by”] ~ /^Django/ m end组合匹配与置信度通过要求多个低可靠性的特征同时出现在同一个匹配规则内用复杂正则实现或用多个passive/headers块组合逻辑可以大幅降低误报率。WhatWeb本身没有内置的置信度权重系统所以设计严谨的匹配逻辑是开发者的责任。4.3 针对特定框架或中间件的插件思路前端框架React, Vue, Angular检测捆绑后生成的JS文件中的全局变量、特定注释或Source Map文件如.map。例如React开发构建的文件常包含webpackJsonp或__REACT_DEVTOOLS_GLOBAL_HOOK__。API框架FastAPI, Django REST检查默认的API文档路径如/docs(Swagger/OpenAPI),/redoc。观察JSON响应格式或特定的错误信息头。反向代理/负载均衡器Nginx, HAProxy, Traefik主要通过Server响应头、自定义错误页面如Nginx的404页面或特定的Cookie如AWSELB表示AWS ELB来识别。云服务/WAF识别Cloudflare、AWS CloudFront、Akamai等通常有独特的HTTP头如CF-RAY(Cloudflare),Server: cloudflare,X-Amz-Cf-Id(AWS CloudFront),X-Akamai-Transformed。5. 插件开发中的常见陷阱与优化实践即使思路正确在开发过程中也容易踩坑。下面是一些我总结的常见问题和优化建议。5.1 常见问题排查表问题现象可能原因排查步骤插件完全不生效1. 插件文件未放在正确路径或未通过-p指定。2. 插件文件名不是.rb后缀。3. 插件语法错误Ruby错误。1. 用whatweb --list-plugins -p ~/.your/plugins/查看插件是否被加载。2. 检查文件名。3. 用ruby -c your_plugin.rb检查语法。插件能加载但不匹配目标1. 匹配规则太严格正则写错。2. 目标特征已改变。3. 使用的是主动匹配(:url)但目标路径不存在或需要认证。1. 使用-v模式查看扫描过程确认WhatWeb收到了什么响应。2. 将目标的响应体保存为文件用文本编辑器或在线正则工具测试你的规则。3. 尝试用浏览器或curl手动访问你定义的:url路径。误报率高匹配了不该匹配的1. 匹配规则太宽松如:text匹配了常见词汇。2. 缺少特征组合验证。1. 审查规则使用更独特的字符串或更精确的正则表达式锚定如^,$。2. 增加辅助匹配规则要求多个特征同时出现通过逻辑组合。版本号提取失败1. 正则表达式捕获组没写对。2. 版本信息不在首次请求的响应中。1. 在Ruby环境中单独测试你的正则确认$1,$2等变量能捕获到正确内容。2. 考虑使用主动匹配去获取包含版本信息的特定文件。扫描速度变慢插件中定义了过多主动匹配(:url)每个URL都会发起新请求。优化插件优先使用被动匹配。如果必须用主动匹配将其数量控制在最低1-2个并选择最独特的路径。5.2 性能与可维护性优化正则表达式优化避免贪婪匹配在不需要的时候使用非贪婪操作符.*?防止正则引擎回溯过多消耗性能。预编译正则对于复杂的、在passive块中多次使用的正则可以在插件顶部用REGEX_PATTERN /.../定义常量提高效率。保持简洁正则表达式应尽可能精确地描述目标特征避免过于宽泛的.*。代码组织为复杂的插件添加清晰的注释说明每个匹配规则针对的是什么特征。如果插件规则非常多比如覆盖一个产品的数十个版本考虑按版本或功能模块将规则分组并用注释分隔提高可读性。将用于版本提取的正则表达式单独定义方便后续更新。测试用例建立一个测试用例库包含正例确定是该应用的目标网页HTML和反例其他类似应用或空白页。每次修改插件后都跑一遍确保没有引入回归错误。可以利用WhatWeb的--input-file功能写一个简单的Shell脚本批量测试。5.3 融入工作流让插件创造持续价值开发插件不是一次性任务。为了让它持续产生价值版本化与共享使用Git管理你的自定义插件目录。这不仅是为了备份也便于与团队共享。你可以建立一个内部的知识库收集大家针对内部系统、常用SaaS等编写的插件。与扫描工具集成WhatWeb可以作为其他自动化扫描框架如Recon-ng的recon/web-app模块的一部分被调用。确保你的自定义插件路径能被这些框架正确加载。定期更新Web应用更新频繁。为重要的资产定期如每季度检查一次指纹是否仍然有效并根据新版本更新匹配规则和MD5哈希库。编写WhatWeb插件是一项将手动侦察经验转化为自动化能力的实践。它没有太高的技术门槛但极其考验观察力、归纳能力和对细节的把握。一个好的插件就像一枚精心打造的钥匙能快速打开一扇通往目标内部信息的大门。从今天开始尝试为你遇到的下一个“未知”系统编写一个检测插件你会发现你对Web应用指纹的理解会深刻得多。