Add visual studio extensions to software inventory (#17501)

#17003

- [X] Changes file added for user-visible changes in `changes/` or
`orbit/changes/`.
See [Changes
files](https://fleetdm.com/docs/contributing/committing-changes#changes-files)
for more information.
- [X] Added support on fleet's osquery simulator `cmd/osquery-perf` for
new osquery data ingestion features.
- [x] Added/updated tests
- [X] Manual QA for all new/changed functionality
This commit is contained in:
Lucas Manuel Rodriguez
2024-03-14 16:33:12 -03:00
committed by GitHub
parent 5028722506
commit cf64d85deb
16 changed files with 1292 additions and 149 deletions
+1 -3
View File
@@ -10,7 +10,7 @@ To test these changes locally, you can:
2. host this file on a local web server
```bash
go run ./tools/file-server 8082 ./server/vulnerabilities/nvd
go run ./tools/file-server/main.go 8082 ./server/vulnerabilities/nvd/
```
3. (re)launch your local fleet server with one of the following
@@ -20,7 +20,6 @@ To test these changes locally, you can:
vulnerabilities:
cpe_translations_url: "http://localhost:8082/cpe_translations.json"
```
Environment method
```bash
@@ -28,7 +27,6 @@ To test these changes locally, you can:
```
4. trigger a vulnerabilities scan
```bash
fleetctl trigger --name vulnerabilities
```
@@ -41,6 +41,8 @@ type CPEMatchingRule struct {
CVEs map[string]struct{}
// IgnoreAll will cause all CPEs to not match hence ignoring a CVE.
IgnoreAll bool
// IgnoreIf is a function that can determine if a CPE matching rule should be ignored or not.
IgnoreIf func(cpeMeta *wfn.Attributes) bool
}
// CPEMatches returns true if the provided CPE matches the rule.
@@ -53,6 +55,10 @@ func (rule CPEMatchingRule) CPEMatches(cpeMeta *wfn.Attributes) bool {
return false
}
if rule.IgnoreIf != nil && rule.IgnoreIf(cpeMeta) {
return false
}
ver, err := semver.NewVersion(wfn.StripSlashes(cpeMeta.Version))
if err != nil {
return false
@@ -2,6 +2,8 @@ package nvd
import (
"fmt"
"github.com/facebookincubator/nvdtools/wfn"
)
type CPEMatchingRules []CPEMatchingRule
@@ -181,6 +183,20 @@ func GetKnownNVDBugRules() (CPEMatchingRules, error) {
},
},
},
// These vulnerabilities in the MongoDB client incorrectly match
// the VS Code extension.
CPEMatchingRule{
CVEs: map[string]struct{}{
"CVE-2012-6619": {},
"CVE-2013-1892": {},
"CVE-2013-2132": {},
"CVE-2015-1609": {},
"CVE-2016-6494": {},
},
IgnoreIf: func(cpeMeta *wfn.Attributes) bool {
return cpeMeta.TargetSW == "visual_studio_code"
},
},
}
for i, rule := range rules {
+251 -10
View File
@@ -466,8 +466,6 @@ func TestLegacyCPEDB(t *testing.T) {
}
func TestCPEFromSoftwareIntegration(t *testing.T) {
nettest.Run(t)
testCases := []struct {
software fleet.Software
cpe string
@@ -1334,6 +1332,245 @@ func TestCPEFromSoftwareIntegration(t *testing.T) {
},
cpe: "cpe:2.3:a:jetbrains:pycharm:2022.1:*:*:*:*:macos:*:*",
},
{
software: fleet.Software{
Name: "eamodio.gitlens",
Source: "vscode_extensions",
Version: "14.9.0",
Vendor: "GitKraken",
},
cpe: "cpe:2.3:a:gitkraken:gitlens:14.9.0:*:*:*:*:visual_studio_code:*:*",
},
{
software: fleet.Software{
Name: "ms-python.python",
Source: "vscode_extensions",
Version: "2024.2.1",
Vendor: "Microsoft",
},
cpe: "cpe:2.3:a:microsoft:python_extension:2024.2.1:*:*:*:*:visual_studio_code:*:*",
},
{
software: fleet.Software{
Name: "ms-toolsai.jupyter",
Source: "vscode_extensions",
Version: "2024.2.0",
Vendor: "Microsoft",
},
cpe: "cpe:2.3:a:microsoft:jupyter:2024.2.0:*:*:*:*:visual_studio_code:*:*",
},
{
software: fleet.Software{
Name: "ms-vsliveshare.vsliveshare",
Source: "vscode_extensions",
Version: "1.0.5918",
Vendor: "Microsoft",
},
cpe: "cpe:2.3:a:microsoft:visual_studio_live_share:1.0.5918:*:*:*:*:visual_studio_code:*:*",
},
{
software: fleet.Software{
Name: "dbaeumer.vscode-eslint",
Source: "vscode_extensions",
Version: "2.4.4",
Vendor: "Microsoft",
},
cpe: "cpe:2.3:a:microsoft:visual_studio_code_eslint_extension:2.4.4:*:*:*:*:visual_studio_code:*:*",
},
{
software: fleet.Software{
Name: "vscjava.vscode-maven",
Source: "vscode_extensions",
Version: "0.44.0",
Vendor: "Microsoft",
},
cpe: "cpe:2.3:a:microsoft:vscode-maven:0.44.0:*:*:*:*:visual_studio_code:*:*",
},
{
software: fleet.Software{
Name: "ms-vscode.powershell",
Source: "vscode_extensions",
Version: "2024.0.0",
Vendor: "Microsoft",
},
cpe: "cpe:2.3:a:microsoft:powershell_extension:2024.0.0:*:*:*:*:visual_studio_code:*:*",
},
{
software: fleet.Software{
Name: "ms-vscode-remote.vscode-remote-extensionpack",
Source: "vscode_extensions",
Version: "0.25.0",
Vendor: "Microsoft",
},
cpe: "cpe:2.3:a:microsoft:remote_development:0.25.0:*:*:*:*:visual_studio_code:*:*",
},
{
software: fleet.Software{
Name: "vknabel.vscode-swiftlint",
Source: "vscode_extensions",
Version: "1.8.3",
Vendor: "vknabel",
},
cpe: "cpe:2.3:a:swiftlint_project:swiftlint:1.8.3:*:*:*:*:visual_studio_code:*:*",
},
{
software: fleet.Software{
Name: "vknabel.vscode-swiftformat",
Source: "vscode_extensions",
Version: "1.6.7",
Vendor: "vknabel",
},
cpe: "cpe:2.3:a:swiftformat_project:swiftformat:1.6.7:*:*:*:*:visual_studio_code:*:*",
},
{
software: fleet.Software{
Name: "jbenden.c-cpp-flylint",
Source: "vscode_extensions",
Version: "1.14.0",
Vendor: "Joseph Benden",
},
cpe: `cpe:2.3:a:c\/c\+\+_advanced_lint_project:c\/c\+\+_advanced_lint:1.14.0:*:*:*:*:visual_studio_code:*:*`,
},
{
software: fleet.Software{
Name: "stripe.vscode-stripe",
Source: "vscode_extensions",
Version: "2.0.14",
Vendor: "Stripe",
},
cpe: `cpe:2.3:a:stripe:stripe:2.0.14:*:*:*:*:visual_studio_code:*:*`,
},
{
software: fleet.Software{
Name: "vscodevim.vim",
Source: "vscode_extensions",
Version: "1.27.2",
Vendor: "vscodevim",
},
cpe: `cpe:2.3:a:vim_project:vim:1.27.2:*:*:*:*:visual_studio_code:*:*`,
},
{
software: fleet.Software{
Name: "svelte.svelte-vscode",
Source: "vscode_extensions",
Version: "108.3.1",
Vendor: "Svelte",
},
cpe: `cpe:2.3:a:svelte:svelte:108.3.1:*:*:*:*:visual_studio_code:*:*`,
},
{
software: fleet.Software{
Name: "lextudio.restructuredtext",
Source: "vscode_extensions",
Version: "189.3.0",
Vendor: "LeXtudio Inc.",
},
cpe: `cpe:2.3:a:lextudio:restructuredtext:189.3.0:*:*:*:*:visual_studio_code:*:*`,
},
{
software: fleet.Software{
Name: "ms-vscode-remote.remote-containers",
Source: "vscode_extensions",
Version: "0.348.0",
Vendor: "Microsoft",
},
cpe: `cpe:2.3:a:microsoft:remote:0.348.0:*:*:*:*:visual_studio_code:*:*`,
},
{
software: fleet.Software{
Name: "ms-kubernetes-tools.vscode-kubernetes-tools",
Source: "vscode_extensions",
Version: "0.348.0",
Vendor: "Microsoft",
},
cpe: `cpe:2.3:a:microsoft:kubernetes_tools:0.348.0:*:*:*:*:visual_studio_code:*:*`,
},
{
software: fleet.Software{
Name: "ms-dotnettools.vscode-dotnet-sdk",
Source: "vscode_extensions",
Version: "0.8.0",
Vendor: "Microsoft",
},
cpe: `cpe:2.3:a:microsoft:.net_education_bundle_sdk_install_tool:0.8.0:*:*:*:*:visual_studio_code:*:*`,
},
{
software: fleet.Software{
Name: "ms-dotnettools.vscode-dotnet-runtime",
Source: "vscode_extensions",
Version: "2.0.2",
Vendor: "Microsoft",
},
cpe: `cpe:2.3:a:microsoft:.net_install_tool_for_extension_authors:2.0.2:*:*:*:*:visual_studio_code:*:*`,
},
{
software: fleet.Software{
Name: "ms-vscode-remote.remote-wsl",
Source: "vscode_extensions",
Version: "0.86.0",
Vendor: "Microsoft",
},
cpe: `cpe:2.3:a:microsoft:windows_subsystem_for_linux:0.86.0:*:*:*:*:visual_studio_code:*:*`,
},
{
software: fleet.Software{
Name: "mongodb.mongodb-vscode",
Source: "vscode_extensions",
Version: "1.5.0",
Vendor: "MongoDB",
},
cpe: `cpe:2.3:a:mongodb:mongodb:1.5.0:*:*:*:*:visual_studio_code:*:*`,
},
{
software: fleet.Software{
Name: "oracle.mysql-shell-for-vs-code",
Source: "vscode_extensions",
Version: "1.14.2",
Vendor: "MongoDB",
},
cpe: `cpe:2.3:a:oracle:mysql_shell:1.14.2:*:*:*:*:visual_studio_code:*:*`,
},
{
software: fleet.Software{
Name: "snyk-security.snyk-vulnerability-scanner",
Source: "vscode_extensions",
Version: "2.3.6",
Vendor: "Snyk",
},
cpe: `cpe:2.3:a:snyk:snyk_security:2.3.6:*:*:*:*:visual_studio_code:*:*`,
},
{
software: fleet.Software{
Name: "sourcegraph.cody-ai",
Source: "vscode_extensions",
Version: "1.8.0",
Vendor: "Sourcegraph",
},
cpe: `cpe:2.3:a:sourcegraph:cody:1.8.0:*:*:*:*:visual_studio_code:*:*`,
},
// There are vulnerabilities for `cpe:2.3:a:redhat:vscode-xml:` in
// NVD's database but there's no entry for `cpe:2.3:a:redhat:vscode-xml:0.26.1`
// in NVD's CPE database.
/*
{
software: fleet.Software{
Name: "redhat.vscode-xml",
Source: "vscode_extensions",
Version: "0.26.1",
Vendor: "Red Hat",
},
cpe: `cpe:2.3:a:redhat:vscode-xml:0.26.1:*:*:*:*:visual_studio_code:*:*`,
},
*/
{
software: fleet.Software{
Name: "github.vscode-pull-request-github",
Source: "vscode_extensions",
Version: "0.82.0",
Vendor: "GitHub",
},
cpe: `cpe:2.3:a:github:pull_requests_and_issues:0.82.0:*:*:*:*:visual_studio_code:*:*`,
},
{
software: fleet.Software{
Name: "Google Chrome Helper.app",
@@ -1369,18 +1606,22 @@ func TestCPEFromSoftwareIntegration(t *testing.T) {
},
}
tempDir := t.TempDir()
// NVD_TEST_CPEDB_PATH can be used to speed up development (sync cpe.sqlite only once).
dbPath := os.Getenv("NVD_TEST_CPEDB_PATH")
if dbPath == "" {
nettest.Run(t)
tempDir := t.TempDir()
err := DownloadCPEDBFromGithub(tempDir, "")
require.NoError(t, err)
dbPath = filepath.Join(tempDir, "cpe.sqlite")
} else {
require.FileExists(t, dbPath)
t.Logf("Using %s as database file", dbPath)
}
err := DownloadCPEDBFromGithub(tempDir, "")
require.NoError(t, err)
dbPath := filepath.Join(tempDir, "cpe.sqlite")
db, err := sqliteDB(dbPath)
require.NoError(t, err)
err = DownloadCPETranslationsFromGithub(tempDir, "")
require.NoError(t, err)
cpeTranslationsPath := filepath.Join(".", cpeTranslationsFilename)
cpeTranslations, err := loadCPETranslations(cpeTranslationsPath)
require.NoError(t, err)
@@ -140,6 +140,216 @@
"vendor": ["jetbrains"]
}
},
{
"software": {
"name": ["ms-python.python"],
"source": ["vscode_extensions"]
},
"filter": {
"product": ["python_extension"],
"vendor": ["microsoft"]
}
},
{
"software": {
"name": ["ms-vsliveshare.vsliveshare"],
"source": ["vscode_extensions"]
},
"filter": {
"product": ["visual_studio_live_share"],
"vendor": ["microsoft"]
}
},
{
"software": {
"name": ["dbaeumer.vscode-eslint"],
"source": ["vscode_extensions"]
},
"filter": {
"product": ["visual_studio_code_eslint_extension"],
"vendor": ["microsoft"]
}
},
{
"software": {
"name": ["ms-vscode.powershell"],
"source": ["vscode_extensions"]
},
"filter": {
"product": ["powershell_extension"],
"vendor": ["microsoft"]
}
},
{
"software": {
"name": ["ms-vscode-remote.vscode-remote-extensionpack"],
"source": ["vscode_extensions"]
},
"filter": {
"product": ["remote_development"],
"vendor": ["microsoft"]
}
},
{
"software": {
"name": ["vknabel.vscode-swiftlint"],
"source": ["vscode_extensions"]
},
"filter": {
"product": ["swiftlint"],
"vendor": ["swiftlint_project"]
}
},
{
"software": {
"name": ["vknabel.vscode-swiftformat"],
"source": ["vscode_extensions"]
},
"filter": {
"product": ["swiftformat"],
"vendor": ["swiftformat_project"]
}
},
{
"software": {
"name": ["jbenden.c-cpp-flylint"],
"source": ["vscode_extensions"]
},
"filter": {
"product": ["c\\/c\\+\\+_advanced_lint"],
"vendor": ["c\\/c\\+\\+_advanced_lint_project"]
}
},
{
"software": {
"name": ["stripe.vscode-stripe"],
"source": ["vscode_extensions"]
},
"filter": {
"product": ["stripe"],
"vendor": ["stripe"]
}
},
{
"software": {
"name": ["vscodevim.vim"],
"source": ["vscode_extensions"]
},
"filter": {
"product": ["vim"],
"vendor": ["vim_project"]
}
},
{
"software": {
"name": ["svelte.svelte-vscode"],
"source": ["vscode_extensions"]
},
"filter": {
"product": ["svelte"],
"vendor": ["svelte"]
}
},
{
"software": {
"name": ["ms-vscode-remote.remote-containers"],
"source": ["vscode_extensions"]
},
"filter": {
"product": ["remote"],
"vendor": ["microsoft"]
}
},
{
"software": {
"name": ["ms-kubernetes-tools.vscode-kubernetes-tools"],
"source": ["vscode_extensions"]
},
"filter": {
"product": ["kubernetes_tools"],
"vendor": ["microsoft"]
}
},
{
"software": {
"name": ["ms-dotnettools.vscode-dotnet-sdk"],
"source": ["vscode_extensions"]
},
"filter": {
"product": [".net_education_bundle_sdk_install_tool"],
"vendor": ["microsoft"]
}
},
{
"software": {
"name": ["ms-dotnettools.vscode-dotnet-runtime"],
"source": ["vscode_extensions"]
},
"filter": {
"product": [".net_install_tool_for_extension_authors"],
"vendor": ["microsoft"]
}
},
{
"software": {
"name": ["ms-vscode-remote.remote-wsl"],
"source": ["vscode_extensions"]
},
"filter": {
"product": ["windows_subsystem_for_linux"],
"vendor": ["microsoft"]
}
},
{
"software": {
"name": ["mongodb.mongodb-vscode"],
"source": ["vscode_extensions"]
},
"filter": {
"product": ["mongodb"],
"vendor": ["mongodb"]
}
},
{
"software": {
"name": ["oracle.mysql-shell-for-vs-code"],
"source": ["vscode_extensions"]
},
"filter": {
"product": ["mysql_shell"],
"vendor": ["oracle"]
}
},
{
"software": {
"name": ["snyk-security.snyk-vulnerability-scanner"],
"source": ["vscode_extensions"]
},
"filter": {
"product": ["snyk_security"],
"vendor": ["snyk"]
}
},
{
"software": {
"name": ["sourcegraph.cody-ai"],
"source": ["vscode_extensions"]
},
"filter": {
"product": ["cody"],
"vendor": ["sourcegraph"]
}
},
{
"software": {
"name": ["github.vscode-pull-request-github"],
"source": ["vscode_extensions"]
},
"filter": {
"product": ["pull_requests_and_issues"],
"vendor": ["github"]
}
},
{
"software": {
"name": ["Microsoft Edge"],
+93 -51
View File
@@ -404,66 +404,69 @@ func checkCVEs(
// No such vendor in the Vulnerability dictionary
continue
}
cacheHits := cache.Get([]*wfn.Attributes{CPEItem.GetMeta()})
for _, matches := range cacheHits {
if len(matches.CPEs) == 0 {
continue
}
if rule, ok := knownNVDBugRules.FindMatch(
matches.CVE.ID(),
); ok {
if !rule.CPEMatches(CPEItem.GetMeta()) {
cpeItemsWithAliases := expandCPEAliases(CPEItem.GetMeta())
for _, cpeItem := range cpeItemsWithAliases {
cacheHits := cache.Get([]*wfn.Attributes{cpeItem})
for _, matches := range cacheHits {
if len(matches.CPEs) == 0 {
continue
}
}
// For chrome/firefox extensions we only want to match vulnerabilities
// that are reported explicitly for target_sw == "chrome" or target_sw = "firefox".
//
// Why? In many ocassions the NVD dataset reports vulnerabilities in client applications
// with target_sw == "*", meaning the client application is vulnerable on all operating systems.
// Such rules we want to ignore here to prevent many false positives that do not apply to the
// Chrome or Firefox environment.
if CPEItem.GetMeta().TargetSW == "chrome" || CPEItem.GetMeta().TargetSW == "firefox" {
if !matchesExactTargetSW(
CPEItem.GetMeta().TargetSW,
[]string{"chrome", "firefox"},
matches.CVE.Config(),
) {
continue
}
}
resolvedVersion, err := getMatchingVersionEndExcluding(ctx, matches.CVE.ID(), CPEItem.GetMeta(), dict, logger)
if err != nil {
level.Debug(logger).Log("err", err)
}
if _, ok := CPEItem.(softwareCPEWithNVDMeta); ok {
vuln := fleet.SoftwareVulnerability{
SoftwareID: CPEItem.GetID(),
CVE: matches.CVE.ID(),
ResolvedInVersion: ptr.String(resolvedVersion),
if rule, ok := knownNVDBugRules.FindMatch(
matches.CVE.ID(),
); ok {
if !rule.CPEMatches(cpeItem) {
continue
}
}
softwareMu.Lock()
foundSoftwareVulns = append(foundSoftwareVulns, vuln)
softwareMu.Unlock()
} else if _, ok := CPEItem.(osCPEWithNVDMeta); ok {
vuln := fleet.OSVulnerability{
OSID: CPEItem.GetID(),
CVE: matches.CVE.ID(),
ResolvedInVersion: ptr.String(resolvedVersion),
// For chrome/firefox extensions we only want to match vulnerabilities
// that are reported explicitly for target_sw == "chrome" or target_sw = "firefox".
//
// Why? In many occasions the NVD dataset reports vulnerabilities in client applications
// with target_sw == "*", meaning the client application is vulnerable on all operating systems.
// Such rules we want to ignore here to prevent many false positives that do not apply to the
// Chrome or Firefox environment.
if cpeItem.TargetSW == "chrome" || cpeItem.TargetSW == "firefox" {
if !matchesExactTargetSW(
cpeItem.TargetSW,
[]string{"chrome", "firefox"},
matches.CVE.Config(),
) {
continue
}
}
osMu.Lock()
foundOSVulns = append(foundOSVulns, vuln)
osMu.Unlock()
}
resolvedVersion, err := getMatchingVersionEndExcluding(ctx, matches.CVE.ID(), cpeItem, dict, logger)
if err != nil {
level.Debug(logger).Log("err", err)
}
if _, ok := CPEItem.(softwareCPEWithNVDMeta); ok {
vuln := fleet.SoftwareVulnerability{
SoftwareID: CPEItem.GetID(),
CVE: matches.CVE.ID(),
ResolvedInVersion: ptr.String(resolvedVersion),
}
softwareMu.Lock()
foundSoftwareVulns = append(foundSoftwareVulns, vuln)
softwareMu.Unlock()
} else if _, ok := CPEItem.(osCPEWithNVDMeta); ok {
vuln := fleet.OSVulnerability{
OSID: CPEItem.GetID(),
CVE: matches.CVE.ID(),
ResolvedInVersion: ptr.String(resolvedVersion),
}
osMu.Lock()
foundOSVulns = append(foundOSVulns, vuln)
osMu.Unlock()
}
}
}
case <-ctx.Done():
level.Debug(logger).Log("msg", "quitting")
@@ -485,6 +488,45 @@ func checkCVEs(
return foundSoftwareVulns, foundOSVulns, nil
}
// expandCPEAliases will generate new *wfn.Attributes from the given cpeItem.
// It returns a slice with the given cpeItem plus the generated *wfn.Attributes.
//
// We need this because entries in the CPE database are not consistent.
// E.g. some Visual Studio Code extensions are defined with target_sw=visual_studio_code
// and others are defined with target_sw=visual_studio.
// E.g. The python extension for Visual Studio Code is defined with
// product=python_extension,target_sw=visual_studio_code and with
// product=visual_studio_code,target_sw=python.
func expandCPEAliases(cpeItem *wfn.Attributes) []*wfn.Attributes {
cpeItems := []*wfn.Attributes{cpeItem}
// Some VSCode extensions are defined with target_sw=visual_studio_code
// and others are defined with target_sw=visual_studio.
for _, cpeItem := range cpeItems {
if cpeItem.TargetSW == "visual_studio_code" {
cpeItem2 := *cpeItem
cpeItem2.TargetSW = "visual_studio"
cpeItems = append(cpeItems, &cpeItem2)
}
}
// The python extension is defined in two ways in the CPE database:
// cpe:2.3:a:microsoft:python_extension:2024.2.1:*:*:*:*:visual_studio_code:*:*
// cpe:2.3:a:microsoft:visual_studio_code:2024.2.1:*:*:*:*:python:*:*
for _, cpeItem := range cpeItems {
if cpeItem.TargetSW == "visual_studio_code" &&
cpeItem.Vendor == "microsoft" &&
cpeItem.Product == "python_extension" {
cpeItem2 := *cpeItem
cpeItem2.Product = "visual_studio_code"
cpeItem2.TargetSW = "python"
cpeItems = append(cpeItems, &cpeItem2)
}
}
return cpeItems
}
// Returns the versionEndExcluding string for the given CVE and host software meta
// data, if it exists in the NVD feed. This effectively gives us the version of the
// software it needs to upgrade to in order to address the CVE.
+128 -12
View File
@@ -5,6 +5,7 @@ import (
"fmt"
"net/http"
"net/http/httptest"
"os"
"strings"
"sync"
"testing"
@@ -130,20 +131,26 @@ func (d *threadSafeDSMock) InsertSoftwareVulnerability(ctx context.Context, vuln
}
func TestTranslateCPEToCVE(t *testing.T) {
nettest.Run(t)
tempDir := t.TempDir()
ctx := context.Background()
// download the CVEs once for all sub-tests, and then disable syncing
err := nettest.RunWithNetRetry(t, func() error {
// We use cveFeedPrefixURL="https://nvd.nist.gov/feeds/json/cve/1.1/" because a full sync
// with the NVD API 2.0 takes a long time (>15m). These feeds will be deprecated
// on December 15th and this test will start failing then.
return DownloadNVDCVEFeed(tempDir, "https://nvd.nist.gov/feeds/json/cve/1.1/", false, log.NewNopLogger())
})
require.NoError(t, err)
// NVD_TEST_VULNDB_DIR can be used to speed up development (sync vulnerability data only once).
tempDir := os.Getenv("NVD_TEST_VULNDB_DIR")
if tempDir == "" {
nettest.Run(t)
// download the CVEs once for all sub-tests, and then disable syncing
tempDir = t.TempDir()
err := nettest.RunWithNetRetry(t, func() error {
// We use cveFeedPrefixURL="https://nvd.nist.gov/feeds/json/cve/1.1/" because a full sync
// with the NVD API 2.0 takes a long time (>15m). These feeds will be deprecated
// TBD during 2024 and this test will start failing then.
// For more information see: https://nvd.nist.gov/general/news/change-timeline.
return DownloadNVDCVEFeed(tempDir, "https://nvd.nist.gov/feeds/json/cve/1.1/", false, log.NewNopLogger())
})
require.NoError(t, err)
} else {
require.DirExists(t, tempDir)
t.Logf("Using %s as database path", tempDir)
}
cveTests := map[string]struct {
cpe string
@@ -261,6 +268,48 @@ func TestTranslateCPEToCVE(t *testing.T) {
},
continuesToUpdate: true,
},
"cpe:2.3:a:microsoft:windows_subsystem_for_linux:0.63.10:*:*:*:*:visual_studio_code:*:*": {
includedCVEs: []cve{
{ID: "CVE-2021-43907", resolvedInVersion: "0.63.11"},
},
continuesToUpdate: false,
},
"cpe:2.3:a:github:pull_requests_and_issues:0.66.1:*:*:*:*:visual_studio_code:*:*": {
includedCVEs: []cve{
{ID: "CVE-2023-36867", resolvedInVersion: "0.66.2"},
},
continuesToUpdate: false,
},
"cpe:2.3:a:microsoft:python_extension:2020.9.1:*:*:*:*:visual_studio_code:*:*": {
includedCVEs: []cve{
{ID: "CVE-2020-17163", resolvedInVersion: "2020.9.2"},
},
continuesToUpdate: false,
},
"cpe:2.3:a:microsoft:jupyter:2023.10.10:*:*:*:*:visual_studio_code:*:*": {
includedCVEs: []cve{
{ID: "CVE-2023-36018", resolvedInVersion: "2023.10.1100000000"},
},
continuesToUpdate: false,
},
"cpe:2.3:a:microsoft:jupyter:2024.2.0:*:*:*:*:visual_studio_code:*:*": {
includedCVEs: []cve{},
continuesToUpdate: false,
},
"cpe:2.3:a:microsoft:visual_studio_code_eslint_extension:2.0.0:*:*:*:*:visual_studio_code:*:*": {
includedCVEs: []cve{
{ID: "CVE-2020-1481", resolvedInVersion: "2.1.7"},
},
continuesToUpdate: false,
},
"cpe:2.3:a:microsoft:python_extension:2020.4.0:*:*:*:*:visual_studio_code:*:*": {
includedCVEs: []cve{
{ID: "CVE-2020-1171", resolvedInVersion: "2020.5.0"},
{ID: "CVE-2020-1192", resolvedInVersion: "2020.5.0"},
{ID: "CVE-2020-17163", resolvedInVersion: "2020.9.2"},
},
continuesToUpdate: false,
},
}
cveOSTests := []struct {
@@ -701,3 +750,70 @@ func loadDict(t *testing.T, path string) cvefeed.Dictionary {
}
return dict
}
func TestExpandCPEAliases(t *testing.T) {
firefox := &wfn.Attributes{
Vendor: "mozilla",
Product: "firefox",
Version: "93.0.100",
}
chromePlugin := &wfn.Attributes{
Vendor: "google",
Product: "plugin foobar",
Version: "93.0.100",
TargetSW: "chrome",
}
vsCodeExtension := &wfn.Attributes{
Vendor: "Microsoft",
Product: "foo.extension",
Version: "2024.2.1",
TargetSW: "visual_studio_code",
}
vsCodeExtensionAlias := *vsCodeExtension
vsCodeExtensionAlias.TargetSW = "visual_studio"
pythonCodeExtension := &wfn.Attributes{
Vendor: "microsoft",
Product: "python_extension",
Version: "2024.2.1",
TargetSW: "visual_studio_code",
}
pythonCodeExtensionAlias1 := *pythonCodeExtension
pythonCodeExtensionAlias1.TargetSW = "visual_studio"
pythonCodeExtensionAlias2 := *pythonCodeExtension
pythonCodeExtensionAlias2.Product = "visual_studio_code"
pythonCodeExtensionAlias2.TargetSW = "python"
for _, tc := range []struct {
name string
cpeItem *wfn.Attributes
expectedAliases []*wfn.Attributes
}{
{
name: "no expansion without target_sw",
cpeItem: firefox,
expectedAliases: []*wfn.Attributes{firefox},
},
{
name: "no expansion with target_sw",
cpeItem: chromePlugin,
expectedAliases: []*wfn.Attributes{chromePlugin},
},
{
name: "visual studio code extension",
cpeItem: vsCodeExtension,
expectedAliases: []*wfn.Attributes{vsCodeExtension, &vsCodeExtensionAlias},
},
{
name: "python visual studio code extension",
cpeItem: pythonCodeExtension,
expectedAliases: []*wfn.Attributes{pythonCodeExtension, &pythonCodeExtensionAlias1, &pythonCodeExtensionAlias2},
},
} {
t.Run(tc.name, func(t *testing.T) {
aliases := expandCPEAliases(tc.cpeItem)
require.Equal(t, tc.expectedAliases, aliases)
})
}
}
+18
View File
@@ -145,6 +145,14 @@ func productVariations(s *fleet.Software) []string {
r = append(r, re)
}
// VSCode extensions have a unique s.Name of the form "<vendor>.<extension>" (aka extension ID)
if s.Source == "vscode_extensions" {
parts := strings.SplitN(s.Name, ".", 2)
if len(parts) == 2 && parts[1] != "" {
r = append(r, parts[1])
}
}
return r
}
@@ -178,6 +186,14 @@ func vendorVariations(s *fleet.Software) []string {
}
}
// VSCode extensions have a unique s.Name of the form "<vendor>.<extension>" (aka extension ID)
if s.Source == "vscode_extensions" {
parts := strings.SplitN(s.Name, ".", 2)
if len(parts) == 2 && parts[0] != "" {
r = append(r, parts[0])
}
}
return r
}
@@ -211,6 +227,8 @@ func targetSW(s *fleet.Software) string {
return `node.js`
case "programs":
return "windows"
case "vscode_extensions":
return "visual_studio_code"
}
return "*"
}
@@ -157,6 +157,11 @@ func TestVariations(t *testing.T) {
vendorVariations: []string{"apple", "python3"},
productVariations: []string{"python"},
},
{
software: fleet.Software{Name: "ms-python.python", Version: "3.8.9", BundleIdentifier: "", Source: "vscode_extensions", Vendor: "Microsoft"},
vendorVariations: []string{"microsoft", "ms-python"},
productVariations: []string{"python", "ms-python.python"},
},
}
for _, tc := range variationsTestCases {