mirror of
https://github.com/lunchcat/sif.git
synced 2026-07-28 22:40:54 -07:00
* fix(detectors): let django csrf body field detect
csrfmiddlewaretoken is a hidden form body field django templates
render, never a header, so marking it HeaderOnly meant it could never
match and a real django form page (csrf field plus csrftoken cookie)
went undetected.
* fix(detectors): tighten aspnet, angular, astro and nextjs markers
* fix(frameworks): surface header version patterns to extraction
version extraction only ever searched the response body, so
header-shaped patterns like ASP.NET's "X-AspNet-Version: x.y.z" or
Flask's "Werkzeug/x.y.z" Server header could never match even when the
detector itself fired off that same header. add
ExtractVersionFromResponse, which also searches canonical header
lines, and point aspnet and flask at it. fix the aspnet header regexes
to match case-insensitively, since Go canonicalizes header names
("X-AspNet-Version" becomes "X-Aspnet-Version") and the old literal
pattern never matched the canonical form.
* test(detectors): pin fp/fn corpus for framework detection
promote the ad-hoc probe/sweep scratch tests used to find these
defects into a proper regression file: for each fix, assert both the
real-product positive still detects and the prose/other-product
negative does not, plus a sweep of unrelated pages against every
registered detector.
194 lines
6.1 KiB
Go
194 lines
6.1 KiB
Go
/*
|
|
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
|
|
: :
|
|
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
|
|
: ▄█ █ █▀ · BSD 3-Clause License :
|
|
: :
|
|
: (c) 2022-2026 vmfunc, xyzeva, :
|
|
: lunchcat alumni & contributors :
|
|
: :
|
|
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
|
|
*/
|
|
|
|
/*
|
|
|
|
BSD 3-Clause License
|
|
(c) 2022-2026 vmfunc, xyzeva & contributors
|
|
|
|
*/
|
|
|
|
package detectors
|
|
|
|
import (
|
|
"net/http"
|
|
|
|
fw "github.com/vmfunc/sif/internal/scan/frameworks"
|
|
)
|
|
|
|
func init() {
|
|
// Register all meta-framework detectors
|
|
fw.Register(&nextjsDetector{})
|
|
fw.Register(&nuxtDetector{})
|
|
fw.Register(&sveltekitDetector{})
|
|
fw.Register(&gatsbyDetector{})
|
|
fw.Register(&remixDetector{})
|
|
fw.Register(&astroDetector{})
|
|
}
|
|
|
|
// nextjsDetector detects Next.js framework.
|
|
type nextjsDetector struct{}
|
|
|
|
func (d *nextjsDetector) Name() string { return "Next.js" }
|
|
|
|
func (d *nextjsDetector) Signatures() []fw.Signature {
|
|
return []fw.Signature{
|
|
{Pattern: "__NEXT_DATA__", Weight: 0.5},
|
|
// same attribute-form-vs-prose rationale as Angular's ng-version marker.
|
|
{Pattern: `="/_next/static/`, Weight: 0.6},
|
|
{Pattern: "__next", Weight: 0.3},
|
|
{Pattern: "x-nextjs", Weight: 0.3, HeaderOnly: true},
|
|
}
|
|
}
|
|
|
|
func (d *nextjsDetector) Detect(body string, headers http.Header) (float32, string) {
|
|
base := fw.NewBaseDetector(d.Name(), d.Signatures())
|
|
score := base.MatchSignatures(body, headers)
|
|
confidence := sigmoidConfidence(score)
|
|
|
|
var version string
|
|
if confidence > 0.5 {
|
|
version = fw.ExtractVersionOptimized(body, d.Name()).Version
|
|
}
|
|
return confidence, version
|
|
}
|
|
|
|
// nuxtDetector detects Nuxt.js framework.
|
|
type nuxtDetector struct{}
|
|
|
|
func (d *nuxtDetector) Name() string { return "Nuxt.js" }
|
|
|
|
func (d *nuxtDetector) Signatures() []fw.Signature {
|
|
return []fw.Signature{
|
|
{Pattern: "__NUXT__", Weight: 0.5},
|
|
{Pattern: "_nuxt/", Weight: 0.4},
|
|
{Pattern: "nuxt", Weight: 0.2},
|
|
}
|
|
}
|
|
|
|
func (d *nuxtDetector) Detect(body string, headers http.Header) (float32, string) {
|
|
base := fw.NewBaseDetector(d.Name(), d.Signatures())
|
|
score := base.MatchSignatures(body, headers)
|
|
confidence := sigmoidConfidence(score)
|
|
|
|
var version string
|
|
if confidence > 0.5 {
|
|
version = fw.ExtractVersionOptimized(body, d.Name()).Version
|
|
}
|
|
return confidence, version
|
|
}
|
|
|
|
// sveltekitDetector detects SvelteKit framework.
|
|
type sveltekitDetector struct{}
|
|
|
|
func (d *sveltekitDetector) Name() string { return "SvelteKit" }
|
|
|
|
func (d *sveltekitDetector) Signatures() []fw.Signature {
|
|
return []fw.Signature{
|
|
{Pattern: "__sveltekit", Weight: 0.5},
|
|
{Pattern: "_app/immutable", Weight: 0.4},
|
|
{Pattern: "sveltekit", Weight: 0.3},
|
|
}
|
|
}
|
|
|
|
func (d *sveltekitDetector) Detect(body string, headers http.Header) (float32, string) {
|
|
base := fw.NewBaseDetector(d.Name(), d.Signatures())
|
|
score := base.MatchSignatures(body, headers)
|
|
confidence := sigmoidConfidence(score)
|
|
|
|
var version string
|
|
if confidence > 0.5 {
|
|
version = fw.ExtractVersionOptimized(body, d.Name()).Version
|
|
}
|
|
return confidence, version
|
|
}
|
|
|
|
// gatsbyDetector detects Gatsby framework.
|
|
type gatsbyDetector struct{}
|
|
|
|
func (d *gatsbyDetector) Name() string { return "Gatsby" }
|
|
|
|
func (d *gatsbyDetector) Signatures() []fw.Signature {
|
|
// "gatsby-" alone cleared the detection threshold on prose merely naming a
|
|
// plugin (a migration guide, a plugin comparison). the remaining patterns
|
|
// are structural markers that only appear when gatsby rendered the page.
|
|
return []fw.Signature{
|
|
{Pattern: "___gatsby", Weight: 0.5},
|
|
{Pattern: "/page-data/", Weight: 0.3},
|
|
}
|
|
}
|
|
|
|
func (d *gatsbyDetector) Detect(body string, headers http.Header) (float32, string) {
|
|
base := fw.NewBaseDetector(d.Name(), d.Signatures())
|
|
score := base.MatchSignatures(body, headers)
|
|
confidence := sigmoidConfidence(score)
|
|
|
|
var version string
|
|
if confidence > 0.5 {
|
|
version = fw.ExtractVersionOptimized(body, d.Name()).Version
|
|
}
|
|
return confidence, version
|
|
}
|
|
|
|
// remixDetector detects Remix framework.
|
|
type remixDetector struct{}
|
|
|
|
func (d *remixDetector) Name() string { return "Remix" }
|
|
|
|
func (d *remixDetector) Signatures() []fw.Signature {
|
|
return []fw.Signature{
|
|
{Pattern: "__remixContext", Weight: 0.5},
|
|
}
|
|
}
|
|
|
|
func (d *remixDetector) Detect(body string, headers http.Header) (float32, string) {
|
|
base := fw.NewBaseDetector(d.Name(), d.Signatures())
|
|
score := base.MatchSignatures(body, headers)
|
|
confidence := sigmoidConfidence(score)
|
|
|
|
var version string
|
|
if confidence > 0.5 {
|
|
version = fw.ExtractVersionOptimized(body, d.Name()).Version
|
|
}
|
|
return confidence, version
|
|
}
|
|
|
|
// astroDetector detects Astro framework.
|
|
type astroDetector struct{}
|
|
|
|
func (d *astroDetector) Name() string { return "Astro" }
|
|
|
|
func (d *astroDetector) Signatures() []fw.Signature {
|
|
return []fw.Signature{
|
|
// definitive quote-anchored marker; same threshold rationale as Next.js above.
|
|
{Pattern: `<meta name="generator" content="Astro`, Weight: 1.1},
|
|
{Pattern: "astro-island", Weight: 0.5},
|
|
{Pattern: "data-astro-cid-", Weight: 0.4},
|
|
{Pattern: "/_astro/", Weight: 0.4},
|
|
{Pattern: "data-astro-transition", Weight: 0.3},
|
|
{Pattern: "data-astro-reload", Weight: 0.3},
|
|
{Pattern: "data-astro-history", Weight: 0.3},
|
|
}
|
|
}
|
|
|
|
func (d *astroDetector) Detect(body string, headers http.Header) (float32, string) {
|
|
base := fw.NewBaseDetector(d.Name(), d.Signatures())
|
|
score := base.MatchSignatures(body, headers)
|
|
confidence := sigmoidConfidence(score)
|
|
|
|
var version string
|
|
if confidence > 0.5 {
|
|
version = fw.ExtractVersionOptimized(body, d.Name()).Version
|
|
}
|
|
return confidence, version
|
|
}
|