mirror of
https://github.com/lunchcat/sif.git
synced 2026-07-28 22:40:54 -07:00
drupal's generator meta only exposes a bare major, so the extracted version is "10"/"9". versionAffected only matched when the affected entry was a component-prefix of the version, never the reverse, so a bare major never matched a dotted affected entry and every drupal cve was silently missed. match on dotted boundaries in either direction.
107 lines
4.3 KiB
Go
107 lines
4.3 KiB
Go
/*
|
|
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
|
|
: :
|
|
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
|
|
: ▄█ █ █▀ · BSD 3-Clause License :
|
|
: :
|
|
: (c) 2022-2026 vmfunc, xyzeva, :
|
|
: lunchcat alumni & contributors :
|
|
: :
|
|
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
|
|
*/
|
|
|
|
package frameworks
|
|
|
|
import "testing"
|
|
|
|
// the detector usually reports "unknown"; the version dug out of the body must
|
|
// win so the cve lookup runs against a concrete version instead of "unknown".
|
|
func TestResolveVersion(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
detector string
|
|
extracted string
|
|
want string
|
|
}{
|
|
{"detector concrete wins", "9.0.0", "8.4.1", "9.0.0"},
|
|
{"unknown detector falls back to extracted", "unknown", "8.4.1", "8.4.1"},
|
|
{"empty detector falls back to extracted", "", "8.4.1", "8.4.1"},
|
|
{"both unknown stays unknown", "unknown", "unknown", "unknown"},
|
|
{"both empty/unknown stays unknown", "", "", "unknown"},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if got := resolveVersion(tt.detector, tt.extracted); got != tt.want {
|
|
t.Errorf("resolveVersion(%q, %q) = %q, want %q", tt.detector, tt.extracted, got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// the regression itself: with the detector reporting "unknown" but a real
|
|
// version extractable from the body, the cve lookup must use the extracted
|
|
// version and surface the matching CVE - the old path looked up "unknown" and
|
|
// missed it.
|
|
func TestResolveVersionFeedsCVELookup(t *testing.T) {
|
|
const body = "Laravel 8.4.1"
|
|
|
|
// extractor pulls the concrete version out of the body...
|
|
extracted := ExtractVersionOptimized(body, "Laravel").Version
|
|
if extracted != "8.4.1" {
|
|
t.Fatalf("expected extracted version 8.4.1, got %q", extracted)
|
|
}
|
|
|
|
// ...and looking "unknown" up finds nothing, proving the old behavior missed it.
|
|
if cves, _ := getVulnerabilities("Laravel", "unknown"); len(cves) != 0 {
|
|
t.Fatalf("expected no CVEs for unknown version, got %v", cves)
|
|
}
|
|
|
|
// the reconciled version feeds the lookup and the CVE shows up.
|
|
version := resolveVersion("unknown", extracted)
|
|
cves, _ := getVulnerabilities("Laravel", version)
|
|
if len(cves) == 0 {
|
|
t.Errorf("expected Laravel %s to surface a CVE, got none", version)
|
|
}
|
|
}
|
|
|
|
// Drupal's generator meta only exposes a bare major, so the extracted version
|
|
// is "10"/"9". the cve lookup must still surface the matching CVE instead of
|
|
// missing it because the affected entries are dotted.
|
|
func TestGetVulnerabilitiesBareMajor(t *testing.T) {
|
|
if cves, _ := getVulnerabilities("Drupal", "10"); len(cves) == 0 {
|
|
t.Error("expected Drupal 10 to surface CVE-2023-44487, got none")
|
|
}
|
|
if cves, _ := getVulnerabilities("Drupal", "9"); len(cves) == 0 {
|
|
t.Error("expected Drupal 9 to surface CVE-2023-44487, got none")
|
|
}
|
|
}
|
|
|
|
func TestVersionAffected(t *testing.T) {
|
|
tests := []struct {
|
|
version string
|
|
affected string
|
|
want bool
|
|
}{
|
|
{"4.2", "4.2", true},
|
|
{"4.2.1", "4.2", true},
|
|
{"4.2.13", "4.2", true},
|
|
{"4.20", "4.2", false}, // the boundary bug: 4.20 is not a 4.2.x release
|
|
{"4.20.0", "4.2", false},
|
|
{"5.0", "4.2", false},
|
|
// a coarser version covers the listed sub-versions: Drupal's generator
|
|
// only emits a bare major, which must still match its dotted entries.
|
|
{"10", "10.0", true},
|
|
{"9", "9.3", true},
|
|
// coarser matching stays on dotted boundaries: "1" is not "10.x".
|
|
{"1", "10.0", false},
|
|
{"10", "100.0", false},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
if got := versionAffected(tt.version, tt.affected); got != tt.want {
|
|
t.Errorf("versionAffected(%q, %q) = %v, want %v", tt.version, tt.affected, got, tt.want)
|
|
}
|
|
}
|
|
}
|