Add Description text to CVE Metadata (#13856)

This commit is contained in:
Tim Lee 2023-09-15 11:24:10 -06:00 committed by GitHub
parent 741ace0515
commit 5bc6d30aa8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 124 additions and 17 deletions

View File

@ -0,0 +1 @@
- CVE descriptions added to the /fleet/software API

View File

@ -89,8 +89,8 @@ $ docker-compose -f docker-compose.yml -f docker-compose-redis-cluster.yml up
To run all Go unit tests, run the following:
```
REDIS_TEST=1 MYSQL_TEST=1 MINIO_STORAGE_TEST=1 SAML_IDP_TEST=1 make test-go
```bash
REDIS_TEST=1 MYSQL_TEST=1 MINIO_STORAGE_TEST=1 SAML_IDP_TEST=1 NETWORK_TEST=1 make test-go
```
### Go linters

View File

@ -0,0 +1,25 @@
package tables
import (
"database/sql"
"fmt"
)
func init() {
MigrationClient.AddMigration(Up_20230912101759, Down_20230912101759)
}
func Up_20230912101759(tx *sql.Tx) error {
stmt := `
ALTER TABLE cve_meta
ADD COLUMN description TEXT COLLATE utf8mb4_unicode_ci DEFAULT NULL;
`
if _, err := tx.Exec(stmt); err != nil {
return fmt.Errorf("add description to cve_meta: %w", err)
}
return nil
}
func Down_20230912101759(tx *sql.Tx) error {
return nil
}

View File

@ -0,0 +1,46 @@
package tables
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestUp_20230912101759(t *testing.T) {
db := applyUpToPrev(t)
insertStmt := `
INSERT INTO cve_meta
(cve)
VALUES
(?)
`
cveVal := "CVE-2010-3262"
execNoErr(t, db, insertStmt, cveVal)
applyNext(t, db)
// retrieve the stored value
var cveMeta struct {
CVE string `db:"cve"`
Description *string `db:"description"`
}
err := db.Get(&cveMeta, "SELECT cve, description FROM cve_meta WHERE cve = ?", cveVal)
require.NoError(t, err)
require.Equal(t, cveVal, cveMeta.CVE)
require.Nil(t, cveMeta.Description)
insertStmt = `
INSERT INTO cve_meta
(cve, description)
VALUES
(?, ?)
`
cveVal = "CVE-2010-3263"
descVal := "Cross-site scripting (XSS) vulnerability in setup/frames/index.inc.php in the setup script in phpMyAdmin 3.x before 3.3.7 allows remote attackers to inject arbitrary web script or HTML via a server name."
execNoErr(t, db, insertStmt, cveVal, descVal)
err = db.Get(&cveMeta, "SELECT cve, description FROM cve_meta WHERE cve = ?", cveVal)
require.NoError(t, err)
require.Equal(t, cveVal, cveMeta.CVE)
require.Equal(t, &descVal, cveMeta.Description)
}

File diff suppressed because one or more lines are too long

View File

@ -587,6 +587,7 @@ func listSoftwareDB(
cve.EPSSProbability = &result.EPSSProbability
cve.CISAKnownExploit = &result.CISAKnownExploit
cve.CVEPublished = &result.CVEPublished
cve.Description = &result.Description
}
softwares[idx].Vulnerabilities = append(softwares[idx].Vulnerabilities, cve)
}
@ -603,6 +604,7 @@ type softwareCVE struct {
EPSSProbability *float64 `db:"epss_probability"`
CISAKnownExploit *bool `db:"cisa_known_exploit"`
CVEPublished *time.Time `db:"cve_published"`
Description *string `db:"description"`
}
func selectSoftwareSQL(opts fleet.SoftwareListOptions) (string, []interface{}, error) {
@ -696,6 +698,7 @@ func selectSoftwareSQL(opts fleet.SoftwareListOptions) (string, []interface{}, e
goqu.MAX("c.epss_probability").As("epss_probability"), // for ordering
goqu.MAX("c.cisa_known_exploit").As("cisa_known_exploit"), // for ordering
goqu.MAX("c.published").As("cve_published"), // for ordering
goqu.MAX("c.description").As("description"), // for ordering
)
}
@ -763,6 +766,7 @@ func selectSoftwareSQL(opts fleet.SoftwareListOptions) (string, []interface{}, e
"c.cvss_score",
"c.epss_probability",
"c.cisa_known_exploit",
"c.description",
goqu.I("c.published").As("cve_published"),
)
}
@ -1062,6 +1066,7 @@ func (ds *Datastore) SoftwareByID(ctx context.Context, id uint, includeCVEScores
"c.cvss_score",
"c.epss_probability",
"c.cisa_known_exploit",
"c.description",
goqu.I("c.published").As("cve_published"),
)
}
@ -1356,13 +1361,14 @@ func (ds *Datastore) HostsByCVE(ctx context.Context, cve string) ([]fleet.HostVu
func (ds *Datastore) InsertCVEMeta(ctx context.Context, cveMeta []fleet.CVEMeta) error {
query := `
INSERT INTO cve_meta (cve, cvss_score, epss_probability, cisa_known_exploit, published)
INSERT INTO cve_meta (cve, cvss_score, epss_probability, cisa_known_exploit, published, description)
VALUES %s
ON DUPLICATE KEY UPDATE
cvss_score = VALUES(cvss_score),
epss_probability = VALUES(epss_probability),
cisa_known_exploit = VALUES(cisa_known_exploit),
published = VALUES(published)
published = VALUES(published),
description = VALUES(description)
`
batchSize := 500
@ -1374,10 +1380,10 @@ ON DUPLICATE KEY UPDATE
batch := cveMeta[i:end]
valuesFrag := strings.TrimSuffix(strings.Repeat("(?, ?, ?, ?, ?), ", len(batch)), ", ")
valuesFrag := strings.TrimSuffix(strings.Repeat("(?, ?, ?, ?, ?, ?), ", len(batch)), ", ")
var args []interface{}
for _, meta := range batch {
args = append(args, meta.CVE, meta.CVSSScore, meta.EPSSProbability, meta.CISAKnownExploit, meta.Published)
args = append(args, meta.CVE, meta.CVSSScore, meta.EPSSProbability, meta.CISAKnownExploit, meta.Published, meta.Description)
}
query := fmt.Sprintf(query, valuesFrag)
@ -1514,6 +1520,7 @@ func (ds *Datastore) ListCVEs(ctx context.Context, maxAge time.Duration) ([]flee
goqu.C("epss_probability"),
goqu.C("cisa_known_exploit"),
goqu.C("published"),
goqu.C("description"),
).
Where(goqu.C("published").Gte(maxAgeDate))

View File

@ -566,6 +566,7 @@ func testSoftwareList(t *testing.T, ds *Datastore) {
EPSSProbability: ptr.Float64(0.01),
CISAKnownExploit: ptr.Bool(false),
Published: ptr.Time(now.Add(-2 * time.Hour)),
Description: "this is a description for CVE-2022-0001",
},
{
CVE: "CVE-2022-0002",
@ -573,6 +574,7 @@ func testSoftwareList(t *testing.T, ds *Datastore) {
EPSSProbability: ptr.Float64(0.99),
CISAKnownExploit: ptr.Bool(false),
Published: ptr.Time(now),
Description: "this is a description for CVE-2022-0002",
},
{
CVE: "CVE-2022-0003",
@ -580,6 +582,7 @@ func testSoftwareList(t *testing.T, ds *Datastore) {
EPSSProbability: ptr.Float64(0.98),
CISAKnownExploit: ptr.Bool(true),
Published: ptr.Time(now.Add(-1 * time.Hour)),
Description: "this is a description for CVE-2022-0003",
},
}
err = ds.InsertCVEMeta(context.Background(), cveMeta)
@ -598,6 +601,7 @@ func testSoftwareList(t *testing.T, ds *Datastore) {
EPSSProbability: ptr.Float64Ptr(0.01),
CISAKnownExploit: ptr.BoolPtr(false),
CVEPublished: ptr.TimePtr(now.Add(-2 * time.Hour)),
Description: ptr.StringPtr("this is a description for CVE-2022-0001"),
},
{
CVE: "CVE-2022-0002",
@ -606,6 +610,7 @@ func testSoftwareList(t *testing.T, ds *Datastore) {
EPSSProbability: ptr.Float64Ptr(0.99),
CISAKnownExploit: ptr.BoolPtr(false),
CVEPublished: ptr.TimePtr(now),
Description: ptr.StringPtr("this is a description for CVE-2022-0002"),
},
},
}
@ -625,6 +630,7 @@ func testSoftwareList(t *testing.T, ds *Datastore) {
EPSSProbability: ptr.Float64Ptr(0.98),
CISAKnownExploit: ptr.BoolPtr(true),
CVEPublished: ptr.TimePtr(now.Add(-1 * time.Hour)),
Description: ptr.StringPtr("this is a description for CVE-2022-0003"),
},
},
}
@ -1815,10 +1821,10 @@ func testListCVEs(t *testing.T, ds *Datastore) {
twoMonthsAgo := now.Add(-60 * 24 * time.Hour)
testCases := []fleet.CVEMeta{
{CVE: "cve-1", Published: &threeDaysAgo},
{CVE: "cve-2", Published: &twoWeeksAgo},
{CVE: "cve-3", Published: &twoMonthsAgo},
{CVE: "cve-4"},
{CVE: "cve-1", Published: &threeDaysAgo, Description: "cve-1 description"},
{CVE: "cve-2", Published: &twoWeeksAgo, Description: "cve-2 description"},
{CVE: "cve-3", Published: &twoMonthsAgo}, // past maxAge
{CVE: "cve-4"}, // no published date
}
err := ds.InsertCVEMeta(ctx, testCases)
@ -1827,12 +1833,12 @@ func testListCVEs(t *testing.T, ds *Datastore) {
result, err := ds.ListCVEs(ctx, 30*24*time.Hour)
require.NoError(t, err)
expected := []string{"cve-1", "cve-2"}
expected := []string{"cve-1", "cve-1 description", "cve-2", "cve-2 description"}
var actual []string
for _, r := range result {
actual = append(actual, r.CVE)
actual = append(actual, r.Description)
}
require.ElementsMatch(t, expected, actual)
}

View File

@ -16,6 +16,7 @@ type CVE struct {
EPSSProbability **float64 `json:"epss_probability,omitempty" db:"epss_probability"`
CISAKnownExploit **bool `json:"cisa_known_exploit,omitempty" db:"cisa_known_exploit"`
CVEPublished **time.Time `json:"cve_published,omitempty" db:"cve_published"`
Description **string `json:"cve_description,omitempty" db:"description"`
}
type CVEMeta struct {
@ -33,6 +34,8 @@ type CVEMeta struct {
CISAKnownExploit *bool `db:"cisa_known_exploit"`
// Published is when the cve was published according to NIST.score
Published *time.Time `db:"published"`
// CVE text description
Description string `db:"description"`
}
// SoftwareCPE represents an entry in the `software_cpe` table.

View File

@ -32,6 +32,11 @@ func BoolPtr(x bool) **bool {
return &p
}
func StringPtr(x string) **string {
p := String(x)
return &p
}
// Time returns a pointer to the provided time.Time.
func Time(x time.Time) *time.Time {
return &x

View File

@ -2925,6 +2925,7 @@ func (s *integrationEnterpriseTestSuite) TestListSoftware() {
EPSSProbability: ptr.Float64(0.5),
CISAKnownExploit: ptr.Bool(true),
Published: &now,
Description: "a long description of the cve",
}}))
require.NoError(t, s.ds.SyncHostsSoftware(ctx, time.Now().UTC()))
@ -2953,6 +2954,7 @@ func (s *integrationEnterpriseTestSuite) TestListSoftware() {
require.NotNil(t, barPayload.Vulnerabilities[0].EPSSProbability, ptr.Float64Ptr(0.5))
require.NotNil(t, barPayload.Vulnerabilities[0].CISAKnownExploit, ptr.BoolPtr(true))
require.Equal(t, barPayload.Vulnerabilities[0].CVEPublished, ptr.TimePtr(now))
require.Equal(t, barPayload.Vulnerabilities[0].Description, ptr.StringPtr("a long description of the cve"))
}
// TestGitOpsUserActions tests the permissions listed in ../../docs/Using-Fleet/Permissions.md.

View File

@ -16,8 +16,8 @@ import (
"github.com/facebookincubator/nvdtools/providers/nvd"
"github.com/facebookincubator/nvdtools/wfn"
"github.com/fleetdm/fleet/v4/server/fleet"
kitlog "github.com/go-kit/kit/log"
"github.com/go-kit/kit/log/level"
kitlog "github.com/go-kit/log"
"github.com/go-kit/log/level"
)
// DownloadNVDCVEFeed downloads the NVD CVE feed. Skips downloading if the cve feed has not changed since the last time.

View File

@ -210,7 +210,8 @@ func LoadCVEMeta(ctx context.Context, logger log.Logger, vulnPath string, ds fle
schema := vuln.Schema()
meta := fleet.CVEMeta{
CVE: cve,
CVE: cve,
Description: schema.CVE.Description.DescriptionData[0].Value,
}
if schema.Impact.BaseMetricV3 != nil {

View File

@ -64,11 +64,21 @@ func TestLoadCVEMeta(t *testing.T) {
require.Equal(t, float64(7.2), *meta.CVSSScore)
require.Equal(t, float64(0.00885), *meta.EPSSProbability)
require.Equal(t, false, *meta.CISAKnownExploit)
require.Equal(
t,
"CSCMS Music Portal System v4.2 was discovered to contain a SQL injection vulnerability via the id parameter at /admin.php/pic/admin/lists/zhuan.",
*&meta.Description,
)
meta = metaMap["CVE-2022-22587"]
require.Equal(t, float64(9.8), *meta.CVSSScore)
require.Equal(t, float64(0.01843), *meta.EPSSProbability)
require.Equal(t, true, *meta.CISAKnownExploit)
require.Equal(
t,
"A memory corruption issue was addressed with improved input validation. This issue is fixed in iOS 15.3 and iPadOS 15.3, macOS Big Sur 11.6.3, macOS Monterey 12.2. A malicious application may be able to execute arbitrary code with kernel privileges. Apple is aware of a report that this issue may have been actively exploited..",
*&meta.Description,
)
}
func TestDownloadCPETranslations(t *testing.T) {