diff --git a/changes/148940-app-os-vuln-matching b/changes/148940-app-os-vuln-matching new file mode 100644 index 0000000000..4145a8655f --- /dev/null +++ b/changes/148940-app-os-vuln-matching @@ -0,0 +1 @@ +- Fleet now matches vulnerabilies for applications that include an OS scope [example](https://nvd.nist.gov/vuln/detail/CVE-2023-0400) \ No newline at end of file diff --git a/server/vulnerabilities/nvd/cve_test.go b/server/vulnerabilities/nvd/cve_test.go index 4b7f095277..05cdca110d 100644 --- a/server/vulnerabilities/nvd/cve_test.go +++ b/server/vulnerabilities/nvd/cve_test.go @@ -307,6 +307,12 @@ func TestTranslateCPEToCVE(t *testing.T) { }, continuesToUpdate: false, }, + "cpe:2.3:a:adobe:animate:*:*:*:*:*:macos:*:*": { + includedCVEs: []cve{ + {ID: "CVE-2023-44325"}, + }, + continuesToUpdate: true, + }, } cveOSTests := []struct { diff --git a/server/vulnerabilities/nvd/tools/cvefeed/matching_json_test.go b/server/vulnerabilities/nvd/tools/cvefeed/matching_json_test.go index baa73cad43..df6629b88b 100644 --- a/server/vulnerabilities/nvd/tools/cvefeed/matching_json_test.go +++ b/server/vulnerabilities/nvd/tools/cvefeed/matching_json_test.go @@ -94,6 +94,15 @@ func TestMatchJSON(t *testing.T) { {Part: "a", Vendor: "mozilla", Product: "firefox", Version: "64\\.0"}, }, }, + { + Rule: 3, + Inventory: []*wfn.Attributes{ + {Part: "o", Vendor: "apple", Product: "macos", Version: "14\\.1\\.2"}, + }, + Matches: []*wfn.Attributes{ + {Part: "o", Vendor: "apple", Product: "macos", Version: "14\\.1\\.2"}, + }, + }, } items, err := ParseJSON(bytes.NewBufferString(testJSONdict)) if err != nil { @@ -103,7 +112,7 @@ func TestMatchJSON(t *testing.T) { t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { mm := items[c.Rule].Match(c.Inventory, false) if len(mm) != len(c.Matches) { - t.Fatalf("expected %d matches, got %d matches", len(mm), len(c.Matches)) + t.Fatalf("expected %d matches, got %d matches", len(c.Matches), len(mm)) } if len(mm) > 0 && !matchesAll(mm, c.Matches) { t.Fatalf("wrong match: expected %v, got %v", c.Matches, mm) @@ -112,6 +121,45 @@ func TestMatchJSON(t *testing.T) { } } +func TestTargetSWMatching(t *testing.T) { + inventoryAcrobat := []*wfn.Attributes{ + {Part: "a", Vendor: "adobe", Product: "acrobat", Version: "20\\.001\\.3005", TargetSW: "macos"}, + } + + items, err := ParseJSON(bytes.NewBufferString(targetSWMatchingJSON)) + if err != nil { + t.Fatalf("failed to parse the dictionary: %v", err) + } + // matches OS on targetSW + if mm := items[0].Match(inventoryAcrobat, true); len(mm) == 0 { + t.Fatal("expected Match to match, it did not") + } + + // does not match OS on targetSW + if mm := items[1].Match(inventoryAcrobat, true); len(mm) != 0 { + t.Fatal("expected Match to not match, it did") + } + + // matches when OS is not present + if mm := items[2].Match(inventoryAcrobat, true); len(mm) == 0 { + t.Fatal("expected Match to match, it did not") + } + + // does not match OS on targetSW with multiple nodes + if mm := items[3].Match(inventoryAcrobat, true); len(mm) != 0 { + t.Fatal("expected Match to not match, it did") + } + + inventoryWrongOS := []*wfn.Attributes{ + {Part: "a", Vendor: "adobe", Product: "acrobat", Version: "20\\.001\\.3005", TargetSW: "linux"}, + } + + // does not match OS on targetSW + if mm := items[0].Match(inventoryWrongOS, true); len(mm) != 0 { + t.Fatal("expected Match to not match, it did") + } +} + func TestMatchJSONrequireVersion(t *testing.T) { inventory := []*wfn.Attributes{ {Part: "a", Vendor: "microsoft", Product: "ie", Version: "6\\.0"}, @@ -279,9 +327,265 @@ var testJSONdict = `{ } ] } - } + }, + { + "cve": { + "affects": null, + "CVE_data_meta": { + "ASSIGNER": "product-security@apple.com", + "ID": "CVE-2023-42919" + }, + "data_format": "MITRE", + "data_type": "CVE", + "data_version": "4.0" + }, + "configurations": { + "CVE_data_version": "4.0", + "nodes": [ + { + "cpe_match": [ + { + "cpe23Uri": "cpe:2.3:o:apple:ipados:*:*:*:*:*:*:*:*", + "versionEndExcluding": "16.7.3", + "vulnerable": true + }, + { + "cpe23Uri": "cpe:2.3:o:apple:ipados:*:*:*:*:*:*:*:*", + "versionEndExcluding": "17.2", + "versionStartIncluding": "17.0", + "vulnerable": true + }, + { + "cpe23Uri": "cpe:2.3:o:apple:iphone_os:*:*:*:*:*:*:*:*", + "versionEndExcluding": "16.7.3", + "vulnerable": true + }, + { + "cpe23Uri": "cpe:2.3:o:apple:iphone_os:*:*:*:*:*:*:*:*", + "versionEndExcluding": "17.2", + "versionStartIncluding": "17.0", + "vulnerable": true + }, + { + "cpe23Uri": "cpe:2.3:o:apple:macos:*:*:*:*:*:*:*:*", + "versionEndExcluding": "12.7.2", + "versionStartIncluding": "12.0.0", + "vulnerable": true + }, + { + "cpe23Uri": "cpe:2.3:o:apple:macos:*:*:*:*:*:*:*:*", + "versionEndExcluding": "13.6.3", + "versionStartIncluding": "13.0", + "vulnerable": true + }, + { + "cpe23Uri": "cpe:2.3:o:apple:macos:*:*:*:*:*:*:*:*", + "versionEndExcluding": "14.2", + "versionStartIncluding": "14.0", + "vulnerable": true + } + ], + "operator": "OR" + } + ] + } +} ] }` +var targetSWMatchingJSON = `{ + "CVE_data_type" : "CVE", + "CVE_data_format" : "MITRE", + "CVE_data_version" : "4.0", + "CVE_data_numberOfCVEs" : "7083", + "CVE_data_timestamp" : "2018-07-31T07:00Z", + "CVE_Items" : [ { + "cve" : { + "data_type" : "CVE", + "data_format" : "MITRE", + "data_version" : "4.0", + "CVE_data_meta" : { + "ID" : "CVE-2023-26369", + "ASSIGNER" : "psirt@adobe.com" + } + }, + "configurations" : { + "CVE_data_version" : "4.0", + "nodes" : [ { + "operator" : "AND", + "children" : [ { + "operator" : "OR", + "children" : [ ], + "cpe_match" : [ { + "vulnerable" : true, + "cpe23Uri" : "cpe:2.3:a:adobe:acrobat:*:*:*:*:classic:*:*:*", + "versionStartIncluding" : "20.001.3005", + "versionEndExcluding" : "20.005.30524", + "cpe_name" : [ ] + }, { + "vulnerable" : true, + "cpe23Uri" : "cpe:2.3:a:adobe:acrobat_dc:*:*:*:*:continuous:*:*:*", + "versionStartIncluding" : "15.007.20033", + "versionEndExcluding" : "23.006.20320", + "cpe_name" : [ ] + }, { + "vulnerable" : true, + "cpe23Uri" : "cpe:2.3:a:adobe:acrobat_reader:*:*:*:*:classic:*:*:*", + "versionStartIncluding" : "20.001.3005", + "versionEndExcluding" : "20.005.30524", + "cpe_name" : [ ] + }, { + "vulnerable" : true, + "cpe23Uri" : "cpe:2.3:a:adobe:acrobat_reader_dc:*:*:*:*:continuous:*:*:*", + "versionStartIncluding" : "15.007.20033", + "versionEndExcluding" : "23.006.20320", + "cpe_name" : [ ] + } ] + }, { + "operator" : "OR", + "children" : [ ], + "cpe_match" : [ { + "vulnerable" : false, + "cpe23Uri" : "cpe:2.3:o:apple:macos:-:*:*:*:*:*:*:*", + "cpe_name" : [ ] + }, { + "vulnerable" : false, + "cpe23Uri" : "cpe:2.3:o:microsoft:windows:-:*:*:*:*:*:*:*", + "cpe_name" : [ ] + } ] + } ], + "cpe_match" : [ ] + } ] + } + }, { + "cve" : { + "data_type" : "CVE", + "data_format" : "MITRE", + "data_version" : "4.0", + "CVE_data_meta" : { + "ID" : "CVE-2023-27928", + "ASSIGNER" : "product-security@apple.com" + } + }, + "configurations" : { + "CVE_data_version" : "4.0", + "nodes" : [ { + "operator" : "OR", + "children" : [ ], + "cpe_match" : [ { + "vulnerable" : true, + "cpe23Uri" : "cpe:2.3:o:apple:macos:*:*:*:*:*:*:*:*", + "versionStartIncluding" : "13.0", + "versionEndExcluding" : "13.3", + "cpe_name" : [ ] + }, { + "vulnerable" : true, + "cpe23Uri" : "cpe:2.3:o:apple:tvos:*:*:*:*:*:*:*:*", + "versionEndExcluding" : "16.4", + "cpe_name" : [ ] + }, { + "vulnerable" : true, + "cpe23Uri" : "cpe:2.3:o:apple:watchos:*:*:*:*:*:*:*:*", + "versionEndExcluding" : "9.4", + "cpe_name" : [ ] + }, { + "vulnerable" : true, + "cpe23Uri" : "cpe:2.3:o:apple:macos:*:*:*:*:*:*:*:*", + "versionEndExcluding" : "11.7.5", + "cpe_name" : [ ] + } ] + } ] + } + }, { + "cve" : { + "data_type" : "CVE", + "data_format" : "MITRE", + "data_version" : "4.0", + "CVE_data_meta" : { + "ID" : "CVE-2023-27928", + "ASSIGNER" : "product-security@apple.com" + } + }, + "configurations" : { + "CVE_data_version" : "4.0", + "nodes" : [ { + "operator" : "OR", + "children" : [ ], + "cpe_match" : [ { + "vulnerable" : true, + "cpe23Uri" : "cpe:2.3:a:adobe:acrobat:*:*:*:*:classic:*:*:*", + "versionStartIncluding" : "20.001.3005", + "versionEndExcluding" : "20.005.30524", + "cpe_name" : [ ] + }, { + "vulnerable" : true, + "cpe23Uri" : "cpe:2.3:a:adobe:acrobat_dc:*:*:*:*:continuous:*:*:*", + "versionStartIncluding" : "15.007.20033", + "versionEndExcluding" : "23.006.20320", + "cpe_name" : [ ] + }, { + "vulnerable" : true, + "cpe23Uri" : "cpe:2.3:a:adobe:acrobat_reader:*:*:*:*:classic:*:*:*", + "versionStartIncluding" : "20.001.3005", + "versionEndExcluding" : "20.005.30524", + "cpe_name" : [ ] + }, { + "vulnerable" : true, + "cpe23Uri" : "cpe:2.3:a:adobe:acrobat_reader_dc:*:*:*:*:continuous:*:*:*", + "versionStartIncluding" : "15.007.20033", + "versionEndExcluding" : "23.006.20320", + "cpe_name" : [ ] + } ] + } ] + } + }, { + "cve" : { + "data_type" : "CVE", + "data_format" : "MITRE", + "data_version" : "4.0", + "CVE_data_meta" : { + "ID" : "CVE-2023-28321", + "ASSIGNER" : "support@hackerone.com" + } + }, + "configurations" : { + "CVE_data_version" : "4.0", + "nodes" : [ { + "operator" : "OR", + "children" : [ ], + "cpe_match" : [ { + "vulnerable" : true, + "cpe23Uri" : "cpe:2.3:a:haxx:curl:*:*:*:*:*:*:*:*", + "versionEndExcluding" : "8.1.0", + "cpe_name" : [ ] + } ] + }, { + "operator" : "OR", + "children" : [ ], + "cpe_match" : [ { + "vulnerable" : true, + "cpe23Uri" : "cpe:2.3:o:apple:macos:*:*:*:*:*:*:*:*", + "versionStartIncluding" : "13.0", + "versionEndExcluding" : "13.5", + "cpe_name" : [ ] + }, { + "vulnerable" : true, + "cpe23Uri" : "cpe:2.3:o:apple:macos:*:*:*:*:*:*:*:*", + "versionStartIncluding" : "12.0", + "versionEndExcluding" : "12.6.8", + "cpe_name" : [ ] + }, { + "vulnerable" : true, + "cpe23Uri" : "cpe:2.3:o:apple:macos:*:*:*:*:*:*:*:*", + "versionStartIncluding" : "11.0", + "versionEndExcluding" : "11.7.9", + "cpe_name" : [ ] + } ] + } ] + } + } +] } +` + func matchesAll(src, tgt []*wfn.Attributes) bool { if len(src) != len(tgt) { return false diff --git a/server/vulnerabilities/nvd/tools/cvefeed/nvd/match_cpe.go b/server/vulnerabilities/nvd/tools/cvefeed/nvd/match_cpe.go index 636a78a93a..f82866a3dc 100644 --- a/server/vulnerabilities/nvd/tools/cvefeed/nvd/match_cpe.go +++ b/server/vulnerabilities/nvd/tools/cvefeed/nvd/match_cpe.go @@ -69,6 +69,9 @@ func (cm *cpeMatch) Match(attrs []*wfn.Attributes, requireVersion bool) (matches if cm.match(attr, requireVersion) { matches = append(matches, attr) } + if osMatch := cm.MatchTargetSW(attr); osMatch != nil { + matches = append(matches, osMatch) + } } return matches } diff --git a/server/vulnerabilities/nvd/tools/wfn/matcher.go b/server/vulnerabilities/nvd/tools/wfn/matcher.go index fca69e6e47..edb0043b4c 100644 --- a/server/vulnerabilities/nvd/tools/wfn/matcher.go +++ b/server/vulnerabilities/nvd/tools/wfn/matcher.go @@ -49,14 +49,37 @@ func (a *Attributes) MatchWithoutVersion(attr *Attributes) bool { matchAttr(a.Other, attr.Other) } +func (a *Attributes) MatchTargetSW(attr *Attributes) *Attributes { + if a == nil || attr == nil { + return nil + } + + var osMatch bool + var osAttr *Attributes + if attr.Part == "a" && attr.TargetSW != "" { + osAttr = &Attributes{ + Part: "o", + Product: attr.TargetSW, + } + + osMatch = matchAttr(a.Part, osAttr.Part) && matchAttr(a.Product, osAttr.Product) + } + + if !osMatch { + return nil + } + + return osAttr +} + // MatchAll returns a Matcher which matches only if all matchers match func MatchAll(ms ...Matcher) Matcher { - return &multiMatcher{ms, true} + return &multiMatcher{matchers: ms, allMatch: true} } // MatchAll returns a Matcher which matches if any of the matchers match func MatchAny(ms ...Matcher) Matcher { - return &multiMatcher{ms, false} + return &multiMatcher{matchers: ms, allMatch: false} } // DontMatch returns a Matcher which matches if the given matchers doesn't @@ -68,12 +91,23 @@ type multiMatcher struct { matchers []Matcher // if true, match will only return something if all matchers matched at least something allMatch bool + depth int } // Match is part of the Matcher interface func (mm *multiMatcher) Match(attrs []*Attributes, requireVersion bool) []*Attributes { + defer func() { + if mm.depth > 0 { + mm.depth-- + } + }() + matched := make(map[*Attributes]bool) for _, matcher := range mm.matchers { + // type check matcher against multiMatcher + if _, ok := matcher.(*multiMatcher); !ok { + mm.depth++ + } matches := matcher.Match(attrs, requireVersion) if mm.allMatch && len(matches) == 0 { // all matchers need to match at least one attr @@ -88,6 +122,11 @@ func (mm *multiMatcher) Match(attrs []*Attributes, requireVersion bool) []*Attri for m := range matched { matches = append(matches, m) } + + if mm.depth == 0 && len(matches) > 1 && !attributesIncludeApp(matches) { + return nil + } + return matches } @@ -118,3 +157,12 @@ func (nm notMatcher) Match(attrs []*Attributes, requireVersion bool) (matches [] } return matches } + +func attributesIncludeApp(attrs []*Attributes) bool { + for _, a := range attrs { + if a.Part == "a" { + return true + } + } + return false +}