fleet/server/vulnerabilities/customcve/matching_rules.go
Konstantin Sykulev d1876a3c70
Fixed false positive for msrc companion apps (#38824)
**Related issue:** Resolves #35281

- [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.

## Testing

- [x] Added/updated automated tests
- [x] QA'd all new/changed functionality manually

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Bug Fixes**
* Fixed false positives when detecting security vulnerabilities in
Microsoft 365 companion apps by improving targeting accuracy.

<sub>✏️ Tip: You can customize this high-level summary in your review
settings.</sub>

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-01-28 13:02:31 -06:00

182 lines
5.6 KiB
Go

package customcve
import (
"context"
"errors"
"fmt"
"strings"
"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)
ExcludeIfNameContains string // Exclude software if name contains this pattern (case-insensitive, in-memory filter)
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",
ExcludeIfNameContains: "companion",
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",
ExcludeIfNameContains: "companion",
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",
ExcludeIfNameContains: "companion",
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",
},
// Gitk and Git GUI CVEs for Homebrew git-gui package
// These CVEs affect gitk/git-gui which is git-gui on Homebrew not git
{
NameLikeMatch: "git-gui",
SourceMatch: "homebrew_packages",
CVEs: []string{"CVE-2025-27613", "CVE-2025-27614", "CVE-2025-46835"},
ResolvedInVersion: "2.50.1",
},
}
}
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
}
var excludePattern string
if r.ExcludeIfNameContains != "" {
excludePattern = strings.ToLower(r.ExcludeIfNameContains)
}
for _, s := range software {
// Skip software that matches the exclusion pattern
if excludePattern != "" && strings.Contains(strings.ToLower(s.Name), excludePattern) {
continue
}
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
}