mirror of
https://github.com/lunchcat/sif.git
synced 2026-01-13 05:16:44 -08:00
- create detector interface and registry for extensibility - extract detectors to separate files: backend.go, frontend.go, cms.go, meta.go - reduce detect.go from 785 lines to 178 lines (pure orchestrator) - export VersionMatch and ExtractVersionOptimized for detector use - create result.go with NewFrameworkResult and WithVulnerabilities helpers - add url validation to New() for early error detection - add sif_test.go with main package tests - update detect_test.go to use external test package pattern
134 lines
3.0 KiB
Go
134 lines
3.0 KiB
Go
/*
|
|
|
|
BSD 3-Clause License
|
|
(c) 2022-2025 vmfunc, xyzeva & contributors
|
|
|
|
*/
|
|
|
|
package frameworks
|
|
|
|
import (
|
|
"net/http"
|
|
"strings"
|
|
"sync"
|
|
)
|
|
|
|
// Signature represents a pattern to match for framework detection.
|
|
type Signature struct {
|
|
Pattern string
|
|
Weight float32
|
|
HeaderOnly bool
|
|
}
|
|
|
|
// Detector is the interface for framework detection plugins.
|
|
type Detector interface {
|
|
// Name returns the unique framework name.
|
|
Name() string
|
|
// Signatures returns patterns to search for this framework.
|
|
Signatures() []Signature
|
|
// Detect performs detection and returns confidence (0.0-1.0) and version.
|
|
// The version can be empty if not detectable.
|
|
Detect(body string, headers http.Header) (confidence float32, version string)
|
|
}
|
|
|
|
// registry holds all registered detectors.
|
|
var (
|
|
registryMu sync.RWMutex
|
|
registry = make(map[string]Detector)
|
|
)
|
|
|
|
// Register adds a detector to the registry. Should be called from init().
|
|
func Register(d Detector) {
|
|
registryMu.Lock()
|
|
defer registryMu.Unlock()
|
|
registry[d.Name()] = d
|
|
}
|
|
|
|
// GetDetectors returns all registered detectors.
|
|
func GetDetectors() map[string]Detector {
|
|
registryMu.RLock()
|
|
defer registryMu.RUnlock()
|
|
|
|
// Return a copy to prevent mutation
|
|
result := make(map[string]Detector, len(registry))
|
|
for k, v := range registry {
|
|
result[k] = v
|
|
}
|
|
return result
|
|
}
|
|
|
|
// GetDetector returns a specific detector by name.
|
|
func GetDetector(name string) (Detector, bool) {
|
|
registryMu.RLock()
|
|
defer registryMu.RUnlock()
|
|
d, ok := registry[name]
|
|
return d, ok
|
|
}
|
|
|
|
// BaseDetector provides common functionality for detector implementations.
|
|
type BaseDetector struct {
|
|
name string
|
|
signatures []Signature
|
|
}
|
|
|
|
// NewBaseDetector creates a new base detector.
|
|
func NewBaseDetector(name string, signatures []Signature) BaseDetector {
|
|
return BaseDetector{name: name, signatures: signatures}
|
|
}
|
|
|
|
// Name returns the framework name.
|
|
func (b BaseDetector) Name() string {
|
|
return b.name
|
|
}
|
|
|
|
// Signatures returns the detection signatures.
|
|
func (b BaseDetector) Signatures() []Signature {
|
|
return b.signatures
|
|
}
|
|
|
|
// MatchSignatures checks body and headers against signatures and returns a weighted score.
|
|
func (b BaseDetector) MatchSignatures(body string, headers http.Header) float32 {
|
|
var weightedScore float32
|
|
var totalWeight float32
|
|
|
|
for _, sig := range b.signatures {
|
|
totalWeight += sig.Weight
|
|
|
|
if sig.HeaderOnly {
|
|
if containsHeader(headers, sig.Pattern) {
|
|
weightedScore += sig.Weight
|
|
}
|
|
} else if strings.Contains(body, sig.Pattern) {
|
|
weightedScore += sig.Weight
|
|
}
|
|
}
|
|
|
|
if totalWeight == 0 {
|
|
return 0
|
|
}
|
|
|
|
return weightedScore / totalWeight
|
|
}
|
|
|
|
// containsHeader checks if a signature pattern exists in headers.
|
|
func containsHeader(headers http.Header, signature string) bool {
|
|
sigLower := strings.ToLower(signature)
|
|
|
|
// Check header names
|
|
for name := range headers {
|
|
if strings.Contains(strings.ToLower(name), sigLower) {
|
|
return true
|
|
}
|
|
}
|
|
|
|
// Check header values
|
|
for _, values := range headers {
|
|
for _, value := range values {
|
|
if strings.Contains(strings.ToLower(value), sigLower) {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|