Files
sif/internal/modules/favicon.go
T
TigahandGitHub 059d7356bf refactor(fingerprint): make the favicon tech table the single source (#358)
* 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.
2026-07-22 12:57:09 -07:00

87 lines
3.2 KiB
Go

/*
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
: :
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
: ▄█ █ █▀ · BSD 3-Clause License :
: :
: (c) 2022-2026 vmfunc, xyzeva, :
: lunchcat alumni & contributors :
: :
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
*/
package modules
import (
"fmt"
"math"
"github.com/vmfunc/sif/internal/fingerprint"
)
// checkFaviconHash reports whether the body's shodan mmh3 hash matches any
// configured value. only the body (the icon) is hashed; part is ignored.
func checkFaviconHash(body string, want []int64) bool {
if len(want) == 0 {
return false
}
got := fingerprint.FaviconHash([]byte(body))
for _, w := range want {
if n, ok := normalizeFaviconHash(w); ok && n == got {
return true
}
}
return false
}
// normalizeFaviconHash folds a hash to the signed int32 shodan stores, accepting
// either 32-bit form so a signed or unsigned value pastes in as-is. out-of-range
// values are rejected so a stray number can't wrap into a false match.
func normalizeFaviconHash(v int64) (int32, bool) {
if v < math.MinInt32 || v > math.MaxUint32 {
return 0, false
}
return int32(uint32(v)), true //nolint:gosec // intentional 32-bit fold to shodan's signed form
}
// faviconEvidence gives the hash as evidence for a favicon-only finding, and
// nothing when a word/regex matcher is present so its body evidence stands.
func faviconEvidence(matchers []Matcher, body string) (string, bool) {
favicon := false
for i := range matchers {
switch matchers[i].Type {
case "word", "regex":
return "", false
case "favicon":
favicon = true
}
}
if !favicon {
return "", false
}
hash := fingerprint.FaviconHash([]byte(body))
if tech, ok := fingerprint.LookupFaviconTech(hash); ok {
return fmt.Sprintf("favicon mmh3=%d tech=%s", hash, tech), true
}
return fmt.Sprintf("favicon mmh3=%d", hash), true
}
// validateMatchers fails favicon matchers that would silently never fire (no
// hash, or one out of 32-bit range) at load rather than at match time.
func validateMatchers(matchers []Matcher) error {
for i := range matchers {
if matchers[i].Type != "favicon" {
continue
}
if len(matchers[i].Hash) == 0 {
return fmt.Errorf("favicon matcher requires at least one hash")
}
for _, h := range matchers[i].Hash {
if _, ok := normalizeFaviconHash(h); !ok {
return fmt.Errorf("favicon hash %d out of range (use a signed int32 or unsigned uint32 value)", h)
}
}
}
return nil
}