From 1e24818ee70631655a51c4475c981dca06e2810b Mon Sep 17 00:00:00 2001 From: MaineK00n Date: Thu, 2 Apr 2026 17:02:54 +0900 Subject: [PATCH 1/2] feat!(detector): detect windows with vuls2 --- config/config.go | 2 - config/tomlloader.go | 1 - config/vulnDictConf.go | 27 --- detector/detector.go | 42 +--- detector/util.go | 12 +- detector/vuls2/vendor.go | 30 +++ detector/vuls2/vuls2.go | 186 ++++++++------ detector/vuls2/vuls2_test.go | 76 ++++++ go.mod | 1 - go.sum | 2 - gost/gost.go | 100 -------- gost/microsoft.go | 435 --------------------------------- gost/microsoft_test.go | 459 ----------------------------------- gost/pseudo.go | 17 -- gost/redhat.go | 158 ------------ gost/redhat_test.go | 34 --- gost/util.go | 201 --------------- reporter/util.go | 1 - server/server.go | 2 +- subcmds/discover.go | 8 - subcmds/report.go | 1 - subcmds/report_windows.go | 1 - subcmds/server.go | 3 +- 23 files changed, 235 insertions(+), 1564 deletions(-) delete mode 100644 gost/gost.go delete mode 100644 gost/microsoft.go delete mode 100644 gost/microsoft_test.go delete mode 100644 gost/pseudo.go delete mode 100644 gost/redhat.go delete mode 100644 gost/redhat_test.go delete mode 100644 gost/util.go diff --git a/config/config.go b/config/config.go index 9d179320bf..7f7552c41f 100644 --- a/config/config.go +++ b/config/config.go @@ -39,7 +39,6 @@ type Config struct { // report CveDict GoCveDictConf `json:"cveDict,omitzero"` - Gost GostConf `json:"gost,omitzero"` Cti CtiConf `json:"cti,omitzero"` Vuls2 Vuls2Conf `json:"vuls2,omitzero"` @@ -186,7 +185,6 @@ func (c *Config) ValidateOnReport() bool { for _, cnf := range []VulnDictInterface{ &Conf.CveDict, - &Conf.Gost, &Conf.Cti, } { if err := cnf.Validate(); err != nil { diff --git a/config/tomlloader.go b/config/tomlloader.go index e8ef261f22..e3d55a0dc4 100644 --- a/config/tomlloader.go +++ b/config/tomlloader.go @@ -39,7 +39,6 @@ func (c TOMLLoader) Load(pathToToml string) error { for _, cnf := range []VulnDictInterface{ &Conf.CveDict, - &Conf.Gost, &Conf.Cti, } { cnf.Init() diff --git a/config/vulnDictConf.go b/config/vulnDictConf.go index 6437d24d52..4b9fbb213a 100644 --- a/config/vulnDictConf.go +++ b/config/vulnDictConf.go @@ -173,33 +173,6 @@ func (cnf *GoCveDictConf) Init() { cnf.DebugSQL = Conf.DebugSQL } -// GostConf is gost config -type GostConf struct { - VulnDict -} - -const gostDBType = "GOSTDB_TYPE" -const gostDBURL = "GOSTDB_URL" -const gostDBPATH = "GOSTDB_SQLITE3_PATH" - -// Init set options with the following priority. -// 1. Environment variable -// 2. config.toml -func (cnf *GostConf) Init() { - cnf.Name = "gost" - if os.Getenv(gostDBType) != "" { - cnf.Type = os.Getenv(gostDBType) - } - if os.Getenv(gostDBURL) != "" { - cnf.URL = os.Getenv(gostDBURL) - } - if os.Getenv(gostDBPATH) != "" { - cnf.SQLite3Path = os.Getenv(gostDBPATH) - } - cnf.setDefault("gost.sqlite3") - cnf.DebugSQL = Conf.DebugSQL -} - // CtiConf is go-cti config type CtiConf struct { VulnDict diff --git a/detector/detector.go b/detector/detector.go index 75796de956..de83fe995b 100644 --- a/detector/detector.go +++ b/detector/detector.go @@ -50,7 +50,7 @@ func Detect(rs []models.ScanResult, dir string) ([]models.ScanResult, error) { return nil, xerrors.Errorf("Failed to fill with Library dependency: %w", err) } - if err := DetectPkgCves(&r, config.Conf.Gost, config.Conf.Vuls2, config.Conf.LogOpts, config.Conf.NoProgress); err != nil { + if err := DetectPkgCves(&r, config.Conf.Vuls2, config.Conf.LogOpts, config.Conf.NoProgress); err != nil { return nil, xerrors.Errorf("Failed to detect Pkg CVE: %w", err) } @@ -296,19 +296,16 @@ func Detect(rs []models.ScanResult, dir string) ([]models.ScanResult, error) { } // DetectPkgCves detects OS pkg cves -func DetectPkgCves(r *models.ScanResult, gostCnf config.GostConf, vuls2Conf config.Vuls2Conf, logOpts logging.LogOpts, noProgress bool) error { +func DetectPkgCves(r *models.ScanResult, vuls2Conf config.Vuls2Conf, logOpts logging.LogOpts, noProgress bool) error { if isPkgCvesDetactable(r) { switch r.Family { case constant.RedHat, constant.CentOS, constant.Fedora, constant.Alma, constant.Rocky, constant.Oracle, constant.Amazon, constant.OpenSUSE, constant.OpenSUSELeap, constant.SUSEEnterpriseServer, constant.SUSEEnterpriseDesktop, - constant.Debian, constant.Raspbian, constant.Ubuntu, constant.Alpine: + constant.Debian, constant.Raspbian, constant.Ubuntu, constant.Alpine, + constant.Windows: if err := vuls2.Detect(r, vuls2Conf, noProgress); err != nil { return xerrors.Errorf("Failed to detect CVE with Vuls2: %w", err) } - case constant.Windows: - if err := detectPkgsCvesWithGost(gostCnf, r, logOpts); err != nil { - return xerrors.Errorf("Failed to detect CVE with gost: %w", err) - } default: return xerrors.Errorf("Unsupported detection methods for %s", r.Family) } @@ -344,27 +341,27 @@ func DetectPkgCves(r *models.ScanResult, gostCnf config.GostConf, vuls2Conf conf return nil } -// isPkgCvesDetactable checks whether CVEs is detactable with gost and vuls2 from the result +// isPkgCvesDetactable checks whether CVEs is detactable with vuls2 from the result func isPkgCvesDetactable(r *models.ScanResult) bool { switch r.Family { case constant.FreeBSD, constant.MacOSX, constant.MacOSXServer, constant.MacOS, constant.MacOSServer, constant.ServerTypePseudo: - logging.Log.Infof("%s type. Skip gost and vuls2 detection", r.Family) + logging.Log.Infof("%s type. Skip vuls2 detection", r.Family) return false case constant.Windows: return true default: if r.ScannedVia == "trivy" { - logging.Log.Infof("r.ScannedVia is trivy. Skip gost and vuls2 detection") + logging.Log.Infof("r.ScannedVia is trivy. Skip vuls2 detection") return false } if r.Release == "" { - logging.Log.Infof("r.Release is empty. Skip gost and vuls2 detection") + logging.Log.Infof("r.Release is empty. Skip vuls2 detection") return false } if len(r.Packages)+len(r.SrcPackages) == 0 { - logging.Log.Infof("Number of packages is 0. Skip gost and vuls2 detection") + logging.Log.Infof("Number of packages is 0. Skip vuls2 detection") return false } return true @@ -480,27 +477,6 @@ func fillCertAlerts(cvedetail *cvemodels.CveDetail) (dict models.AlertDict) { return dict } -func detectPkgsCvesWithGost(cnf config.GostConf, r *models.ScanResult, logOpts logging.LogOpts) error { - client, err := gost.NewGostClient(cnf, r.Family, logOpts) - if err != nil { - return xerrors.Errorf("Failed to new a gost client: %w", err) - } - defer func() { - if err := client.CloseDB(); err != nil { - logging.Log.Errorf("Failed to close the gost DB. err: %+v", err) - } - }() - - nCVEs, err := client.DetectCVEs(r, true) - if err != nil { - return xerrors.Errorf("Failed to detect CVEs with gost: %w", err) - } - - logging.Log.Infof("%s: %d CVEs are detected with gost", r.FormatServerName(), nCVEs) - - return nil -} - // DetectCpeURIsCves detects CVEs of given CPE-URIs func DetectCpeURIsCves(r *models.ScanResult, cpes []Cpe, cnf config.GoCveDictConf, logOpts logging.LogOpts) error { client, err := newGoCveDictClient(&cnf, logOpts) diff --git a/detector/util.go b/detector/util.go index a96a8c6975..34d7b61ea5 100644 --- a/detector/util.go +++ b/detector/util.go @@ -15,7 +15,6 @@ import ( "github.com/future-architect/vuls/config" "github.com/future-architect/vuls/constant" - "github.com/future-architect/vuls/gost" "github.com/future-architect/vuls/logging" "github.com/future-architect/vuls/models" "golang.org/x/xerrors" @@ -134,7 +133,6 @@ func getPlusDiffCves(previous, current models.ScanResult) models.VulnInfos { // TODO commented out because a bug of diff logic when multiple oval defs found for a certain CVE-ID and same updated_at // if these OVAL defs have different affected packages, this logic detects as updated. - // This logic will be uncommented after integration with gost https://github.com/vulsio/gost // } else if isCveFixed(v, previous) { // updated[v.CveID] = v // logging.Log.Debugf("fixed: %s", v.CveID) @@ -266,7 +264,7 @@ func loadOneServerScanResult(jsonFile string) (*models.ScanResult, error) { } // ValidateDBs checks if the databases are accessible and can be closed properly -func ValidateDBs(cveConf config.GoCveDictConf, gostConf config.GostConf, ctiConf config.CtiConf, logOpts logging.LogOpts) error { +func ValidateDBs(cveConf config.GoCveDictConf, ctiConf config.CtiConf, logOpts logging.LogOpts) error { cvec, err := newGoCveDictClient(&cveConf, logOpts) if err != nil { return xerrors.Errorf("Failed to new CVE client. err: %w", err) @@ -275,14 +273,6 @@ func ValidateDBs(cveConf config.GoCveDictConf, gostConf config.GostConf, ctiConf return xerrors.Errorf("Failed to close CVE DB. err: %w", err) } - gostc, err := gost.NewGostClient(gostConf, constant.ServerTypePseudo, logOpts) - if err != nil { - return xerrors.Errorf("Failed to new gost client. err: %w", err) - } - if err := gostc.CloseDB(); err != nil { - return xerrors.Errorf("Failed to close gost DB. err: %w", err) - } - ctic, err := newGoCTIDBClient(&ctiConf, logOpts) if err != nil { return xerrors.Errorf("Failed to new CTI client. err: %w", err) diff --git a/detector/vuls2/vendor.go b/detector/vuls2/vendor.go index 63973e973f..d939f7aba3 100644 --- a/detector/vuls2/vendor.go +++ b/detector/vuls2/vendor.go @@ -51,6 +51,8 @@ func preConvertBinaryVersion(family, version string) string { func toVuls2Family(vuls0Family, vuls0Release string) string { switch vuls0Family { + case constant.Windows: + return ecosystemTypes.EcosystemTypeMicrosoft case constant.Raspbian: return ecosystemTypes.EcosystemTypeDebian case constant.SUSEEnterpriseServer, constant.SUSEEnterpriseDesktop: @@ -598,6 +600,12 @@ func advisoryReference(e ecosystemTypes.Ecosystem, s sourceTypes.SourceID, da mo Source: "SUSE", RefID: da.AdvisoryID, }, nil + case ecosystemTypes.EcosystemTypeMicrosoft: + return models.Reference{ + Link: fmt.Sprintf("https://msrc.microsoft.com/update-guide/vulnerability/%s", da.AdvisoryID), + Source: "MICROSOFT", + RefID: da.AdvisoryID, + }, nil default: return models.Reference{}, xerrors.Errorf("unsupported family: %s", et) } @@ -621,6 +629,8 @@ func cveContentSourceLink(ccType models.CveContentType, v vulnerabilityTypes.Vul return fmt.Sprintf("https://security.alpinelinux.org/vuln/%s", v.Content.ID) case models.Nvd: return fmt.Sprintf("https://nvd.nist.gov/vuln/detail/%s", v.Content.ID) + case models.Microsoft: + return fmt.Sprintf("https://msrc.microsoft.com/update-guide/vulnerability/%s", v.Content.ID) default: return "" } @@ -752,6 +762,22 @@ func compareSourceID(e ecosystemTypes.Ecosystem, a, b sourceTypes.SourceID) int } } return cmp.Compare(preferenceFn(a), preferenceFn(b)) + case ecosystemTypes.EcosystemTypeMicrosoft: + preferenceFn := func(sourceID sourceTypes.SourceID) int { + switch sourceID { + case sourceTypes.MicrosoftCVRF: + return 5 + case sourceTypes.MicrosoftCSAF: + return 4 + case sourceTypes.MicrosoftBulletin: + return 3 + case sourceTypes.MicrosoftWSUSSCN2, sourceTypes.MicrosoftMSUC: + return 2 + default: + return 1 + } + } + return cmp.Compare(preferenceFn(a), preferenceFn(b)) default: return 0 } @@ -849,6 +875,8 @@ func toCveContentType(e ecosystemTypes.Ecosystem, s sourceTypes.SourceID) models } case ecosystemTypes.EcosystemTypeSUSELinuxEnterprise, ecosystemTypes.EcosystemTypeOpenSUSE, ecosystemTypes.EcosystemTypeOpenSUSELeap, ecosystemTypes.EcosystemTypeOpenSUSETumbleweed: return models.SUSE + case ecosystemTypes.EcosystemTypeMicrosoft: + return models.Microsoft default: return models.NewCveContentType(et) } @@ -999,6 +1027,8 @@ func toVuls0Confidence(e ecosystemTypes.Ecosystem, s sourceTypes.SourceID) model default: return models.OvalMatch } + case ecosystemTypes.EcosystemTypeMicrosoft: + return models.WindowsUpdateSearch default: return models.Confidence{ Score: 0, diff --git a/detector/vuls2/vuls2.go b/detector/vuls2/vuls2.go index 61553e7027..393ba3f800 100644 --- a/detector/vuls2/vuls2.go +++ b/detector/vuls2/vuls2.go @@ -202,7 +202,27 @@ func preConvert(sr *models.ScanResult) scanTypes.ScanResult { Version: sr.RunningKernel.Version, RebootRequired: sr.RunningKernel.RebootRequired, }, - OSPackages: slices.Collect(maps.Values(pkgs)), + OSPackages: func() []scanTypes.OSPackage { + ps := slices.Collect(maps.Values(pkgs)) + // For Windows, include the OS release as a synthetic package so that + // kernel-version-based detection can report the correct release name. + if sr.Family == constant.Windows && sr.RunningKernel.Version != "" { + ps = append(ps, scanTypes.OSPackage{ + Name: toVuls2Release(sr.Family, sr.Release), + Version: sr.RunningKernel.Version, + }) + } + return ps + }(), + MicrosoftKB: func() scanTypes.MicrosoftKB { + if sr.WindowsKB == nil { + return scanTypes.MicrosoftKB{} + } + return scanTypes.MicrosoftKB{ + Applied: sr.WindowsKB.Applied, + Unapplied: sr.WindowsKB.Unapplied, + } + }(), ScannedAt: time.Now(), ScannedBy: version.String(), @@ -212,34 +232,32 @@ func preConvert(sr *models.ScanResult) scanTypes.ScanResult { func detect(sesh *session.Session, sr scanTypes.ScanResult) (detectTypes.DetectResult, error) { detected := make(map[dataTypes.RootID]detectTypes.VulnerabilityData) - if len(sr.OSPackages) > 0 { - m, err := ospkg.Detect(sesh.Storage(), sr, runtime.NumCPU()) - if err != nil { - return detectTypes.DetectResult{}, xerrors.Errorf("Failed to detect os packages. err: %w", err) + m, err := ospkg.Detect(sesh.Storage(), sr, runtime.NumCPU()) + if err != nil { + return detectTypes.DetectResult{}, xerrors.Errorf("Failed to detect os packages. err: %w", err) + } + for rootID, d := range m { + base := detectTypes.VulnerabilityData{ + ID: rootID, + Detections: []detectTypes.VulnerabilityDataDetection{d}, } - for rootID, d := range m { - base := detectTypes.VulnerabilityData{ - ID: rootID, - Detections: []detectTypes.VulnerabilityDataDetection{d}, - } - - avs, err := sesh.GetVulnerabilityData(rootID, dbTypes.Filter{ - Contents: []dbTypes.FilterContentType{ - dbTypes.FilterContentTypeAdvisories, - dbTypes.FilterContentTypeVulnerabilities, - }, - RootIDs: []dataTypes.RootID{rootID}, - Ecosystems: []ecosystemTypes.Ecosystem{d.Ecosystem}, - DataSources: slices.Collect(maps.Keys(d.Contents)), - }) - if err != nil { - return detectTypes.DetectResult{}, xerrors.Errorf("Failed to get vulnerability data. RootID: %s, err: %w", rootID, err) - } - base.Advisories = avs.Advisories - base.Vulnerabilities = avs.Vulnerabilities - detected[rootID] = base + avs, err := sesh.GetVulnerabilityData(rootID, dbTypes.Filter{ + Contents: []dbTypes.FilterContentType{ + dbTypes.FilterContentTypeAdvisories, + dbTypes.FilterContentTypeVulnerabilities, + }, + RootIDs: []dataTypes.RootID{rootID}, + Ecosystems: []ecosystemTypes.Ecosystem{d.Ecosystem}, + DataSources: slices.Collect(maps.Keys(d.Contents)), + }) + if err != nil { + return detectTypes.DetectResult{}, xerrors.Errorf("Failed to get vulnerability data. RootID: %s, err: %w", rootID, err) } + + base.Advisories = avs.Advisories + base.Vulnerabilities = avs.Vulnerabilities + detected[rootID] = base } var sourceIDs []sourceTypes.SourceID @@ -466,6 +484,19 @@ func postConvert(scanned scanTypes.ScanResult, detected detectTypes.DetectResult vi.AffectedPackages = ps vi.CpeURIs = am[vi.CveID].cpes + // Populate WindowsKBFixedIns and KB-based DistroAdvisories for Microsoft detections + if string(scanned.Family) == ecosystemTypes.EcosystemTypeMicrosoft { + for _, p := range ps { + if kbID, ok := strings.CutPrefix(p.FixedIn, "KB"); ok { + if !slices.Contains(vi.WindowsKBFixedIns, kbID) { + vi.WindowsKBFixedIns = append(vi.WindowsKBFixedIns, kbID) + } + da := models.DistroAdvisory{AdvisoryID: p.FixedIn, Description: "Microsoft Knowledge Base"} + vi.DistroAdvisories.AppendIfMissing(&da) + } + } + } + vim[vi.CveID] = vi } @@ -598,59 +629,76 @@ func walkCriteria(e ecosystemTypes.Ecosystem, sourceID sourceTypes.SourceID, ca return nil, nil, true, nil } - if cn.Criterion.Type != criterionTypes.CriterionTypeVersion || cn.Criterion.Version == nil { - continue - } - - if ignoreCriterion(e, cn, tag) { - continue - } - - fcn, err := filterCriterion(e, scanned, cn) - if err != nil { - return nil, nil, false, xerrors.Errorf("Failed to filter criterion. err: %w", err) - } + switch cn.Criterion.Type { + case criterionTypes.CriterionTypeVersion: + if cn.Criterion.Version == nil { + continue + } - switch fcn.Criterion.Version.Package.Type { - case vcPackageTypes.PackageTypeBinary, vcPackageTypes.PackageTypeSource: - if !fcn.Criterion.Version.Vulnerable { + if ignoreCriterion(e, cn, tag) { continue } - rangeType, fixedIn := func() (vcAffectedRangeTypes.RangeType, string) { - if fcn.Criterion.Version.Affected == nil { - return vcAffectedRangeTypes.RangeTypeUnknown, "" + fcn, err := filterCriterion(e, scanned, cn) + if err != nil { + return nil, nil, false, xerrors.Errorf("Failed to filter criterion. err: %w", err) + } + + switch fcn.Criterion.Version.Package.Type { + case vcPackageTypes.PackageTypeBinary, vcPackageTypes.PackageTypeSource: + if !fcn.Criterion.Version.Vulnerable { + continue } - return fcn.Criterion.Version.Affected.Type, selectFixedIn(fcn.Criterion.Version.Affected.Type, fcn.Criterion.Version.Affected.Fixed) - }() - for _, index := range fcn.Accepts.Version { - if len(scanned.OSPackages) <= index { - return nil, nil, false, xerrors.Errorf("Too large OSPackage index. len(OSPackage): %d, index: %d", len(scanned.OSPackages), index) + rangeType, fixedIn := func() (vcAffectedRangeTypes.RangeType, string) { + if fcn.Criterion.Version.Affected == nil { + return vcAffectedRangeTypes.RangeTypeUnknown, "" + } + return fcn.Criterion.Version.Affected.Type, selectFixedIn(fcn.Criterion.Version.Affected.Type, fcn.Criterion.Version.Affected.Fixed) + }() + + for _, index := range fcn.Accepts.Version { + if len(scanned.OSPackages) <= index { + return nil, nil, false, xerrors.Errorf("Too large OSPackage index. len(OSPackage): %d, index: %d", len(scanned.OSPackages), index) + } + statuses = append(statuses, packStatus{ + rangeType: rangeType, + status: models.PackageFixStatus{ + Name: affectedPackageName(e, scanned.OSPackages[index]), + FixState: func() string { + if fcn.Criterion.Version.FixStatus == nil { + return "" + } + return fixState(e, sourceID, fcn.Criterion.Version.FixStatus.Vendor) + }(), + FixedIn: fixedIn, + NotFixedYet: fcn.Criterion.Version.FixStatus == nil || fcn.Criterion.Version.FixStatus.Class != vcFixStatusTypes.ClassFixed, + }, + }) } - statuses = append(statuses, packStatus{ - rangeType: rangeType, - status: models.PackageFixStatus{ - Name: affectedPackageName(e, scanned.OSPackages[index]), - FixState: func() string { - if fcn.Criterion.Version.FixStatus == nil { - return "" - } - return fixState(e, sourceID, fcn.Criterion.Version.FixStatus.Vendor) - }(), - FixedIn: fixedIn, - NotFixedYet: fcn.Criterion.Version.FixStatus == nil || fcn.Criterion.Version.FixStatus.Class != vcFixStatusTypes.ClassFixed, - }, - }) - } - case vcPackageTypes.PackageTypeCPE: - for _, index := range fcn.Accepts.Version { - if len(scanned.CPE) <= index { - return nil, nil, false, xerrors.Errorf("Too large CPE index. len(CPE): %d, index: %d", len(scanned.CPE), index) + case vcPackageTypes.PackageTypeCPE: + for _, index := range fcn.Accepts.Version { + if len(scanned.CPE) <= index { + return nil, nil, false, xerrors.Errorf("Too large CPE index. len(CPE): %d, index: %d", len(scanned.CPE), index) + } } + cpes = append(cpes, string(*fcn.Criterion.Version.Package.CPE)) + default: + } + case criterionTypes.CriterionTypeKB: + if cn.Criterion.KB == nil || !cn.Accepts.KB { + continue } - cpes = append(cpes, string(*fcn.Criterion.Version.Package.CPE)) + statuses = append(statuses, packStatus{ + rangeType: vcAffectedRangeTypes.RangeTypeUnknown, + status: models.PackageFixStatus{ + Name: cn.Criterion.KB.Product, + FixedIn: fmt.Sprintf("KB%s", cn.Criterion.KB.KBID), + NotFixedYet: true, + }, + }) default: + continue } } return statuses, cpes, false, nil diff --git a/detector/vuls2/vuls2_test.go b/detector/vuls2/vuls2_test.go index 151afddbad..e3c7174f09 100644 --- a/detector/vuls2/vuls2_test.go +++ b/detector/vuls2/vuls2_test.go @@ -465,6 +465,82 @@ func Test_preConvert(t *testing.T) { }, }, }, + { + name: "windows -> microsoft with WindowsKB", + args: args{ + sr: &models.ScanResult{ + ServerName: "win-server", + Family: "windows", + Release: "Windows 10 Version 21H2 for x64-based Systems", + RunningKernel: models.Kernel{ + Version: "10.0.19044.1234", + }, + WindowsKB: &models.WindowsKB{ + Applied: []string{"5025288"}, + Unapplied: []string{"5025221"}, + }, + Packages: models.Packages{ + "Microsoft Edge": models.Package{ + Name: "Microsoft Edge", + Version: "128.0.2739.79", + }, + }, + }, + }, + want: scanTypes.ScanResult{ + JSONVersion: 0, + ServerName: "win-server", + Family: ecosystemTypes.EcosystemTypeMicrosoft, + Release: "Windows 10 Version 21H2 for x64-based Systems", + + Kernel: scanTypes.Kernel{ + Version: "10.0.19044.1234", + }, + OSPackages: []scanTypes.OSPackage{ + { + Name: "Microsoft Edge", + Version: "128.0.2739.79", + }, + { + Name: "Windows 10 Version 21H2 for x64-based Systems", + Version: "10.0.19044.1234", + }, + }, + MicrosoftKB: scanTypes.MicrosoftKB{ + Applied: []string{"5025288"}, + Unapplied: []string{"5025221"}, + }, + }, + }, + { + name: "windows without WindowsKB (nil)", + args: args{ + sr: &models.ScanResult{ + ServerName: "win-server-no-kb", + Family: "windows", + Release: "Windows Server 2019", + RunningKernel: models.Kernel{ + Version: "10.0.17763.1234", + }, + }, + }, + want: scanTypes.ScanResult{ + JSONVersion: 0, + ServerName: "win-server-no-kb", + Family: ecosystemTypes.EcosystemTypeMicrosoft, + Release: "Windows Server 2019", + + Kernel: scanTypes.Kernel{ + Version: "10.0.17763.1234", + }, + OSPackages: []scanTypes.OSPackage{ + { + Name: "Windows Server 2019", + Version: "10.0.17763.1234", + }, + }, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/go.mod b/go.mod index 2c7cd717a9..5f1872c49c 100644 --- a/go.mod +++ b/go.mod @@ -53,7 +53,6 @@ require ( github.com/spf13/cobra v1.10.2 github.com/vulsio/go-cti v0.3.6 github.com/vulsio/go-cve-dictionary v0.16.2 - github.com/vulsio/gost v0.7.4 go.etcd.io/bbolt v1.4.3 golang.org/x/term v0.42.0 golang.org/x/text v0.36.0 diff --git a/go.sum b/go.sum index 51ddd025e0..9d7b3cb5ce 100644 --- a/go.sum +++ b/go.sum @@ -886,8 +886,6 @@ github.com/vulsio/go-cti v0.3.6 h1:HtSgms2uo4RVcQN0zMVymOFA9nKyTuaCV+vBSl3nAKs= github.com/vulsio/go-cti v0.3.6/go.mod h1:XxVbnyFGhseaeEBmRQIoTJ1pe4ATW1a35mvTVqeFE68= github.com/vulsio/go-cve-dictionary v0.16.2 h1:1sWXzHBXV55PoOYKiWxqtR9OHq8vfNf3kg1XeNeT7Mw= github.com/vulsio/go-cve-dictionary v0.16.2/go.mod h1:cD+HMRAavPw395gGBIa+TOkhOPGRYXkuw94Y31wetRk= -github.com/vulsio/gost v0.7.4 h1:knO5SfQDIpD7tK+NDcVvfiGm5mfwjA+jJnHwQ836M88= -github.com/vulsio/gost v0.7.4/go.mod h1:D26gY8KIGD16BcgSIrnbsoNIqO6hk0YaxgeOkr8lP7M= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= diff --git a/gost/gost.go b/gost/gost.go deleted file mode 100644 index 665ca044e0..0000000000 --- a/gost/gost.go +++ /dev/null @@ -1,100 +0,0 @@ -//go:build !scanner - -package gost - -import ( - "errors" - - "golang.org/x/xerrors" - - "github.com/future-architect/vuls/config" - "github.com/future-architect/vuls/constant" - "github.com/future-architect/vuls/logging" - "github.com/future-architect/vuls/models" - gostdb "github.com/vulsio/gost/db" - gostlog "github.com/vulsio/gost/util" -) - -// Client is the interface of Gost client. -type Client interface { - DetectCVEs(*models.ScanResult, bool) (int, error) - CloseDB() error -} - -// Base is a base struct -type Base struct { - driver gostdb.DB - baseURL string -} - -// CloseDB close a DB connection -func (b Base) CloseDB() error { - if b.driver == nil { - return nil - } - return b.driver.CloseDB() -} - -// FillCVEsWithRedHat fills CVE detailed with Red Hat Security -func FillCVEsWithRedHat(r *models.ScanResult, cnf config.GostConf, o logging.LogOpts) error { - if err := gostlog.SetLogger(o.LogToFile, o.LogDir, o.Debug, o.LogJSON); err != nil { - return err - } - - db, err := newGostDB(&cnf) - if err != nil { - return xerrors.Errorf("Failed to newGostDB. err: %w", err) - } - - client := RedHat{Base{driver: db, baseURL: cnf.GetURL()}} - defer func() { - if err := client.CloseDB(); err != nil { - logging.Log.Errorf("Failed to close DB. err: %+v", err) - } - }() - return client.fillCvesWithRedHatAPI(r) -} - -// NewGostClient make Client by family -func NewGostClient(cnf config.GostConf, family string, o logging.LogOpts) (Client, error) { - if err := gostlog.SetLogger(o.LogToFile, o.LogDir, o.Debug, o.LogJSON); err != nil { - return nil, xerrors.Errorf("Failed to set gost logger. err: %w", err) - } - - db, err := newGostDB(&cnf) - if err != nil { - return nil, xerrors.Errorf("Failed to newGostDB. err: %w", err) - } - - base := Base{driver: db, baseURL: cnf.GetURL()} - switch family { - case constant.Windows: - return Microsoft{base}, nil - case constant.ServerTypePseudo: - return Pseudo{base}, nil - default: - if family == "" { - return nil, xerrors.New("Probably an error occurred during scanning. Check the error message") - } - return nil, xerrors.Errorf("Gost for %s is not implemented yet", family) - } -} - -// NewGostDB returns db client for Gost -func newGostDB(cnf config.VulnDictInterface) (gostdb.DB, error) { - if cnf.IsFetchViaHTTP() { - return nil, nil - } - path := cnf.GetURL() - if cnf.GetType() == "sqlite3" { - path = cnf.GetSQLite3Path() - } - driver, err := gostdb.NewDB(cnf.GetType(), path, cnf.GetDebugSQL(), gostdb.Option{}) - if err != nil { - if errors.Is(err, gostdb.ErrDBLocked) { - return nil, xerrors.Errorf("Failed to init gost DB. SQLite3: %s is locked. err: %w", cnf.GetSQLite3Path(), err) - } - return nil, xerrors.Errorf("Failed to init gost DB. DB Path: %s, err: %w", path, err) - } - return driver, nil -} diff --git a/gost/microsoft.go b/gost/microsoft.go deleted file mode 100644 index b9c743cd9b..0000000000 --- a/gost/microsoft.go +++ /dev/null @@ -1,435 +0,0 @@ -//go:build !scanner - -package gost - -import ( - "cmp" - "encoding/json" - "fmt" - "maps" - "net/http" - "slices" - "strconv" - "strings" - "time" - - "github.com/cenkalti/backoff" - "github.com/hashicorp/go-version" - "github.com/parnurzeal/gorequest" - "golang.org/x/xerrors" - - "github.com/future-architect/vuls/config" - "github.com/future-architect/vuls/logging" - "github.com/future-architect/vuls/models" - "github.com/future-architect/vuls/util" - gostmodels "github.com/vulsio/gost/models" -) - -// Microsoft is Gost client for windows -type Microsoft struct { - Base -} - -// DetectCVEs fills cve information that has in Gost -func (ms Microsoft) DetectCVEs(r *models.ScanResult, _ bool) (nCVEs int, err error) { - var applied, unapplied []string - if r.WindowsKB != nil { - applied = r.WindowsKB.Applied - unapplied = r.WindowsKB.Unapplied - } - if ms.driver == nil { - u, err := util.URLPathJoin(ms.baseURL, "microsoft", "kbs") - if err != nil { - return 0, xerrors.Errorf("Failed to join URLPath. err: %w", err) - } - - content := map[string]any{"applied": applied, "unapplied": unapplied} - var body []byte - var errs []error - var resp *http.Response - f := func() error { - req := gorequest.New().Post(u).SendStruct(content).Type("json") - if config.Conf.Gost.TimeoutSecPerRequest > 0 { - req = req.Timeout(time.Duration(config.Conf.Gost.TimeoutSecPerRequest) * time.Second) - } - resp, body, errs = req.EndBytes() - if 0 < len(errs) || resp == nil || resp.StatusCode != 200 { - return xerrors.Errorf("HTTP POST error. url: %s, resp: %v, err: %+v", u, resp, errs) - } - return nil - } - notify := func(err error, t time.Duration) { - logging.Log.Warnf("Failed to HTTP POST. retrying in %f seconds. err: %+v", t.Seconds(), err) - } - if err := backoff.RetryNotify(f, backoff.NewExponentialBackOff(), notify); err != nil { - return 0, xerrors.Errorf("HTTP Error: %w", err) - } - - var r struct { - Applied []string `json:"applied"` - Unapplied []string `json:"unapplied"` - } - if err := json.Unmarshal(body, &r); err != nil { - return 0, xerrors.Errorf("Failed to Unmarshal. body: %s, err: %w", body, err) - } - applied = r.Applied - unapplied = r.Unapplied - } else { - applied, unapplied, err = ms.driver.GetExpandKB(applied, unapplied) - if err != nil { - return 0, xerrors.Errorf("Failed to detect CVEs. err: %w", err) - } - } - - var products []string - if ms.driver == nil { - u, err := util.URLPathJoin(ms.baseURL, "microsoft", "products") - if err != nil { - return 0, xerrors.Errorf("Failed to join URLPath. err: %w", err) - } - - content := map[string]any{"release": r.Release, "kbs": append(applied, unapplied...)} - var body []byte - var errs []error - var resp *http.Response - f := func() error { - req := gorequest.New().Post(u).SendStruct(content).Type("json") - if config.Conf.Gost.TimeoutSecPerRequest > 0 { - req = req.Timeout(time.Duration(config.Conf.Gost.TimeoutSecPerRequest) * time.Second) - } - resp, body, errs = req.EndBytes() - if 0 < len(errs) || resp == nil || resp.StatusCode != 200 { - return xerrors.Errorf("HTTP POST error. url: %s, resp: %v, err: %+v", u, resp, errs) - } - return nil - } - notify := func(err error, t time.Duration) { - logging.Log.Warnf("Failed to HTTP POST. retrying in %f seconds. err: %+v", t.Seconds(), err) - } - if err := backoff.RetryNotify(f, backoff.NewExponentialBackOff(), notify); err != nil { - return 0, xerrors.Errorf("HTTP Error: %w", err) - } - - if err := json.Unmarshal(body, &products); err != nil { - return 0, xerrors.Errorf("Failed to Unmarshal. body: %s, err: %w", body, err) - } - } else { - ps, err := ms.driver.GetRelatedProducts(r.Release, append(applied, unapplied...)) - if err != nil { - return 0, xerrors.Errorf("Failed to detect CVEs. err: %w", err) - } - products = ps - } - - m := map[string]struct{}{} - for _, p := range products { - m[p] = struct{}{} - } - for _, n := range []string{"Microsoft Edge (Chromium-based)", fmt.Sprintf("Microsoft Edge on %s", r.Release), fmt.Sprintf("Microsoft Edge (Chromium-based) in IE Mode on %s", r.Release), fmt.Sprintf("Microsoft Edge (EdgeHTML-based) on %s", r.Release)} { - delete(m, n) - } - filtered := []string{r.Release} - for _, p := range r.Packages { - switch p.Name { - case "Microsoft Edge": - if ss := strings.Split(p.Version, "."); len(ss) > 0 { - v, err := strconv.ParseInt(ss[0], 10, 8) - if err != nil { - continue - } - if v > 44 { - filtered = append(filtered, "Microsoft Edge (Chromium-based)", fmt.Sprintf("Microsoft Edge on %s", r.Release), fmt.Sprintf("Microsoft Edge (Chromium-based) in IE Mode on %s", r.Release)) - } else { - filtered = append(filtered, fmt.Sprintf("Microsoft Edge on %s", r.Release), fmt.Sprintf("Microsoft Edge (EdgeHTML-based) on %s", r.Release)) - } - } - default: - } - } - filtered = unique(append(filtered, slices.Collect(maps.Keys(m))...)) - - var cves map[string]gostmodels.MicrosoftCVE - if ms.driver == nil { - u, err := util.URLPathJoin(ms.baseURL, "microsoft", "filtered-cves") - if err != nil { - return 0, xerrors.Errorf("Failed to join URLPath. err: %w", err) - } - - content := map[string]any{"products": filtered, "kbs": append(applied, unapplied...)} - var body []byte - var errs []error - var resp *http.Response - f := func() error { - req := gorequest.New().Post(u).SendStruct(content).Type("json") - if config.Conf.Gost.TimeoutSecPerRequest > 0 { - req = req.Timeout(time.Duration(config.Conf.Gost.TimeoutSecPerRequest) * time.Second) - } - resp, body, errs = req.EndBytes() - if 0 < len(errs) || resp == nil || resp.StatusCode != 200 { - return xerrors.Errorf("HTTP POST error. url: %s, resp: %v, err: %+v", u, resp, errs) - } - return nil - } - notify := func(err error, t time.Duration) { - logging.Log.Warnf("Failed to HTTP POST. retrying in %f seconds. err: %+v", t.Seconds(), err) - } - if err := backoff.RetryNotify(f, backoff.NewExponentialBackOff(), notify); err != nil { - return 0, xerrors.Errorf("HTTP Error: %w", err) - } - - if err := json.Unmarshal(body, &cves); err != nil { - return 0, xerrors.Errorf("Failed to Unmarshal. body: %s, err: %w", body, err) - } - } else { - cves, err = ms.driver.GetFilteredCvesMicrosoft(filtered, append(applied, unapplied...)) - if err != nil { - return 0, xerrors.Errorf("Failed to detect CVEs. err: %w", err) - } - } - - for cveID, cve := range cves { - v, err := ms.detect(r, cve, applied, unapplied) - if err != nil { - return 0, xerrors.Errorf("Failed to detect. err: %w", err) - } - if v == nil { - continue - } - nCVEs++ - r.ScannedCves[cveID] = *v - } - return nCVEs, nil -} - -func (ms Microsoft) detect(r *models.ScanResult, cve gostmodels.MicrosoftCVE, applied, unapplied []string) (*models.VulnInfo, error) { - cve.Products = func() []gostmodels.MicrosoftProduct { - var ps []gostmodels.MicrosoftProduct - for _, p := range cve.Products { - if len(p.KBs) == 0 { - switch { - case p.Name == r.Release: - ps = append(ps, p) - case strings.HasPrefix(p.Name, "Microsoft Edge"): - ps = append(ps, p) - default: - } - continue - } - - p.KBs = func() []gostmodels.MicrosoftKB { - var kbs []gostmodels.MicrosoftKB - for _, kb := range p.KBs { - if _, err := strconv.Atoi(kb.Article); err != nil { - switch { - case strings.HasPrefix(p.Name, "Microsoft Edge"): - p, ok := r.Packages["Microsoft Edge"] - if !ok { - break - } - - if kb.FixedBuild == "" { - kbs = append(kbs, kb) - break - } - - vera, err := version.NewVersion(p.Version) - if err != nil { - kbs = append(kbs, kb) - break - } - verb, err := version.NewVersion(kb.FixedBuild) - if err != nil { - kbs = append(kbs, kb) - break - } - if vera.LessThan(verb) { - kbs = append(kbs, kb) - } - default: - } - } else { - if slices.Contains(applied, kb.Article) { - return nil - } - if slices.Contains(unapplied, kb.Article) { - kbs = append(kbs, kb) - } - } - } - return kbs - }() - if len(p.KBs) > 0 { - ps = append(ps, p) - } - } - return ps - }() - if len(cve.Products) == 0 { - return nil, nil - } - - cveCont, mitigations := ms.ConvertToModel(&cve) - vinfo := models.VulnInfo{ - CveID: cve.CveID, - CveContents: models.NewCveContents(*cveCont), - Mitigations: mitigations, - } - - for _, p := range cve.Products { - if len(p.KBs) == 0 { - switch { - case p.Name == r.Release: - vinfo.AffectedPackages = append(vinfo.AffectedPackages, models.PackageFixStatus{ - Name: p.Name, - FixState: "unfixed", - }) - case strings.HasPrefix(p.Name, "Microsoft Edge"): - vinfo.AffectedPackages = append(vinfo.AffectedPackages, models.PackageFixStatus{ - Name: "Microsoft Edge", - FixState: "unknown", - }) - default: - return nil, xerrors.Errorf("unexpected product. expected: %q, actual: %q", []string{r.Release, "Microsoft Edge"}, p.Name) - } - continue - } - - for _, kb := range p.KBs { - if _, err := strconv.Atoi(kb.Article); err != nil { - switch { - case strings.HasPrefix(p.Name, "Microsoft Edge"): - vinfo.AffectedPackages = append(vinfo.AffectedPackages, models.PackageFixStatus{ - Name: "Microsoft Edge", - FixState: func() string { - if func() bool { - if kb.FixedBuild == "" { - return true - } - - if _, err := version.NewVersion(r.Packages["Microsoft Edge"].Version); err != nil { - return true - } - - if _, err := version.NewVersion(kb.FixedBuild); err != nil { - return true - } - - return false - }() { - return "unknown" - } - return "fixed" - }(), - FixedIn: kb.FixedBuild, - }) - default: - return nil, xerrors.Errorf("unexpected product. supported: %q, actual: %q", []string{"Microsoft Edge"}, p.Name) - } - } else { - kbid := fmt.Sprintf("KB%s", kb.Article) - vinfo.DistroAdvisories.AppendIfMissing(new(models.DistroAdvisory{ - AdvisoryID: kbid, - Description: "Microsoft Knowledge Base", - })) - if !slices.Contains(vinfo.WindowsKBFixedIns, kbid) { - vinfo.WindowsKBFixedIns = append(vinfo.WindowsKBFixedIns, kbid) - } - } - } - } - - confs, err := func() (models.Confidences, error) { - var cs models.Confidences - - if len(vinfo.WindowsKBFixedIns) > 0 { - cs.AppendIfMissing(models.WindowsUpdateSearch) - } - - for _, stat := range vinfo.AffectedPackages { - switch stat.FixState { - case "fixed", "unfixed": - cs.AppendIfMissing(models.WindowsUpdateSearch) - case "unknown": - cs.AppendIfMissing(models.WindowsRoughMatch) - default: - return nil, xerrors.Errorf("unexpected fix state. expected: %q, actual: %q", []string{"fixed", "unfixed", "unknown"}, stat.FixState) - } - } - - if len(cs) == 0 { - return nil, xerrors.New("confidences not found") - } - return cs, nil - }() - if err != nil { - return nil, xerrors.Errorf("Failed to detect confidences. err: %w", err) - } - vinfo.Confidences = confs - - return &vinfo, nil -} - -// ConvertToModel converts gost model to vuls model -func (ms Microsoft) ConvertToModel(cve *gostmodels.MicrosoftCVE) (*models.CveContent, []models.Mitigation) { - slices.SortFunc(cve.Products, func(i, j gostmodels.MicrosoftProduct) int { - return cmp.Compare(i.ScoreSet.Vector, j.ScoreSet.Vector) - }) - - p := slices.MaxFunc(cve.Products, func(a, b gostmodels.MicrosoftProduct) int { - va, erra := strconv.ParseFloat(a.ScoreSet.BaseScore, 64) - vb, errb := strconv.ParseFloat(b.ScoreSet.BaseScore, 64) - if erra != nil { - if errb != nil { - return 0 - } - return -1 - } - if errb != nil { - return +1 - } - return cmp.Compare(va, vb) - }) - - var mitigations []models.Mitigation - if cve.Mitigation != "" { - mitigations = append(mitigations, models.Mitigation{ - CveContentType: models.Microsoft, - Mitigation: cve.Mitigation, - URL: cve.URL, - }) - } - if cve.Workaround != "" { - mitigations = append(mitigations, models.Mitigation{ - CveContentType: models.Microsoft, - Mitigation: cve.Workaround, - URL: cve.URL, - }) - } - - return &models.CveContent{ - Type: models.Microsoft, - CveID: cve.CveID, - Title: cve.Title, - Summary: cve.Description, - Cvss3Score: func() float64 { - v, err := strconv.ParseFloat(p.ScoreSet.BaseScore, 64) - if err != nil { - return 0.0 - } - return v - }(), - Cvss3Vector: p.ScoreSet.Vector, - Cvss3Severity: p.Severity, - Published: cve.PublishDate, - LastModified: cve.LastUpdateDate, - SourceLink: cve.URL, - Optional: func() map[string]string { - if 0 < len(cve.ExploitStatus) { - // TODO: CVE-2020-0739 - // "exploit_status": "Publicly Disclosed:No;Exploited:No;Latest Software Release:Exploitation Less Likely;Older Software Release:Exploitation Less Likely;DOS:N/A", - return map[string]string{"exploit": cve.ExploitStatus} - } - return nil - }(), - }, mitigations -} diff --git a/gost/microsoft_test.go b/gost/microsoft_test.go deleted file mode 100644 index 8fa49af60d..0000000000 --- a/gost/microsoft_test.go +++ /dev/null @@ -1,459 +0,0 @@ -//go:build !scanner - -package gost - -import ( - "reflect" - "testing" - - "github.com/future-architect/vuls/constant" - "github.com/future-architect/vuls/models" - gostmodels "github.com/vulsio/gost/models" -) - -func TestMicrosoft_detect(t *testing.T) { - type args struct { - r *models.ScanResult - cve gostmodels.MicrosoftCVE - applied []string - unapplied []string - } - tests := []struct { - name string - args args - want *models.VulnInfo - wantErr bool - }{ - { - name: "microsoft windows not affected", - args: args{ - r: &models.ScanResult{ - Family: constant.Windows, - Release: "Windows Server 2012 R2", - }, - cve: gostmodels.MicrosoftCVE{ - CveID: "CVE-2023-21554", - Products: []gostmodels.MicrosoftProduct{ - { - Name: "Windows Server 2012 R2", - KBs: []gostmodels.MicrosoftKB{ - { - Article: "5025285", - FixedBuild: "6.3.9600.20919", - }, - { - Article: "5025288", - FixedBuild: "6.3.9600.20919", - }, - }, - }, - }, - }, - applied: []string{"5025288"}, - }, - }, - { - name: "microsoft windows not affected2", - args: args{ - r: &models.ScanResult{ - Family: constant.Windows, - Release: "Windows 10 Version 21H2 for x64-based Systems", - }, - cve: gostmodels.MicrosoftCVE{ - CveID: "CVE-2023-21554", - Products: []gostmodels.MicrosoftProduct{ - { - Name: "Windows 10 Version 21H2 for x64-based Systems", - KBs: []gostmodels.MicrosoftKB{ - { - Article: "5025221", - FixedBuild: "10.0.19044.2846", - }, - }, - }, - }, - }, - unapplied: []string{"5026361"}, - }, - }, - { - name: "microsoft windows fixed", - args: args{ - r: &models.ScanResult{ - Family: constant.Windows, - Release: "Windows 10 Version 21H2 for x64-based Systems", - }, - cve: gostmodels.MicrosoftCVE{ - CveID: "CVE-2023-21554", - Products: []gostmodels.MicrosoftProduct{ - { - Name: "Windows 10 Version 21H2 for x64-based Systems", - KBs: []gostmodels.MicrosoftKB{ - { - Article: "5025221", - FixedBuild: "10.0.19044.2846", - }, - }, - }, - }, - }, - unapplied: []string{"5025221"}, - }, - want: &models.VulnInfo{ - CveID: "CVE-2023-21554", - Confidences: models.Confidences{models.WindowsUpdateSearch}, - DistroAdvisories: models.DistroAdvisories{ - { - AdvisoryID: "KB5025221", - Description: "Microsoft Knowledge Base", - }, - }, - CveContents: models.CveContents{ - models.Microsoft: []models.CveContent{ - { - Type: models.Microsoft, - CveID: "CVE-2023-21554", - }, - }, - }, - WindowsKBFixedIns: []string{"KB5025221"}, - }, - }, - { - name: "microsoft windows unfixed", - args: args{ - r: &models.ScanResult{ - Family: constant.Windows, - Release: "Windows 10 Version 21H2 for x64-based Systems", - }, - cve: gostmodels.MicrosoftCVE{ - CveID: "CVE-2013-3900", - Products: []gostmodels.MicrosoftProduct{ - { - Name: "Windows 10 Version 21H2 for x64-based Systems", - }, - }, - }, - }, - want: &models.VulnInfo{ - CveID: "CVE-2013-3900", - Confidences: models.Confidences{models.WindowsUpdateSearch}, - AffectedPackages: models.PackageFixStatuses{ - { - Name: "Windows 10 Version 21H2 for x64-based Systems", - FixState: "unfixed", - }, - }, - CveContents: models.CveContents{ - models.Microsoft: []models.CveContent{ - { - Type: models.Microsoft, - CveID: "CVE-2013-3900", - }, - }, - }, - }, - }, - { - name: "microsoft edge not installed", - args: args{ - r: &models.ScanResult{ - Family: constant.Windows, - Release: "Windows 10 Version 21H2 for x64-based Systems", - }, - cve: gostmodels.MicrosoftCVE{ - CveID: "CVE-2024-8639", - Products: []gostmodels.MicrosoftProduct{ - { - Name: "Microsoft Edge (Chromium-based)", - KBs: []gostmodels.MicrosoftKB{ - { - Article: "Release Notes", - FixedBuild: "128.0.2739.79", - }, - }, - }, - }, - }, - }, - }, - { - name: "microsoft edge not affected", - args: args{ - r: &models.ScanResult{ - Family: constant.Windows, - Release: "Windows 10 Version 21H2 for x64-based Systems", - Packages: models.Packages{ - "Microsoft Edge": { - Name: "Microsoft Edge", - Version: "128.0.2739.79", - }, - }, - }, - cve: gostmodels.MicrosoftCVE{ - CveID: "CVE-2024-8639", - Products: []gostmodels.MicrosoftProduct{ - { - Name: "Microsoft Edge (Chromium-based)", - KBs: []gostmodels.MicrosoftKB{ - { - Article: "Release Notes", - FixedBuild: "128.0.2739.79", - }, - }, - }, - }, - }, - }, - }, - { - name: "microsoft edge fixed", - args: args{ - r: &models.ScanResult{ - Family: constant.Windows, - Release: "Windows Server 2016", - Packages: models.Packages{ - "Microsoft Edge": { - Name: "Microsoft Edge", - Version: "38.14393", - }, - }, - }, - cve: gostmodels.MicrosoftCVE{ - CveID: "CVE-2016-7195", - Products: []gostmodels.MicrosoftProduct{ - { - Name: "Internet Explorer 11 on Windows Server 2016", - KBs: []gostmodels.MicrosoftKB{ - { - Article: "3200970", - }, - }, - }, - { - Name: "Microsoft Edge (EdgeHTML-based) on Windows Server 2016", - KBs: []gostmodels.MicrosoftKB{ - { - Article: "3200970", - }, - }, - }, - }, - }, - unapplied: []string{"3200970"}, - }, - want: &models.VulnInfo{ - CveID: "CVE-2016-7195", - Confidences: models.Confidences{models.WindowsUpdateSearch}, - DistroAdvisories: models.DistroAdvisories{ - { - AdvisoryID: "KB3200970", - Description: "Microsoft Knowledge Base", - }, - }, - CveContents: models.CveContents{ - models.Microsoft: []models.CveContent{ - { - Type: models.Microsoft, - CveID: "CVE-2016-7195", - }, - }, - }, - WindowsKBFixedIns: []string{"KB3200970"}, - }, - }, - { - name: "microsoft edge fixed2", - args: args{ - r: &models.ScanResult{ - Family: constant.Windows, - Release: "Windows 10 Version 21H2 for x64-based Systems", - Packages: models.Packages{ - "Microsoft Edge": { - Name: "Microsoft Edge", - Version: "111.0.1661.41", - }, - }, - }, - cve: gostmodels.MicrosoftCVE{ - CveID: "CVE-2024-8639", - Products: []gostmodels.MicrosoftProduct{ - { - Name: "Microsoft Edge (Chromium-based)", - KBs: []gostmodels.MicrosoftKB{ - { - Article: "Release Notes", - FixedBuild: "128.0.2739.79", - }, - }, - }, - }, - }, - }, - want: &models.VulnInfo{ - CveID: "CVE-2024-8639", - Confidences: models.Confidences{models.WindowsUpdateSearch}, - AffectedPackages: models.PackageFixStatuses{ - { - Name: "Microsoft Edge", - FixState: "fixed", - FixedIn: "128.0.2739.79", - }, - }, - CveContents: models.CveContents{ - models.Microsoft: []models.CveContent{ - { - Type: models.Microsoft, - CveID: "CVE-2024-8639", - }, - }, - }, - }, - }, - { - name: "microsoft edge unknown", - args: args{ - r: &models.ScanResult{ - Family: constant.Windows, - Release: "Windows 10 Version 21H2 for x64-based Systems", - Packages: models.Packages{ - "Microsoft Edge": { - Name: "Microsoft Edge", - Version: "111.0.1661.41", - }, - }, - }, - cve: gostmodels.MicrosoftCVE{ - CveID: "CVE-2020-1195", - Products: []gostmodels.MicrosoftProduct{ - { - Name: "Microsoft Edge (Chromium-based)", - }, - }, - }, - }, - want: &models.VulnInfo{ - CveID: "CVE-2020-1195", - Confidences: models.Confidences{models.WindowsRoughMatch}, - AffectedPackages: models.PackageFixStatuses{ - { - Name: "Microsoft Edge", - FixState: "unknown", - }, - }, - CveContents: models.CveContents{ - models.Microsoft: []models.CveContent{ - { - Type: models.Microsoft, - CveID: "CVE-2020-1195", - }, - }, - }, - }, - }, - { - name: "microsoft edge unknown2", - args: args{ - r: &models.ScanResult{ - Family: constant.Windows, - Release: "Windows 10 Version 21H2 for x64-based Systems", - Packages: models.Packages{ - "Microsoft Edge": { - Name: "Microsoft Edge", - Version: "111.0.1661.41", - }, - }, - }, - cve: gostmodels.MicrosoftCVE{ - CveID: "CVE-2022-4135", - Products: []gostmodels.MicrosoftProduct{ - { - Name: "Microsoft Edge (Chromium-based)", - KBs: []gostmodels.MicrosoftKB{ - { - Article: "Release Notes", - }, - }, - }, - }, - }, - }, - want: &models.VulnInfo{ - CveID: "CVE-2022-4135", - Confidences: models.Confidences{models.WindowsRoughMatch}, - AffectedPackages: models.PackageFixStatuses{ - { - Name: "Microsoft Edge", - FixState: "unknown", - }, - }, - CveContents: models.CveContents{ - models.Microsoft: []models.CveContent{ - { - Type: models.Microsoft, - CveID: "CVE-2022-4135", - }, - }, - }, - }, - }, - { - name: "microsoft other product not support", - args: args{ - r: &models.ScanResult{ - Family: constant.Windows, - Release: "Windows 10 Version 21H2 for x64-based Systems", - Packages: models.Packages{ - "Microsoft Visual Studio Code": { - Name: "Microsoft Visual Studio Code", - Version: "1.76.0", - }, - }, - }, - cve: gostmodels.MicrosoftCVE{ - CveID: "CVE-2024-26165", - Products: []gostmodels.MicrosoftProduct{ - { - Name: "Visual Studio Code", - KBs: []gostmodels.MicrosoftKB{ - { - Article: "Release Notes", - FixedBuild: "1.87.2", - }, - }, - }, - }, - }, - }, - }, - { - name: "microsoft other product not support2", - args: args{ - r: &models.ScanResult{ - Family: constant.Windows, - Release: "Windows Server 2016", - }, - cve: gostmodels.MicrosoftCVE{ - CveID: "ADV200001", - Products: []gostmodels.MicrosoftProduct{ - { - Name: "Internet Explorer 11 on Windows Server 2016", - }, - }, - }, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := (Microsoft{}).detect(tt.args.r, tt.args.cve, tt.args.applied, tt.args.unapplied) - if (err != nil) != tt.wantErr { - t.Errorf("Microsoft.detect() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("Microsoft.detect() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/gost/pseudo.go b/gost/pseudo.go deleted file mode 100644 index 362c45842a..0000000000 --- a/gost/pseudo.go +++ /dev/null @@ -1,17 +0,0 @@ -//go:build !scanner - -package gost - -import ( - "github.com/future-architect/vuls/models" -) - -// Pseudo is Gost client except for RedHat family, Debian, Ubuntu and Windows -type Pseudo struct { - Base -} - -// DetectCVEs fills cve information that has in Gost -func (pse Pseudo) DetectCVEs(_ *models.ScanResult, _ bool) (int, error) { - return 0, nil -} diff --git a/gost/redhat.go b/gost/redhat.go deleted file mode 100644 index 707965ae96..0000000000 --- a/gost/redhat.go +++ /dev/null @@ -1,158 +0,0 @@ -//go:build !scanner - -package gost - -import ( - "encoding/json" - "strconv" - "strings" - - "github.com/future-architect/vuls/models" - "github.com/future-architect/vuls/util" - gostmodels "github.com/vulsio/gost/models" -) - -// RedHat is Gost client for RedHat family linux -type RedHat struct { - Base -} - -func (red RedHat) fillCvesWithRedHatAPI(r *models.ScanResult) error { - cveIDs := []string{} - for cveID, vuln := range r.ScannedCves { - if _, ok := vuln.CveContents[models.RedHatAPI]; ok { - continue - } - cveIDs = append(cveIDs, cveID) - } - - if red.driver == nil { - prefix, err := util.URLPathJoin(red.baseURL, "redhat", "cves") - if err != nil { - return err - } - responses, err := getCvesViaHTTP(cveIDs, prefix) - if err != nil { - return err - } - for _, res := range responses { - redCve := gostmodels.RedhatCVE{} - if err := json.Unmarshal([]byte(res.json), &redCve); err != nil { - return err - } - if redCve.ID == 0 { - continue - } - red.setFixedCveToScanResult(&redCve, r) - } - } else { - redCves, err := red.driver.GetRedhatMulti(cveIDs) - if err != nil { - return err - } - for _, redCve := range redCves { - if len(redCve.Name) == 0 { - continue - } - red.setFixedCveToScanResult(&redCve, r) - } - } - - return nil -} - -func (red RedHat) setFixedCveToScanResult(cve *gostmodels.RedhatCVE, r *models.ScanResult) { - cveCont, mitigations := red.ConvertToModel(cve) - v, ok := r.ScannedCves[cveCont.CveID] - if ok { - if v.CveContents == nil { - v.CveContents = models.NewCveContents(*cveCont) - } else { - v.CveContents[models.RedHatAPI] = []models.CveContent{*cveCont} - } - } else { - v = models.VulnInfo{ - CveID: cveCont.CveID, - CveContents: models.NewCveContents(*cveCont), - Confidences: models.Confidences{models.RedHatAPIMatch}, - } - } - v.Mitigations = append(v.Mitigations, mitigations...) - r.ScannedCves[cveCont.CveID] = v -} - -func (red RedHat) parseCwe(str string) (cwes []string) { - if str != "" { - s := strings.ReplaceAll(str, "(", "|") - s = strings.ReplaceAll(s, ")", "|") - s = strings.ReplaceAll(s, "->", "|") - for s := range strings.SplitSeq(s, "|") { - if s != "" { - cwes = append(cwes, s) - } - } - } - return -} - -// ConvertToModel converts gost model to vuls model -func (red RedHat) ConvertToModel(cve *gostmodels.RedhatCVE) (*models.CveContent, []models.Mitigation) { - cwes := red.parseCwe(cve.Cwe) - - details := make([]string, 0, len(cve.Details)) - for _, detail := range cve.Details { - details = append(details, detail.Detail) - } - - v2score := 0.0 - if cve.Cvss.CvssBaseScore != "" { - v2score, _ = strconv.ParseFloat(cve.Cvss.CvssBaseScore, 64) - } - v2severity := "" - if v2score != 0 { - v2severity = cve.ThreatSeverity - } - - v3score := 0.0 - if cve.Cvss3.Cvss3BaseScore != "" { - v3score, _ = strconv.ParseFloat(cve.Cvss3.Cvss3BaseScore, 64) - } - v3severity := "" - if v3score != 0 { - v3severity = cve.ThreatSeverity - } - - refs := make([]models.Reference, 0, len(cve.References)) - for _, r := range cve.References { - refs = append(refs, models.Reference{Link: r.Reference}) - } - - vendorURL := "https://access.redhat.com/security/cve/" + cve.Name - mitigations := []models.Mitigation{} - if cve.Mitigation != "" { - mitigations = []models.Mitigation{ - { - CveContentType: models.RedHatAPI, - Mitigation: cve.Mitigation, - URL: vendorURL, - }, - } - } - - return &models.CveContent{ - Type: models.RedHatAPI, - CveID: cve.Name, - Title: cve.Bugzilla.Description, - Summary: strings.Join(details, "\n"), - Cvss2Score: v2score, - Cvss2Vector: cve.Cvss.CvssScoringVector, - Cvss2Severity: v2severity, - Cvss3Score: v3score, - Cvss3Vector: cve.Cvss3.Cvss3ScoringVector, - Cvss3Severity: v3severity, - References: refs, - CweIDs: cwes, - Published: cve.PublicDate, - SourceLink: vendorURL, - }, mitigations -} diff --git a/gost/redhat_test.go b/gost/redhat_test.go deleted file mode 100644 index 61ffd840d1..0000000000 --- a/gost/redhat_test.go +++ /dev/null @@ -1,34 +0,0 @@ -//go:build !scanner - -package gost - -import ( - "reflect" - "testing" -) - -func TestParseCwe(t *testing.T) { - var tests = []struct { - in string - out []string - }{ - { - in: "CWE-665->(CWE-200|CWE-89)", - out: []string{"CWE-665", "CWE-200", "CWE-89"}, - }, - { - in: "CWE-841->CWE-770->CWE-454", - out: []string{"CWE-841", "CWE-770", "CWE-454"}, - }, - { - in: "(CWE-122|CWE-125)", - out: []string{"CWE-122", "CWE-125"}, - }, - } - - for i, tt := range tests { - if out := (RedHat{}).parseCwe(tt.in); !reflect.DeepEqual(tt.out, out) { - t.Errorf("[%d]expected: %s, actual: %s", i, tt.out, out) - } - } -} diff --git a/gost/util.go b/gost/util.go deleted file mode 100644 index 3a9cca8043..0000000000 --- a/gost/util.go +++ /dev/null @@ -1,201 +0,0 @@ -//go:build !scanner - -package gost - -import ( - "maps" - "net/http" - "slices" - "strings" - "time" - - "github.com/cenkalti/backoff" - "github.com/parnurzeal/gorequest" - "golang.org/x/xerrors" - - "github.com/future-architect/vuls/config" - "github.com/future-architect/vuls/logging" - "github.com/future-architect/vuls/models" - "github.com/future-architect/vuls/util" -) - -type response struct { - request request - json string -} - -func getCvesViaHTTP(cveIDs []string, urlPrefix string) ( - responses []response, err error) { - nReq := len(cveIDs) - reqChan := make(chan request, nReq) - resChan := make(chan response, nReq) - errChan := make(chan error, nReq) - defer close(reqChan) - defer close(resChan) - defer close(errChan) - - go func() { - for _, cveID := range cveIDs { - reqChan <- request{ - cveID: cveID, - } - } - }() - - concurrency := 10 - tasks := util.GenWorkers(concurrency) - for range nReq { - tasks <- func() { - req := <-reqChan - url, err := util.URLPathJoin( - urlPrefix, - req.cveID, - ) - if err != nil { - errChan <- err - } else { - logging.Log.Debugf("HTTP Request to %s", url) - httpGet(url, req, resChan, errChan) - } - } - } - - var timeout <-chan time.Time - if config.Conf.Gost.TimeoutSec > 0 { - timeout = time.After(time.Duration(config.Conf.Gost.TimeoutSec) * time.Second) - } - var errs []error - for range nReq { - select { - case res := <-resChan: - responses = append(responses, res) - case err := <-errChan: - errs = append(errs, err) - case <-timeout: - return nil, xerrors.New("Timeout Fetching Gost") - } - } - if len(errs) != 0 { - return nil, xerrors.Errorf("Failed to fetch Gost. err: %w", errs) - } - return -} - -type request struct { - packName string - isSrcPack bool - cveID string -} - -func getCvesWithFixStateViaHTTP(r *models.ScanResult, urlPrefix, fixState string) (responses []response, err error) { - nReq := len(r.SrcPackages) - reqChan := make(chan request, nReq) - resChan := make(chan response, nReq) - errChan := make(chan error, nReq) - defer close(reqChan) - defer close(resChan) - defer close(errChan) - - go func() { - for _, pack := range r.SrcPackages { - n := pack.Name - if models.IsKernelSourcePackage(r.Family, pack.Name) { - n = models.RenameKernelSourcePackageName(r.Family, pack.Name) - } - reqChan <- request{ - packName: n, - isSrcPack: true, - } - } - }() - - concurrency := 10 - tasks := util.GenWorkers(concurrency) - for range nReq { - tasks <- func() { - req := <-reqChan - url, err := util.URLPathJoin( - urlPrefix, - req.packName, - fixState, - ) - if err != nil { - errChan <- err - } else { - logging.Log.Debugf("HTTP Request to %s", url) - httpGet(url, req, resChan, errChan) - } - } - } - - var timeout <-chan time.Time - if config.Conf.Gost.TimeoutSec > 0 { - timeout = time.After(time.Duration(config.Conf.Gost.TimeoutSec) * time.Second) - } - var errs []error - for range nReq { - select { - case res := <-resChan: - responses = append(responses, res) - case err := <-errChan: - errs = append(errs, err) - case <-timeout: - return nil, xerrors.New("Timeout Fetching Gost") - } - } - if len(errs) != 0 { - return nil, xerrors.Errorf("Failed to fetch Gost. err: %w", errs) - } - return -} - -func httpGet(url string, req request, resChan chan<- response, errChan chan<- error) { - var body string - var errs []error - var resp *http.Response - count, retryMax := 0, 3 - f := func() (err error) { - req := gorequest.New().Get(url) - if config.Conf.Gost.TimeoutSecPerRequest > 0 { - req = req.Timeout(time.Duration(config.Conf.Gost.TimeoutSecPerRequest) * time.Second) - } - resp, body, errs = req.End() - if 0 < len(errs) || resp == nil || resp.StatusCode != 200 { - count++ - if count == retryMax { - return nil - } - return xerrors.Errorf("HTTP GET error, url: %s, resp: %v, err: %+v", url, resp, errs) - } - return nil - } - notify := func(err error, t time.Duration) { - logging.Log.Warnf("Failed to HTTP GET. retrying in %f seconds. err: %+v", t.Seconds(), err) - } - err := backoff.RetryNotify(f, backoff.NewExponentialBackOff(), notify) - if err != nil { - errChan <- xerrors.Errorf("HTTP Error %w", err) - return - } - if count == retryMax { - errChan <- xerrors.New("Retry count exceeded") - return - } - - resChan <- response{ - request: req, - json: body, - } -} - -func major(osVer string) (majorVersion string) { - return strings.Split(osVer, ".")[0] -} - -func unique[T comparable](s []T) []T { - m := map[T]struct{}{} - for _, v := range s { - m[v] = struct{}{} - } - return slices.Collect(maps.Keys(m)) -} diff --git a/reporter/util.go b/reporter/util.go index 281275ce33..f1a80c0219 100644 --- a/reporter/util.go +++ b/reporter/util.go @@ -791,7 +791,6 @@ func getPlusDiffCves(previous, current models.ScanResult) models.VulnInfos { // TODO commented out because a bug of diff logic when multiple oval defs found for a certain CVE-ID and same updated_at // if these OVAL defs have different affected packages, this logic detects as updated. - // This logic will be uncommented after integration with gost https://github.com/vulsio/gost // } else if isCveFixed(v, previous) { // updated[v.CveID] = v // logging.Log.Debugf("fixed: %s", v.CveID) diff --git a/server/server.go b/server/server.go index ebb5372a40..861791f58f 100644 --- a/server/server.go +++ b/server/server.go @@ -62,7 +62,7 @@ func (h VulsHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } - if err := detector.DetectPkgCves(&r, config.Conf.Gost, config.Conf.Vuls2, config.Conf.LogOpts, config.Conf.NoProgress); err != nil { + if err := detector.DetectPkgCves(&r, config.Conf.Vuls2, config.Conf.LogOpts, config.Conf.NoProgress); err != nil { logging.Log.Errorf("Failed to detect Pkg CVE: %+v", err) http.Error(w, err.Error(), http.StatusServiceUnavailable) return diff --git a/subcmds/discover.go b/subcmds/discover.go index b2409d43e3..6f729adaea 100644 --- a/subcmds/discover.go +++ b/subcmds/discover.go @@ -92,14 +92,6 @@ func printConfigToml(ips []string) (err error) { #timeoutSec = 0 #timeoutSecPerRequest = 0 -[gost] -#type = ["sqlite3", "mysql", "postgres", "redis", "http" ] -#sqlite3Path = "/path/to/gost.sqlite3" -#url = "" -#debugSQL = false -#timeoutSec = 0 -#timeoutSecPerRequest = 0 - [cti] #type = ["sqlite3", "mysql", "postgres", "redis", "http" ] #sqlite3Path = "/path/to/go-cti.sqlite3" diff --git a/subcmds/report.go b/subcmds/report.go index 9432a6f893..e3fc56dd83 100644 --- a/subcmds/report.go +++ b/subcmds/report.go @@ -209,7 +209,6 @@ func (p *ReportCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...any) subcom if p.configPath == "" { for _, cnf := range []config.VulnDictInterface{ &config.Conf.CveDict, - &config.Conf.Gost, &config.Conf.Cti, } { cnf.Init() diff --git a/subcmds/report_windows.go b/subcmds/report_windows.go index cfaf8ffc06..98483b9df7 100644 --- a/subcmds/report_windows.go +++ b/subcmds/report_windows.go @@ -206,7 +206,6 @@ func (p *ReportCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...any) subcom if p.configPath == "" { for _, cnf := range []config.VulnDictInterface{ &config.Conf.CveDict, - &config.Conf.Gost, &config.Conf.Cti, } { cnf.Init() diff --git a/subcmds/server.go b/subcmds/server.go index e20f3ef213..1f5b4a0166 100644 --- a/subcmds/server.go +++ b/subcmds/server.go @@ -98,7 +98,6 @@ func (p *ServerCmd) Execute(_ context.Context, _ *flag.FlagSet, _ ...any) subcom if p.configPath == "" { for _, cnf := range []config.VulnDictInterface{ &config.Conf.CveDict, - &config.Conf.Gost, &config.Conf.Cti, } { cnf.Init() @@ -116,7 +115,7 @@ func (p *ServerCmd) Execute(_ context.Context, _ *flag.FlagSet, _ ...any) subcom } logging.Log.Info("Validating DBs...") - if err := detector.ValidateDBs(config.Conf.CveDict, config.Conf.Gost, config.Conf.Cti, config.Conf.LogOpts); err != nil { + if err := detector.ValidateDBs(config.Conf.CveDict, config.Conf.Cti, config.Conf.LogOpts); err != nil { logging.Log.Errorf("Failed to validate DBs. err: %+v", err) return subcommands.ExitFailure } From 8257d23af8810f731a45dc99b8d8846673713ee0 Mon Sep 17 00:00:00 2001 From: MaineK00n Date: Tue, 14 Apr 2026 21:41:35 +0900 Subject: [PATCH 2/2] fix(microsoft): align vuls2 KB detection output with gost behavior - Do not populate AffectedPackages for KB-only detections (keep nil) - WindowsKBFixedIns now includes KB prefix (e.g. "KB5075899") - KB IDs flow through separate kbIDs path instead of packStatuses - Update walkCriteria to return kbIDs separately from pack statuses - Fix build error from AcceptQueriesKB -> KB type rename Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- detector/detector.go | 1 - detector/vuls2/vuls2.go | 68 ++++++++++++--------- detector/vuls2/vuls2_test.go | 113 +++++++++++++++++++++++++++++++++++ 3 files changed, 152 insertions(+), 30 deletions(-) diff --git a/detector/detector.go b/detector/detector.go index de83fe995b..53e1cd0657 100644 --- a/detector/detector.go +++ b/detector/detector.go @@ -17,7 +17,6 @@ import ( "github.com/future-architect/vuls/contrib/owasp-dependency-check/parser" "github.com/future-architect/vuls/cwe" "github.com/future-architect/vuls/detector/vuls2" - "github.com/future-architect/vuls/gost" "github.com/future-architect/vuls/logging" "github.com/future-architect/vuls/models" "github.com/future-architect/vuls/reporter" diff --git a/detector/vuls2/vuls2.go b/detector/vuls2/vuls2.go index 393ba3f800..eb21b21912 100644 --- a/detector/vuls2/vuls2.go +++ b/detector/vuls2/vuls2.go @@ -318,6 +318,7 @@ type sourceData struct { vulninfos models.VulnInfos packStatuses []packStatus cpes []string + kbIDs []string } type rootTag struct { @@ -352,6 +353,7 @@ func postConvert(scanned scanTypes.ScanResult, detected detectTypes.DetectResult type affected struct { packm map[string]pack cpes []string + kbIDs []string } am := make(map[string]affected) @@ -431,6 +433,17 @@ func postConvert(scanned scanTypes.ScanResult, detected detectTypes.DetectResult } } + if len(vd.kbIDs) > 0 { + for _, kbID := range vd.kbIDs { + if !slices.Contains(base.kbIDs, kbID) { + base.kbIDs = append(base.kbIDs, kbID) + } + } + if !slices.Contains(vd.detectableCveIDs, vi.CveID) { + vd.detectableCveIDs = append(vd.detectableCveIDs, vi.CveID) + } + } + am[vi.CveID] = base } @@ -481,19 +494,20 @@ func postConvert(scanned scanTypes.ScanResult, detected detectTypes.DetectResult for _, p := range am[vi.CveID].packm { ps = append(ps, p.packStatus.status) } - vi.AffectedPackages = ps + if len(ps) > 0 { + vi.AffectedPackages = ps + } vi.CpeURIs = am[vi.CveID].cpes // Populate WindowsKBFixedIns and KB-based DistroAdvisories for Microsoft detections if string(scanned.Family) == ecosystemTypes.EcosystemTypeMicrosoft { - for _, p := range ps { - if kbID, ok := strings.CutPrefix(p.FixedIn, "KB"); ok { - if !slices.Contains(vi.WindowsKBFixedIns, kbID) { - vi.WindowsKBFixedIns = append(vi.WindowsKBFixedIns, kbID) - } - da := models.DistroAdvisory{AdvisoryID: p.FixedIn, Description: "Microsoft Knowledge Base"} - vi.DistroAdvisories.AppendIfMissing(&da) + for _, kbID := range am[vi.CveID].kbIDs { + kbWithPrefix := fmt.Sprintf("KB%s", kbID) + if !slices.Contains(vi.WindowsKBFixedIns, kbWithPrefix) { + vi.WindowsKBFixedIns = append(vi.WindowsKBFixedIns, kbWithPrefix) } + da := models.DistroAdvisory{AdvisoryID: kbWithPrefix, Description: "Microsoft Knowledge Base"} + vi.DistroAdvisories.AppendIfMissing(&da) } } @@ -513,11 +527,11 @@ func walkVulnerabilityDetections(m map[source]sourceData, scanned scanTypes.Scan return xerrors.Errorf("Failed to prune criteria. err: %w", err) } - statuses, cpes, _, err := walkCriteria(d.Ecosystem, sourceID, ca, fcond.Tag, scanned) + statuses, cpes, kbIDs, _, err := walkCriteria(d.Ecosystem, sourceID, ca, fcond.Tag, scanned) if err != nil { return xerrors.Errorf("Failed to walk criteria. err: %w", err) } - if len(statuses) == 0 && len(cpes) == 0 { + if len(statuses) == 0 && len(cpes) == 0 && len(kbIDs) == 0 { continue } @@ -532,6 +546,7 @@ func walkVulnerabilityDetections(m map[source]sourceData, scanned scanTypes.Scan base := m[src] base.packStatuses = append(base.packStatuses, statuses...) base.cpes = append(base.cpes, cpes...) + base.kbIDs = append(base.kbIDs, kbIDs...) m[src] = base } } @@ -600,33 +615,35 @@ func pruneCriteria(c criteriaTypes.FilteredCriteria) (criteriaTypes.FilteredCrit return pruned, nil } -func walkCriteria(e ecosystemTypes.Ecosystem, sourceID sourceTypes.SourceID, ca criteriaTypes.FilteredCriteria, tag segmentTypes.DetectionTag, scanned scanTypes.ScanResult) ([]packStatus, []string, bool, error) { +func walkCriteria(e ecosystemTypes.Ecosystem, sourceID sourceTypes.SourceID, ca criteriaTypes.FilteredCriteria, tag segmentTypes.DetectionTag, scanned scanTypes.ScanResult) ([]packStatus, []string, []string, bool, error) { var ( statuses []packStatus cpes []string + kbIDs []string ) for _, child := range ca.Criterias { - ss, cs, ignore, err := walkCriteria(e, sourceID, child, tag, scanned) + ss, cs, ks, ignore, err := walkCriteria(e, sourceID, child, tag, scanned) if err != nil { - return nil, nil, false, xerrors.Errorf("Failed to walk criteria. err: %w", err) + return nil, nil, nil, false, xerrors.Errorf("Failed to walk criteria. err: %w", err) } if ignore { switch ca.Operator { case criteriaTypes.CriteriaOperatorTypeAND: - return nil, nil, ignore, nil + return nil, nil, nil, ignore, nil case criteriaTypes.CriteriaOperatorTypeOR: continue default: - return nil, nil, false, xerrors.Errorf("unexpected operator: %s", ca.Operator) + return nil, nil, nil, false, xerrors.Errorf("unexpected operator: %s", ca.Operator) } } statuses = append(statuses, ss...) cpes = append(cpes, cs...) + kbIDs = append(kbIDs, ks...) } for _, cn := range ca.Criterions { if ignoreCriteria(e, sourceID, cn) { - return nil, nil, true, nil + return nil, nil, nil, true, nil } switch cn.Criterion.Type { @@ -641,7 +658,7 @@ func walkCriteria(e ecosystemTypes.Ecosystem, sourceID sourceTypes.SourceID, ca fcn, err := filterCriterion(e, scanned, cn) if err != nil { - return nil, nil, false, xerrors.Errorf("Failed to filter criterion. err: %w", err) + return nil, nil, nil, false, xerrors.Errorf("Failed to filter criterion. err: %w", err) } switch fcn.Criterion.Version.Package.Type { @@ -659,7 +676,7 @@ func walkCriteria(e ecosystemTypes.Ecosystem, sourceID sourceTypes.SourceID, ca for _, index := range fcn.Accepts.Version { if len(scanned.OSPackages) <= index { - return nil, nil, false, xerrors.Errorf("Too large OSPackage index. len(OSPackage): %d, index: %d", len(scanned.OSPackages), index) + return nil, nil, nil, false, xerrors.Errorf("Too large OSPackage index. len(OSPackage): %d, index: %d", len(scanned.OSPackages), index) } statuses = append(statuses, packStatus{ rangeType: rangeType, @@ -679,29 +696,22 @@ func walkCriteria(e ecosystemTypes.Ecosystem, sourceID sourceTypes.SourceID, ca case vcPackageTypes.PackageTypeCPE: for _, index := range fcn.Accepts.Version { if len(scanned.CPE) <= index { - return nil, nil, false, xerrors.Errorf("Too large CPE index. len(CPE): %d, index: %d", len(scanned.CPE), index) + return nil, nil, nil, false, xerrors.Errorf("Too large CPE index. len(CPE): %d, index: %d", len(scanned.CPE), index) } } cpes = append(cpes, string(*fcn.Criterion.Version.Package.CPE)) default: } case criterionTypes.CriterionTypeKB: - if cn.Criterion.KB == nil || !cn.Accepts.KB { + if cn.Criterion.KB == nil || (!cn.Accepts.KB.Covered && !cn.Accepts.KB.Unapplied) { continue } - statuses = append(statuses, packStatus{ - rangeType: vcAffectedRangeTypes.RangeTypeUnknown, - status: models.PackageFixStatus{ - Name: cn.Criterion.KB.Product, - FixedIn: fmt.Sprintf("KB%s", cn.Criterion.KB.KBID), - NotFixedYet: true, - }, - }) + kbIDs = append(kbIDs, cn.Criterion.KB.KBID) default: continue } } - return statuses, cpes, false, nil + return statuses, cpes, kbIDs, false, nil } func walkVulnerabilityDatas(m map[source]sourceData, vds []detectTypes.VulnerabilityData) error { diff --git a/detector/vuls2/vuls2_test.go b/detector/vuls2/vuls2_test.go index e3c7174f09..4d72b35b95 100644 --- a/detector/vuls2/vuls2_test.go +++ b/detector/vuls2/vuls2_test.go @@ -17,6 +17,7 @@ import ( conditionTypes "github.com/MaineK00n/vuls-data-update/pkg/extract/types/data/detection/condition" criteriaTypes "github.com/MaineK00n/vuls-data-update/pkg/extract/types/data/detection/condition/criteria" criterionTypes "github.com/MaineK00n/vuls-data-update/pkg/extract/types/data/detection/condition/criteria/criterion" + kbcriterionTypes "github.com/MaineK00n/vuls-data-update/pkg/extract/types/data/detection/condition/criteria/criterion/kbcriterion" noneexistcriterionTypes "github.com/MaineK00n/vuls-data-update/pkg/extract/types/data/detection/condition/criteria/criterion/noneexistcriterion" necBinaryPackageTypes "github.com/MaineK00n/vuls-data-update/pkg/extract/types/data/detection/condition/criteria/criterion/noneexistcriterion/binary" versioncriterionTypes "github.com/MaineK00n/vuls-data-update/pkg/extract/types/data/detection/condition/criteria/criterion/versioncriterion" @@ -8772,6 +8773,118 @@ func Test_postConvert(t *testing.T) { }, }, }, + { + name: "microsoft kb detection", + args: args{ + scanned: scanTypes.ScanResult{ + Family: ecosystemTypes.EcosystemTypeMicrosoft, + Release: "Windows 10 Version 21H2 for x64-based Systems", + MicrosoftKB: scanTypes.MicrosoftKB{ + Applied: []string{"5025288"}, + Unapplied: []string{"5025221"}, + }, + }, + detected: detectTypes.DetectResult{ + Detected: []detectTypes.VulnerabilityData{ + { + ID: "CVE-2025-21234", + Vulnerabilities: []dbTypes.VulnerabilityDataVulnerability{ + { + ID: "CVE-2025-21234", + Contents: map[sourceTypes.SourceID]map[dataTypes.RootID][]vulnerabilityTypes.Vulnerability{ + sourceTypes.MicrosoftCVRF: { + dataTypes.RootID("CVE-2025-21234"): []vulnerabilityTypes.Vulnerability{ + { + Content: vulnerabilityContentTypes.Content{ + ID: "CVE-2025-21234", + Title: "Windows Win32k Elevation of Privilege Vulnerability", + Description: "A privilege escalation vulnerability.", + Severity: []severityTypes.Severity{ + { + Type: severityTypes.SeverityTypeCVSSv31, + CVSSv31: new(cvssV31Types.CVSSv31{ + Vector: "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", + BaseScore: 7.8, + BaseSeverity: "HIGH", + }), + }, + }, + Published: new(time.Date(2025, 5, 13, 0, 0, 0, 0, time.UTC)), + }, + Segments: []segmentTypes.Segment{ + { + Ecosystem: ecosystemTypes.EcosystemTypeMicrosoft, + }, + }, + }, + }, + }, + }, + }, + }, + Detections: []detectTypes.VulnerabilityDataDetection{ + { + Ecosystem: ecosystemTypes.EcosystemTypeMicrosoft, + Contents: map[sourceTypes.SourceID][]conditionTypes.FilteredCondition{ + sourceTypes.MicrosoftCVRF: { + { + Criteria: criteriaTypes.FilteredCriteria{ + Operator: criteriaTypes.CriteriaOperatorTypeOR, + Criterions: []criterionTypes.FilteredCriterion{ + { + Criterion: criterionTypes.Criterion{ + Type: criterionTypes.CriterionTypeKB, + KB: &kbcriterionTypes.Criterion{ + Product: "Windows 10 Version 21H2 for x64-based Systems", + KBID: "5025221", + }, + }, + Accepts: criterionTypes.AcceptQueries{ + KB: criterionTypes.KB{Unapplied: true}, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + want: models.VulnInfos{ + "CVE-2025-21234": { + CveID: "CVE-2025-21234", + Confidences: models.Confidences{models.WindowsUpdateSearch}, + DistroAdvisories: models.DistroAdvisories{ + { + AdvisoryID: "KB5025221", + Description: "Microsoft Knowledge Base", + }, + }, + WindowsKBFixedIns: []string{"KB5025221"}, + CveContents: models.CveContents{ + models.Microsoft: []models.CveContent{ + { + Type: models.Microsoft, + CveID: "CVE-2025-21234", + Title: "Windows Win32k Elevation of Privilege Vulnerability", + Summary: "A privilege escalation vulnerability.", + Cvss3Score: 7.8, + Cvss3Vector: "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", + Cvss3Severity: "HIGH", + SourceLink: "https://msrc.microsoft.com/update-guide/vulnerability/CVE-2025-21234", + Published: time.Date(2025, 5, 13, 0, 0, 0, 0, time.UTC), + LastModified: time.Date(1000, time.January, 1, 0, 0, 0, 0, time.UTC), + Optional: map[string]string{"vuls2-sources": `[{"root_id":"CVE-2025-21234","source_id":"microsoft-cvrf","segment":{"ecosystem":"microsoft"}}]`}, + }, + }, + }, + }, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) {