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.
104 lines
4.0 KiB
Go
104 lines
4.0 KiB
Go
/*
|
|
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
|
|
: :
|
|
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
|
|
: ▄█ █ █▀ · BSD 3-Clause License :
|
|
: :
|
|
: (c) 2022-2026 vmfunc, xyzeva, :
|
|
: lunchcat alumni & contributors :
|
|
: :
|
|
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
|
|
*/
|
|
|
|
package fingerprint
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// goldenFaviconBytes is a fixed payload long enough to span multiple base64
|
|
// lines, so the python-style 76-char chunking is actually exercised by the hash.
|
|
var goldenFaviconBytes = []byte(strings.Repeat("sif-favicon-golden-test-bytes-", 8))
|
|
|
|
// goldenFaviconHash is the pinned shodan mmh3 hash of goldenFaviconBytes: the python
|
|
// base64.encodebytes byte stream (76-char lines + trailing newline) through murmur3-32,
|
|
// reinterpreted as a signed int32. if the chunking or signedness regress, this test fails.
|
|
const goldenFaviconHash int32 = -1554620260
|
|
|
|
// goldenHelloHash pins a short single-line case so a regression in the trailing
|
|
// newline (which the small case still has) is caught independently.
|
|
const goldenHelloHash int32 = 1155597304
|
|
|
|
func TestFaviconHashGolden(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
in []byte
|
|
want int32
|
|
}{
|
|
{name: "multi-line fixture", in: goldenFaviconBytes, want: goldenFaviconHash},
|
|
{name: "single-line hello", in: []byte("hello"), want: goldenHelloHash},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if got := FaviconHash(tt.in); got != tt.want {
|
|
t.Errorf("FaviconHash = %d, want %d", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestLookupFaviconTech proves the SSOT table is the single place all ten
|
|
// facts are resolved: every entry round-trips, an unknown hash misses, and the
|
|
// count is pinned so an accidental addition/removal is caught.
|
|
func TestLookupFaviconTech(t *testing.T) {
|
|
if len(faviconTech) != 10 {
|
|
t.Fatalf("faviconTech has %d entries, want 10", len(faviconTech))
|
|
}
|
|
for hash, want := range faviconTech {
|
|
got, ok := LookupFaviconTech(hash)
|
|
if !ok {
|
|
t.Errorf("LookupFaviconTech(%d) ok = false, want true", hash)
|
|
}
|
|
if got != want {
|
|
t.Errorf("LookupFaviconTech(%d) = %q, want %q", hash, got, want)
|
|
}
|
|
}
|
|
|
|
if tech, ok := LookupFaviconTech(0); ok {
|
|
t.Errorf("LookupFaviconTech(0) = (%q, true), want (\"\", false)", tech)
|
|
}
|
|
|
|
tests := []struct {
|
|
hash int32
|
|
want string
|
|
}{
|
|
{hash: -1255347784, want: "GitLab"},
|
|
{hash: 116323821, want: "Apache Tomcat"},
|
|
}
|
|
for _, tt := range tests {
|
|
if got, ok := LookupFaviconTech(tt.hash); !ok || got != tt.want {
|
|
t.Errorf("LookupFaviconTech(%d) = (%q, %v), want (%q, true)", tt.hash, got, ok, tt.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestFaviconBase64Chunking pins the encode step against python's
|
|
// base64.encodebytes: a 60-byte input encodes to 80 base64 chars, so it must
|
|
// wrap into two newline-terminated lines.
|
|
func TestFaviconBase64Chunking(t *testing.T) {
|
|
in := []byte(strings.Repeat("A", 60))
|
|
got := string(encodeFaviconBase64(in))
|
|
|
|
lines := strings.Split(strings.TrimRight(got, "\n"), "\n")
|
|
if len(lines) != 2 {
|
|
t.Fatalf("expected 2 wrapped lines, got %d: %q", len(lines), got)
|
|
}
|
|
if len(lines[0]) != b64LineLen {
|
|
t.Errorf("first line = %d chars, want %d", len(lines[0]), b64LineLen)
|
|
}
|
|
if !strings.HasSuffix(got, "\n") {
|
|
t.Errorf("encoding must end in a trailing newline, got %q", got)
|
|
}
|
|
}
|