mirror of
https://github.com/shadow1ng/fscan.git
synced 2026-09-25 04:31:52 +08:00
feat: v2.1.0 核心重构与功能增强
## 架构重构
- 全局变量消除,迁移至 Config/State 对象
- SMB 插件融合(smb/smb2/smbghost/smbinfo)
- 服务探测重构,实现 Nmap 风格 fallback 机制
- 输出系统重构,TXT 实时刷盘 + 双写机制
- i18n 框架升级至 go-i18n
## 性能优化
- 正则表达式预编译
- 内存优化 map[string]struct{}
- 并发指纹匹配
- SOCKS5 连接复用
- 滑动窗口调度 + 自适应线程池
## 新功能
- Web 管理界面
- 多格式 POC 适配(xray/afrog)
- 增强指纹库(3139条)
- Favicon hash 指纹识别
- 插件选择性编译(Build Tags)
- fscan-lab 靶场环境
- 默认端口扩展(62→133)
## 构建系统
- 添加 no_local tag 支持排除本地插件
- 多版本构建:fscan/fscan-nolocal/fscan-web
- CI 添加 snapshot 模式支持仅测试构建
## Bug 修复
- 修复 120+ 个问题,包括 RDP panic、批量扫描漏报、
JSON 输出格式、Redis 检测、Context 超时等
## 测试增强
- 单元测试覆盖率 74-100%
- 并发安全测试
- 集成测试(Web/端口/服务/SSH/ICMP)
This commit is contained in:
@@ -0,0 +1,248 @@
|
||||
# fscan 增强指纹库
|
||||
|
||||
## 概述
|
||||
|
||||
fscan现在集成了[FingerprintHub](https://github.com/0x727/FingerprintHub)的Web指纹库,实现**双指纹库并行识别**,识别能力提升**12.5倍**。
|
||||
|
||||
## 指纹库规模
|
||||
|
||||
### 基础指纹库 (原有)
|
||||
- **正则规则**: 242条 (RuleDatas)
|
||||
- **MD5指纹**: 30条 (Md5Datas)
|
||||
- **特点**: 国内OA/WAF为主,轻量高效
|
||||
- **文件**: `rules.go` (硬编码)
|
||||
|
||||
### 增强指纹库 (新增)
|
||||
- **指纹数量**: 3,139条
|
||||
- **来源**: FingerprintHub v4.0
|
||||
- **特点**: 国内外主流应用,社区维护
|
||||
- **文件**: `web_fingerprint_v4.json` (1.3MB)
|
||||
|
||||
### 总计
|
||||
```
|
||||
基础指纹: 272条
|
||||
增强指纹: 3,139条
|
||||
─────────────────
|
||||
总计: 3,411条
|
||||
提升倍数: 12.5x
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 技术实现
|
||||
|
||||
### 架构设计
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ fingerprint_scanner.go │
|
||||
│ InfoCheck() 统一入口 │
|
||||
└─────────────────────────────────────┘
|
||||
│
|
||||
├─> 基础指纹库
|
||||
│ ├─ matchByRegex() (242条)
|
||||
│ └─ matchByMd5() (30条)
|
||||
│
|
||||
└─> 增强指纹库
|
||||
└─ MatchEnhancedFingerprints() (3139条)
|
||||
├─ matchWords() (关键词匹配)
|
||||
├─ matchRegex() (正则匹配)
|
||||
└─ matchFavicon() (icon hash)
|
||||
```
|
||||
|
||||
### 核心特性
|
||||
|
||||
#### 1. 多种matcher类型
|
||||
```go
|
||||
支持的matcher类型:
|
||||
- word: 关键词匹配 (大小写可选)
|
||||
- regex: 正则表达式匹配
|
||||
- favicon: 图标hash匹配
|
||||
```
|
||||
|
||||
#### 2. Condition逻辑
|
||||
```go
|
||||
- or: 任一条件满足即匹配 (默认)
|
||||
- and: 所有条件都满足才匹配
|
||||
```
|
||||
|
||||
#### 3. Part选择
|
||||
```go
|
||||
- body: 响应体匹配 (默认)
|
||||
- header: 响应头匹配
|
||||
```
|
||||
|
||||
#### 4. 性能优化
|
||||
- **正则缓存**: 编译后的正则表达式缓存
|
||||
- **Lazy加载**: 首次调用时才加载JSON
|
||||
- **Embed内嵌**: 编译时打包,无需外部文件
|
||||
|
||||
---
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 基本用法
|
||||
```go
|
||||
import "github.com/shadow1ng/fscan/webscan/fingerprint"
|
||||
|
||||
// 自动加载并匹配
|
||||
body := []byte("<html>...</html>")
|
||||
headers := "Server: nginx/1.18.0"
|
||||
|
||||
// 计算 favicon hash(同时支持 mmh3 和 MD5 格式)
|
||||
faviconData := []byte{...} // 从 /favicon.ico 下载的数据
|
||||
favicon := fingerprint.CalculateFaviconHashes(faviconData)
|
||||
|
||||
matched := fingerprint.MatchEnhancedFingerprints(body, headers, favicon)
|
||||
// 返回: ["nginx", "wordpress", ...]
|
||||
```
|
||||
|
||||
### 指纹格式示例
|
||||
|
||||
**WordPress指纹**:
|
||||
```json
|
||||
{
|
||||
"id": "wordpress",
|
||||
"info": {
|
||||
"name": "wordpress",
|
||||
"tags": "detect,tech,wordpress"
|
||||
},
|
||||
"http": [{
|
||||
"matchers": [{
|
||||
"type": "word",
|
||||
"words": [
|
||||
"/wp-content/themes/",
|
||||
"/wp-includes/"
|
||||
],
|
||||
"case-insensitive": true
|
||||
}]
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
**禅道OA指纹** (AND条件):
|
||||
```json
|
||||
{
|
||||
"id": "zentao",
|
||||
"http": [{
|
||||
"matchers": [{
|
||||
"type": "word",
|
||||
"words": [
|
||||
"/zentao/theme",
|
||||
"zentaosid"
|
||||
],
|
||||
"condition": "and"
|
||||
}]
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
**Favicon Hash指纹**:
|
||||
```json
|
||||
{
|
||||
"id": "openemr",
|
||||
"http": [{
|
||||
"matchers": [{
|
||||
"type": "favicon",
|
||||
"hash": ["1971268439"]
|
||||
}]
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 验证结果
|
||||
|
||||
### 测试用例
|
||||
```bash
|
||||
$ go run test_enhanced_fingerprint.go
|
||||
|
||||
✅ 增强指纹库加载成功
|
||||
|
||||
测试1 - WordPress识别:
|
||||
匹配结果: [wordpress]
|
||||
✅ WordPress指纹匹配成功
|
||||
|
||||
测试2 - Nginx识别:
|
||||
匹配结果: [nginx]
|
||||
✅ Nginx指纹匹配成功
|
||||
|
||||
测试3 - 禅道OA识别:
|
||||
匹配结果: [zentao-system zentao]
|
||||
✅ 禅道指纹匹配成功
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 文件说明
|
||||
|
||||
| 文件 | 大小 | 说明 |
|
||||
|------|------|------|
|
||||
| `web_fingerprint_v4.json` | 1.3MB | FingerprintHub指纹库 |
|
||||
| `enhanced.go` | ~7KB | 增强指纹匹配引擎 |
|
||||
| `rules.go` | ~150KB | 基础指纹库(原有) |
|
||||
| `fingerprint_scanner.go` | ~3KB | 统一入口(修改) |
|
||||
|
||||
---
|
||||
|
||||
## 性能影响
|
||||
|
||||
### 编译后二进制
|
||||
- **旧版本**: ~30MB
|
||||
- **新版本**: ~57MB (+27MB)
|
||||
- **原因**: embed了1.3MB JSON + 引擎代码
|
||||
|
||||
### 运行时性能
|
||||
- **内存**: 首次加载 +2MB (JSON解析)
|
||||
- **速度**: 正则缓存后无明显影响
|
||||
- **并发**: 两套指纹库并行匹配
|
||||
|
||||
---
|
||||
|
||||
## 与原有指纹库的对比
|
||||
|
||||
| 维度 | 基础指纹库 | 增强指纹库 |
|
||||
|------|-----------|-----------|
|
||||
| **数量** | 272条 | 3,139条 |
|
||||
| **来源** | 内置 | FingerprintHub |
|
||||
| **格式** | Go代码 | JSON |
|
||||
| **扩展性** | 需改代码 | 社区更新 |
|
||||
| **覆盖范围** | 国内OA/WAF | 国内外全栈 |
|
||||
| **Favicon** | ❌ | ✅ |
|
||||
| **版本提取** | ❌ | ✅ (待实现) |
|
||||
| **Condition** | 简单 | AND/OR |
|
||||
| **Part** | 固定 | 可选 |
|
||||
|
||||
---
|
||||
|
||||
## 后续计划
|
||||
|
||||
### 已实现功能
|
||||
- [x] Favicon自动下载和hash计算 ✅
|
||||
- [x] 使用mmh3算法计算favicon hash ✅
|
||||
- [x] 并发指纹匹配(~267μs/3000+规则)✅
|
||||
- [x] Version extractor(通用版本提取)✅
|
||||
- [x] 指纹优先级排序(favicon > regex > word)✅
|
||||
|
||||
### 待实现功能
|
||||
- [ ] 支持加载外部JSON文件
|
||||
|
||||
### 潜在改进
|
||||
- [ ] 支持更多matcher类型 (status, size, binary)
|
||||
- [ ] 指纹库热更新机制
|
||||
- [ ] 匹配结果包含CPE信息
|
||||
|
||||
---
|
||||
|
||||
## 参考资料
|
||||
|
||||
- [FingerprintHub GitHub](https://github.com/0x727/FingerprintHub)
|
||||
- [Observer Ward](https://github.com/emo-crab/observer_ward)
|
||||
- [Nuclei Templates](https://github.com/projectdiscovery/nuclei-templates)
|
||||
|
||||
---
|
||||
|
||||
## License
|
||||
|
||||
增强指纹库来自FingerprintHub项目,遵循其原始License。
|
||||
@@ -0,0 +1,513 @@
|
||||
package fingerprint
|
||||
|
||||
import (
|
||||
"crypto/md5" //nolint:gosec
|
||||
_ "embed"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
//go:embed web_fingerprint_v4.json
|
||||
var fingerprintHubData []byte
|
||||
|
||||
// EnhancedFingerprint FingerprintHub增强指纹结构
|
||||
type EnhancedFingerprint struct {
|
||||
ID string `json:"id"`
|
||||
Info struct {
|
||||
Name string `json:"name"`
|
||||
Author string `json:"author"`
|
||||
Tags string `json:"tags"`
|
||||
Severity string `json:"severity"`
|
||||
Metadata map[string]interface{} `json:"metadata"`
|
||||
} `json:"info"`
|
||||
HTTP []struct {
|
||||
Method string `json:"method"`
|
||||
Path []string `json:"path"`
|
||||
Matchers []struct {
|
||||
Type string `json:"type"`
|
||||
Words []string `json:"words"`
|
||||
Regex []string `json:"regex"`
|
||||
Hash []string `json:"hash"` // favicon hash
|
||||
Part string `json:"part"` // header, body
|
||||
CaseInsensitive bool `json:"case-insensitive"`
|
||||
Condition string `json:"condition"` // and, or
|
||||
} `json:"matchers"`
|
||||
} `json:"http"`
|
||||
}
|
||||
|
||||
// EnhancedFingerprintDB 增强指纹数据库
|
||||
type EnhancedFingerprintDB struct {
|
||||
Fingerprints []*EnhancedFingerprint
|
||||
// 预编译的正则表达式缓存
|
||||
regexCache map[string]*regexp.Regexp
|
||||
regexCacheMu sync.RWMutex // 保护regexCache的并发访问
|
||||
}
|
||||
|
||||
var (
|
||||
enhancedDB *EnhancedFingerprintDB
|
||||
enhancedDBOnce sync.Once // 保证只初始化一次
|
||||
)
|
||||
|
||||
// LoadEnhancedFingerprints 加载增强指纹库
|
||||
func LoadEnhancedFingerprints() error {
|
||||
var fps []*EnhancedFingerprint
|
||||
if err := json.Unmarshal(fingerprintHubData, &fps); err != nil {
|
||||
return fmt.Errorf("解析增强指纹库失败: %w", err)
|
||||
}
|
||||
|
||||
enhancedDB = &EnhancedFingerprintDB{
|
||||
Fingerprints: fps,
|
||||
regexCache: make(map[string]*regexp.Regexp),
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// fingerprintMatch 指纹匹配结果(带优先级)
|
||||
type fingerprintMatch struct {
|
||||
Name string
|
||||
Priority int // 优先级分数,越高越优先
|
||||
}
|
||||
|
||||
// MatchEnhancedFingerprints 匹配增强指纹(并发版本,结果按优先级排序)
|
||||
func MatchEnhancedFingerprints(body []byte, headers string, favicon FaviconHashes) []string {
|
||||
// 使用 sync.Once 保证只初始化一次,线程安全
|
||||
enhancedDBOnce.Do(func() {
|
||||
_ = LoadEnhancedFingerprints() // 忽略错误,enhancedDB 为 nil 时下面会返回 nil
|
||||
})
|
||||
|
||||
if enhancedDB == nil || len(enhancedDB.Fingerprints) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
bodyStr := string(body)
|
||||
fingerprints := enhancedDB.Fingerprints
|
||||
total := len(fingerprints)
|
||||
|
||||
// 根据 CPU 核心数决定并发数,但不超过指纹数量
|
||||
workers := runtime.NumCPU()
|
||||
if workers > total {
|
||||
workers = total
|
||||
}
|
||||
if workers < 1 {
|
||||
workers = 1
|
||||
}
|
||||
|
||||
// 每个 worker 处理的指纹数量
|
||||
chunkSize := (total + workers - 1) / workers
|
||||
|
||||
// 结果收集 channel(带优先级信息)
|
||||
resultCh := make(chan fingerprintMatch, total)
|
||||
var wg sync.WaitGroup
|
||||
|
||||
// 启动 worker
|
||||
for i := 0; i < workers; i++ {
|
||||
start := i * chunkSize
|
||||
end := start + chunkSize
|
||||
if end > total {
|
||||
end = total
|
||||
}
|
||||
if start >= total {
|
||||
break
|
||||
}
|
||||
|
||||
wg.Add(1)
|
||||
go func(fps []*EnhancedFingerprint) {
|
||||
defer wg.Done()
|
||||
for _, fp := range fps {
|
||||
if len(fp.HTTP) == 0 {
|
||||
continue
|
||||
}
|
||||
httpRule := fp.HTTP[0]
|
||||
for _, matcher := range httpRule.Matchers {
|
||||
if matchMatcher(matcher, bodyStr, headers, favicon, enhancedDB.regexCache) {
|
||||
resultCh <- fingerprintMatch{
|
||||
Name: fp.Info.Name,
|
||||
Priority: calcPriority(fp, matcher.Type),
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}(fingerprints[start:end])
|
||||
}
|
||||
|
||||
// 等待所有 worker 完成后关闭 channel
|
||||
go func() {
|
||||
wg.Wait()
|
||||
close(resultCh)
|
||||
}()
|
||||
|
||||
// 收集结果
|
||||
var matches []fingerprintMatch
|
||||
for m := range resultCh {
|
||||
matches = append(matches, m)
|
||||
}
|
||||
|
||||
// 按优先级排序(降序)
|
||||
sort.Slice(matches, func(i, j int) bool {
|
||||
if matches[i].Priority != matches[j].Priority {
|
||||
return matches[i].Priority > matches[j].Priority
|
||||
}
|
||||
return matches[i].Name < matches[j].Name // 同优先级按名称排序
|
||||
})
|
||||
|
||||
// 提取名称
|
||||
result := make([]string, len(matches))
|
||||
for i, m := range matches {
|
||||
result[i] = m.Name
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// calcPriority 计算指纹优先级分数
|
||||
func calcPriority(fp *EnhancedFingerprint, matcherType string) int {
|
||||
priority := 0
|
||||
|
||||
// 匹配类型权重(favicon 最精确)
|
||||
switch matcherType {
|
||||
case "favicon":
|
||||
priority += 100
|
||||
case "regex":
|
||||
priority += 50
|
||||
case "word":
|
||||
priority += 30
|
||||
}
|
||||
|
||||
// verified 状态加分
|
||||
if fp.Info.Metadata != nil {
|
||||
if verified, ok := fp.Info.Metadata["verified"].(bool); ok && verified {
|
||||
priority += 20
|
||||
}
|
||||
}
|
||||
|
||||
return priority
|
||||
}
|
||||
|
||||
// matchMatcher 匹配单个matcher
|
||||
func matchMatcher(matcher struct {
|
||||
Type string `json:"type"`
|
||||
Words []string `json:"words"`
|
||||
Regex []string `json:"regex"`
|
||||
Hash []string `json:"hash"`
|
||||
Part string `json:"part"`
|
||||
CaseInsensitive bool `json:"case-insensitive"`
|
||||
Condition string `json:"condition"`
|
||||
}, body, headers string, favicon FaviconHashes, regexCache map[string]*regexp.Regexp) bool {
|
||||
|
||||
switch matcher.Type {
|
||||
case "word":
|
||||
return matchWords(matcher, body, headers)
|
||||
case "regex":
|
||||
return matchRegex(matcher, body, headers, regexCache)
|
||||
case "favicon":
|
||||
return matchFavicon(matcher, favicon)
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// matchWords 匹配关键词
|
||||
func matchWords(matcher struct {
|
||||
Type string `json:"type"`
|
||||
Words []string `json:"words"`
|
||||
Regex []string `json:"regex"`
|
||||
Hash []string `json:"hash"`
|
||||
Part string `json:"part"`
|
||||
CaseInsensitive bool `json:"case-insensitive"`
|
||||
Condition string `json:"condition"`
|
||||
}, body, headers string) bool {
|
||||
|
||||
// 确定匹配目标
|
||||
target := body
|
||||
if matcher.Part == "header" {
|
||||
target = headers
|
||||
}
|
||||
|
||||
// 预处理搜索词,避免循环内重复转换
|
||||
searchWords := matcher.Words
|
||||
if matcher.CaseInsensitive {
|
||||
target = strings.ToLower(target)
|
||||
searchWords = make([]string, len(matcher.Words))
|
||||
for i, w := range matcher.Words {
|
||||
searchWords[i] = strings.ToLower(w)
|
||||
}
|
||||
}
|
||||
|
||||
// 默认condition为or
|
||||
isAnd := matcher.Condition == "and"
|
||||
matchCount := 0
|
||||
|
||||
for _, searchWord := range searchWords {
|
||||
if strings.Contains(target, searchWord) {
|
||||
if !isAnd {
|
||||
// OR条件:匹配任一即可
|
||||
return true
|
||||
}
|
||||
matchCount++
|
||||
} else if isAnd {
|
||||
// AND条件:任一不匹配即失败
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// AND条件:全部匹配
|
||||
return isAnd && matchCount == len(matcher.Words)
|
||||
}
|
||||
|
||||
// matchRegex 匹配正则表达式
|
||||
func matchRegex(matcher struct {
|
||||
Type string `json:"type"`
|
||||
Words []string `json:"words"`
|
||||
Regex []string `json:"regex"`
|
||||
Hash []string `json:"hash"`
|
||||
Part string `json:"part"`
|
||||
CaseInsensitive bool `json:"case-insensitive"`
|
||||
Condition string `json:"condition"`
|
||||
}, body, headers string, regexCache map[string]*regexp.Regexp) bool {
|
||||
|
||||
// 确定匹配目标
|
||||
target := body
|
||||
if matcher.Part == "header" {
|
||||
target = headers
|
||||
}
|
||||
|
||||
// 默认condition为or
|
||||
isAnd := matcher.Condition == "and"
|
||||
|
||||
for _, pattern := range matcher.Regex {
|
||||
// 从缓存获取或编译正则(线程安全)
|
||||
var re *regexp.Regexp
|
||||
|
||||
// 先尝试读取缓存(读锁)
|
||||
enhancedDB.regexCacheMu.RLock()
|
||||
re, exists := regexCache[pattern]
|
||||
enhancedDB.regexCacheMu.RUnlock()
|
||||
|
||||
if !exists {
|
||||
// 不存在,需要编译并写入缓存(写锁)
|
||||
enhancedDB.regexCacheMu.Lock()
|
||||
// Double-check:可能其他goroutine已经编译了
|
||||
re, exists = regexCache[pattern]
|
||||
if !exists {
|
||||
var err error
|
||||
if matcher.CaseInsensitive {
|
||||
re, err = regexp.Compile("(?i)" + pattern)
|
||||
} else {
|
||||
re, err = regexp.Compile(pattern)
|
||||
}
|
||||
if err != nil {
|
||||
enhancedDB.regexCacheMu.Unlock()
|
||||
continue
|
||||
}
|
||||
regexCache[pattern] = re
|
||||
}
|
||||
enhancedDB.regexCacheMu.Unlock()
|
||||
}
|
||||
|
||||
// 确保 re 不为 nil(防止并发场景下的 nil panic)
|
||||
if re != nil && re.MatchString(target) {
|
||||
if !isAnd {
|
||||
return true
|
||||
}
|
||||
} else if isAnd {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// AND条件需要全部匹配
|
||||
return isAnd && len(matcher.Regex) > 0
|
||||
}
|
||||
|
||||
// matchFavicon 匹配favicon hash(同时支持 mmh3 和 MD5 格式)
|
||||
func matchFavicon(matcher struct {
|
||||
Type string `json:"type"`
|
||||
Words []string `json:"words"`
|
||||
Regex []string `json:"regex"`
|
||||
Hash []string `json:"hash"`
|
||||
Part string `json:"part"`
|
||||
CaseInsensitive bool `json:"case-insensitive"`
|
||||
Condition string `json:"condition"`
|
||||
}, favicon FaviconHashes) bool {
|
||||
|
||||
if favicon.MMH3 == "" && favicon.MD5 == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, hash := range matcher.Hash {
|
||||
// 同时匹配 mmh3(数字格式)和 MD5(十六进制格式)
|
||||
if hash == favicon.MMH3 || hash == favicon.MD5 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// FaviconHashes 包含 mmh3 和 MD5 两种格式的 hash
|
||||
type FaviconHashes struct {
|
||||
MMH3 string // Shodan/FOFA 风格: 有符号32位整数
|
||||
MD5 string // 传统 MD5 十六进制
|
||||
}
|
||||
|
||||
// VersionInfo 版本提取结果
|
||||
type VersionInfo struct {
|
||||
Name string // 产品名称
|
||||
Version string // 版本号
|
||||
}
|
||||
|
||||
// 通用版本提取正则(预编译)
|
||||
var versionExtractors = []struct {
|
||||
pattern *regexp.Regexp
|
||||
name string // 产品名称(空表示从匹配中提取)
|
||||
}{
|
||||
// Server header: nginx/1.18.0, Apache/2.4.41, IIS/10.0
|
||||
{regexp.MustCompile(`(?i)(?:^|[\s,])(?P<name>nginx|apache|iis|lighttpd|openresty|tengine)[/\s]?(?P<ver>[\d.]+)`), ""},
|
||||
|
||||
// X-Powered-By: PHP/7.4.3, ASP.NET/4.0
|
||||
{regexp.MustCompile(`(?i)(?:x-powered-by[:\s]*)?(?P<name>php|asp\.net|express|servlet)[/\s]?(?P<ver>[\d.]+)`), ""},
|
||||
|
||||
// Generator meta: WordPress 6.0, Drupal 9.0
|
||||
{regexp.MustCompile(`(?i)(?:generator|powered[\s-]*by)["\s:]*(?P<name>wordpress|drupal|joomla|typo3|hugo|jekyll|ghost)[\s/]*(?P<ver>[\d.]+)?`), ""},
|
||||
|
||||
// jQuery/Vue/React
|
||||
{regexp.MustCompile(`(?i)(?P<name>jquery|vue|react|angular)[\s./-]*(?:v|version)?[\s]*(?P<ver>\d+(?:\.\d+)+)`), ""},
|
||||
|
||||
// 通用 version= 或 ver= 模式
|
||||
{regexp.MustCompile(`(?i)(?P<name>[a-z][\w-]*?)[\s_-]*(?:version|ver)[=:\s"']*(?P<ver>[\d]+(?:\.[\d]+)+)`), ""},
|
||||
|
||||
// Tomcat, WebLogic, WebSphere
|
||||
{regexp.MustCompile(`(?i)(?P<name>tomcat|weblogic|websphere|jetty|jboss|wildfly)[/\s-]*(?P<ver>[\d.]+)`), ""},
|
||||
|
||||
// 数据库版本
|
||||
{regexp.MustCompile(`(?i)(?P<name>mysql|mariadb|postgresql|mongodb|redis|elasticsearch)[/\s-]*(?P<ver>[\d.]+)`), ""},
|
||||
|
||||
// OpenSSL, OpenSSH
|
||||
{regexp.MustCompile(`(?i)(?P<name>openssl|openssh)[/\s-]*(?P<ver>[\d.]+[a-z]?)`), ""},
|
||||
}
|
||||
|
||||
// ExtractVersions 从 HTTP 响应中提取软件版本信息
|
||||
func ExtractVersions(body string, headers string) []VersionInfo {
|
||||
content := headers + "\n" + body
|
||||
var results []VersionInfo
|
||||
seen := make(map[string]struct{})
|
||||
|
||||
for _, extractor := range versionExtractors {
|
||||
matches := extractor.pattern.FindAllStringSubmatch(content, -1)
|
||||
for _, match := range matches {
|
||||
var name, version string
|
||||
|
||||
// 提取命名捕获组
|
||||
for i, groupName := range extractor.pattern.SubexpNames() {
|
||||
if i > 0 && i < len(match) {
|
||||
switch groupName {
|
||||
case "name":
|
||||
name = strings.ToLower(match[i])
|
||||
case "ver":
|
||||
version = match[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 跳过空结果或已存在的
|
||||
if name == "" || version == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
key := name + ":" + version
|
||||
if _, exists := seen[key]; exists {
|
||||
continue
|
||||
}
|
||||
seen[key] = struct{}{}
|
||||
|
||||
results = append(results, VersionInfo{
|
||||
Name: name,
|
||||
Version: version,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
// CalculateFaviconHashes 计算 favicon 的 hash(同时返回 mmh3 和 MD5)
|
||||
func CalculateFaviconHashes(data []byte) FaviconHashes {
|
||||
if len(data) == 0 {
|
||||
return FaviconHashes{}
|
||||
}
|
||||
|
||||
// mmh3: base64编码后计算(Shodan/FOFA 标准)
|
||||
b64 := base64.StdEncoding.EncodeToString(data)
|
||||
mmh3Hash := mmh3Hash32([]byte(b64))
|
||||
|
||||
// MD5: 直接计算原始数据
|
||||
//nolint:gosec
|
||||
md5Hash := md5.Sum(data)
|
||||
|
||||
return FaviconHashes{
|
||||
MMH3: fmt.Sprintf("%d", mmh3Hash),
|
||||
MD5: fmt.Sprintf("%x", md5Hash),
|
||||
}
|
||||
}
|
||||
|
||||
// mmh3Hash32 计算 MurmurHash3 32位 hash(有符号整数)
|
||||
func mmh3Hash32(data []byte) int32 {
|
||||
const (
|
||||
c1 uint32 = 0xcc9e2d51
|
||||
c2 uint32 = 0x1b873593
|
||||
seed uint32 = 0
|
||||
)
|
||||
|
||||
length := len(data)
|
||||
nblocks := length / 4
|
||||
h1 := seed
|
||||
|
||||
// 处理4字节块
|
||||
for i := 0; i < nblocks; i++ {
|
||||
k1 := uint32(data[i*4]) | uint32(data[i*4+1])<<8 |
|
||||
uint32(data[i*4+2])<<16 | uint32(data[i*4+3])<<24
|
||||
|
||||
k1 *= c1
|
||||
k1 = (k1 << 15) | (k1 >> 17)
|
||||
k1 *= c2
|
||||
|
||||
h1 ^= k1
|
||||
h1 = (h1 << 13) | (h1 >> 19)
|
||||
h1 = h1*5 + 0xe6546b64
|
||||
}
|
||||
|
||||
// 处理剩余字节
|
||||
tail := data[nblocks*4:]
|
||||
var k1 uint32
|
||||
switch len(tail) {
|
||||
case 3:
|
||||
k1 ^= uint32(tail[2]) << 16
|
||||
fallthrough
|
||||
case 2:
|
||||
k1 ^= uint32(tail[1]) << 8
|
||||
fallthrough
|
||||
case 1:
|
||||
k1 ^= uint32(tail[0])
|
||||
k1 *= c1
|
||||
k1 = (k1 << 15) | (k1 >> 17)
|
||||
k1 *= c2
|
||||
h1 ^= k1
|
||||
}
|
||||
|
||||
// 最终混合
|
||||
h1 ^= uint32(length)
|
||||
h1 ^= h1 >> 16
|
||||
h1 *= 0x85ebca6b
|
||||
h1 ^= h1 >> 13
|
||||
h1 *= 0xc2b2ae35
|
||||
h1 ^= h1 >> 16
|
||||
|
||||
return int32(h1)
|
||||
}
|
||||
@@ -0,0 +1,640 @@
|
||||
package fingerprint
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
/*
|
||||
enhanced_test.go - Web指纹匹配引擎测试
|
||||
|
||||
测试重点:
|
||||
1. matchWords - 关键词匹配逻辑(AND/OR条件、大小写)
|
||||
2. matchFavicon - favicon hash匹配
|
||||
3. CalculateFaviconHash - hash计算一致性
|
||||
|
||||
不测试:
|
||||
- MatchEnhancedFingerprints - 依赖嵌入的JSON数据和全局状态
|
||||
- matchRegex - 依赖全局regexCache,需要集成测试
|
||||
*/
|
||||
|
||||
// =============================================================================
|
||||
// matchWords 关键词匹配测试
|
||||
// =============================================================================
|
||||
|
||||
// 创建测试用的matcher结构
|
||||
func createMatcher(matcherType string, words, regex, hash []string, part, condition string, caseInsensitive bool) struct {
|
||||
Type string `json:"type"`
|
||||
Words []string `json:"words"`
|
||||
Regex []string `json:"regex"`
|
||||
Hash []string `json:"hash"`
|
||||
Part string `json:"part"`
|
||||
CaseInsensitive bool `json:"case-insensitive"`
|
||||
Condition string `json:"condition"`
|
||||
} {
|
||||
return struct {
|
||||
Type string `json:"type"`
|
||||
Words []string `json:"words"`
|
||||
Regex []string `json:"regex"`
|
||||
Hash []string `json:"hash"`
|
||||
Part string `json:"part"`
|
||||
CaseInsensitive bool `json:"case-insensitive"`
|
||||
Condition string `json:"condition"`
|
||||
}{
|
||||
Type: matcherType,
|
||||
Words: words,
|
||||
Regex: regex,
|
||||
Hash: hash,
|
||||
Part: part,
|
||||
CaseInsensitive: caseInsensitive,
|
||||
Condition: condition,
|
||||
}
|
||||
}
|
||||
|
||||
// TestMatchWords_ORCondition 测试OR条件匹配
|
||||
func TestMatchWords_ORCondition(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
words []string
|
||||
body string
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "匹配第一个词",
|
||||
words: []string{"nginx", "apache", "iis"},
|
||||
body: "Server: nginx/1.18.0",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "匹配中间词",
|
||||
words: []string{"nginx", "Apache", "iis"},
|
||||
body: "Apache/2.4.41",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "匹配最后词",
|
||||
words: []string{"nginx", "apache", "IIS"},
|
||||
body: "Microsoft-IIS/10.0",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "无匹配",
|
||||
words: []string{"nginx", "apache", "iis"},
|
||||
body: "lighttpd/1.4.55",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "空body",
|
||||
words: []string{"nginx"},
|
||||
body: "",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "空words",
|
||||
words: []string{},
|
||||
body: "nginx",
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
matcher := createMatcher("word", tt.words, nil, nil, "body", "", false)
|
||||
result := matchWords(matcher, tt.body, "")
|
||||
if result != tt.expected {
|
||||
t.Errorf("matchWords() = %v, 期望 %v", result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestMatchWords_ANDCondition 测试AND条件匹配
|
||||
func TestMatchWords_ANDCondition(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
words []string
|
||||
body string
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "全部匹配",
|
||||
words: []string{"WordPress", "wp-content", "wp-includes"},
|
||||
body: "<html>WordPress site with wp-content and wp-includes</html>",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "部分匹配",
|
||||
words: []string{"WordPress", "wp-content", "wp-includes"},
|
||||
body: "WordPress site with wp-content",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "无匹配",
|
||||
words: []string{"WordPress", "wp-content"},
|
||||
body: "Joomla CMS",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "单词全匹配",
|
||||
words: []string{"nginx"},
|
||||
body: "nginx/1.18.0",
|
||||
expected: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
matcher := createMatcher("word", tt.words, nil, nil, "body", "and", false)
|
||||
result := matchWords(matcher, tt.body, "")
|
||||
if result != tt.expected {
|
||||
t.Errorf("matchWords(AND) = %v, 期望 %v", result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestMatchWords_CaseInsensitive 测试大小写不敏感匹配
|
||||
func TestMatchWords_CaseInsensitive(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
words []string
|
||||
body string
|
||||
caseInsensitive bool
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "大小写敏感-精确匹配",
|
||||
words: []string{"WordPress"},
|
||||
body: "WordPress",
|
||||
caseInsensitive: false,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "大小写敏感-不匹配",
|
||||
words: []string{"WordPress"},
|
||||
body: "wordpress",
|
||||
caseInsensitive: false,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "大小写不敏感-小写匹配大写",
|
||||
words: []string{"wordpress"},
|
||||
body: "WORDPRESS",
|
||||
caseInsensitive: true,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "大小写不敏感-混合大小写",
|
||||
words: []string{"WoRdPrEsS"},
|
||||
body: "wordpress site",
|
||||
caseInsensitive: true,
|
||||
expected: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
matcher := createMatcher("word", tt.words, nil, nil, "body", "", tt.caseInsensitive)
|
||||
result := matchWords(matcher, tt.body, "")
|
||||
if result != tt.expected {
|
||||
t.Errorf("matchWords(caseInsensitive=%v) = %v, 期望 %v",
|
||||
tt.caseInsensitive, result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestMatchWords_HeaderPart 测试header部分匹配
|
||||
func TestMatchWords_HeaderPart(t *testing.T) {
|
||||
body := "<html>Body content</html>"
|
||||
headers := "Server: nginx\r\nX-Powered-By: PHP/7.4"
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
words []string
|
||||
part string
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "匹配header",
|
||||
words: []string{"nginx"},
|
||||
part: "header",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "header中不存在",
|
||||
words: []string{"apache"},
|
||||
part: "header",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "匹配body",
|
||||
words: []string{"Body content"},
|
||||
part: "body",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "body中不存在header内容",
|
||||
words: []string{"nginx"},
|
||||
part: "body",
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
matcher := createMatcher("word", tt.words, nil, nil, tt.part, "", false)
|
||||
result := matchWords(matcher, body, headers)
|
||||
if result != tt.expected {
|
||||
t.Errorf("matchWords(part=%s) = %v, 期望 %v",
|
||||
tt.part, result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// matchFavicon 测试
|
||||
// =============================================================================
|
||||
|
||||
// TestMatchFavicon_Basic 测试favicon hash匹配
|
||||
func TestMatchFavicon_Basic(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
hashes []string
|
||||
favicon FaviconHashes
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "mmh3匹配",
|
||||
hashes: []string{"1386054408", "def456"},
|
||||
favicon: FaviconHashes{MMH3: "1386054408", MD5: "abc"},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "MD5匹配",
|
||||
hashes: []string{"abc123", "e2e2ba13339c2fea220f8b4fa6c32c0d"},
|
||||
favicon: FaviconHashes{MMH3: "123", MD5: "e2e2ba13339c2fea220f8b4fa6c32c0d"},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "无匹配",
|
||||
hashes: []string{"abc123", "def456"},
|
||||
favicon: FaviconHashes{MMH3: "xyz789", MD5: "111"},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "空favicon",
|
||||
hashes: []string{"abc123"},
|
||||
favicon: FaviconHashes{},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "空hashes",
|
||||
hashes: []string{},
|
||||
favicon: FaviconHashes{MMH3: "abc123", MD5: "def"},
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
matcher := createMatcher("favicon", nil, nil, tt.hashes, "", "", false)
|
||||
result := matchFavicon(matcher, tt.favicon)
|
||||
if result != tt.expected {
|
||||
t.Errorf("matchFavicon() = %v, 期望 %v", result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// CalculateFaviconHashes 测试
|
||||
// =============================================================================
|
||||
|
||||
// TestCalculateFaviconHashes_Consistency 测试hash计算一致性
|
||||
func TestCalculateFaviconHashes_Consistency(t *testing.T) {
|
||||
data := []byte("test favicon data")
|
||||
|
||||
hash1 := CalculateFaviconHashes(data)
|
||||
hash2 := CalculateFaviconHashes(data)
|
||||
|
||||
if hash1.MMH3 != hash2.MMH3 {
|
||||
t.Errorf("相同数据应产生相同mmh3 hash: %s vs %s", hash1.MMH3, hash2.MMH3)
|
||||
}
|
||||
if hash1.MD5 != hash2.MD5 {
|
||||
t.Errorf("相同数据应产生相同MD5 hash: %s vs %s", hash1.MD5, hash2.MD5)
|
||||
}
|
||||
}
|
||||
|
||||
// TestCalculateFaviconHashes_Different 测试不同数据产生不同hash
|
||||
func TestCalculateFaviconHashes_Different(t *testing.T) {
|
||||
data1 := []byte("favicon data 1")
|
||||
data2 := []byte("favicon data 2")
|
||||
|
||||
hash1 := CalculateFaviconHashes(data1)
|
||||
hash2 := CalculateFaviconHashes(data2)
|
||||
|
||||
if hash1.MMH3 == hash2.MMH3 {
|
||||
t.Error("不同数据应产生不同mmh3 hash")
|
||||
}
|
||||
if hash1.MD5 == hash2.MD5 {
|
||||
t.Error("不同数据应产生不同MD5 hash")
|
||||
}
|
||||
}
|
||||
|
||||
// TestCalculateFaviconHashes_Empty 测试空数据
|
||||
func TestCalculateFaviconHashes_Empty(t *testing.T) {
|
||||
hash := CalculateFaviconHashes([]byte{})
|
||||
|
||||
if hash.MMH3 != "" || hash.MD5 != "" {
|
||||
t.Errorf("空数据应返回空FaviconHashes,实际: mmh3=%s, md5=%s", hash.MMH3, hash.MD5)
|
||||
}
|
||||
}
|
||||
|
||||
// TestCalculateFaviconHashes_Nil 测试nil数据
|
||||
func TestCalculateFaviconHashes_Nil(t *testing.T) {
|
||||
hash := CalculateFaviconHashes(nil)
|
||||
|
||||
if hash.MMH3 != "" || hash.MD5 != "" {
|
||||
t.Errorf("nil数据应返回空FaviconHashes,实际: mmh3=%s, md5=%s", hash.MMH3, hash.MD5)
|
||||
}
|
||||
}
|
||||
|
||||
// TestCalculateFaviconHashes_Format 测试hash格式
|
||||
func TestCalculateFaviconHashes_Format(t *testing.T) {
|
||||
data := []byte("test data")
|
||||
hash := CalculateFaviconHashes(data)
|
||||
|
||||
// mmh3 应该是有符号整数格式(可能是负数)
|
||||
if hash.MMH3 == "" {
|
||||
t.Error("mmh3 hash不应为空")
|
||||
}
|
||||
|
||||
// MD5产生32字符的十六进制字符串
|
||||
if len(hash.MD5) != 32 {
|
||||
t.Errorf("MD5 hash长度应为32,实际: %d", len(hash.MD5))
|
||||
}
|
||||
|
||||
// 验证MD5是有效的十六进制
|
||||
for _, c := range hash.MD5 {
|
||||
if (c < '0' || c > '9') && (c < 'a' || c > 'f') {
|
||||
t.Errorf("MD5 hash包含无效字符: %c", c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestMMH3_KnownValue 测试mmh3已知值(验证算法正确性)
|
||||
func TestMMH3_KnownValue(t *testing.T) {
|
||||
// 使用简单的测试字符串验证mmh3算法
|
||||
// mmh3("hello") with seed=0 应该产生一个固定值
|
||||
result := mmh3Hash32([]byte("hello"))
|
||||
|
||||
// mmh3("hello", seed=0) = 613153351 (根据标准实现)
|
||||
expected := int32(613153351)
|
||||
if result != expected {
|
||||
t.Errorf("mmh3('hello') = %d, 期望 %d", result, expected)
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// matchMatcher 分发测试
|
||||
// =============================================================================
|
||||
|
||||
// TestMatchMatcher_TypeDispatch 测试类型分发
|
||||
func TestMatchMatcher_TypeDispatch(t *testing.T) {
|
||||
// 注意:matchRegex需要全局regexCache,这里只测试word和favicon
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
matcherType string
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "word类型",
|
||||
matcherType: "word",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "favicon类型",
|
||||
matcherType: "favicon",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "未知类型",
|
||||
matcherType: "unknown",
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var matcher struct {
|
||||
Type string `json:"type"`
|
||||
Words []string `json:"words"`
|
||||
Regex []string `json:"regex"`
|
||||
Hash []string `json:"hash"`
|
||||
Part string `json:"part"`
|
||||
CaseInsensitive bool `json:"case-insensitive"`
|
||||
Condition string `json:"condition"`
|
||||
}
|
||||
|
||||
switch tt.matcherType {
|
||||
case "word":
|
||||
matcher = createMatcher("word", []string{"nginx"}, nil, nil, "body", "", false)
|
||||
case "favicon":
|
||||
matcher = createMatcher("favicon", nil, nil, []string{"abc123"}, "", "", false)
|
||||
default:
|
||||
matcher = createMatcher(tt.matcherType, nil, nil, nil, "", "", false)
|
||||
}
|
||||
|
||||
result := matchMatcher(matcher, "nginx server", "Server: nginx", FaviconHashes{MMH3: "abc123", MD5: "def456"}, nil)
|
||||
if result != tt.expected {
|
||||
t.Errorf("matchMatcher(type=%s) = %v, 期望 %v",
|
||||
tt.matcherType, result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// 边界情况测试
|
||||
// =============================================================================
|
||||
|
||||
// TestMatchWords_SpecialCharacters 测试特殊字符
|
||||
func TestMatchWords_SpecialCharacters(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
words []string
|
||||
body string
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "包含点号",
|
||||
words: []string{"nginx/1.18.0"},
|
||||
body: "Server: nginx/1.18.0",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "包含括号",
|
||||
words: []string{"(Ubuntu)"},
|
||||
body: "Apache/2.4.41 (Ubuntu)",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "包含中文",
|
||||
words: []string{"欢迎"},
|
||||
body: "<title>欢迎访问</title>",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "包含换行符",
|
||||
words: []string{"Content-Type"},
|
||||
body: "Header:\r\nContent-Type: text/html",
|
||||
expected: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
matcher := createMatcher("word", tt.words, nil, nil, "body", "", false)
|
||||
result := matchWords(matcher, tt.body, "")
|
||||
if result != tt.expected {
|
||||
t.Errorf("matchWords() = %v, 期望 %v", result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestMatchWords_LargeBody 测试大body
|
||||
func TestMatchWords_LargeBody(t *testing.T) {
|
||||
// 构造100KB的body
|
||||
largeBody := make([]byte, 100*1024)
|
||||
for i := range largeBody {
|
||||
largeBody[i] = 'x'
|
||||
}
|
||||
// 在中间插入关键词
|
||||
copy(largeBody[50*1024:], []byte("WordPress"))
|
||||
|
||||
matcher := createMatcher("word", []string{"WordPress"}, nil, nil, "body", "", false)
|
||||
result := matchWords(matcher, string(largeBody), "")
|
||||
|
||||
if !result {
|
||||
t.Error("大body中的关键词应被匹配")
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// 性能基准测试
|
||||
// =============================================================================
|
||||
|
||||
// =============================================================================
|
||||
// 版本提取测试
|
||||
// =============================================================================
|
||||
|
||||
// TestExtractVersions_ServerHeaders 测试从 Server 头提取版本
|
||||
func TestExtractVersions_ServerHeaders(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
headers string
|
||||
expected map[string]string // name -> version
|
||||
}{
|
||||
{
|
||||
name: "nginx版本",
|
||||
headers: "Server: nginx/1.18.0",
|
||||
expected: map[string]string{"nginx": "1.18.0"},
|
||||
},
|
||||
{
|
||||
name: "Apache版本",
|
||||
headers: "Server: Apache/2.4.41 (Ubuntu)",
|
||||
expected: map[string]string{"apache": "2.4.41"},
|
||||
},
|
||||
{
|
||||
name: "多个版本",
|
||||
headers: "Server: nginx/1.18.0\nX-Powered-By: PHP/7.4.3",
|
||||
expected: map[string]string{"nginx": "1.18.0", "php": "7.4.3"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
results := ExtractVersions("", tt.headers)
|
||||
for _, v := range results {
|
||||
if expected, ok := tt.expected[v.Name]; ok {
|
||||
if v.Version != expected {
|
||||
t.Errorf("%s 版本不匹配: got %s, want %s", v.Name, v.Version, expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestExtractVersions_BodyContent 测试从 body 提取版本
|
||||
func TestExtractVersions_BodyContent(t *testing.T) {
|
||||
body := `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="generator" content="WordPress 6.0" />
|
||||
<script src="/js/jquery-3.6.0.min.js"></script>
|
||||
</head>
|
||||
<body>Powered by Tomcat/9.0.41</body>
|
||||
</html>`
|
||||
|
||||
results := ExtractVersions(body, "")
|
||||
|
||||
// 检查是否提取到预期的版本
|
||||
found := make(map[string]bool)
|
||||
for _, v := range results {
|
||||
found[v.Name] = true
|
||||
t.Logf("提取到: %s %s", v.Name, v.Version)
|
||||
}
|
||||
|
||||
// WordPress 可能无法提取(因为正则需要调整),但 jQuery 和 Tomcat 应该可以
|
||||
if !found["jquery"] && !found["tomcat"] {
|
||||
t.Error("应至少提取到 jquery 或 tomcat 版本")
|
||||
}
|
||||
}
|
||||
|
||||
// TestExtractVersions_Empty 测试空输入
|
||||
func TestExtractVersions_Empty(t *testing.T) {
|
||||
results := ExtractVersions("", "")
|
||||
if len(results) != 0 {
|
||||
t.Errorf("空输入应返回空结果,实际: %d", len(results))
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkExtractVersions 基准测试:版本提取性能
|
||||
func BenchmarkExtractVersions(b *testing.B) {
|
||||
headers := "Server: nginx/1.18.0\nX-Powered-By: PHP/8.0.3\n"
|
||||
body := `<meta name="generator" content="WordPress 6.0" />`
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = ExtractVersions(body, headers)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkMatchEnhancedFingerprints 基准测试:并发指纹匹配
|
||||
func BenchmarkMatchEnhancedFingerprints(b *testing.B) {
|
||||
// 模拟真实的 HTTP 响应
|
||||
body := []byte(`<!DOCTYPE html>
|
||||
<html>
|
||||
<head><title>WordPress Site</title></head>
|
||||
<body>
|
||||
<meta name="generator" content="WordPress 6.0" />
|
||||
<link rel="stylesheet" href="/wp-content/themes/flavor/style.css" />
|
||||
Powered by nginx/1.18.0
|
||||
</body>
|
||||
</html>`)
|
||||
headers := "Server: nginx/1.18.0\nX-Powered-By: PHP/8.0\n"
|
||||
favicon := FaviconHashes{MMH3: "1386054408", MD5: "abc123"}
|
||||
|
||||
// 预热:确保指纹库已加载
|
||||
_ = MatchEnhancedFingerprints(body, headers, favicon)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = MatchEnhancedFingerprints(body, headers, favicon)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,339 @@
|
||||
package fingerprint
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
|
||||
"github.com/shadow1ng/fscan/common"
|
||||
)
|
||||
|
||||
// RuleData 指纹识别规则数据
|
||||
type RuleData struct {
|
||||
Name string
|
||||
Type string
|
||||
Rule string
|
||||
Compiled *regexp.Regexp // 预编译的正则表达式
|
||||
}
|
||||
|
||||
// init 在程序启动时预编译所有正则表达式
|
||||
func init() {
|
||||
for i := range RuleDatas {
|
||||
re, err := regexp.Compile(RuleDatas[i].Rule)
|
||||
if err != nil {
|
||||
common.LogDebug("fingerprint rule compile failed: " + RuleDatas[i].Name + " - " + err.Error())
|
||||
continue
|
||||
}
|
||||
RuleDatas[i].Compiled = re
|
||||
}
|
||||
}
|
||||
|
||||
// Md5Data MD5指纹数据
|
||||
type Md5Data struct {
|
||||
Name string
|
||||
Md5Str string
|
||||
}
|
||||
|
||||
// PocData POC漏洞检测数据
|
||||
type PocData struct {
|
||||
Name string
|
||||
Alias string
|
||||
}
|
||||
|
||||
// RuleDatas 指纹识别规则数据集
|
||||
var RuleDatas = []RuleData{
|
||||
{Name: "宝塔", Type: "code", Rule: "(app.bt.cn/static/app.png|安全入口校验失败|<title>入口校验失败</title>|href=\"http://www.bt.cn/bbs)"},
|
||||
{Name: "深信服防火墙类产品", Type: "code", Rule: "(SANGFOR FW)"},
|
||||
{Name: "360网站卫士", Type: "code", Rule: "(webscan.360.cn/status/pai/hash|wzws-waf-cgi|zhuji.360.cn/guard/firewall/stopattack.html)"},
|
||||
{Name: "360网站卫士", Type: "headers", Rule: "(360wzws|CWAP-waf|zhuji.360.cn|X-Safe-Firewall)"},
|
||||
{Name: "绿盟防火墙", Type: "code", Rule: "(NSFOCUS NF)"},
|
||||
{Name: "绿盟防火墙", Type: "headers", Rule: "(NSFocus)"},
|
||||
{Name: "Topsec-Waf", Type: "index", Rule: `(<META NAME="Copyright" CONTENT="Topsec Network Security Technology Co.,Ltd"/>","<META NAME="DESCRIPTION" CONTENT="Topsec web UI"/>)`},
|
||||
{Name: "Anquanbao", Type: "headers", Rule: "(Anquanbao)"},
|
||||
{Name: "BaiduYunjiasu", Type: "headers", Rule: "(yunjiasu)"},
|
||||
{Name: "BigIP", Type: "headers", Rule: "(BigIP|BIGipServer)"},
|
||||
{Name: "BinarySEC", Type: "headers", Rule: "(binarysec)"},
|
||||
{Name: "BlockDoS", Type: "headers", Rule: "(BlockDos.net)"},
|
||||
{Name: "CloudFlare", Type: "headers", Rule: "(cloudflare)"},
|
||||
{Name: "Cloudfront", Type: "headers", Rule: "(cloudfront)"},
|
||||
{Name: "Comodo", Type: "headers", Rule: "(Protected by COMODO)"},
|
||||
{Name: "IBM-DataPower", Type: "headers", Rule: "(X-Backside-Transport)"},
|
||||
{Name: "DenyAll", Type: "headers", Rule: "(sessioncookie=)"},
|
||||
{Name: "dotDefender", Type: "headers", Rule: "(dotDefender)"},
|
||||
{Name: "Incapsula", Type: "headers", Rule: "(X-CDN|Incapsula)"},
|
||||
{Name: "Jiasule", Type: "headers", Rule: "(jsluid=)"},
|
||||
{Name: "KONA", Type: "headers", Rule: "(AkamaiGHost)"},
|
||||
{Name: "ModSecurity", Type: "headers", Rule: "(Mod_Security|NOYB)"},
|
||||
{Name: "NetContinuum", Type: "headers", Rule: "(Cneonction|nnCoection|citrix_ns_id)"},
|
||||
{Name: "Newdefend", Type: "headers", Rule: "(newdefend)"},
|
||||
{Name: "Safe3", Type: "headers", Rule: "(Safe3WAF|Safe3 Web Firewall)"},
|
||||
{Name: "Safedog", Type: "code", Rule: "(404.safedog.cn/images/safedogsite/broswer_logo.jpg)"},
|
||||
{Name: "Safedog", Type: "headers", Rule: "(Safedog|WAF/2.0)"},
|
||||
{Name: "SonicWALL", Type: "headers", Rule: "(SonicWALL)"},
|
||||
{Name: "Stingray", Type: "headers", Rule: "(X-Mapping-)"},
|
||||
{Name: "Sucuri", Type: "headers", Rule: "(Sucuri/Cloudproxy)"},
|
||||
{Name: "Usp-Sec", Type: "headers", Rule: "(Secure Entry Server)"},
|
||||
{Name: "Varnish", Type: "headers", Rule: "(varnish)"},
|
||||
{Name: "Wallarm", Type: "headers", Rule: "(wallarm)"},
|
||||
{Name: "阿里云", Type: "code", Rule: "(errors.aliyun.com)"},
|
||||
{Name: "WebKnight", Type: "headers", Rule: "(WebKnight)"},
|
||||
{Name: "Yundun", Type: "headers", Rule: "(YUNDUN)"},
|
||||
{Name: "Yunsuo", Type: "headers", Rule: "(yunsuo)"},
|
||||
{Name: "Coding pages", Type: "header", Rule: "(Coding Pages)"},
|
||||
{Name: "启明防火墙", Type: "code", Rule: "(/cgi-bin/webui?op=get_product_model)"},
|
||||
{Name: "Shiro", Type: "headers", Rule: "(=deleteMe|rememberMe=)"},
|
||||
{Name: "Portainer(Docker管理)", Type: "code", Rule: "(portainer.updatePassword|portainer.init.admin)"},
|
||||
{Name: "Gogs简易Git服务", Type: "cookie", Rule: "(i_like_gogs)"},
|
||||
{Name: "Gitea简易Git服务", Type: "cookie", Rule: "(i_like_gitea)"},
|
||||
{Name: "Nexus", Type: "code", Rule: "(Nexus Repository Manager)"},
|
||||
{Name: "Nexus", Type: "cookie", Rule: "(NX-ANTI-CSRF-TOKEN)"},
|
||||
{Name: "Harbor", Type: "code", Rule: "(<title>Harbor</title>)"},
|
||||
{Name: "Harbor", Type: "cookie", Rule: "(harbor-lang)"},
|
||||
{Name: "禅道", Type: "code", Rule: "(/theme/default/images/main/zt-logo.png|/zentao/theme/zui/css/min.css)"},
|
||||
{Name: "禅道", Type: "cookie", Rule: "(zentaosid)"},
|
||||
{Name: "协众OA", Type: "code", Rule: "(Powered by 协众OA)"},
|
||||
{Name: "协众OA", Type: "cookie", Rule: "(CNOAOASESSID)"},
|
||||
{Name: "xxl-job", Type: "code", Rule: "(分布式任务调度平台XXL-JOB)"},
|
||||
{Name: "atmail-WebMail", Type: "cookie", Rule: "(atmail6)"},
|
||||
{Name: "atmail-WebMail", Type: "code", Rule: "(/index.php/mail/auth/processlogin|Powered by Atmail)"},
|
||||
{Name: "weblogic", Type: "code", Rule: "(/console/framework/skins/wlsconsole/images/login_WebLogic_branding.png|Welcome to Weblogic Application Server|<i>Hypertext Transfer Protocol -- HTTP/1.1</i>)"},
|
||||
{Name: "致远OA", Type: "code", Rule: "(/seeyon/common/|/seeyon/USER-DATA/IMAGES/LOGIN/login.gif)"},
|
||||
{Name: "discuz", Type: "code", Rule: "(content=\"Discuz! X\")"},
|
||||
{Name: "Typecho", Type: "code", Rule: "(Typecho</a>)"},
|
||||
{Name: "金蝶EAS", Type: "code", Rule: "(easSessionId)"},
|
||||
{Name: "phpMyAdmin", Type: "cookie", Rule: "(pma_lang|phpMyAdmin)"},
|
||||
{Name: "phpMyAdmin", Type: "code", Rule: "(/themes/pmahomme/img/logo_right.png)"},
|
||||
{Name: "H3C-AM8000", Type: "code", Rule: "(AM8000)"},
|
||||
{Name: "360企业版", Type: "code", Rule: "(360EntWebAdminMD5Secret)"},
|
||||
{Name: "H3C公司产品", Type: "code", Rule: "([email protected])"},
|
||||
{Name: "H3C ICG 1000", Type: "code", Rule: "(ICG 1000系统管理)"},
|
||||
{Name: "Citrix-Metaframe", Type: "code", Rule: "(window.location=\"/Citrix/MetaFrame)"},
|
||||
{Name: "H3C ER5100", Type: "code", Rule: "(ER5100系统管理)"},
|
||||
{Name: "阿里云CDN", Type: "code", Rule: "(cdn.aliyuncs.com)"},
|
||||
{Name: "CISCO_EPC3925", Type: "code", Rule: "(Docsis_system)"},
|
||||
{Name: "CISCO ASR", Type: "code", Rule: "(CISCO ASR)"},
|
||||
{Name: "H3C ER3200", Type: "code", Rule: "(ER3200系统管理)"},
|
||||
{Name: "万户oa", Type: "code", Rule: "(/defaultroot/templates/template_system/common/css/|/defaultroot/scripts/|css/css_whir.css)"},
|
||||
{Name: "Spark_Master", Type: "code", Rule: "(Spark Master at)"},
|
||||
{Name: "华为_HUAWEI_SRG2220", Type: "code", Rule: "(HUAWEI SRG2220)"},
|
||||
{Name: "蓝凌OA", Type: "code", Rule: "(/scripts/jquery.landray.common.js)"},
|
||||
{Name: "深信服ssl-vpn", Type: "code", Rule: "(login_psw.csp)"},
|
||||
{Name: "华为 NetOpen", Type: "code", Rule: "(/netopen/theme/css/inFrame.css)"},
|
||||
{Name: "Citrix-Web-PN-Server", Type: "code", Rule: "(Citrix Web PN Server)"},
|
||||
{Name: "juniper_vpn", Type: "code", Rule: "(welcome.cgi?p=logo|/images/logo_juniper_reversed.gif)"},
|
||||
{Name: "360主机卫士", Type: "headers", Rule: "(zhuji.360.cn)"},
|
||||
{Name: "Nagios", Type: "headers", Rule: "(Nagios Access)"},
|
||||
{Name: "H3C ER8300", Type: "code", Rule: "(ER8300系统管理)"},
|
||||
{Name: "Citrix-Access-Gateway", Type: "code", Rule: "(Citrix Access Gateway)"},
|
||||
{Name: "华为 MCU", Type: "code", Rule: "(McuR5-min.js)"},
|
||||
{Name: "TP-LINK Wireless WDR3600", Type: "code", Rule: "(TP-LINK Wireless WDR3600)"},
|
||||
{Name: "泛微OA", Type: "headers", Rule: "(ecology_JSessionid)"},
|
||||
{Name: "泛微OA", Type: "code", Rule: "(/spa/portal/public/index.js)"},
|
||||
{Name: "华为_HUAWEI_ASG2050", Type: "code", Rule: "(HUAWEI ASG2050)"},
|
||||
{Name: "360网站卫士", Type: "code", Rule: "(360wzb)"},
|
||||
{Name: "Citrix-XenServer", Type: "code", Rule: "(Citrix Systems, Inc. XenServer)"},
|
||||
{Name: "H3C ER2100V2", Type: "code", Rule: "(ER2100V2系统管理)"},
|
||||
{Name: "zabbix", Type: "cookie", Rule: "(zbx_sessionid)"},
|
||||
{Name: "zabbix", Type: "code", Rule: "(images/general/zabbix.ico|Zabbix SIA|zabbix-server: Zabbix)"},
|
||||
{Name: "CISCO_VPN", Type: "headers", Rule: "(webvpn)"},
|
||||
{Name: "360站长平台", Type: "code", Rule: "(360-site-verification)"},
|
||||
{Name: "H3C ER3108GW", Type: "code", Rule: "(ER3108GW系统管理)"},
|
||||
{Name: "o2security_vpn", Type: "headers", Rule: "(client_param=install_active)"},
|
||||
{Name: "H3C ER3260G2", Type: "code", Rule: "(ER3260G2系统管理)"},
|
||||
{Name: "H3C ICG1000", Type: "code", Rule: "(ICG1000系统管理)"},
|
||||
{Name: "CISCO-CX20", Type: "code", Rule: "(CISCO-CX20)"},
|
||||
{Name: "H3C ER5200", Type: "code", Rule: "(ER5200系统管理)"},
|
||||
{Name: "linksys-vpn-bragap14-parintins", Type: "code", Rule: "(linksys-vpn-bragap14-parintins)"},
|
||||
{Name: "360网站卫士常用前端公共库", Type: "code", Rule: "(libs.useso.com)"},
|
||||
{Name: "H3C ER3100", Type: "code", Rule: "(ER3100系统管理)"},
|
||||
{Name: "H3C-SecBlade-FireWall", Type: "code", Rule: "(js/MulPlatAPI.js)"},
|
||||
{Name: "360webfacil_360WebManager", Type: "code", Rule: "(publico/template/)"},
|
||||
{Name: "Citrix_Netscaler", Type: "code", Rule: "(ns_af)"},
|
||||
{Name: "H3C ER6300G2", Type: "code", Rule: "(ER6300G2系统管理)"},
|
||||
{Name: "H3C ER3260", Type: "code", Rule: "(ER3260系统管理)"},
|
||||
{Name: "华为_HUAWEI_SRG3250", Type: "code", Rule: "(HUAWEI SRG3250)"},
|
||||
{Name: "exchange", Type: "code", Rule: "(/owa/auth.owa|Exchange Admin Center)"},
|
||||
{Name: "Spark_Worker", Type: "code", Rule: "(Spark Worker at)"},
|
||||
{Name: "H3C ER3108G", Type: "code", Rule: "(ER3108G系统管理)"},
|
||||
{Name: "Citrix-ConfProxy", Type: "code", Rule: "(confproxy)"},
|
||||
{Name: "360网站安全检测", Type: "code", Rule: "(webscan.360.cn/status/pai/hash)"},
|
||||
{Name: "H3C ER5200G2", Type: "code", Rule: "(ER5200G2系统管理)"},
|
||||
{Name: "华为(HUAWEI)安全设备", Type: "code", Rule: "(sweb-lib/resource/)"},
|
||||
{Name: "华为(HUAWEI)USG", Type: "code", Rule: "(UI_component/commonDefine/UI_regex_define.js)"},
|
||||
{Name: "H3C ER6300", Type: "code", Rule: "(ER6300系统管理)"},
|
||||
{Name: "华为_HUAWEI_ASG2100", Type: "code", Rule: "(HUAWEI ASG2100)"},
|
||||
{Name: "TP-Link 3600 DD-WRT", Type: "code", Rule: "(TP-Link 3600 DD-WRT)"},
|
||||
{Name: "NETGEAR WNDR3600", Type: "code", Rule: "(NETGEAR WNDR3600)"},
|
||||
{Name: "H3C ER2100", Type: "code", Rule: "(ER2100系统管理)"},
|
||||
{Name: "jira", Type: "code", Rule: "(jira.webresources)"},
|
||||
{Name: "金和协同管理平台", Type: "code", Rule: "(金和协同管理平台)"},
|
||||
{Name: "Citrix-NetScaler", Type: "code", Rule: "(NS-CACHE)"},
|
||||
{Name: "linksys-vpn", Type: "headers", Rule: "(linksys-vpn)"},
|
||||
{Name: "通达OA", Type: "code", Rule: "(/static/images/tongda.ico|http://www.tongda2000.com|通达OA移动版|Office Anywhere)"},
|
||||
{Name: "华为(HUAWEI)Secoway设备", Type: "code", Rule: "(Secoway)"},
|
||||
{Name: "华为_HUAWEI_SRG1220", Type: "code", Rule: "(HUAWEI SRG1220)"},
|
||||
{Name: "H3C ER2100n", Type: "code", Rule: "(ER2100n系统管理)"},
|
||||
{Name: "H3C ER8300G2", Type: "code", Rule: "(ER8300G2系统管理)"},
|
||||
{Name: "金蝶政务GSiS", Type: "code", Rule: "(/kdgs/script/kdgs.js)"},
|
||||
{Name: "Jboss", Type: "code", Rule: "(Welcome to JBoss|jboss.css)"},
|
||||
{Name: "Jboss", Type: "headers", Rule: "(JBoss)"},
|
||||
{Name: "泛微E-mobile", Type: "code", Rule: "(Weaver E-mobile|weaver,e-mobile)"},
|
||||
{Name: "泛微E-mobile", Type: "headers", Rule: "(EMobileServer)"},
|
||||
{Name: "齐治堡垒机", Type: "code", Rule: "(logo-icon-ico72.png|resources/themes/images/logo-login.png)"},
|
||||
{Name: "ThinkPHP", Type: "headers", Rule: "(ThinkPHP)"},
|
||||
{Name: "ThinkPHP", Type: "code", Rule: "(/Public/static/js/)"},
|
||||
{Name: "weaver-ebridge", Type: "code", Rule: "(e-Bridge,http://wx.weaver)"},
|
||||
{Name: "Laravel", Type: "headers", Rule: "(laravel_session)"},
|
||||
{Name: "DWR", Type: "code", Rule: "(dwr/engine.js)"},
|
||||
{Name: "swagger_ui", Type: "code", Rule: "(swagger-ui/css|\"swagger\":|swagger-ui.min.js)"},
|
||||
{Name: "大汉版通发布系统", Type: "code", Rule: "(大汉版通发布系统|大汉网络)"},
|
||||
{Name: "druid", Type: "code", Rule: "(druid.index|DruidDrivers|DruidVersion|Druid Stat Index)"},
|
||||
{Name: "Jenkins", Type: "code", Rule: "(Jenkins)"},
|
||||
{Name: "红帆OA", Type: "code", Rule: "(iOffice)"},
|
||||
{Name: "VMware vSphere", Type: "code", Rule: "(VMware vSphere)"},
|
||||
{Name: "打印机", Type: "code", Rule: "(打印机|media/canon.gif)"},
|
||||
{Name: "finereport", Type: "code", Rule: "(isSupportForgetPwd|FineReport,Web Reporting Tool)"},
|
||||
{Name: "蓝凌OA", Type: "code", Rule: "(蓝凌软件|StylePath:\"/resource/style/default/\"|/resource/customization|sys/ui/extend/theme/default/style/profile.css|sys/ui/extend/theme/default/style/icon.css)"},
|
||||
{Name: "GitLab", Type: "code", Rule: "(href=\"https://about.gitlab.com/)"},
|
||||
{Name: "Jquery-1.7.2", Type: "code", Rule: "(/webui/js/jquerylib/jquery-1.7.2.min.js)"},
|
||||
{Name: "Hadoop Applications", Type: "code", Rule: "(/cluster/app/application)"},
|
||||
{Name: "海昌OA", Type: "code", Rule: "(/loginmain4/js/jquery.min.js)"},
|
||||
{Name: "帆软报表", Type: "code", Rule: "(WebReport/login.html|ReportServer)"},
|
||||
{Name: "帆软报表", Type: "headers", Rule: "(数据决策系统)"},
|
||||
{Name: "华夏ERP", Type: "headers", Rule: "(华夏ERP)"},
|
||||
{Name: "金和OA", Type: "cookie", Rule: "(ASPSESSIONIDSSCDTDBS)"},
|
||||
{Name: "久其财务报表", Type: "code", Rule: "(netrep/login.jsp|/netrep/intf)"},
|
||||
{Name: "若依管理系统", Type: "code", Rule: "(ruoyi/login.js|ruoyi/js/ry-ui.js)"},
|
||||
{Name: "启莱OA", Type: "code", Rule: "(js/jQselect.js|js/jquery-1.4.2.min.js)"},
|
||||
{Name: "智慧校园管理系统", Type: "code", Rule: "(DC_Login/QYSignUp)"},
|
||||
{Name: "JQuery-1.7.2", Type: "code", Rule: "(webui/js/jquerylib/jquery-1.7.2.min.js)"},
|
||||
{Name: "浪潮 ClusterEngineV4.0", Type: "code", Rule: "(0;url=module/login/login.html)"},
|
||||
{Name: "会捷通云视讯平台", Type: "code", Rule: "(him/api/rest/v1.0/node/role|him.app)"},
|
||||
{Name: "源码泄露账号密码 F12查看", Type: "code", Rule: "(get_dkey_passwd)"},
|
||||
{Name: "Smartbi Insight", Type: "code", Rule: "(smartbi.gcf.gcfutil)"},
|
||||
{Name: "汉王人脸考勤管理系统", Type: "code", Rule: "(汉王人脸考勤管理系统|/Content/image/hanvan.png|/Content/image/hvicon.ico)"},
|
||||
{Name: "亿赛通-电子文档安全管理系统", Type: "code", Rule: "(电子文档安全管理系统|/CDGServer3/index.jsp|/CDGServer3/SysConfig.jsp|/CDGServer3/help/getEditionInfo.jsp)"},
|
||||
{Name: "天融信 TopApp-LB 负载均衡系统", Type: "code", Rule: "(TopApp-LB 负载均衡系统)"},
|
||||
{Name: "中新金盾信息安全管理系统", Type: "code", Rule: "(中新金盾信息安全管理系统|中新网络信息安全股份有限公司)"},
|
||||
{Name: "好视通", Type: "code", Rule: "(深圳银澎云计算有限公司|itunes.apple.com/us/app/id549407870|hao-shi-tong-yun-hui-yi-yuan)"},
|
||||
{Name: "蓝海卓越计费管理系统", Type: "code", Rule: "(蓝海卓越计费管理系统|星锐蓝海网络科技有限公司)"},
|
||||
{Name: "和信创天云桌面系统", Type: "code", Rule: "(和信下一代云桌面VENGD|/vesystem/index.php)"},
|
||||
{Name: "金山", Type: "code", Rule: "(北京猎鹰安全科技有限公司|金山终端安全系统V9.0Web控制台|北京金山安全管理系统技术有限公司|金山V8)"},
|
||||
{Name: "WIFISKY-7层流控路由器", Type: "code", Rule: "(深圳市领空技术有限公司|WIFISKY 7层流控路由器)"},
|
||||
{Name: "MetInfo-米拓建站", Type: "code", Rule: "(MetInfo|/skin/style/metinfo.css|/skin/style/metinfo-v2.css)"},
|
||||
{Name: "IBM-Lotus-Domino", Type: "code", Rule: "(/mailjump.nsf|/domcfg.nsf|/names.nsf|/homepage.nsf)"},
|
||||
{Name: "APACHE-kylin", Type: "code", Rule: "(url=kylin)"},
|
||||
{Name: "C-Lodop打印服务系统", Type: "code", Rule: "(/CLodopfuncs.js|www.c-lodop.com)"},
|
||||
{Name: "HFS", Type: "code", Rule: "(href=\"http://www.rejetto.com/hfs/)"},
|
||||
{Name: "Jellyfin", Type: "code", Rule: "(content=\"http://jellyfin.org\")"},
|
||||
{Name: "FIT2CLOUD-JumpServer-堡垒机", Type: "code", Rule: "(<title>JumpServer</title>)"},
|
||||
{Name: "Alibaba Nacos", Type: "code", Rule: "(<title>Nacos</title>)"},
|
||||
{Name: "Nagios", Type: "headers", Rule: "(nagios admin)"},
|
||||
{Name: "Pulse Connect Secure", Type: "code", Rule: "(/dana-na/imgs/space.gif)"},
|
||||
{Name: "h5ai", Type: "code", Rule: "(powered by h5ai)"},
|
||||
{Name: "jeesite", Type: "cookie", Rule: "(jeesite.session.id)"},
|
||||
{Name: "拓尔思SSO", Type: "cookie", Rule: "(trsidsssosessionid)"},
|
||||
{Name: "拓尔思WCMv7/6", Type: "cookie", Rule: "(com.trs.idm.coSessionId)"},
|
||||
{Name: "天融信脆弱性扫描与管理系统", Type: "code", Rule: "(/js/report/horizontalReportPanel.js)"},
|
||||
{Name: "天融信网络审计系统", Type: "code", Rule: "(onclick=dlg_download())"},
|
||||
{Name: "天融信日志收集与分析系统", Type: "code", Rule: "(天融信日志收集与分析系统)"},
|
||||
{Name: "URP教务系统", Type: "code", Rule: "(北京清元优软科技有限公司)"},
|
||||
{Name: "科来RAS", Type: "code", Rule: "(科来软件 版权所有|i18ninit.min.js)"},
|
||||
{Name: "正方OA", Type: "code", Rule: "(zfoausername)"},
|
||||
{Name: "希尔OA", Type: "code", Rule: "(/heeroa/login.do)"},
|
||||
{Name: "泛普建筑工程施工OA", Type: "code", Rule: "(/dwr/interface/LoginService.js)"},
|
||||
{Name: "中望OA", Type: "code", Rule: "(/IMAGES/default/first/xtoa_logo.png|/app_qjuserinfo/qjuserinfoadd.jsp)"},
|
||||
{Name: "海天OA", Type: "code", Rule: "(HTVOS.js)"},
|
||||
{Name: "信达OA", Type: "code", Rule: "(http://www.xdoa.cn</a>)"},
|
||||
{Name: "任我行CRM", Type: "code", Rule: "(CRM_LASTLOGINUSERKEY)"},
|
||||
{Name: "Spammark邮件信息安全网关", Type: "code", Rule: "(/cgi-bin/spammark?empty=1)"},
|
||||
{Name: "winwebmail", Type: "code", Rule: "(WinWebMail Server|images/owin.css)"},
|
||||
{Name: "浪潮政务系统", Type: "code", Rule: "(LangChao.ECGAP.OutPortal|OnlineQuery/QueryList.aspx)"},
|
||||
{Name: "天融信防火墙", Type: "code", Rule: "(/cgi/maincgi.cgi)"},
|
||||
{Name: "网神防火墙", Type: "code", Rule: "(css/lsec/login.css)"},
|
||||
{Name: "帕拉迪统一安全管理和综合审计系统", Type: "code", Rule: "(module/image/pldsec.css)"},
|
||||
{Name: "蓝盾BDWebGuard", Type: "code", Rule: "(BACKGROUND: url(images/loginbg.jpg) #e5f1fc)"},
|
||||
{Name: "Huawei SMC", Type: "code", Rule: "(Script/SmcScript.js?version=)"},
|
||||
{Name: "coremail", Type: "code", Rule: "(/coremail/bundle/|contextRoot: \"/coremail\"|coremail/common)"},
|
||||
{Name: "activemq", Type: "code", Rule: "(activemq_logo|Manage ActiveMQ broker)"},
|
||||
{Name: "锐捷网络", Type: "code", Rule: "(static/img/title.ico|support.ruijie.com.cn|Ruijie - NBR|eg.login.loginBtn)"},
|
||||
{Name: "禅道", Type: "code", Rule: "(/theme/default/images/main/zt-logo.png|zentaosid)"},
|
||||
{Name: "weblogic", Type: "code", Rule: "(/console/framework/skins/wlsconsole/images/login_WebLogic_branding.png|Welcome to Weblogic Application Server|<i>Hypertext Transfer Protocol -- HTTP/1.1</i>|<TITLE>Error 404--Not Found</TITLE>|Welcome to Weblogic Application Server|<title>Oracle WebLogic Server 管理控制台</title>)"},
|
||||
{Name: "weblogic", Type: "headers", Rule: "(WebLogic)"},
|
||||
{Name: "致远OA", Type: "code", Rule: "(/seeyon/USER-DATA/IMAGES/LOGIN/login.gif|/seeyon/common/)"},
|
||||
{Name: "蓝凌EIS智慧协同平台", Type: "code", Rule: "(/scripts/jquery.landray.common.js)"},
|
||||
{Name: "深信服ssl-vpn", Type: "code", Rule: "(login_psw.csp|loginPageSP/loginPrivacy.js|/por/login_psw.csp)"},
|
||||
{Name: "Struts2", Type: "code", Rule: "(org.apache.struts2|Struts Problem Report|struts.devMode|struts-tags|There is no Action mapped for namespace)"},
|
||||
{Name: "泛微OA", Type: "code", Rule: "(/spa/portal/public/index.js|wui/theme/ecology8/page/images/login/username_wev8.png|/wui/index.html#/?logintype=1)"},
|
||||
{Name: "Swagger UI", Type: "code", Rule: "(/swagger-ui.css|swagger-ui-bundle.js|swagger-ui-standalone-preset.js)"},
|
||||
{Name: "金蝶政务GSiS", Type: "code", Rule: "(/kdgs/script/kdgs.js|HTML5/content/themes/kdcss.min.css|/ClientBin/Kingdee.BOS.XPF.App.xap)"},
|
||||
{Name: "蓝凌OA", Type: "code", Rule: "(蓝凌软件|StylePath:\"/resource/style/default/\"|/resource/customization|sys/ui/extend/theme/default/style/icon.css|sys/ui/extend/theme/default/style/profile.css)"},
|
||||
{Name: "用友NC", Type: "code", Rule: "(Yonyou UAP|YONYOU NC|/Client/Uclient/UClient.dmg|logo/images/ufida_nc.png|iufo/web/css/menu.css|/System/Login/Login.asp?AppID=|/nc/servlet/nc.ui.iufo.login.Index)"},
|
||||
{Name: "用友IUFO", Type: "code", Rule: "(iufo/web/css/menu.css)"},
|
||||
{Name: "TELEPORT堡垒机", Type: "code", Rule: "(/static/plugins/blur/background-blur.js)"},
|
||||
{Name: "JEECMS", Type: "code", Rule: "(/r/cms/www/red/js/common.js|/r/cms/www/red/js/indexshow.js|Powered by JEECMS|JEECMS|/jeeadmin/jeecms/index.do)"},
|
||||
{Name: "CMS", Type: "code", Rule: "(Powered by .*CMS)"},
|
||||
{Name: "目录遍历", Type: "code", Rule: "(Directory listing for /)"},
|
||||
{Name: "ATLASSIAN-Confluence", Type: "code", Rule: "(com.atlassian.confluence)"},
|
||||
{Name: "ATLASSIAN-Confluence", Type: "headers", Rule: "(X-Confluence)"},
|
||||
{Name: "向日葵", Type: "code", Rule: "({\"success\":false,\"msg\":\"Verification failure\"})"},
|
||||
{Name: "Kubernetes", Type: "code", Rule: "(Kubernetes Dashboard</title>|Kubernetes Enterprise Manager|Mirantis Kubernetes Engine|Kubernetes Resource Report)"},
|
||||
{Name: "WordPress", Type: "code", Rule: "(/wp-login.php?action=lostpassword|WordPress</title>)"},
|
||||
{Name: "RabbitMQ", Type: "code", Rule: "(RabbitMQ Management)"},
|
||||
{Name: "dubbo", Type: "headers", Rule: "(Basic realm=\"dubbo\")"},
|
||||
{Name: "Spring env", Type: "code", Rule: "(logback)"},
|
||||
{Name: "ueditor", Type: "code", Rule: "(ueditor.all.js|UE.getEditor)"},
|
||||
{Name: "亿邮电子邮件系统", Type: "code", Rule: "(亿邮电子邮件系统|亿邮邮件整体解决方案)"},
|
||||
}
|
||||
|
||||
// Md5Datas MD5指纹数据集
|
||||
var Md5Datas = []Md5Data{
|
||||
{"BIG-IP", "04d9541338e525258daf47cc844d59f3"},
|
||||
{"蓝凌OA", "302464c3f6207d57240649926cfc7bd4"},
|
||||
{"JBOSS", "799f70b71314a7508326d1d2f68f7519"},
|
||||
{"锐捷网络", "d8d7c9138e93d43579ebf2e384745ba8"},
|
||||
{"锐捷网络", "9c21df9129aeec032df8ac15c84e050d"},
|
||||
{"锐捷网络", "a45883b12d753bc87aff5bddbef16ab3"},
|
||||
{"深信服edr", "0b24d4d5c7d300d50ee1cd96059a9e85"},
|
||||
{"致远OA", "cdc85452665e7708caed3009ecb7d4e2"},
|
||||
{"致远OA", "17ac348fcce0b320e7bfab3fe2858dfa"},
|
||||
{"致远OA", "57f307ad3764553df84e7b14b7a85432"},
|
||||
{"致远OA", "3c8df395ec2cbd72782286d18a286a9a"},
|
||||
{"致远OA", "2f761c27b6b7f9386bbd61403635dc42"},
|
||||
{"齐治堡垒机", "48ee373f098d8e96e53b7dd778f09ff4"},
|
||||
{"SpringBoot", "0488faca4c19046b94d07c3ee83cf9d6"},
|
||||
{"ThinkPHP", "f49c4a4bde1eec6c0b80c2277c76e3db"},
|
||||
{"通达OA", "ed0044587917c76d08573577c8b72883"},
|
||||
{"泛微E-mobile", "41eca7a9245394106a09b2534d8030df"},
|
||||
{"泛微OA", "c27547e27e1d2c7514545cd8d5988946"},
|
||||
{"泛微OA", "9b1d3f08ede38dbe699d6b2e72a8febb"},
|
||||
{"泛微OA", "281348dd57383c1f214ffb8aed3a1210"},
|
||||
{"GitLab", "85c754581e1d4b628be5b7712c042224"},
|
||||
{"Hikvision-视频监控", "89b932fcc47cf4ca3faadb0cfdef89cf"},
|
||||
{"华夏erp", "c68b15c45cf80115a943772f7d0028a6"},
|
||||
{"OpenSNS", "08711abfb016a55c0e84f7b54bef5632"},
|
||||
{"MetInfo-米拓建站", "2a9541b5c2225ed2f28734c0d75e456f"},
|
||||
{"IBM-Lotus-Domino", "36c1002bb579edf52a472b9d2e39bb50"},
|
||||
{"IBM-Lotus-Domino", "639b61409215d770a99667b446c80ea1"},
|
||||
{"ATLASSIAN-Confluence", "b91d19259cf480661ef93b67beb45234"},
|
||||
{"activemq", "05664fb0c7afcd6436179437e31f3aa6"},
|
||||
{"coremail", "ad74ff8f9a2f630fc2c5e6b3aa0a5cb8"},
|
||||
}
|
||||
|
||||
// PocDatas POC漏洞检测数据集
|
||||
var PocDatas = []PocData{
|
||||
{"致远OA", "seeyon"},
|
||||
{"泛微OA", "weaver"},
|
||||
{"通达OA", "tongda"},
|
||||
{"蓝凌OA", "landray"},
|
||||
{"ThinkPHP", "thinkphp"},
|
||||
{"Nexus", "nexus"},
|
||||
{"齐治堡垒机", "qizhi"},
|
||||
{"weaver-ebridge", "weaver-ebridge"},
|
||||
{"weblogic", "weblogic"},
|
||||
{"zabbix", "zabbix"},
|
||||
{"VMware vSphere", "vmware"},
|
||||
{"Jboss", "jboss"},
|
||||
{"用友", "yongyou"},
|
||||
{"用友IUFO", "yongyou"},
|
||||
{"coremail", "coremail"},
|
||||
{"金山", "kingsoft"},
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user