fleet/server/vulnerabilities/customcve/matching_rules.go
Ian Littman 89ca35c66b
Switch vulns cron false positive clear to clear vulns based on when the vulns run started, rather than based on periodicity (#31364)
Fixes #26404.

This means that for long vulns runs vulns will stick around longer, so
we don't wind up nuking vulns that were added earlier in the run, and in
cases where the vulns run takes less than 2h we'll see vulns clear
cleanly more quickly.

# Checklist for submitter

If some of the following don't apply, delete the relevant line.

- [x] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`.
See [Changes
files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/guides/committing-changes.md#changes-files)
for more information.

- [x] Input data is properly validated, `SELECT *` is avoided, SQL
injection is prevented (using placeholders for values in statements)

## Testing

- [x] Added/updated automated tests

- [ ] QA'd all new/changed functionality manually

---------

Co-authored-by: Jahziel Villasana-Espinoza <jahziel@fleetdm.com>
2025-07-29 10:14:14 -05:00

159 lines
4.7 KiB
Go

package customcve
import (
"context"
"errors"
"fmt"
"time"
"github.com/fleetdm/fleet/v4/server/fleet"
"github.com/fleetdm/fleet/v4/server/vulnerabilities/nvd/tools/cvefeed/nvd"
"github.com/go-kit/log"
"github.com/go-kit/log/level"
)
var (
MissingCVEsErr = errors.New("CVEs must be specified")
MissingNameLikeMatch = errors.New("NameLikeMatch must be specified")
MissingResolvedInVersionErr = errors.New("ResolvedInVersion must be specified")
)
// CVEMatchingRuleSpec contains custom matching rules for matching software
// with a list of CVEs. These rules address false negatives in the NVD data.
// Add an interface if you want to add more rule types.
type CVEMatchingRule struct {
NameLikeMatch string // Name of software to match (like match)
SourceMatch string // Source of software to match (exact match)
CVEs []string // List of CVEs to assign to software
ResolvedInVersion string // Version of software that resolves the CVEs
}
type CVEMatchingRules []CVEMatchingRule
// getCVEMatchingRules returns a list of custom rules for matching software with CVEs
// Currently only supporting CVEMatchingRules, but can be extended to support other types.
// Append new rules here.
func getCVEMatchingRules() CVEMatchingRules {
return []CVEMatchingRule{
// June 11 2024 Office 365 Vulnerabilities
// https://learn.microsoft.com/en-us/officeupdates/microsoft365-apps-security-updates
{
NameLikeMatch: "Microsoft 365",
SourceMatch: "programs",
CVEs: []string{"CVE-2024-30101", "CVE-2024-30102", "CVE-2024-30103", "CVE-2024-30104"},
ResolvedInVersion: "16.0.17628.20144",
},
// July 9 2024 Office 365 Vulnerabilities
// https://learn.microsoft.com/en-us/officeupdates/microsoft365-apps-security-updates
{
NameLikeMatch: "Microsoft 365",
SourceMatch: "programs",
CVEs: []string{"CVE-2023-38545", "CVE-2024-38020", "CVE-2024-38021"},
ResolvedInVersion: "16.0.17726.20160",
},
// August 13 2024 Office 365 Vulnerabilities
// https://learn.microsoft.com/en-us/officeupdates/microsoft365-apps-security-updates
{
NameLikeMatch: "Microsoft 365",
SourceMatch: "programs",
CVEs: []string{
"CVE-2024-38172",
"CVE-2024-38170",
"CVE-2024-38173",
"CVE-2024-38171",
"CVE-2024-38189",
"CVE-2024-38169",
"CVE-2024-38200",
},
ResolvedInVersion: "16.0.17830.20166",
},
}
}
func (r CVEMatchingRule) match(ctx context.Context, ds fleet.Datastore) ([]fleet.SoftwareVulnerability, error) {
var vulns []fleet.SoftwareVulnerability
filter := fleet.VulnSoftwareFilter{
Name: r.NameLikeMatch,
Source: r.SourceMatch,
}
software, err := ds.ListSoftwareForVulnDetection(ctx, filter)
if err != nil {
return nil, err
}
for _, s := range software {
if nvd.SmartVerCmp(s.Version, r.ResolvedInVersion) < 0 {
for _, cve := range r.CVEs {
vulns = append(vulns, fleet.SoftwareVulnerability{
SoftwareID: s.ID,
CVE: cve,
ResolvedInVersion: &r.ResolvedInVersion,
})
}
}
}
return vulns, nil
}
func (r CVEMatchingRule) validate() error {
if len(r.CVEs) == 0 {
return MissingCVEsErr
}
if r.NameLikeMatch == "" {
return MissingNameLikeMatch
}
if r.ResolvedInVersion == "" {
return MissingResolvedInVersionErr
}
return nil
}
// ValidateAll returns an error if any rule in the list fails to validate
func (r CVEMatchingRules) ValidateAll() error {
for i, rule := range r {
if err := rule.validate(); err != nil {
return fmt.Errorf("invalid rule %d: %v", i, err)
}
}
return nil
}
// CheckCustomVulnerabilities matches software against custom rules and inserts vulnerabilities
func CheckCustomVulnerabilities(ctx context.Context, ds fleet.Datastore, logger log.Logger, startTime time.Time) ([]fleet.SoftwareVulnerability, error) {
rules := getCVEMatchingRules()
if err := rules.ValidateAll(); err != nil {
return nil, fmt.Errorf("invalid rules: %w", err)
}
var vulns []fleet.SoftwareVulnerability
for i, rule := range rules {
v, err := rule.match(ctx, ds)
if err != nil {
level.Error(logger).Log("msg", "Error matching rule", "ruleIndex", i, "err", err)
continue
}
vulns = append(vulns, v...)
}
var newVulns []fleet.SoftwareVulnerability
for _, v := range vulns {
ok, err := ds.InsertSoftwareVulnerability(ctx, v, fleet.CustomSource)
if err != nil {
level.Error(logger).Log("msg", "Error inserting software vulnerability", "err", err)
continue
}
if ok {
newVulns = append(newVulns, v)
}
}
if err := ds.DeleteOutOfDateVulnerabilities(ctx, fleet.CustomSource, startTime); err != nil {
level.Error(logger).Log("msg", "Error deleting out of date vulnerabilities", "err", err)
}
return newVulns, nil
}