mirror of
https://github.com/lunchcat/sif.git
synced 2026-07-28 14:37:01 -07:00
* refactor(fingerprint): make the favicon tech table the single source move the hash->tech map from internal/scan into internal/fingerprint beside the hash function, exposed as LookupFaviconTech. the scan Favicon path now resolves tech through it instead of a private map. drop the demo-sync guard test: it existed only to keep scan's map in sync with the yaml demo module, and that map no longer exists. * feat(modules): name the matched tech in favicon evidence favicon module findings now read the tech straight from the shared fingerprint table, so a canonical hit reports "favicon mmh3=<n> tech=<name>" instead of a bare hash. unknown hashes are unaffected.
80 lines
3.4 KiB
Go
80 lines
3.4 KiB
Go
/*
|
|
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
|
|
: :
|
|
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
|
|
: ▄█ █ █▀ · BSD 3-Clause License :
|
|
: :
|
|
: (c) 2022-2026 vmfunc, xyzeva, :
|
|
: lunchcat alumni & contributors :
|
|
: :
|
|
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
|
|
*/
|
|
|
|
// Package fingerprint holds small response-fingerprinting primitives shared by
|
|
// the scan checks and the module engine, so both compute identical values.
|
|
package fingerprint
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"strings"
|
|
|
|
"github.com/twmb/murmur3"
|
|
)
|
|
|
|
// b64LineLen is python's base64.encodebytes line width. mmh3/shodan hash the
|
|
// chunked base64 (newline every 76 chars, trailing newline), so we must wrap at
|
|
// exactly this width to land on the same hash.
|
|
const b64LineLen = 76
|
|
|
|
// FaviconHash computes shodan's favicon hash: murmur3 32-bit over the python
|
|
// base64.encodebytes encoding of the raw icon (newline every 76 chars plus a
|
|
// trailing newline), reinterpreted as a signed int32 (both load-bearing, golden-pinned).
|
|
func FaviconHash(data []byte) int32 {
|
|
encoded := encodeFaviconBase64(data)
|
|
return int32(murmur3.Sum32(encoded)) //nolint:gosec // shodan stores the signed reinterpretation on purpose
|
|
}
|
|
|
|
// faviconTech maps a known shodan favicon hash to the tech that ships it.
|
|
// these are stable default icons for panels/frameworks/c2; a hit is a strong
|
|
// fingerprint. kept small on purpose - high-signal defaults, not an exhaustive db.
|
|
var faviconTech = map[int32]string{
|
|
116323821: "Apache Tomcat",
|
|
81586312: "Spring Boot (default whitelabel)",
|
|
-235701012: "Jenkins",
|
|
-1255347784: "GitLab",
|
|
1278322581: "Grafana",
|
|
743365239: "Kibana",
|
|
-1462443472: "phpMyAdmin",
|
|
999357577: "Cobalt Strike (default beacon)",
|
|
-1521704893: "Metasploit",
|
|
-1893514588: "Gitea",
|
|
}
|
|
|
|
// LookupFaviconTech returns the tech that ships the given shodan favicon hash and
|
|
// whether the hash is known.
|
|
func LookupFaviconTech(hash int32) (string, bool) {
|
|
tech, ok := faviconTech[hash]
|
|
return tech, ok
|
|
}
|
|
|
|
// encodeFaviconBase64 mirrors python's base64.encodebytes: standard base64 with
|
|
// a newline inserted every 76 output characters and a trailing newline. this is
|
|
// the exact byte stream shodan feeds to mmh3, so it must match byte-for-byte.
|
|
func encodeFaviconBase64(data []byte) []byte {
|
|
raw := base64.StdEncoding.EncodeToString(data)
|
|
|
|
var b strings.Builder
|
|
// final size: the base64 body plus one '\n' per (full or partial) 76-char
|
|
// line. preallocate so the builder never regrows mid-loop.
|
|
b.Grow(len(raw) + len(raw)/b64LineLen + 1)
|
|
for i := 0; i < len(raw); i += b64LineLen {
|
|
end := i + b64LineLen
|
|
if end > len(raw) {
|
|
end = len(raw)
|
|
}
|
|
b.WriteString(raw[i:end])
|
|
b.WriteByte('\n')
|
|
}
|
|
return []byte(b.String())
|
|
}
|