{
"id": "typo3",
"info": {
"name": "typo3",
"author": "your-name",
"tags": "detect,tech,typo3",
"severity": "info",
"metadata": {
"product": "typo3",
"vendor": "typo3",
"verified": false
}
},
"http": [
{
"method": "GET",
"path": ["/"],
"matchers": [
{
"type": "word",
"words": [
"<meta name="generator" content="TYPO3 CMS"",
"This website is powered by TYPO3"
],
"case-insensitive": false
},
{
"type": "word",
"words": [
"typo3conf/",
"typo3temp/"
],
"case-insensitive": true
},
{
"type": "regex",
"regex": [
"(?mi)<!--\s*TYPO3"
]
}
]
},
{
"method": "GET",
"path": ["/typo3/"],
"matchers": [
{
"type": "word",
"words": [
"TYPO3 CMS Login",
"typo3-login"
],
"case-insensitive": true
}
]
}
]
} 就以这个为例你能解释一下其中的各个参数吗
────
Thought for 2s (ctrl+o to expand)
● 好的,我逐层拆解这个 JSON 的每一个字段,从外到内解释。
第一层:根结构
{
"id": "typo3",
"info": { ... },
"http": [ ... ]
}
┌──────┬────────┬──────────────────────────────────────────────────────────────────────────────────────────────────┐
│ 字段 │ 类型 │ 作用 │
├──────┼────────┼──────────────────────────────────────────────────────────────────────────────────────────────────┤
│ id │ string │ 指纹的唯一标识符,不能和其他条目重复。引擎内部用它去重、索引。通常和 name 保持一致 │
├──────┼────────┼──────────────────────────────────────────────────────────────────────────────────────────────────┤
│ info │ object │ 元数据区,描述 "这是谁"—— 不参与匹配,只用于分类、搜索、展示结果 │
├──────┼────────┼──────────────────────────────────────────────────────────────────────────────────────────────────┤
│ http │ array │ 匹配规则区,核心部分。每个元素是一个 HTTP │
│ │ │ 请求 + 匹配规则;引擎逐个发送请求,只要任一元素的规则全部命中就算匹配成功 │
└──────┴────────┴──────────────────────────────────────────────────────────────────────────────────────────────────┘
第二层:info 元数据
"info": {
"name": "typo3",
"author": "your-name",
"tags": "detect,tech,typo3",
"severity": "info",
"metadata": {
"product": "typo3",
"vendor": "typo3",
"verified": false
}
}
info 各字段
┌──────────┬────────┬──────────────────────────────────────────────────────────────────────────────────────────────┐
│ 字段 │ 类型 │ 含义 │
├──────────┼────────┼──────────────────────────────────────────────────────────────────────────────────────────────┤
│ name │ string │ 指纹显示名称,扫描结果里展示的就是这个 │
├──────────┼────────┼──────────────────────────────────────────────────────────────────────────────────────────────┤
│ author │ string │ 作者署名,用于溯源 │
├──────────┼────────┼──────────────────────────────────────────────────────────────────────────────────────────────┤
│ tags │ string │ 逗号分隔的标签,用于分类过滤。detect 表示这是个探测型指纹,tech 表示技术栈识别。你可以用 │
│ │ │ observer -tags detect,typo3 只跑特定标签 │
├──────────┼────────┼──────────────────────────────────────────────────────────────────────────────────────────────┤
│ severity │ string │ 严重级别。这里写 info 因为只是资产识别,不是漏洞;如果是指纹某个漏洞版本才会写 │
│ │ │ critical/high/medium/low │
├──────────┼────────┼──────────────────────────────────────────────────────────────────────────────────────────────┤
│ metadata │ object │ 扩展元数据,里面是键值对,字段自由定义 │
└──────────┴────────┴──────────────────────────────────────────────────────────────────────────────────────────────┘
metadata 各字段
┌──────────┬───────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ 字段 │ 含义 │
├──────────┼───────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ product │ 产品名,标准化命名,方便和其他工具联动 │
├──────────┼───────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ vendor │ 厂商名 │
├──────────┼───────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ verified │ 重要 ——true 表示这个指纹经过人工验证确认可用;false │
│ │ 表示未验证(你自己刚写的)。提交到社区库前应该验证后改成 true │
└──────────┴───────────────────────────────────────────────────────────────────────────────────────────────────────┘
▎ 💡 metadata 里还可以加 fofa-query、shodan-query 等字段,像库里那些条目一样,方便交叉搜索。
第三层:http 数组
"http": [
{
"method": "GET",
"path": ["/"],
"matchers": [ ... ]
},
{
"method": "GET",
"path": ["/typo3/"],
"matchers": [ ... ]
}
]
为什么是数组?
一个目标可能需要多个请求才能确认。比如:
先请求首页,检查 HTML 关键字
再请求后台路径,检查登录页特征
触发逻辑:数组中每个元素是一个 "请求块"。引擎依次(或并发)发送请求,每个请求块的 matchers
全部命中,该请求块才算匹配。整个指纹的匹配逻辑是 —— 任何一个请求块命中,就算识别成功。这相当于:请求块之间是 OR 关系,请求块内部的 matchers 之间是 AND 关系。
每个请求块的字段
┌──────────┬────────┬──────────────────────────────────────────────────────────────────────────────────────────────┐
│ 字段 │ 类型 │ 含义 │
├──────────┼────────┼──────────────────────────────────────────────────────────────────────────────────────────────┤
│ method │ string │ HTTP 方法:GET / POST / PUT 等。绝大多数指纹用 GET,因为我们是去 "看" 页面,不是改数据 │
├──────────┼────────┼──────────────────────────────────────────────────────────────────────────────────────────────┤
│ │ │ 请求路径列表。 │
│ path │ array │ 是变量占位符,引擎会自动替换成用户输入的目标地址。数组意味着会尝试多个路径,比如 ["/", │
│ │ │ "/typo3/"] 会先试首页再试后台 │
├──────────┼────────┼──────────────────────────────────────────────────────────────────────────────────────────────┤
│ matchers │ array │ 匹配规则集,见下文 │
└──────────┴────────┴──────────────────────────────────────────────────────────────────────────────────────────────┘第四层:matchers 匹配规则
这是指纹的核心 —— 回答 "打到响应后,怎么判断它是不是 TYPO3"。
规则 1:精确关键字匹配
{
"type": "word", "words": [ "<meta name=\"generator\" content=\"TYPO3 CMS\"", "This website is powered by TYPO3" ], "case-insensitive": false}
┌──────────────────────┬───────────────────────────────────────────────────────────────────────────────────────────┐
│ 字段 │ 含义 │
├──────────────────────┼───────────────────────────────────────────────────────────────────────────────────────────┤
│ type: "word" │ 关键字匹配。引擎在响应体(HTML)中搜索这些字符串 │
├──────────────────────┼───────────────────────────────────────────────────────────────────────────────────────────┤
│ words │ 关键字列表。数组内是 OR 关系 —— 命中任意一个即匹配。这里两条都指向 TYPO3:第一条是标准 meta │
│ │ 标签;第二条是默认页脚的版权声明 │
├──────────────────────┼───────────────────────────────────────────────────────────────────────────────────────────┤
│ case-insensitive: │ 区分大小写。TYPO3 官方写死了 TYPO3 CMS 这个大小写格式,所以用精确匹配能降低误报。如果写 │
│ false │ true,"typo3 cms" 也能命中,但误报率会稍高 │
└──────────────────────┴───────────────────────────────────────────────────────────────────────────────────────────┘▎ 🔑 为什么用两条? TYPO3 不同模板可能隐藏 meta 但保留页脚,反之亦然。拆成 OR 覆盖更多场景。
规则 2:路径特征匹配
{
"type": "word", "words": [ "typo3conf/", "typo3temp/" ], "case-insensitive": true}
┌─────────────────────┬───────────────────────────────────────────────────────────────────────────────────────────┐
│ 特点 │ 说明 │
├─────────────────────┼───────────────────────────────────────────────────────────────────────────────────────────┤
│ case-insensitive: │ URL 路径在不同服务器上可能有大小写差异,所以忽略大小写更稳健 │
│ true │ │
├─────────────────────┼───────────────────────────────────────────────────────────────────────────────────────────┤
│ 为什么选这两个路径 │ typo3conf/ 是 TYPO3 的配置目录,typo3temp/ 是临时文件目录 —— 这两个目录是 TYPO3 │
│ │ 架构独有的,其他 CMS 不会用这种命名规范。它们会出现在 CSS/JS 引用或图片链接中 │
├─────────────────────┼───────────────────────────────────────────────────────────────────────────────────────────┤
│ 没有 condition │ 默认是 OR,命中任意一个即匹配 │
└─────────────────────┴───────────────────────────────────────────────────────────────────────────────────────────┘规则 3:正则匹配(HTML 注释)
{
"type": "regex", "regex": [ "(?mi)<!--\\s*TYPO3" ], "case-insensitive": true}
┌────────────────────┬─────────────────────────────────────────────────────────────────────────────────────────────┐
│ 字段 │ 含义 │
├────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────┤
│ type: "regex" │ 正则表达式匹配,比 word 更灵活 │
├────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────┤
│ regex │ 正则数组,同样是 OR 关系 │
├────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────┤
│ │ 正则分解:<br>・(?mi) = 多行模式 + 忽略大小写 < br>・<!-- = HTML 注释开头 < br>・\s* = │
│ (?mi)<!--\s*TYPO3 │ 零或多个空白符 < br>・TYPO3 = 注释内出现 TYPO3<br><br>TYPO3 经常在 HTML 源码里留下 <!-- │
│ │ TYPO3SEARCH_begin --> 或 <!-- This website is powered by TYPO3 --> 等注释 │
└────────────────────┴─────────────────────────────────────────────────────────────────────────────────────────────┘没有出现的 condition 字段
库里有些规则是这样的:
{
"type": "word", "words": ["name=\"dede_fields", "name=\"dede_fieldshash"], "condition": "and", "case-insensitive": true}
condition 有两种值:
不写 / "or":words 数组里命中任意一个就算匹配(宽松)
"and":words 数组里全部都要命中才算匹配(严格)
▎ 🔑 什么时候用 and? 当单独一个词太常见(如 "admin"),误报率高,但多个词同时出现就能唯一确定目标时。比如同时出现
▎ dede_fields 和 dede_fieldshash 才能确认是 DedeCMS。第二个请求块
{
"method": "GET", "path": ["{{BaseURL}}/typo3/"], "matchers": [ { "type": "word", "words": ["TYPO3 CMS Login", "typo3-login"], "case-insensitive": true } ]}
┌─────────────────┬────────────────────────────────┐
│ 要点 │ 说明 │
├─────────────────┼────────────────────────────────┤
│ 路径 /typo3/ │ TYPO3 后台登录的默认路径 │
├─────────────────┼────────────────────────────────┤
│ TYPO3 CMS Login │ 登录页标题 / 标题栏常见文案 │
├─────────────────┼────────────────────────────────┤
│ typo3-login │ 登录表单的 CSS class,非常独特 │
└─────────────────┴────────────────────────────────┘这个请求块的意义是:即使首页被大幅定制找不到特征,后台登录页的路径和文案几乎不会被改动,相当于一个兜底检测。
整体匹配逻辑总结
┌─ 请求块 1: GET /
│ ├─ 规则 1: word ── 命中 "meta...TYPO3 CMS" 或 "powered by TYPO3" ← OR
│ ├─ 规则 2: word ── 命中 "typo3conf/" 或 "typo3temp/" ← OR
│ └─ 规则 3: regex ─ 命中 /<!--\s*TYPO3/ ← AND (规则之间)
│ → 三条规则全部通过 → 请求块 1 命中 ✅
│
└─ 请求块 2: GET /typo3/└─ 规则1: word ── 命中 "TYPO3 CMS Login" 或 "typo3-login" ← 单条规则 → 规则通过 → 请求块2 命中 ✅│ 要点 │ 说明 │
├─────────────────┼────────────────────────────────┤
│ 路径 /typo3/ │ TYPO3 后台登录的默认路径 │
├─────────────────┼────────────────────────────────┤
│ TYPO3 CMS Login │ 登录页标题 / 标题栏常见文案 │
├─────────────────┼────────────────────────────────┤
│ typo3-login │ 登录表单的 CSS class,非常独特 │
└─────────────────┴────────────────────────────────┘这个请求块的意义是:即使首页被大幅定制找不到特征,后台登录页的路径和文案几乎不会被改动,相当于一个兜底检测。
整体匹配逻辑总结
┌─ 请求块 1: GET /
│ ├─ 规则 1: word ── 命中 "meta...TYPO3 CMS" 或 "powered by TYPO3" ← OR
│ ├─ 规则 2: word ── 命中 "typo3conf/" 或 "typo3temp/" ← OR
│ └─ 规则 3: regex ─ 命中 /<!--\s*TYPO3/ ← AND (规则之间)
│ → 三条规则全部通过 → 请求块 1 命中 ✅
│
└─ 请求块 2: GET /typo3/└─ 规则1: word ── 命中 "TYPO3 CMS Login" 或 "typo3-login" ← 单条规则 → 规则通过 → 请求块2 命中 ✅请求块 1 命中 OR 请求块 2 命中 → 输出: "检测到 TYPO3"
用伪代码表示:
def match(response_list):
for http_block in fingerprint["http"]: response = send_request(http_block["method"], http_block["path"]) block_matched = True for matcher in http_block["matchers"]: if not evaluate(matcher, response): block_matched = False break # 该请求块失败,跳到下一个请求块 if block_matched: return True # 指纹命中 return False # 所有请求块都不匹配关键记住三句话:
matchers 之间是 AND(全部规则通过才算)
words/regex 数组内是 OR(命中一个即可)
http 数组之间是 OR(任何一个请求块命中就识别成功)
这样就构建了一个既灵敏又抗误报的指纹规则。