500 行 Go 实现 Web 指纹识别引擎:Header + favicon Hash + Title + Cookie 组合投票
来源:微信公众号
为什么漏洞扫描器扫一下网站,就能识别出 Nginx、WordPress、OpenWrt,甚至 Jenkins、禅道?
底层并不是什么黑科技,而是几个最基础的步骤:规则积累 + 特征匹配。今天用 Go 一步步拆解 Web 指纹识别引擎的核心实现。
第一步:发起 HTTP 请求
Go 标准库已经帮我们做好了一切:
client := &http.Client{Timeout: 5 * time.Second}
resp, err := client.Get(target)
if err != nil {
return
}
defer resp.Body.Close()
一次请求,就拿到了两样最重要的东西:HTTP Header 和 HTML 内容——仅凭这两个信息,就已经能够识别大量 Web 产品。
第二步:HTTP Header 往往已暴露身份
很多服务器会主动声明自己的身份:
| Header | 推断结果 |
|---|---|
Server: nginx | Nginx |
Server: Apache | Apache HTTP Server |
X-Powered-By: PHP/8.2 | PHP 后端 |
Server: Jetty / Server: Undertow | Java 技术栈 |
Go 获取 Header:
server := resp.Header.Get("Server")
powered := resp.Header.Get("X-Powered-By")
仅凭 Header,很多情况下就已经完成了一半的指纹识别。
第三步:favicon Hash 才是真正的"身份证"
Web 指纹识别里最经典的一招:favicon Hash。
几乎每个网站都有 /favicon.ico,图片虽小,很多产品几十年不换。扫描器通常:
- 下载 favicon
- 计算 Hash
- 查询规则库
resp, _ := client.Get(target + "/favicon.ico")
body, _ := io.ReadAll(resp.Body)
sum := md5.Sum(body)
fmt.Printf("%x\n", sum)
// 例如:e3b0c44298fc1c149afbf4c8996fb924 → Jenkins
为什么不直接比较图片?因为 Hash 更快、更稳定。成熟产品一般使用 MurmurHash3 而非 MD5,以兼容 FOFA 等公开指纹体系。
第四步:HTML 页面里藏着大量信息
很多 CMS、博客平台、后台系统都喜欢在页面里留下"签名":
<meta name="generator" content="WordPress 6.8">
<meta name="generator" content="Discuz! X3.5">
<title>Jenkins</title>
<title>OpenWrt</title>
Go 读取页面并匹配关键字:
body, _ := io.ReadAll(resp.Body)
html := string(body)
if strings.Contains(html, "WordPress") {
// ...
}
第五步:Cookie 也是重要线索
很多后台系统有固定的 Cookie 名称:
| Cookie | 技术栈 |
|---|---|
JSESSIONID | Java |
PHPSESSID | PHP |
sysauth | OpenWrt |
Go 获取 Cookie:
for _, c := range resp.Cookies() {
fmt.Println(c.Name)
}
第六步:URL 本身就是特征
很多系统有固定路径:
| 路径 | 产品 |
|---|---|
/wp-admin | WordPress |
/login.action | Java 系统 |
/swagger-ui | Spring Boot |
/nacos | Nacos |
Go 直接请求目标路径,返回 200 OK 即命中规则加分。
第七步:HTTPS 证书也会"泄密"
TLS 证书里经常暴露很多信息——有些公司甚至把产品名直接写进证书:
state := resp.TLS
if state != nil {
for _, cert := range state.PeerCertificates {
fmt.Println(cert.Subject.CommonName)
}
}
多特征投票机制
真正的商业扫描器不会"Header 命中就结束",而是采用加权投票机制:
| 特征 | 命中结果 | 权重 |
|---|---|---|
| Header | Nginx | 10 |
| favicon | OpenWrt | 15 |
| Title | OpenWrt | 20 |
| Cookie | sysauth | 25 |
| URL | /cgi-bin/luci | 30 |
最终 OpenWrt 累计得分最高,可信度 99%。
一个简单的规则引擎
将规则抽象为结构体:
type Rule struct {
Product string
Header string
Title string
Cookie string
Favicon string
URL string
Weight int
}
扫描流程:
HTTP 请求 → 收集特征 → 匹配规则 → 累计权重 → 输出结果
比大量 if...else 更容易维护,也是很多商业扫描器的设计思路。
Go 为什么特别适合做指纹识别?
对于 1000 个网站的扫描:
- 传统做法:1000 个线程,或复杂的异步框架
- Go 做法:
for _, url := range urls {
go fingerprint(url)
}
每个目标一个 Goroutine,网络 IO 等待时 Runtime 自动调度其他任务。CPU 几乎一直在工作,而非等待网络。IO 密集型场景下,Go 优势非常明显。
总结
Web 指纹识别的核心只有一句话:尽可能多地收集目标特征,再综合判断。
Header、Title、Cookie、favicon Hash、TLS 证书、URL 路径……每个特征单独看都可能不准确,但组合起来准确率会越来越高。
真正决定识别效果的,不是语言,而是规则库的质量和持续更新。写几百行 Go 代码不难,难的是维护一套与日俱进的规则体系。