feat(kevc): add known exploited vulnerability catalog (#152)
This commit is contained in:
parent
67f99a96ff
commit
0875550b05
4
.github/workflows/update.yml
vendored
4
.github/workflows/update.yml
vendored
@ -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()
|
||||
|
@ -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
82
kevc/kevc.go
Normal 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
73
kevc/kevc_test.go
Normal 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)
|
||||
})
|
||||
}
|
||||
}
|
10
kevc/testdata/golden/kevc/2014/CVE-2014-0160.json
vendored
Normal file
10
kevc/testdata/golden/kevc/2014/CVE-2014-0160.json
vendored
Normal 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"
|
||||
}
|
10
kevc/testdata/golden/kevc/2022/CVE-2022-1388.json
vendored
Normal file
10
kevc/testdata/golden/kevc/2022/CVE-2022-1388.json
vendored
Normal 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"
|
||||
}
|
28
kevc/testdata/happy/known_exploited_vulnerabilities.json
vendored
Normal file
28
kevc/testdata/happy/known_exploited_vulnerabilities.json
vendored
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
1
kevc/testdata/sad/known_exploited_vulnerabilities.json
vendored
Normal file
1
kevc/testdata/sad/known_exploited_vulnerabilities.json
vendored
Normal file
@ -0,0 +1 @@
|
||||
{
|
22
kevc/types.go
Normal file
22
kevc/types.go
Normal 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"`
|
||||
}
|
9
main.go
9
main.go
@ -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")
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user