feat(kevc): add known exploited vulnerability catalog (#152)

This commit is contained in:
Masahiro331 2022-05-26 13:08:21 +09:00 committed by GitHub
parent 67f99a96ff
commit 0875550b05
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 246 additions and 7 deletions

View File

@ -107,6 +107,10 @@ jobs:
name: Go Vulnerability Database
run: ./vuln-list-update -target go-vulndb
- if: always()
name: Known Exploited Vulnerabilities Catalog
run: ./vuln-list-update -target kevc
# Red Hat Security Data API is unstable.
# It should be split into small pieces to reduce the impact of failure.
- if: always()

View File

@ -20,7 +20,7 @@ https://github.com/aquasecurity/vuln-list/
$ vuln-list-update -h
Usage of vuln-list-update:
-target string
update target (nvd, alpine, alpine-unfixed, redhat, redhat-oval, debian, debian-oval, ubuntu, amazon, oracle-oval, suse-cvrf, photon, arch-linux, ghsa, glad, cwe, osv, go-vulndb, mariner)
update target (nvd, alpine, alpine-unfixed, redhat, redhat-oval, debian, debian-oval, ubuntu, amazon, oracle-oval, suse-cvrf, photon, arch-linux, ghsa, glad, cwe, osv, go-vulndb, mariner, kevc)
-years string
update years (only redhat)
```

82
kevc/kevc.go Normal file
View File

@ -0,0 +1,82 @@
package kevc
import (
"encoding/json"
"github.com/aquasecurity/vuln-list-update/utils"
"github.com/cheggaaa/pb"
"golang.org/x/xerrors"
"log"
"path/filepath"
)
const (
kevcURL = "https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json"
retry = 5
kevcDir = "kevc"
)
type Config struct {
url string
dir string
retry int
}
type option func(config *Config)
func WithURL(url string) option {
return func(c *Config) { c.url = url }
}
func WithDir(dir string) option {
return func(c *Config) { c.dir = dir }
}
func WithRetry(retry int) option {
return func(c *Config) { c.retry = retry }
}
func NewConfig(opts ...option) Config {
c := Config{
url: kevcURL,
dir: filepath.Join(utils.VulnListDir(), kevcDir),
retry: retry,
}
for _, opt := range opts {
opt(&c)
}
return c
}
func (c Config) Update() error {
log.Print("Fetching Known Exploited Vulnerabilities Catalog")
res, err := utils.FetchURL(c.url, "", c.retry)
if err != nil {
return xerrors.Errorf("failed to fetch KEVC: %w", err)
}
kevc := KEVC{}
if err := json.Unmarshal(res, &kevc); err != nil {
return xerrors.Errorf("failed to KEVC json unmarshal error: %w", err)
}
if kevc.Count != len(kevc.Vulnerabilities) {
return xerrors.Errorf("failed to Vulnerabilities count error: kevc.Count %d, kevc.Vulnerability length %d", kevc.Count, len(kevc.Vulnerabilities))
}
if err := c.update(kevc); err != nil {
return xerrors.Errorf("failed to update KEVC: %w", err)
}
return nil
}
func (c Config) update(kevc KEVC) error {
bar := pb.StartNew(kevc.Count)
for _, vuln := range kevc.Vulnerabilities {
if err := utils.SaveCVEPerYear(c.dir, vuln.CveID, vuln); err != nil {
return xerrors.Errorf("failed to save KEVC per year: %w", err)
}
bar.Increment()
}
bar.Finish()
return nil
}

73
kevc/kevc_test.go Normal file
View File

@ -0,0 +1,73 @@
package kevc_test
import (
"github.com/aquasecurity/vuln-list-update/kevc"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"testing"
)
func TestUpdate(t *testing.T) {
tests := []struct {
name string
inputFileDir string
wantErr string
}{
{
name: "happy path",
inputFileDir: "testdata/happy/",
},
{
name: "sad path, invalid json",
inputFileDir: "testdata/sad/",
wantErr: "failed to KEVC json unmarshal",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ts := httptest.NewServer(http.FileServer(http.Dir(tt.inputFileDir)))
defer ts.Close()
tmpDir := t.TempDir()
cc := kevc.NewConfig(
kevc.WithURL(ts.URL+"/known_exploited_vulnerabilities.json"),
kevc.WithDir(tmpDir),
kevc.WithRetry(0),
)
err := cc.Update()
if tt.wantErr != "" {
require.Error(t, err)
assert.Contains(t, err.Error(), tt.wantErr)
return
}
require.NoError(t, err)
err = filepath.Walk(tmpDir, func(path string, info os.FileInfo, errfp error) error {
if errfp != nil {
return errfp
}
if info.IsDir() {
return nil
}
dir, file := filepath.Split(path)
want, err := os.ReadFile(filepath.Join("testdata", "golden", "kevc", filepath.Base(dir), file))
assert.NoError(t, err, "failed to open the golden file")
got, err := os.ReadFile(path)
assert.NoError(t, err, "failed to open the result file")
assert.JSONEq(t, string(want), string(got))
return nil
})
require.NoError(t, err, tt.name)
})
}
}

View File

@ -0,0 +1,10 @@
{
"cveID": "CVE-2014-0160",
"vendorProject": "OpenSSL",
"product": "OpenSSL",
"vulnerabilityName": "OpenSSL Information Disclosure Vulnerability",
"dateAdded": "2022-05-04",
"shortDescription": "The TLS and DTLS implementations in OpenSSL do not properly handle Heartbeat Extension packets, which allows remote attackers to obtain sensitive information.",
"requiredAction": "Apply updates per vendor instructions.",
"dueDate": "2022-05-25"
}

View File

@ -0,0 +1,10 @@
{
"cveID": "CVE-2022-1388",
"vendorProject": "F5",
"product": "BIG-IP",
"vulnerabilityName": "F5 BIG-IP Missing Authentication Vulnerability",
"dateAdded": "2022-05-10",
"shortDescription": "F5 BIG-IP contains a missing authentication in critical function vulnerability which can allow for remote code execution, creation or deletion of files, or disabling services.",
"requiredAction": "Apply updates per vendor instructions.",
"dueDate": "2022-05-31"
}

View File

@ -0,0 +1,28 @@
{
"title": "CISA Catalog of Known Exploited Vulnerabilities",
"catalogVersion": "2022.05.13",
"dateReleased": "2022-05-13T19:57:27.2727Z",
"count": 2,
"vulnerabilities": [
{
"cveID": "CVE-2014-0160",
"vendorProject": "OpenSSL",
"product": "OpenSSL",
"vulnerabilityName": "OpenSSL Information Disclosure Vulnerability",
"dateAdded": "2022-05-04",
"shortDescription": "The TLS and DTLS implementations in OpenSSL do not properly handle Heartbeat Extension packets, which allows remote attackers to obtain sensitive information.",
"requiredAction": "Apply updates per vendor instructions.",
"dueDate": "2022-05-25"
},
{
"cveID": "CVE-2022-1388",
"vendorProject": "F5",
"product": "BIG-IP",
"vulnerabilityName": "F5 BIG-IP Missing Authentication Vulnerability",
"dateAdded": "2022-05-10",
"shortDescription": "F5 BIG-IP contains a missing authentication in critical function vulnerability which can allow for remote code execution, creation or deletion of files, or disabling services.",
"requiredAction": "Apply updates per vendor instructions.",
"dueDate": "2022-05-31"
}
]
}

View File

@ -0,0 +1 @@
{

22
kevc/types.go Normal file
View File

@ -0,0 +1,22 @@
package kevc
import "time"
type KEVC struct {
Title string `json:"title"`
CatalogVersion string `json:"catalogVersion"`
DateReleased time.Time `json:"dateReleased"`
Count int `json:"count"`
Vulnerabilities []Vulnerability `json:"vulnerabilities"`
}
type Vulnerability struct {
CveID string `json:"cveID"`
VendorProject string `json:"vendorProject"`
Product string `json:"product"`
VulnerabilityName string `json:"vulnerabilityName"`
DateAdded string `json:"dateAdded"`
ShortDescription string `json:"shortDescription"`
RequiredAction string `json:"requiredAction"`
DueDate string `json:"dueDate"`
}

View File

@ -4,6 +4,7 @@ import (
"context"
"flag"
"fmt"
"github.com/aquasecurity/vuln-list-update/kevc"
"log"
"os"
"strconv"
@ -46,7 +47,7 @@ const (
var (
target = flag.String("target", "", "update target (nvd, alpine, alpine-unfixed, redhat, redhat-oval, "+
"debian, debian-oval, ubuntu, amazon, oracle-oval, suse-cvrf, photon, arch-linux, ghsa, glad, cwe, osv, go-vulndb, mariner)")
"debian, debian-oval, ubuntu, amazon, oracle-oval, suse-cvrf, photon, arch-linux, ghsa, glad, cwe, osv, go-vulndb, mariner, kevc)")
years = flag.String("years", "", "update years (only redhat)")
targetUri = flag.String("target-uri", "", "alternative repository URI (only glad)")
targetBranch = flag.String("target-branch", "", "alternative repository branch (only glad)")
@ -223,6 +224,12 @@ func run() error {
return xerrors.Errorf("CBL-Mariner Vulnerability Data update error: %w", err)
}
commitMsg = "CBL-Mariner Vulnerability Data"
case "kevc":
src := kevc.NewConfig()
if err := src.Update(); err != nil {
return xerrors.Errorf("Known Exploited Vulnerability Catalog update error: %w", err)
}
commitMsg = "Known Exploited Vulnerability Catalog"
default:
return xerrors.New("unknown target")
}

View File

@ -7,6 +7,7 @@ import (
"encoding/json"
"fmt"
"log"
"path/filepath"
"strings"
"time"
@ -109,7 +110,7 @@ func save(nvd *NVD) error {
continue
}
if err = utils.SaveCVEPerYear(nvdDir, cveID, item); err != nil {
if err = utils.SaveCVEPerYear(filepath.Join(utils.VulnListDir(), nvdDir), cveID, item); err != nil {
return xerrors.Errorf("failed to save NVD CVE detail: %w", err)
}
}

View File

@ -4,6 +4,7 @@ import (
"encoding/json"
"fmt"
"log"
"path/filepath"
"time"
"golang.org/x/xerrors"
@ -51,7 +52,7 @@ func update(year int) error {
}
for cveID, cve := range cves {
if err = utils.SaveCVEPerYear(redhatDir, cveID, cve); err != nil {
if err = utils.SaveCVEPerYear(filepath.Join(utils.VulnListDir(), redhatDir), cveID, cve); err != nil {
return xerrors.Errorf("failed to save RedHat CVE detail: %w", err)
}
}

View File

@ -131,7 +131,7 @@ func walkDir(root string) error {
return xerrors.Errorf("error in parse: %w", err)
}
if err = utils.SaveCVEPerYear(ubuntuDir, vuln.Candidate, vuln); err != nil {
if err = utils.SaveCVEPerYear(filepath.Join(utils.VulnListDir(), ubuntuDir), vuln.Candidate, vuln); err != nil {
return xerrors.Errorf("error in save: %w", err)
}

View File

@ -32,13 +32,13 @@ func VulnListDir() string {
return filepath.Join(CacheDir(), "vuln-list")
}
func SaveCVEPerYear(dirName string, cveID string, data interface{}) error {
func SaveCVEPerYear(dirPath string, cveID string, data interface{}) error {
s := strings.Split(cveID, "-")
if len(s) != 3 {
return xerrors.Errorf("invalid CVE-ID format: %s\n", cveID)
}
yearDir := filepath.Join(VulnListDir(), dirName, s[1])
yearDir := filepath.Join(dirPath, s[1])
if err := os.MkdirAll(yearDir, os.ModePerm); err != nil {
return err
}