refactor(nvd): use API instead of JSON feeds (#258)

Signed-off-by: knqyf263 <knqyf263@gmail.com>
Co-authored-by: knqyf263 <knqyf263@gmail.com>
This commit is contained in:
DmitriyLewen 2023-12-18 22:34:54 +06:00 committed by GitHub
parent 4e31879ddb
commit f1f4c3e8e6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 2181 additions and 184 deletions

View File

@ -1,5 +1,5 @@
run:
go: 1.20
go: 1.21
timeout: 5m
linters:
enable:

View File

@ -2,7 +2,6 @@ package alpine_test
import (
"flag"
"io/ioutil"
"net/http"
"net/http/httptest"
"net/url"
@ -118,10 +117,10 @@ func TestUpdater_Update(t *testing.T) {
goldenPath, ok := tt.goldenFiles[path]
require.True(t, ok, path)
if *update {
err = ioutil.WriteFile(goldenPath, actual, 0666)
err = os.WriteFile(goldenPath, actual, 0666)
require.NoError(t, err, goldenPath)
}
expected, err := ioutil.ReadFile(goldenPath)
expected, err := os.ReadFile(goldenPath)
assert.NoError(t, err, goldenPath)
assert.JSONEq(t, string(expected), string(actual), path)

View File

@ -2,7 +2,6 @@ package chainguard_test
import (
"flag"
"io/ioutil"
"net/http"
"net/http/httptest"
"net/url"
@ -99,10 +98,10 @@ func TestUpdater_Update(t *testing.T) {
goldenPath, ok := tt.goldenFiles[path]
require.True(t, ok, path)
if *update {
err = ioutil.WriteFile(goldenPath, actual, 0666)
err = os.WriteFile(goldenPath, actual, 0666)
require.NoError(t, err, goldenPath)
}
expected, err := ioutil.ReadFile(goldenPath)
expected, err := os.ReadFile(goldenPath)
assert.NoError(t, err, goldenPath)
assert.JSONEq(t, string(expected), string(actual), path)

View File

@ -6,7 +6,7 @@ import (
"encoding/json"
"encoding/xml"
"fmt"
"io/ioutil"
"io"
"log"
"os"
"path/filepath"
@ -74,7 +74,7 @@ func (c CWEConfig) Update() error {
}
func (c CWEConfig) saveFile(b []byte, fileType string) error {
if err := ioutil.WriteFile(filepath.Join(c.cweDir, fileType), b, 0600); err != nil {
if err := os.WriteFile(filepath.Join(c.cweDir, fileType), b, 0600); err != nil {
return xerrors.Errorf("failed to write %s file: %w", fileType, err)
}
return nil
@ -104,7 +104,7 @@ func readZipFile(zf *zip.File) ([]byte, error) {
return nil, err
}
defer f.Close()
return ioutil.ReadAll(f)
return io.ReadAll(f)
}
func xmlToJSON(b []byte) (WeaknessCatalog, error) {

View File

@ -2,7 +2,6 @@ package cwe
import (
"io"
"io/ioutil"
"net/http"
"net/http/httptest"
"os"
@ -57,7 +56,7 @@ func TestUpdate(t *testing.T) {
cweURL = tc.cweServerUrl
} else {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
b, _ := ioutil.ReadFile(tc.inputZipFile)
b, _ := os.ReadFile(tc.inputZipFile)
_, _ = io.WriteString(w, string(b))
}))
cweURL = ts.URL
@ -66,11 +65,7 @@ func TestUpdate(t *testing.T) {
}()
}
dir, _ := ioutil.TempDir("", "TestUpdate-*")
defer func() {
_ = os.RemoveAll(dir)
}()
dir := t.TempDir()
c := NewCWEWithConfig(cweURL, filepath.Join(dir), 0)
err := c.Update()
switch {
@ -78,10 +73,10 @@ func TestUpdate(t *testing.T) {
require.Error(t, err, tc.name)
default:
// CWE-209.json is one file within good-small-cwe.xml.zip
gotJSON, err := ioutil.ReadFile(filepath.Join(dir, "CWE-209.json"))
gotJSON, err := os.ReadFile(filepath.Join(dir, "CWE-209.json"))
require.NoError(t, err, tc.name)
wantJSON, _ := ioutil.ReadFile(tc.expectedOutputJSONFile)
wantJSON, _ := os.ReadFile(tc.expectedOutputJSONFile)
assert.JSONEq(t, string(wantJSON), string(gotJSON), tc.name)
}
})

View File

@ -5,7 +5,6 @@ import (
"errors"
"flag"
"fmt"
"io/ioutil"
"os"
"testing"
"time"
@ -560,11 +559,11 @@ func TestConfig_Update(t *testing.T) {
assert.True(t, ok, tc.name)
if *update {
err = ioutil.WriteFile(goldenPath, actual, 0666)
err = os.WriteFile(goldenPath, actual, 0666)
assert.NoError(t, err, tc.name)
}
expected, err := ioutil.ReadFile(goldenPath)
expected, err := os.ReadFile(goldenPath)
assert.NoError(t, err, tc.name)
assert.Equal(t, string(expected), string(actual), tc.name)

View File

@ -2,8 +2,6 @@ package glad
import (
"flag"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"testing"
@ -80,12 +78,11 @@ func TestUpdater_WalkDir(t *testing.T) {
goldenPath := filepath.Join(tc.goldenDir, relPath)
if *update {
fmt.Println(goldenPath)
err = ioutil.WriteFile(goldenPath, got, 0666)
err = os.WriteFile(goldenPath, got, 0666)
assert.NoError(t, err, tc.name)
}
want, err := ioutil.ReadFile(goldenPath)
want, err := os.ReadFile(goldenPath)
assert.NoError(t, err, goldenPath)
assert.JSONEq(t, string(want), string(got), tc.name)

3
go.mod
View File

@ -1,6 +1,6 @@
module github.com/aquasecurity/vuln-list-update
go 1.20
go 1.21.5
require (
github.com/PuerkitoBio/goquery v1.8.0
@ -10,7 +10,6 @@ require (
github.com/cheggaaa/pb/v3 v3.1.4
github.com/hashicorp/go-getter v1.7.3
github.com/kylelemons/godebug v1.1.0
github.com/mattn/go-jsonpointer v0.0.1
github.com/parnurzeal/gorequest v0.2.16
github.com/shurcooL/githubv4 v0.0.0-20191127044304-8f68eb5628d0
github.com/shurcooL/graphql v0.0.0-20181231061246-d48a9a75455f

2
go.sum
View File

@ -384,8 +384,6 @@ github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNx
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-jsonpointer v0.0.1 h1:j5m5P9BdP4B/zn6J7oH3KIQSOa2OHmcNAKEozUW7wuE=
github.com/mattn/go-jsonpointer v0.0.1/go.mod h1:1s8vx7JSjlgVRF+LW16MPpWSRZAxyrc1/FYzOonxeao=
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=

View File

@ -5,7 +5,6 @@ import (
"flag"
"log"
"os"
"time"
githubql "github.com/shurcooL/githubv4"
"golang.org/x/oauth2"
@ -53,7 +52,6 @@ func main() {
func run() error {
flag.Parse()
now := time.Now().UTC()
if *vulnListDir != "" {
utils.SetVulnListDir(*vulnListDir)
@ -61,7 +59,8 @@ func run() error {
switch *target {
case "nvd":
if err := nvd.Update(now.Year()); err != nil {
u := nvd.NewUpdater()
if err := u.Update(); err != nil {
return xerrors.Errorf("NVD update error: %w", err)
}
case "redhat":

View File

@ -1,162 +1,232 @@
package nvd
import (
"bufio"
"bytes"
"compress/gzip"
"encoding/json"
"fmt"
"log"
"io"
"log/slog"
"net/http"
"net/url"
"os"
"path/filepath"
"strings"
"strconv"
"time"
pb "github.com/cheggaaa/pb/v3"
jsonpointer "github.com/mattn/go-jsonpointer"
"golang.org/x/xerrors"
"github.com/aquasecurity/vuln-list-update/utils"
)
type NVD struct {
CVEItems []interface{} `json:"CVE_Items"`
}
const (
baseURL = "https://nvd.nist.gov/feeds/json/cve/1.1"
feedDir = "feed"
concurrency = 5
wait = 0
retry = 5
retry = 50
url20 = "https://services.nvd.nist.gov/rest/json/cves/2.0/"
apiDir = "api"
nvdTimeFormat = "2006-01-02T15:04:05"
maxResultsPerPage = 2000
retryAfter = 30 * time.Second
apiKeyEnvName = "NVD_API_KEY"
)
func Update(thisYear int) error {
lastUpdatedDate, err := utils.GetLastUpdatedDate("nvd")
if err != nil {
return err
type Option func(*Updater)
func WithLastModEndDate(lastModEndDate time.Time) Option {
return func(u *Updater) {
u.lastModEndDate = lastModEndDate
}
var old bool
var feeds []string
for _, feed := range []string{"modified", "recent"} {
lastModifiedDate, err := fetchLastModifiedDate(feed)
if err != nil {
return err
}
if lastUpdatedDate.After(lastModifiedDate) {
continue
}
feeds = append(feeds, feed)
duration := lastModifiedDate.Sub(lastUpdatedDate)
if duration > 24*time.Hour*7 {
old = true
}
}
if old {
// Fetch all years
feeds = []string{}
for year := 2002; year <= thisYear; year++ {
feeds = append(feeds, fmt.Sprint(year))
}
}
feedCount := len(feeds)
if feedCount == 0 {
return nil
}
urls := make([]string, feedCount)
for i, feed := range feeds {
url := fmt.Sprintf("%s/nvdcve-1.1-%s.json.gz", baseURL, feed)
urls[i] = url
}
log.Println("Fetching NVD data...")
responses, err := utils.FetchConcurrently(urls, concurrency, wait, retry)
if err != nil {
return xerrors.Errorf("failed to fetch concurrently: %w", err)
}
log.Println("Saving NVD data...")
bar := pb.StartNew(len(responses))
for _, res := range responses {
nvd, err := decode(res)
if err != nil {
return xerrors.Errorf("failed to decode NVD response: %w", err)
}
if err := save(nvd); err != nil {
return err
}
bar.Increment()
}
bar.Finish()
return nil
}
func save(nvd *NVD) error {
for _, item := range nvd.CVEItems {
v, err := jsonpointer.Get(item, "/cve/CVE_data_meta/ID")
if err != nil {
log.Println(err)
continue
}
cveID, ok := v.(string)
if !ok {
log.Println("failed to type assertion")
continue
}
if err = utils.SaveCVEPerYear(filepath.Join(utils.VulnListDir(), feedDir), cveID, item); err != nil {
return xerrors.Errorf("failed to save NVD CVE detail: %w", err)
}
func WithMaxResultsPerPage(maxResultsPerPage int) Option {
return func(u *Updater) {
u.maxResultsPerPage = maxResultsPerPage
}
return nil
}
func fetchLastModifiedDate(feed string) (time.Time, error) {
log.Printf("Fetching NVD metadata(%s)...\n", feed)
func WithBaseURL(url string) Option {
return func(u *Updater) {
u.baseURL = url
}
}
url := fmt.Sprintf("%s/nvdcve-1.1-%s.meta", baseURL, feed)
res, err := utils.FetchURL(url, "", 5)
if err != nil {
return time.Time{}, xerrors.Errorf("fetch error: %w", err)
func WithRetry(retry int) Option {
return func(u *Updater) {
u.retry = retry
}
}
func WithRetryAfter(retryAfter time.Duration) Option {
return func(u *Updater) {
u.retryAfter = retryAfter
}
}
type Updater struct {
baseURL string
apiKey string
maxResultsPerPage int
retry int
retryAfter time.Duration
lastModEndDate time.Time // time.Now() by default
}
func NewUpdater(opts ...Option) *Updater {
u := &Updater{
baseURL: url20,
apiKey: os.Getenv(apiKeyEnvName),
maxResultsPerPage: maxResultsPerPage,
retry: retry,
retryAfter: retryAfter,
lastModEndDate: time.Now().UTC(),
}
scanner := bufio.NewScanner(bytes.NewBuffer(res))
for scanner.Scan() {
line := scanner.Text()
s := strings.SplitN(line, ":", 2)
if len(s) != 2 {
continue
}
if s[0] == "lastModifiedDate" {
t, err := time.Parse(time.RFC3339, s[1])
if err != nil {
return time.Time{}, err
for _, opt := range opts {
opt(u)
}
return u
}
func (u Updater) Update() error {
intervals, err := TimeIntervals(u.lastModEndDate)
if err != nil {
return xerrors.Errorf("unable to build time intervals: %w", err)
}
for _, interval := range intervals {
slog.Info("Fetching NVD entries...", slog.String("start", interval.LastModStartDate),
slog.String("end", interval.LastModEndDate))
totalResults := 1 // Set a dummy value to start the loop
for startIndex := 0; startIndex < totalResults; startIndex += u.maxResultsPerPage {
if totalResults, err = u.saveEntry(interval, startIndex); err != nil {
return xerrors.Errorf("unable to save entry CVEs for %q: %w", interval, err)
}
return t, nil
slog.Info("Fetched NVD entries", slog.Int("total", totalResults), slog.Int("start_index", startIndex))
}
}
return time.Unix(0, 0), nil
// Update last_updated.json at the end.
if err = utils.SetLastUpdatedDate(apiDir, u.lastModEndDate); err != nil {
return xerrors.Errorf("unable to update last_updated.json file: %w", err)
}
return nil
}
func decode(b []byte) (*NVD, error) {
zr, err := gzip.NewReader(bytes.NewBuffer(b))
func (u Updater) saveEntry(interval TimeInterval, startIndex int) (int, error) {
entryURL, err := urlWithParams(u.baseURL, startIndex, u.maxResultsPerPage, interval)
if err != nil {
return nil, err
return 0, xerrors.Errorf("unable to get url with query parameters: %w", err)
}
defer zr.Close()
nvd := &NVD{}
err = json.NewDecoder(zr).Decode(nvd)
entry, err := u.fetchEntry(entryURL)
if err != nil {
return nil, err
return 0, xerrors.Errorf("unable to get entry for %q: %w", entryURL, err)
}
return nvd, nil
for _, vuln := range entry.Vulnerabilities {
if err := utils.SaveCVEPerYear(filepath.Join(utils.VulnListDir(), apiDir), vuln.Cve.ID, vuln.Cve); err != nil {
return 0, xerrors.Errorf("unable to write %s: %w", vuln.Cve.ID, err)
}
}
return entry.TotalResults, nil
}
func (u Updater) fetchEntry(url string) (Entry, error) {
var entry Entry
r, err := u.fetchURL(url)
if err != nil {
return Entry{}, xerrors.Errorf("unable to fetch: %w", err)
} else if r == nil {
return Entry{}, xerrors.Errorf("unable to get entry from %q", url)
}
defer r.Close()
if err = json.NewDecoder(r).Decode(&entry); err != nil {
return Entry{}, xerrors.Errorf("unable to decode response for %q: %w", url, err)
}
return entry, nil
}
func (u Updater) fetchURL(url string) (io.ReadCloser, error) {
var c http.Client
for i := 0; i <= u.retry; i++ {
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
return nil, xerrors.Errorf("unable to build request for %q: %w", url, err)
}
if u.apiKey != "" {
req.Header.Set("apiKey", u.apiKey)
}
resp, err := c.Do(req)
if err != nil {
slog.Error("Response error. Try to get the entry again.", slog.String("error", err.Error()))
continue
}
switch resp.StatusCode {
case http.StatusForbidden:
slog.Error("NVD rate limit. Wait to gain access.")
// NVD limits:
// Without API key: 5 requests / 30 seconds window
// With API key: 50 requests / 30 seconds window
time.Sleep(u.retryAfter)
continue
case http.StatusServiceUnavailable, http.StatusRequestTimeout, http.StatusBadGateway, http.StatusGatewayTimeout:
slog.Error("NVD API is unstable. Try to fetch URL again.", slog.String("status_code", resp.Status))
// NVD API works unstable
time.Sleep(time.Duration(i) * time.Second)
continue
case http.StatusOK:
return resp.Body, nil
default:
return nil, xerrors.Errorf("unexpected status code: %s", resp.Status)
}
}
return nil, xerrors.Errorf("unable to fetch url. Retry limit exceeded.")
}
// TimeIntervals returns time intervals for NVD API
// NVD API doesn't allow to get more than 120 days per request.
// So we need to split the time range into intervals.
func TimeIntervals(endTime time.Time) ([]TimeInterval, error) {
lastUpdatedDate, err := utils.GetLastUpdatedDate(apiDir)
if err != nil {
return nil, xerrors.Errorf("unable to get lastUpdatedDate: %w", err)
}
var intervals []TimeInterval
for endTime.Sub(lastUpdatedDate).Hours()/24 > 120 {
newLastUpdatedDate := lastUpdatedDate.Add(120 * 24 * time.Hour)
intervals = append(intervals, TimeInterval{
LastModStartDate: lastUpdatedDate.Format(nvdTimeFormat),
LastModEndDate: newLastUpdatedDate.Format(nvdTimeFormat),
})
lastUpdatedDate = newLastUpdatedDate
}
// fill latest interval
intervals = append(intervals, TimeInterval{
LastModStartDate: lastUpdatedDate.Format(nvdTimeFormat),
LastModEndDate: endTime.Format(nvdTimeFormat),
})
return intervals, nil
}
func urlWithParams(baseUrl string, startIndex, resultsPerPage int, interval TimeInterval) (string, error) {
u, err := url.Parse(baseUrl)
if err != nil {
return "", xerrors.Errorf("unable to parse %q base url: %w", baseUrl, err)
}
q := u.Query()
q.Set("lastModStartDate", interval.LastModStartDate)
q.Set("lastModEndDate", interval.LastModEndDate)
q.Set("startIndex", strconv.Itoa(startIndex))
q.Set("resultsPerPage", strconv.Itoa(resultsPerPage))
// NVD API doesn't work with escaped `:`
// So we only need to escape `+` for `Z`:
// https://nvd.nist.gov/developers/vulnerabilities:
// `Please note, if a positive Z value is used (such as +01:00 for Central European Time) then the "+" should be encoded in the request as "%2B".`
decoded, err := url.QueryUnescape(q.Encode())
if err != nil {
return "", xerrors.Errorf("unable to decode query params: %w", err)
}
u.RawQuery = decoded
return u.String(), nil
}

265
nvd/nvd_test.go Normal file
View File

@ -0,0 +1,265 @@
package nvd_test
import (
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/aquasecurity/vuln-list-update/nvd"
"github.com/aquasecurity/vuln-list-update/utils"
)
func TestUpdate(t *testing.T) {
tests := []struct {
name string
maxResultsPerPage int
retry int
wantApiKey string
respFiles map[string]string
respStatus int
lastUpdatedTime time.Time
fakeTimeNow time.Time
wantFiles []string
wantError string
}{
{
name: "happy path 1 page",
maxResultsPerPage: 10,
wantApiKey: "test_api_key",
lastUpdatedTime: time.Date(2023, 11, 26, 0, 0, 0, 0, time.UTC),
fakeTimeNow: time.Date(2023, 11, 28, 0, 0, 0, 0, time.UTC),
respFiles: map[string]string{
"resultsPerPage=1&startIndex=0": "testdata/fixtures/rootResp.json",
"resultsPerPage=10&startIndex=0": "testdata/fixtures/respPageFull.json",
},
respStatus: 200,
wantFiles: []string{
filepath.Join("api", "2020", "CVE-2020-8167.json"),
filepath.Join("api", "2021", "CVE-2021-22903.json"),
filepath.Join("api", "2021", "CVE-2021-3881.json"),
"last_updated.json",
},
},
{
name: "happy path 1 page after reconnect",
maxResultsPerPage: 10,
wantApiKey: "test_api_key",
retry: 1,
lastUpdatedTime: time.Date(2023, 11, 26, 0, 0, 0, 0, time.UTC),
fakeTimeNow: time.Date(2023, 11, 28, 0, 0, 0, 0, time.UTC),
respFiles: map[string]string{
"resultsPerPage=1&startIndex=0": "testdata/fixtures/rootResp.json",
"resultsPerPage=10&startIndex=0": "testdata/fixtures/respPageFull.json",
},
respStatus: 403,
wantFiles: []string{
filepath.Join("api", "2020", "CVE-2020-8167.json"),
filepath.Join("api", "2021", "CVE-2021-22903.json"),
filepath.Join("api", "2021", "CVE-2021-3881.json"),
"last_updated.json",
},
},
{
name: "happy path 2 pages",
maxResultsPerPage: 2,
lastUpdatedTime: time.Date(2023, 11, 26, 0, 0, 0, 0, time.UTC),
fakeTimeNow: time.Date(2023, 11, 28, 0, 0, 0, 0, time.UTC),
respFiles: map[string]string{
"resultsPerPage=1&startIndex=0": "testdata/fixtures/rootResp.json",
"resultsPerPage=2&startIndex=0": "testdata/fixtures/respPage1.json",
"resultsPerPage=2&startIndex=2": "testdata/fixtures/respPage2.json",
},
respStatus: 200,
wantFiles: []string{
filepath.Join("api", "2020", "CVE-2020-8167.json"),
filepath.Join("api", "2021", "CVE-2021-22903.json"),
filepath.Join("api", "2021", "CVE-2021-3881.json"),
"last_updated.json",
},
},
{
name: "503 response",
maxResultsPerPage: 10,
wantApiKey: "test_api_key",
lastUpdatedTime: time.Date(2023, 11, 26, 0, 0, 0, 0, time.UTC),
fakeTimeNow: time.Date(2023, 11, 28, 0, 0, 0, 0, time.UTC),
respStatus: 503,
wantError: "unable to fetch url",
},
{
name: "408 response",
maxResultsPerPage: 10,
wantApiKey: "test_api_key",
lastUpdatedTime: time.Date(2023, 11, 26, 0, 0, 0, 0, time.UTC),
fakeTimeNow: time.Date(2023, 11, 28, 0, 0, 0, 0, time.UTC),
respStatus: 408,
wantError: "unable to fetch url",
},
{
name: "502 response",
maxResultsPerPage: 10,
wantApiKey: "test_api_key",
lastUpdatedTime: time.Date(2023, 11, 26, 0, 0, 0, 0, time.UTC),
fakeTimeNow: time.Date(2023, 11, 28, 0, 0, 0, 0, time.UTC),
respStatus: 502,
wantError: "unable to fetch url",
},
{
name: "504 response",
maxResultsPerPage: 10,
wantApiKey: "test_api_key",
lastUpdatedTime: time.Date(2023, 11, 26, 0, 0, 0, 0, time.UTC),
fakeTimeNow: time.Date(2023, 11, 28, 0, 0, 0, 0, time.UTC),
respStatus: 504,
wantError: "unable to fetch url",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.wantApiKey != "" {
t.Setenv("NVD_API_KEY", tt.wantApiKey)
}
// overwrite vuln-list dir
tmpDir := t.TempDir()
savedVulnListDir := utils.VulnListDir()
utils.SetVulnListDir(tmpDir)
defer utils.SetVulnListDir(savedVulnListDir)
// create last_updated.json file into temp dir
err := utils.SetLastUpdatedDate("api", tt.lastUpdatedTime)
require.NoError(t, err)
respStatus := tt.respStatus
mux := http.NewServeMux()
mux.HandleFunc("/", func(resp http.ResponseWriter, req *http.Request) {
if respStatus != 200 {
resp.WriteHeader(respStatus)
// update respStatus update status after reconnection (if retry > 0)
respStatus = 200
return
}
if tt.wantApiKey != "" {
gotApiKey := req.Header.Get("apiKey")
require.Equal(t, tt.wantApiKey, gotApiKey)
}
var filePath string
for params, path := range tt.respFiles {
if strings.Contains(req.URL.String(), params) {
filePath = path
break
}
}
if filePath == "" {
t.Errorf("response files doesn't exist for %q", req.URL.String())
}
b, err := os.ReadFile(filePath)
require.NoError(t, err)
_, err = resp.Write(b)
require.NoError(t, err)
})
ts := httptest.NewServer(mux)
defer ts.Close()
u := nvd.NewUpdater(nvd.WithBaseURL(ts.URL), nvd.WithMaxResultsPerPage(tt.maxResultsPerPage),
nvd.WithRetry(tt.retry), nvd.WithLastModEndDate(tt.fakeTimeNow), nvd.WithRetryAfter(1*time.Second))
err = u.Update()
if tt.wantError != "" {
require.ErrorContains(t, err, tt.wantError)
return
}
require.NoError(t, err)
for _, wantFile := range tt.wantFiles {
got, err := os.ReadFile(filepath.Join(tmpDir, wantFile))
require.NoError(t, err)
want, err := os.ReadFile(filepath.Join("testdata", "golden", wantFile))
require.NoError(t, err)
require.JSONEq(t, string(want), string(got))
}
})
}
}
func TestTimeIntervals(t *testing.T) {
tests := []struct {
name string
lastUpdatedTime time.Time
fakeTimeNow time.Time
wantIntervals []nvd.TimeInterval
}{
{
name: "one interval",
lastUpdatedTime: time.Date(2023, 11, 26, 0, 0, 0, 0, time.UTC),
fakeTimeNow: time.Date(2023, 11, 28, 0, 0, 0, 0, time.UTC),
wantIntervals: []nvd.TimeInterval{
{
LastModStartDate: "2023-11-26T00:00:00",
LastModEndDate: "2023-11-28T00:00:00",
},
},
},
{
name: "two intervals",
lastUpdatedTime: time.Date(2023, 5, 28, 0, 0, 0, 0, time.UTC),
fakeTimeNow: time.Date(2023, 11, 28, 0, 0, 0, 0, time.UTC),
wantIntervals: []nvd.TimeInterval{
{
LastModStartDate: "2023-05-28T00:00:00",
LastModEndDate: "2023-09-25T00:00:00",
},
{
LastModStartDate: "2023-09-25T00:00:00",
LastModEndDate: "2023-11-28T00:00:00",
},
},
},
{
name: "last_updated.json file doesn't exist",
lastUpdatedTime: time.Unix(0, 0),
fakeTimeNow: time.Date(1970, 03, 01, 0, 0, 0, 0, time.UTC),
wantIntervals: []nvd.TimeInterval{
{
LastModStartDate: "1970-01-01T00:00:00",
LastModEndDate: "1970-03-01T00:00:00",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// overwrite vuln-list dir
tmpDir := t.TempDir()
savedVulnListDir := utils.VulnListDir()
utils.SetVulnListDir(tmpDir)
defer utils.SetVulnListDir(savedVulnListDir)
if tt.lastUpdatedTime != time.Unix(0, 0) {
// create last_updated.json file into temp dir
err := utils.SetLastUpdatedDate("api", tt.lastUpdatedTime)
assert.NoError(t, err)
}
gotIntervals, err := nvd.TimeIntervals(tt.fakeTimeNow)
assert.NoError(t, err)
assert.Equal(t, tt.wantIntervals, gotIntervals)
})
}
}

View File

@ -0,0 +1,3 @@
{
"api": "2023-05-10T18:06:42.577411575Z"
}

299
nvd/testdata/fixtures/respPage1.json vendored Normal file
View File

@ -0,0 +1,299 @@
{
"resultsPerPage": 2,
"startIndex": 0,
"totalResults": 3,
"format": "NVD_CVE",
"version": "2.0",
"timestamp": "2023-11-27T04:36:56.737",
"vulnerabilities": [
{
"cve": {
"id": "CVE-2020-8167",
"sourceIdentifier": "support@hackerone.com",
"published": "2020-06-19T18:15:11.163",
"lastModified": "2021-10-21T14:35:21.047",
"vulnStatus": "Analyzed",
"descriptions": [
{
"lang": "en",
"value": "A CSRF vulnerability exists in rails <= 6.0.3 rails-ujs module that could allow attackers to send CSRF tokens to wrong domains."
},
{
"lang": "es",
"value": "Se presenta una vulnerabilidad de tipo CSRF en el módulo rails versiones anteriores a 6.0.3 incluyéndola, rails-ujs que podría permitir a atacantes enviar tokens CSRF a dominios incorrectos"
}
],
"metrics": {
"cvssMetricV31": [
{
"source": "nvd@nist.gov",
"type": "Primary",
"cvssData": {
"version": "3.1",
"vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:N",
"attackVector": "NETWORK",
"attackComplexity": "LOW",
"privilegesRequired": "NONE",
"userInteraction": "REQUIRED",
"scope": "UNCHANGED",
"confidentialityImpact": "NONE",
"integrityImpact": "HIGH",
"availabilityImpact": "NONE",
"baseScore": 6.5,
"baseSeverity": "MEDIUM"
},
"exploitabilityScore": 2.8,
"impactScore": 3.6
}
],
"cvssMetricV2": [
{
"source": "nvd@nist.gov",
"type": "Primary",
"cvssData": {
"version": "2.0",
"vectorString": "AV:N/AC:M/Au:N/C:N/I:P/A:N",
"accessVector": "NETWORK",
"accessComplexity": "MEDIUM",
"authentication": "NONE",
"confidentialityImpact": "NONE",
"integrityImpact": "PARTIAL",
"availabilityImpact": "NONE",
"baseScore": 4.3
},
"baseSeverity": "MEDIUM",
"exploitabilityScore": 8.6,
"impactScore": 2.9,
"acInsufInfo": false,
"obtainAllPrivilege": false,
"obtainUserPrivilege": false,
"obtainOtherPrivilege": false,
"userInteractionRequired": true
}
]
},
"weaknesses": [
{
"source": "nvd@nist.gov",
"type": "Primary",
"description": [
{
"lang": "en",
"value": "CWE-352"
}
]
},
{
"source": "support@hackerone.com",
"type": "Secondary",
"description": [
{
"lang": "en",
"value": "CWE-352"
}
]
}
],
"configurations": [
{
"nodes": [
{
"operator": "OR",
"negate": false,
"cpeMatch": [
{
"vulnerable": true,
"criteria": "cpe:2.3:a:rubyonrails:rails:*:*:*:*:*:*:*:*",
"versionEndExcluding": "5.2.4.3",
"matchCriteriaId": "4357891D-A07C-4E1B-B540-92D6C477E7BB"
},
{
"vulnerable": true,
"criteria": "cpe:2.3:a:rubyonrails:rails:*:*:*:*:*:*:*:*",
"versionStartIncluding": "6.0.0",
"versionEndExcluding": "6.0.3.1",
"matchCriteriaId": "12B5617A-91AC-4B94-BE1A-057DBF322808"
}
]
}
]
},
{
"nodes": [
{
"operator": "OR",
"negate": false,
"cpeMatch": [
{
"vulnerable": true,
"criteria": "cpe:2.3:o:debian:debian_linux:10.0:*:*:*:*:*:*:*",
"matchCriteriaId": "07B237A9-69A3-4A9C-9DA0-4E06BD37AE73"
}
]
}
]
}
],
"references": [
{
"url": "https://groups.google.com/g/rubyonrails-security/c/x9DixQDG9a0",
"source": "support@hackerone.com",
"tags": [
"Mailing List",
"Patch",
"Third Party Advisory"
]
},
{
"url": "https://hackerone.com/reports/189878",
"source": "support@hackerone.com",
"tags": [
"Exploit",
"Third Party Advisory"
]
},
{
"url": "https://www.debian.org/security/2020/dsa-4766",
"source": "support@hackerone.com",
"tags": [
"Third Party Advisory"
]
}
]
}
},
{
"cve": {
"id": "CVE-2021-22903",
"sourceIdentifier": "support@hackerone.com",
"published": "2021-06-11T16:15:11.437",
"lastModified": "2021-10-21T14:32:48.653",
"vulnStatus": "Analyzed",
"descriptions": [
{
"lang": "en",
"value": "The actionpack ruby gem before 6.1.3.2 suffers from a possible open redirect vulnerability. Specially crafted Host headers in combination with certain \"allowed host\" formats can cause the Host Authorization middleware in Action Pack to redirect users to a malicious website. This is similar to CVE-2021-22881. Strings in config.hosts that do not have a leading dot are converted to regular expressions without proper escaping. This causes, for example, `config.hosts << \"sub.example.com\"` to permit a request with a Host header value of `sub-example.com`."
},
{
"lang": "es",
"value": "El actionpack ruby gem versiones anteriores a 6.1.3.2, sufre una posible vulnerabilidad de redireccionamiento abierto. Las cabeceras de Host especialmente diseñadas en combinación con determinados formatos \"allowed host\" pueden hacer que el middleware Host Authorization de Action Pack redirija a usuarios hacia un sitio web malicioso. Esto es similar a CVE-2021-22881. Las cadenas en config.hosts que no tienen un punto inicial se convierten en expresiones regulares sin un escape apropiado. Esto hace que, por ejemplo, \"config.hosts (( \"sub.example.com\"\" permita una petición con un valor de cabecera Host de \"sub-example.com\""
}
],
"metrics": {
"cvssMetricV31": [
{
"source": "nvd@nist.gov",
"type": "Primary",
"cvssData": {
"version": "3.1",
"vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N",
"attackVector": "NETWORK",
"attackComplexity": "LOW",
"privilegesRequired": "NONE",
"userInteraction": "REQUIRED",
"scope": "CHANGED",
"confidentialityImpact": "LOW",
"integrityImpact": "LOW",
"availabilityImpact": "NONE",
"baseScore": 6.1,
"baseSeverity": "MEDIUM"
},
"exploitabilityScore": 2.8,
"impactScore": 2.7
}
],
"cvssMetricV2": [
{
"source": "nvd@nist.gov",
"type": "Primary",
"cvssData": {
"version": "2.0",
"vectorString": "AV:N/AC:M/Au:N/C:P/I:P/A:N",
"accessVector": "NETWORK",
"accessComplexity": "MEDIUM",
"authentication": "NONE",
"confidentialityImpact": "PARTIAL",
"integrityImpact": "PARTIAL",
"availabilityImpact": "NONE",
"baseScore": 5.8
},
"baseSeverity": "MEDIUM",
"exploitabilityScore": 8.6,
"impactScore": 4.9,
"acInsufInfo": false,
"obtainAllPrivilege": false,
"obtainUserPrivilege": false,
"obtainOtherPrivilege": false,
"userInteractionRequired": true
}
]
},
"weaknesses": [
{
"source": "nvd@nist.gov",
"type": "Primary",
"description": [
{
"lang": "en",
"value": "CWE-601"
}
]
},
{
"source": "support@hackerone.com",
"type": "Secondary",
"description": [
{
"lang": "en",
"value": "CWE-601"
}
]
}
],
"configurations": [
{
"nodes": [
{
"operator": "OR",
"negate": false,
"cpeMatch": [
{
"vulnerable": true,
"criteria": "cpe:2.3:a:rubyonrails:rails:*:*:*:*:*:*:*:*",
"versionStartIncluding": "6.1.1",
"versionEndExcluding": "6.1.3.2",
"matchCriteriaId": "3CAFC5D0-4073-430A-B9A1-5CF37A75EC7F"
},
{
"vulnerable": true,
"criteria": "cpe:2.3:a:rubyonrails:rails:6.1.0:rc2:*:*:*:*:*:*",
"matchCriteriaId": "B4431B78-31D7-4845-920B-238B355BF890"
}
]
}
]
}
],
"references": [
{
"url": "https://discuss.rubyonrails.org/t/cve-2021-22903-possible-open-redirect-vulnerability-in-action-pack/77867",
"source": "support@hackerone.com",
"tags": [
"Mitigation",
"Patch",
"Vendor Advisory"
]
},
{
"url": "https://hackerone.com/reports/1148025",
"source": "support@hackerone.com",
"tags": [
"Permissions Required",
"Third Party Advisory"
]
}
]
}
}
]
}

158
nvd/testdata/fixtures/respPage2.json vendored Normal file
View File

@ -0,0 +1,158 @@
{
"resultsPerPage": 1,
"startIndex": 3,
"totalResults": 3,
"format": "NVD_CVE",
"version": "2.0",
"timestamp": "2023-11-27T04:36:56.737",
"vulnerabilities": [
{
"cve": {
"id": "CVE-2021-3881",
"sourceIdentifier": "security@huntr.dev",
"published": "2021-10-15T14:15:07.907",
"lastModified": "2021-10-22T12:29:28.390",
"vulnStatus": "Analyzed",
"descriptions": [
{
"lang": "en",
"value": "libmobi is vulnerable to Out-of-bounds Read"
},
{
"lang": "es",
"value": "libmobi es vulnerable a una lectura fuera de límites"
}
],
"metrics": {
"cvssMetricV31": [
{
"source": "nvd@nist.gov",
"type": "Primary",
"cvssData": {
"version": "3.1",
"vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
"attackVector": "NETWORK",
"attackComplexity": "LOW",
"privilegesRequired": "NONE",
"userInteraction": "NONE",
"scope": "UNCHANGED",
"confidentialityImpact": "HIGH",
"integrityImpact": "HIGH",
"availabilityImpact": "HIGH",
"baseScore": 9.8,
"baseSeverity": "CRITICAL"
},
"exploitabilityScore": 3.9,
"impactScore": 5.9
}
],
"cvssMetricV30": [
{
"source": "security@huntr.dev",
"type": "Secondary",
"cvssData": {
"version": "3.0",
"vectorString": "CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:L",
"attackVector": "NETWORK",
"attackComplexity": "LOW",
"privilegesRequired": "NONE",
"userInteraction": "REQUIRED",
"scope": "CHANGED",
"confidentialityImpact": "LOW",
"integrityImpact": "LOW",
"availabilityImpact": "LOW",
"baseScore": 7.1,
"baseSeverity": "HIGH"
},
"exploitabilityScore": 2.8,
"impactScore": 3.7
}
],
"cvssMetricV2": [
{
"source": "nvd@nist.gov",
"type": "Primary",
"cvssData": {
"version": "2.0",
"vectorString": "AV:N/AC:L/Au:N/C:P/I:P/A:P",
"accessVector": "NETWORK",
"accessComplexity": "LOW",
"authentication": "NONE",
"confidentialityImpact": "PARTIAL",
"integrityImpact": "PARTIAL",
"availabilityImpact": "PARTIAL",
"baseScore": 7.5
},
"baseSeverity": "HIGH",
"exploitabilityScore": 10,
"impactScore": 6.4,
"acInsufInfo": false,
"obtainAllPrivilege": false,
"obtainUserPrivilege": false,
"obtainOtherPrivilege": false,
"userInteractionRequired": false
}
]
},
"weaknesses": [
{
"source": "nvd@nist.gov",
"type": "Primary",
"description": [
{
"lang": "en",
"value": "CWE-125"
}
]
},
{
"source": "security@huntr.dev",
"type": "Secondary",
"description": [
{
"lang": "en",
"value": "CWE-125"
}
]
}
],
"configurations": [
{
"nodes": [
{
"operator": "OR",
"negate": false,
"cpeMatch": [
{
"vulnerable": true,
"criteria": "cpe:2.3:a:libmobi_project:libmobi:*:*:*:*:*:*:*:*",
"versionEndIncluding": "0.7",
"matchCriteriaId": "D2333D42-14FA-48A8-873C-573E718CBD86"
}
]
}
]
}
],
"references": [
{
"url": "https://github.com/bfabiszewski/libmobi/commit/bec783e6212439a335ba6e8df7ab8ed610ca9a21",
"source": "security@huntr.dev",
"tags": [
"Patch",
"Third Party Advisory"
]
},
{
"url": "https://huntr.dev/bounties/540fd115-7de4-4e19-a918-5ee61f5157c1",
"source": "security@huntr.dev",
"tags": [
"Exploit",
"Third Party Advisory"
]
}
]
}
}
]
}

447
nvd/testdata/fixtures/respPageFull.json vendored Normal file
View File

@ -0,0 +1,447 @@
{
"resultsPerPage": 1,
"startIndex": 3,
"totalResults": 3,
"format": "NVD_CVE",
"version": "2.0",
"timestamp": "2023-11-27T04:36:56.737",
"vulnerabilities": [
{
"cve": {
"id": "CVE-2020-8167",
"sourceIdentifier": "support@hackerone.com",
"published": "2020-06-19T18:15:11.163",
"lastModified": "2021-10-21T14:35:21.047",
"vulnStatus": "Analyzed",
"descriptions": [
{
"lang": "en",
"value": "A CSRF vulnerability exists in rails <= 6.0.3 rails-ujs module that could allow attackers to send CSRF tokens to wrong domains."
},
{
"lang": "es",
"value": "Se presenta una vulnerabilidad de tipo CSRF en el módulo rails versiones anteriores a 6.0.3 incluyéndola, rails-ujs que podría permitir a atacantes enviar tokens CSRF a dominios incorrectos"
}
],
"metrics": {
"cvssMetricV31": [
{
"source": "nvd@nist.gov",
"type": "Primary",
"cvssData": {
"version": "3.1",
"vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:N",
"attackVector": "NETWORK",
"attackComplexity": "LOW",
"privilegesRequired": "NONE",
"userInteraction": "REQUIRED",
"scope": "UNCHANGED",
"confidentialityImpact": "NONE",
"integrityImpact": "HIGH",
"availabilityImpact": "NONE",
"baseScore": 6.5,
"baseSeverity": "MEDIUM"
},
"exploitabilityScore": 2.8,
"impactScore": 3.6
}
],
"cvssMetricV2": [
{
"source": "nvd@nist.gov",
"type": "Primary",
"cvssData": {
"version": "2.0",
"vectorString": "AV:N/AC:M/Au:N/C:N/I:P/A:N",
"accessVector": "NETWORK",
"accessComplexity": "MEDIUM",
"authentication": "NONE",
"confidentialityImpact": "NONE",
"integrityImpact": "PARTIAL",
"availabilityImpact": "NONE",
"baseScore": 4.3
},
"baseSeverity": "MEDIUM",
"exploitabilityScore": 8.6,
"impactScore": 2.9,
"acInsufInfo": false,
"obtainAllPrivilege": false,
"obtainUserPrivilege": false,
"obtainOtherPrivilege": false,
"userInteractionRequired": true
}
]
},
"weaknesses": [
{
"source": "nvd@nist.gov",
"type": "Primary",
"description": [
{
"lang": "en",
"value": "CWE-352"
}
]
},
{
"source": "support@hackerone.com",
"type": "Secondary",
"description": [
{
"lang": "en",
"value": "CWE-352"
}
]
}
],
"configurations": [
{
"nodes": [
{
"operator": "OR",
"negate": false,
"cpeMatch": [
{
"vulnerable": true,
"criteria": "cpe:2.3:a:rubyonrails:rails:*:*:*:*:*:*:*:*",
"versionEndExcluding": "5.2.4.3",
"matchCriteriaId": "4357891D-A07C-4E1B-B540-92D6C477E7BB"
},
{
"vulnerable": true,
"criteria": "cpe:2.3:a:rubyonrails:rails:*:*:*:*:*:*:*:*",
"versionStartIncluding": "6.0.0",
"versionEndExcluding": "6.0.3.1",
"matchCriteriaId": "12B5617A-91AC-4B94-BE1A-057DBF322808"
}
]
}
]
},
{
"nodes": [
{
"operator": "OR",
"negate": false,
"cpeMatch": [
{
"vulnerable": true,
"criteria": "cpe:2.3:o:debian:debian_linux:10.0:*:*:*:*:*:*:*",
"matchCriteriaId": "07B237A9-69A3-4A9C-9DA0-4E06BD37AE73"
}
]
}
]
}
],
"references": [
{
"url": "https://groups.google.com/g/rubyonrails-security/c/x9DixQDG9a0",
"source": "support@hackerone.com",
"tags": [
"Mailing List",
"Patch",
"Third Party Advisory"
]
},
{
"url": "https://hackerone.com/reports/189878",
"source": "support@hackerone.com",
"tags": [
"Exploit",
"Third Party Advisory"
]
},
{
"url": "https://www.debian.org/security/2020/dsa-4766",
"source": "support@hackerone.com",
"tags": [
"Third Party Advisory"
]
}
]
}
},
{
"cve": {
"id": "CVE-2021-22903",
"sourceIdentifier": "support@hackerone.com",
"published": "2021-06-11T16:15:11.437",
"lastModified": "2021-10-21T14:32:48.653",
"vulnStatus": "Analyzed",
"descriptions": [
{
"lang": "en",
"value": "The actionpack ruby gem before 6.1.3.2 suffers from a possible open redirect vulnerability. Specially crafted Host headers in combination with certain \"allowed host\" formats can cause the Host Authorization middleware in Action Pack to redirect users to a malicious website. This is similar to CVE-2021-22881. Strings in config.hosts that do not have a leading dot are converted to regular expressions without proper escaping. This causes, for example, `config.hosts << \"sub.example.com\"` to permit a request with a Host header value of `sub-example.com`."
},
{
"lang": "es",
"value": "El actionpack ruby gem versiones anteriores a 6.1.3.2, sufre una posible vulnerabilidad de redireccionamiento abierto. Las cabeceras de Host especialmente diseñadas en combinación con determinados formatos \"allowed host\" pueden hacer que el middleware Host Authorization de Action Pack redirija a usuarios hacia un sitio web malicioso. Esto es similar a CVE-2021-22881. Las cadenas en config.hosts que no tienen un punto inicial se convierten en expresiones regulares sin un escape apropiado. Esto hace que, por ejemplo, \"config.hosts (( \"sub.example.com\"\" permita una petición con un valor de cabecera Host de \"sub-example.com\""
}
],
"metrics": {
"cvssMetricV31": [
{
"source": "nvd@nist.gov",
"type": "Primary",
"cvssData": {
"version": "3.1",
"vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N",
"attackVector": "NETWORK",
"attackComplexity": "LOW",
"privilegesRequired": "NONE",
"userInteraction": "REQUIRED",
"scope": "CHANGED",
"confidentialityImpact": "LOW",
"integrityImpact": "LOW",
"availabilityImpact": "NONE",
"baseScore": 6.1,
"baseSeverity": "MEDIUM"
},
"exploitabilityScore": 2.8,
"impactScore": 2.7
}
],
"cvssMetricV2": [
{
"source": "nvd@nist.gov",
"type": "Primary",
"cvssData": {
"version": "2.0",
"vectorString": "AV:N/AC:M/Au:N/C:P/I:P/A:N",
"accessVector": "NETWORK",
"accessComplexity": "MEDIUM",
"authentication": "NONE",
"confidentialityImpact": "PARTIAL",
"integrityImpact": "PARTIAL",
"availabilityImpact": "NONE",
"baseScore": 5.8
},
"baseSeverity": "MEDIUM",
"exploitabilityScore": 8.6,
"impactScore": 4.9,
"acInsufInfo": false,
"obtainAllPrivilege": false,
"obtainUserPrivilege": false,
"obtainOtherPrivilege": false,
"userInteractionRequired": true
}
]
},
"weaknesses": [
{
"source": "nvd@nist.gov",
"type": "Primary",
"description": [
{
"lang": "en",
"value": "CWE-601"
}
]
},
{
"source": "support@hackerone.com",
"type": "Secondary",
"description": [
{
"lang": "en",
"value": "CWE-601"
}
]
}
],
"configurations": [
{
"nodes": [
{
"operator": "OR",
"negate": false,
"cpeMatch": [
{
"vulnerable": true,
"criteria": "cpe:2.3:a:rubyonrails:rails:*:*:*:*:*:*:*:*",
"versionStartIncluding": "6.1.1",
"versionEndExcluding": "6.1.3.2",
"matchCriteriaId": "3CAFC5D0-4073-430A-B9A1-5CF37A75EC7F"
},
{
"vulnerable": true,
"criteria": "cpe:2.3:a:rubyonrails:rails:6.1.0:rc2:*:*:*:*:*:*",
"matchCriteriaId": "B4431B78-31D7-4845-920B-238B355BF890"
}
]
}
]
}
],
"references": [
{
"url": "https://discuss.rubyonrails.org/t/cve-2021-22903-possible-open-redirect-vulnerability-in-action-pack/77867",
"source": "support@hackerone.com",
"tags": [
"Mitigation",
"Patch",
"Vendor Advisory"
]
},
{
"url": "https://hackerone.com/reports/1148025",
"source": "support@hackerone.com",
"tags": [
"Permissions Required",
"Third Party Advisory"
]
}
]
}
},
{
"cve": {
"id": "CVE-2021-3881",
"sourceIdentifier": "security@huntr.dev",
"published": "2021-10-15T14:15:07.907",
"lastModified": "2021-10-22T12:29:28.390",
"vulnStatus": "Analyzed",
"descriptions": [
{
"lang": "en",
"value": "libmobi is vulnerable to Out-of-bounds Read"
},
{
"lang": "es",
"value": "libmobi es vulnerable a una lectura fuera de límites"
}
],
"metrics": {
"cvssMetricV31": [
{
"source": "nvd@nist.gov",
"type": "Primary",
"cvssData": {
"version": "3.1",
"vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
"attackVector": "NETWORK",
"attackComplexity": "LOW",
"privilegesRequired": "NONE",
"userInteraction": "NONE",
"scope": "UNCHANGED",
"confidentialityImpact": "HIGH",
"integrityImpact": "HIGH",
"availabilityImpact": "HIGH",
"baseScore": 9.8,
"baseSeverity": "CRITICAL"
},
"exploitabilityScore": 3.9,
"impactScore": 5.9
}
],
"cvssMetricV30": [
{
"source": "security@huntr.dev",
"type": "Secondary",
"cvssData": {
"version": "3.0",
"vectorString": "CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:L",
"attackVector": "NETWORK",
"attackComplexity": "LOW",
"privilegesRequired": "NONE",
"userInteraction": "REQUIRED",
"scope": "CHANGED",
"confidentialityImpact": "LOW",
"integrityImpact": "LOW",
"availabilityImpact": "LOW",
"baseScore": 7.1,
"baseSeverity": "HIGH"
},
"exploitabilityScore": 2.8,
"impactScore": 3.7
}
],
"cvssMetricV2": [
{
"source": "nvd@nist.gov",
"type": "Primary",
"cvssData": {
"version": "2.0",
"vectorString": "AV:N/AC:L/Au:N/C:P/I:P/A:P",
"accessVector": "NETWORK",
"accessComplexity": "LOW",
"authentication": "NONE",
"confidentialityImpact": "PARTIAL",
"integrityImpact": "PARTIAL",
"availabilityImpact": "PARTIAL",
"baseScore": 7.5
},
"baseSeverity": "HIGH",
"exploitabilityScore": 10,
"impactScore": 6.4,
"acInsufInfo": false,
"obtainAllPrivilege": false,
"obtainUserPrivilege": false,
"obtainOtherPrivilege": false,
"userInteractionRequired": false
}
]
},
"weaknesses": [
{
"source": "nvd@nist.gov",
"type": "Primary",
"description": [
{
"lang": "en",
"value": "CWE-125"
}
]
},
{
"source": "security@huntr.dev",
"type": "Secondary",
"description": [
{
"lang": "en",
"value": "CWE-125"
}
]
}
],
"configurations": [
{
"nodes": [
{
"operator": "OR",
"negate": false,
"cpeMatch": [
{
"vulnerable": true,
"criteria": "cpe:2.3:a:libmobi_project:libmobi:*:*:*:*:*:*:*:*",
"versionEndIncluding": "0.7",
"matchCriteriaId": "D2333D42-14FA-48A8-873C-573E718CBD86"
}
]
}
]
}
],
"references": [
{
"url": "https://github.com/bfabiszewski/libmobi/commit/bec783e6212439a335ba6e8df7ab8ed610ca9a21",
"source": "security@huntr.dev",
"tags": [
"Patch",
"Third Party Advisory"
]
},
{
"url": "https://huntr.dev/bounties/540fd115-7de4-4e19-a918-5ee61f5157c1",
"source": "security@huntr.dev",
"tags": [
"Exploit",
"Third Party Advisory"
]
}
]
}
}
]
}

166
nvd/testdata/fixtures/rootResp.json vendored Normal file
View File

@ -0,0 +1,166 @@
{
"resultsPerPage": 1,
"startIndex": 0,
"totalResults": 3,
"format": "NVD_CVE",
"version": "2.0",
"timestamp": "2023-11-27T04:36:56.737",
"vulnerabilities": [
{
"cve": {
"id": "CVE-2020-8167",
"sourceIdentifier": "support@hackerone.com",
"published": "2020-06-19T18:15:11.163",
"lastModified": "2021-10-21T14:35:21.047",
"vulnStatus": "Analyzed",
"descriptions": [
{
"lang": "en",
"value": "A CSRF vulnerability exists in rails <= 6.0.3 rails-ujs module that could allow attackers to send CSRF tokens to wrong domains."
},
{
"lang": "es",
"value": "Se presenta una vulnerabilidad de tipo CSRF en el módulo rails versiones anteriores a 6.0.3 incluyéndola, rails-ujs que podría permitir a atacantes enviar tokens CSRF a dominios incorrectos"
}
],
"metrics": {
"cvssMetricV31": [
{
"source": "nvd@nist.gov",
"type": "Primary",
"cvssData": {
"version": "3.1",
"vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:N",
"attackVector": "NETWORK",
"attackComplexity": "LOW",
"privilegesRequired": "NONE",
"userInteraction": "REQUIRED",
"scope": "UNCHANGED",
"confidentialityImpact": "NONE",
"integrityImpact": "HIGH",
"availabilityImpact": "NONE",
"baseScore": 6.5,
"baseSeverity": "MEDIUM"
},
"exploitabilityScore": 2.8,
"impactScore": 3.6
}
],
"cvssMetricV2": [
{
"source": "nvd@nist.gov",
"type": "Primary",
"cvssData": {
"version": "2.0",
"vectorString": "AV:N/AC:M/Au:N/C:N/I:P/A:N",
"accessVector": "NETWORK",
"accessComplexity": "MEDIUM",
"authentication": "NONE",
"confidentialityImpact": "NONE",
"integrityImpact": "PARTIAL",
"availabilityImpact": "NONE",
"baseScore": 4.3
},
"baseSeverity": "MEDIUM",
"exploitabilityScore": 8.6,
"impactScore": 2.9,
"acInsufInfo": false,
"obtainAllPrivilege": false,
"obtainUserPrivilege": false,
"obtainOtherPrivilege": false,
"userInteractionRequired": true
}
]
},
"weaknesses": [
{
"source": "nvd@nist.gov",
"type": "Primary",
"description": [
{
"lang": "en",
"value": "CWE-352"
}
]
},
{
"source": "support@hackerone.com",
"type": "Secondary",
"description": [
{
"lang": "en",
"value": "CWE-352"
}
]
}
],
"configurations": [
{
"nodes": [
{
"operator": "OR",
"negate": false,
"cpeMatch": [
{
"vulnerable": true,
"criteria": "cpe:2.3:a:rubyonrails:rails:*:*:*:*:*:*:*:*",
"versionEndExcluding": "5.2.4.3",
"matchCriteriaId": "4357891D-A07C-4E1B-B540-92D6C477E7BB"
},
{
"vulnerable": true,
"criteria": "cpe:2.3:a:rubyonrails:rails:*:*:*:*:*:*:*:*",
"versionStartIncluding": "6.0.0",
"versionEndExcluding": "6.0.3.1",
"matchCriteriaId": "12B5617A-91AC-4B94-BE1A-057DBF322808"
}
]
}
]
},
{
"nodes": [
{
"operator": "OR",
"negate": false,
"cpeMatch": [
{
"vulnerable": true,
"criteria": "cpe:2.3:o:debian:debian_linux:10.0:*:*:*:*:*:*:*",
"matchCriteriaId": "07B237A9-69A3-4A9C-9DA0-4E06BD37AE73"
}
]
}
]
}
],
"references": [
{
"url": "https://groups.google.com/g/rubyonrails-security/c/x9DixQDG9a0",
"source": "support@hackerone.com",
"tags": [
"Mailing List",
"Patch",
"Third Party Advisory"
]
},
{
"url": "https://hackerone.com/reports/189878",
"source": "support@hackerone.com",
"tags": [
"Exploit",
"Third Party Advisory"
]
},
{
"url": "https://www.debian.org/security/2020/dsa-4766",
"source": "support@hackerone.com",
"tags": [
"Third Party Advisory"
]
}
]
}
}
]
}

View File

@ -0,0 +1,154 @@
{
"id": "CVE-2020-8167",
"sourceIdentifier": "support@hackerone.com",
"published": "2020-06-19T18:15:11.163",
"lastModified": "2021-10-21T14:35:21.047",
"vulnStatus": "Analyzed",
"descriptions": [
{
"lang": "en",
"value": "A CSRF vulnerability exists in rails \u003c= 6.0.3 rails-ujs module that could allow attackers to send CSRF tokens to wrong domains."
},
{
"lang": "es",
"value": "Se presenta una vulnerabilidad de tipo CSRF en el módulo rails versiones anteriores a 6.0.3 incluyéndola, rails-ujs que podría permitir a atacantes enviar tokens CSRF a dominios incorrectos"
}
],
"metrics": {
"cvssMetricV31": [
{
"source": "nvd@nist.gov",
"type": "Primary",
"cvssData": {
"version": "3.1",
"vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:N",
"attackVector": "NETWORK",
"attackComplexity": "LOW",
"privilegesRequired": "NONE",
"userInteraction": "REQUIRED",
"scope": "UNCHANGED",
"confidentialityImpact": "NONE",
"integrityImpact": "HIGH",
"availabilityImpact": "NONE",
"baseScore": 6.5,
"baseSeverity": "MEDIUM"
},
"exploitabilityScore": 2.8,
"impactScore": 3.6
}
],
"cvssMetricV2": [
{
"source": "nvd@nist.gov",
"type": "Primary",
"cvssData": {
"version": "2.0",
"vectorString": "AV:N/AC:M/Au:N/C:N/I:P/A:N",
"accessVector": "NETWORK",
"accessComplexity": "MEDIUM",
"authentication": "NONE",
"confidentialityImpact": "NONE",
"integrityImpact": "PARTIAL",
"availabilityImpact": "NONE",
"baseScore": 4.3
},
"baseSeverity": "MEDIUM",
"exploitabilityScore": 8.6,
"impactScore": 2.9,
"acInsufInfo": false,
"obtainAllPrivilege": false,
"obtainUserPrivilege": false,
"obtainOtherPrivilege": false,
"userInteractionRequired": true
}
]
},
"weaknesses": [
{
"source": "nvd@nist.gov",
"type": "Primary",
"description": [
{
"lang": "en",
"value": "CWE-352"
}
]
},
{
"source": "support@hackerone.com",
"type": "Secondary",
"description": [
{
"lang": "en",
"value": "CWE-352"
}
]
}
],
"configurations": [
{
"nodes": [
{
"operator": "OR",
"negate": false,
"cpeMatch": [
{
"vulnerable": true,
"criteria": "cpe:2.3:a:rubyonrails:rails:*:*:*:*:*:*:*:*",
"matchCriteriaId": "4357891D-A07C-4E1B-B540-92D6C477E7BB",
"versionEndExcluding": "5.2.4.3"
},
{
"vulnerable": true,
"criteria": "cpe:2.3:a:rubyonrails:rails:*:*:*:*:*:*:*:*",
"matchCriteriaId": "12B5617A-91AC-4B94-BE1A-057DBF322808",
"versionStartIncluding": "6.0.0",
"versionEndExcluding": "6.0.3.1"
}
]
}
]
},
{
"nodes": [
{
"operator": "OR",
"negate": false,
"cpeMatch": [
{
"vulnerable": true,
"criteria": "cpe:2.3:o:debian:debian_linux:10.0:*:*:*:*:*:*:*",
"matchCriteriaId": "07B237A9-69A3-4A9C-9DA0-4E06BD37AE73"
}
]
}
]
}
],
"references": [
{
"url": "https://groups.google.com/g/rubyonrails-security/c/x9DixQDG9a0",
"source": "support@hackerone.com",
"tags": [
"Mailing List",
"Patch",
"Third Party Advisory"
]
},
{
"url": "https://hackerone.com/reports/189878",
"source": "support@hackerone.com",
"tags": [
"Exploit",
"Third Party Advisory"
]
},
{
"url": "https://www.debian.org/security/2020/dsa-4766",
"source": "support@hackerone.com",
"tags": [
"Third Party Advisory"
]
}
]
}

View File

@ -0,0 +1,131 @@
{
"id": "CVE-2021-22903",
"sourceIdentifier": "support@hackerone.com",
"published": "2021-06-11T16:15:11.437",
"lastModified": "2021-10-21T14:32:48.653",
"vulnStatus": "Analyzed",
"descriptions": [
{
"lang": "en",
"value": "The actionpack ruby gem before 6.1.3.2 suffers from a possible open redirect vulnerability. Specially crafted Host headers in combination with certain \"allowed host\" formats can cause the Host Authorization middleware in Action Pack to redirect users to a malicious website. This is similar to CVE-2021-22881. Strings in config.hosts that do not have a leading dot are converted to regular expressions without proper escaping. This causes, for example, `config.hosts \u003c\u003c \"sub.example.com\"` to permit a request with a Host header value of `sub-example.com`."
},
{
"lang": "es",
"value": "El actionpack ruby gem versiones anteriores a 6.1.3.2, sufre una posible vulnerabilidad de redireccionamiento abierto. Las cabeceras de Host especialmente diseñadas en combinación con determinados formatos \"allowed host\" pueden hacer que el middleware Host Authorization de Action Pack redirija a usuarios hacia un sitio web malicioso. Esto es similar a CVE-2021-22881. Las cadenas en config.hosts que no tienen un punto inicial se convierten en expresiones regulares sin un escape apropiado. Esto hace que, por ejemplo, \"config.hosts (( \"sub.example.com\"\" permita una petición con un valor de cabecera Host de \"sub-example.com\""
}
],
"metrics": {
"cvssMetricV31": [
{
"source": "nvd@nist.gov",
"type": "Primary",
"cvssData": {
"version": "3.1",
"vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N",
"attackVector": "NETWORK",
"attackComplexity": "LOW",
"privilegesRequired": "NONE",
"userInteraction": "REQUIRED",
"scope": "CHANGED",
"confidentialityImpact": "LOW",
"integrityImpact": "LOW",
"availabilityImpact": "NONE",
"baseScore": 6.1,
"baseSeverity": "MEDIUM"
},
"exploitabilityScore": 2.8,
"impactScore": 2.7
}
],
"cvssMetricV2": [
{
"source": "nvd@nist.gov",
"type": "Primary",
"cvssData": {
"version": "2.0",
"vectorString": "AV:N/AC:M/Au:N/C:P/I:P/A:N",
"accessVector": "NETWORK",
"accessComplexity": "MEDIUM",
"authentication": "NONE",
"confidentialityImpact": "PARTIAL",
"integrityImpact": "PARTIAL",
"availabilityImpact": "NONE",
"baseScore": 5.8
},
"baseSeverity": "MEDIUM",
"exploitabilityScore": 8.6,
"impactScore": 4.9,
"acInsufInfo": false,
"obtainAllPrivilege": false,
"obtainUserPrivilege": false,
"obtainOtherPrivilege": false,
"userInteractionRequired": true
}
]
},
"weaknesses": [
{
"source": "nvd@nist.gov",
"type": "Primary",
"description": [
{
"lang": "en",
"value": "CWE-601"
}
]
},
{
"source": "support@hackerone.com",
"type": "Secondary",
"description": [
{
"lang": "en",
"value": "CWE-601"
}
]
}
],
"configurations": [
{
"nodes": [
{
"operator": "OR",
"negate": false,
"cpeMatch": [
{
"vulnerable": true,
"criteria": "cpe:2.3:a:rubyonrails:rails:*:*:*:*:*:*:*:*",
"matchCriteriaId": "3CAFC5D0-4073-430A-B9A1-5CF37A75EC7F",
"versionStartIncluding": "6.1.1",
"versionEndExcluding": "6.1.3.2"
},
{
"vulnerable": true,
"criteria": "cpe:2.3:a:rubyonrails:rails:6.1.0:rc2:*:*:*:*:*:*",
"matchCriteriaId": "B4431B78-31D7-4845-920B-238B355BF890"
}
]
}
]
}
],
"references": [
{
"url": "https://discuss.rubyonrails.org/t/cve-2021-22903-possible-open-redirect-vulnerability-in-action-pack/77867",
"source": "support@hackerone.com",
"tags": [
"Mitigation",
"Patch",
"Vendor Advisory"
]
},
{
"url": "https://hackerone.com/reports/1148025",
"source": "support@hackerone.com",
"tags": [
"Permissions Required",
"Third Party Advisory"
]
}
]
}

View File

@ -0,0 +1,146 @@
{
"id": "CVE-2021-3881",
"sourceIdentifier": "security@huntr.dev",
"published": "2021-10-15T14:15:07.907",
"lastModified": "2021-10-22T12:29:28.390",
"vulnStatus": "Analyzed",
"descriptions": [
{
"lang": "en",
"value": "libmobi is vulnerable to Out-of-bounds Read"
},
{
"lang": "es",
"value": "libmobi es vulnerable a una lectura fuera de límites"
}
],
"metrics": {
"cvssMetricV31": [
{
"source": "nvd@nist.gov",
"type": "Primary",
"cvssData": {
"version": "3.1",
"vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
"attackVector": "NETWORK",
"attackComplexity": "LOW",
"privilegesRequired": "NONE",
"userInteraction": "NONE",
"scope": "UNCHANGED",
"confidentialityImpact": "HIGH",
"integrityImpact": "HIGH",
"availabilityImpact": "HIGH",
"baseScore": 9.8,
"baseSeverity": "CRITICAL"
},
"exploitabilityScore": 3.9,
"impactScore": 5.9
}
],
"cvssMetricV30": [
{
"source": "security@huntr.dev",
"type": "Secondary",
"cvssData": {
"version": "3.0",
"vectorString": "CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:L",
"attackVector": "NETWORK",
"attackComplexity": "LOW",
"privilegesRequired": "NONE",
"userInteraction": "REQUIRED",
"scope": "CHANGED",
"confidentialityImpact": "LOW",
"integrityImpact": "LOW",
"availabilityImpact": "LOW",
"baseScore": 7.1,
"baseSeverity": "HIGH"
},
"exploitabilityScore": 2.8,
"impactScore": 3.7
}
],
"cvssMetricV2": [
{
"source": "nvd@nist.gov",
"type": "Primary",
"cvssData": {
"version": "2.0",
"vectorString": "AV:N/AC:L/Au:N/C:P/I:P/A:P",
"accessVector": "NETWORK",
"accessComplexity": "LOW",
"authentication": "NONE",
"confidentialityImpact": "PARTIAL",
"integrityImpact": "PARTIAL",
"availabilityImpact": "PARTIAL",
"baseScore": 7.5
},
"baseSeverity": "HIGH",
"exploitabilityScore": 10,
"impactScore": 6.4,
"acInsufInfo": false,
"obtainAllPrivilege": false,
"obtainUserPrivilege": false,
"obtainOtherPrivilege": false,
"userInteractionRequired": false
}
]
},
"weaknesses": [
{
"source": "nvd@nist.gov",
"type": "Primary",
"description": [
{
"lang": "en",
"value": "CWE-125"
}
]
},
{
"source": "security@huntr.dev",
"type": "Secondary",
"description": [
{
"lang": "en",
"value": "CWE-125"
}
]
}
],
"configurations": [
{
"nodes": [
{
"operator": "OR",
"negate": false,
"cpeMatch": [
{
"vulnerable": true,
"criteria": "cpe:2.3:a:libmobi_project:libmobi:*:*:*:*:*:*:*:*",
"matchCriteriaId": "D2333D42-14FA-48A8-873C-573E718CBD86",
"versionEndIncluding": "0.7"
}
]
}
]
}
],
"references": [
{
"url": "https://github.com/bfabiszewski/libmobi/commit/bec783e6212439a335ba6e8df7ab8ed610ca9a21",
"source": "security@huntr.dev",
"tags": [
"Patch",
"Third Party Advisory"
]
},
{
"url": "https://huntr.dev/bounties/540fd115-7de4-4e19-a918-5ee61f5157c1",
"source": "security@huntr.dev",
"tags": [
"Exploit",
"Third Party Advisory"
]
}
]
}

3
nvd/testdata/golden/last_updated.json vendored Normal file
View File

@ -0,0 +1,3 @@
{
"api":"2023-11-28T00:00:00Z"
}

175
nvd/types.go Normal file
View File

@ -0,0 +1,175 @@
package nvd
// Entry is based on https://csrc.nist.gov/schema/nvd/api/2.0/cve_api_json_2.0.schema
type Entry struct {
ResultsPerPage int `json:"resultsPerPage"`
StartIndex int `json:"startIndex"`
TotalResults int `json:"totalResults"`
Format string `json:"format"`
Version string `json:"version"`
Timestamp string `json:"timestamp"`
Vulnerabilities []Vulnerability `json:"vulnerabilities"`
}
type Vulnerability struct {
Cve Cve
}
type Cve struct {
ID string `json:"id"`
SourceIdentifier string `json:"sourceIdentifier,omitempty"`
Published string `json:"published"`
LastModified string `json:"lastModified"`
VulnStatus string `json:"vulnStatus,omitempty"`
EvaluatorComment string `json:"evaluatorComment,omitempty"`
EvaluatorSolution string `json:"evaluatorSolution,omitempty"`
EvaluatorImpact string `json:"evaluatorImpact,omitempty"`
CisaExploitAdd string `json:"cisaExploitAdd,omitempty"`
CisaActionDue string `json:"cisaActionDue,omitempty"`
Descriptions []LangString `json:"descriptions"`
Metrics Metrics `json:"metrics,omitempty"`
Weaknesses []Weakness `json:"weaknesses,omitempty"`
Configurations []Configuration `json:"configurations,omitempty"`
References []Reference `json:"references"`
VendorComments []VendorComment `json:"vendorComments,omitempty"`
}
type LangString struct {
Lang string `json:"lang"`
Value string `json:"value"`
}
type Reference struct {
URL string `json:"url"`
Source string `json:"source,omitempty"`
Tags []string `json:"tags,omitempty"`
}
type Metrics struct {
CvssMetricV31 []CvssMetricV3 `json:"cvssMetricV31,omitempty"`
CvssMetricV30 []CvssMetricV3 `json:"cvssMetricV30,omitempty"`
CvssMetricV2 []CvssMetricV2 `json:"cvssMetricV2,omitempty"`
}
// CvssMetricV3 is based on https://csrc.nist.gov/schema/nvd/api/2.0/cve_api_json_2.0.schema.
// v3.0 and v3.1 have only one difference: `cvssData`.
// But we can use `cvssData` v3.0 for v3.1 (see below).
// So we can use the same structure for v3.0 and v3.1.
type CvssMetricV3 struct {
Source string `json:"source"`
Type string `json:"type"`
CvssData CvssDataV30 `json:"cvssData"`
ExploitabilityScore float64 `json:"exploitabilityScore,omitempty"`
ImpactScore float64 `json:"impactScore,omitempty"`
}
// CvssDataV30 is based on https://csrc.nist.gov/schema/nvd/api/2.0/external/cvss-v3.0.json
// v3.0 and v3.1 have only one difference: `patterns` for `vectorString`.
// So we can use version 3.0 for version 3.1.
type CvssDataV30 struct {
Version string `json:"version"`
VectorString string `json:"vectorString"`
AttackVector string `json:"attackVector,omitempty"`
AttackComplexity string `json:"attackComplexity,omitempty"`
PrivilegesRequired string `json:"privilegesRequired,omitempty"`
UserInteraction string `json:"userInteraction,omitempty"`
Scope string `json:"scope,omitempty"`
ConfidentialityImpact string `json:"confidentialityImpact,omitempty"`
IntegrityImpact string `json:"integrityImpact,omitempty"`
AvailabilityImpact string `json:"availabilityImpact,omitempty"`
BaseScore float64 `json:"baseScore"`
BaseSeverity string `json:"baseSeverity"`
ExploitCodeMaturity string `json:"exploitCodeMaturity,omitempty"`
RemediationLevel string `json:"remediationLevel,omitempty"`
ReportConfidence string `json:"reportConfidence,omitempty"`
TemporalScore float64 `json:"temporalScore,omitempty"`
TemporalSeverity string `json:"temporalSeverity,omitempty"`
ConfidentialityRequirement string `json:"confidentialityRequirement,omitempty"`
IntegrityRequirement string `json:"integrityRequirement,omitempty"`
AvailabilityRequirement string `json:"availabilityRequirement,omitempty"`
ModifiedAttackVector string `json:"modifiedAttackVector,omitempty"`
ModifiedAttackComplexity string `json:"modifiedAttackComplexity,omitempty"`
ModifiedPrivilegesRequired string `json:"modifiedPrivilegesRequired,omitempty"`
ModifiedUserInteraction string `json:"modifiedUserInteraction,omitempty"`
ModifiedScope string `json:"modifiedScope,omitempty"`
ModifiedConfidentialityImpact string `json:"modifiedConfidentialityImpact,omitempty"`
ModifiedIntegrityImpact string `json:"modifiedIntegrityImpact,omitempty"`
ModifiedAvailabilityImpact string `json:"modifiedAvailabilityImpact,omitempty"`
EnvironmentalScore float64 `json:"environmentalScore,omitempty"`
EnvironmentalSeverity string `json:"environmentalSeverity,omitempty"`
}
type CvssMetricV2 struct {
Source string `json:"source"`
Type string `json:"type"`
CvssData CvssDataV20 `json:"cvssData"`
BaseSeverity string `json:"baseSeverity,omitempty"`
ExploitabilityScore float64 `json:"exploitabilityScore,omitempty"`
ImpactScore float64 `json:"impactScore,omitempty"`
AcInsufInfo bool `json:"acInsufInfo"`
ObtainAllPrivilege bool `json:"obtainAllPrivilege"`
ObtainUserPrivilege bool `json:"obtainUserPrivilege"`
ObtainOtherPrivilege bool `json:"obtainOtherPrivilege"`
UserInteractionRequired bool `json:"userInteractionRequired"`
}
// CvssDataV20 is based on https://csrc.nist.gov/schema/nvd/api/2.0/external/cvss-v2.0.json
type CvssDataV20 struct {
Version string `json:"version"`
VectorString string `json:"vectorString"`
AccessVector string `json:"accessVector,omitempty"`
AccessComplexity string `json:"accessComplexity,omitempty"`
Authentication string `json:"authentication,omitempty"`
ConfidentialityImpact string `json:"confidentialityImpact,omitempty"`
IntegrityImpact string `json:"integrityImpact,omitempty"`
AvailabilityImpact string `json:"availabilityImpact,omitempty"`
BaseScore float64 `json:"baseScore"`
Exploitability string `json:"exploitability,omitempty"`
RemediationLevel string `json:"remediationLevel,omitempty"`
ReportConfidence string `json:"reportConfidence,omitempty"`
TemporalScore float64 `json:"temporalScore,omitempty"`
CollateralDamagePotential string `json:"collateralDamagePotential,omitempty"`
TargetDistribution string `json:"targetDistribution,omitempty"`
ConfidentialityRequirement string `json:"confidentialityRequirement,omitempty"`
IntegrityRequirement string `json:"integrityRequirement,omitempty"`
AvailabilityRequirement string `json:"availabilityRequirement,omitempty"`
EnvironmentalScore float64 `json:"environmentalScore,omitempty"`
}
type Weakness struct {
Source string `json:"source"`
Type string `json:"type"`
Description []LangString `json:"description"`
}
type Configuration struct {
Operator string `json:"operator,omitempty"`
Negate bool `json:"negate,omitempty"`
Nodes []Node `json:"nodes"`
}
type Node struct {
Operator string `json:"operator"`
Negate bool `json:"negate"`
CpeMatch []CpeMatch `json:"cpeMatch"`
}
type CpeMatch struct {
Vulnerable bool `json:"vulnerable"`
Criteria string `json:"criteria"`
MatchCriteriaID string `json:"matchCriteriaId"`
VersionStartExcluding string `json:"versionStartExcluding,omitempty"`
VersionStartIncluding string `json:"versionStartIncluding,omitempty"`
VersionEndExcluding string `json:"versionEndExcluding,omitempty"`
VersionEndIncluding string `json:"versionEndIncluding,omitempty"`
}
type VendorComment struct {
Organization string `json:"organization"`
Comment string `json:"comment"`
LastModified string `json:"lastModified"`
}
type TimeInterval struct {
LastModStartDate string
LastModEndDate string
}

View File

@ -2,7 +2,6 @@ package oval_test
import (
"flag"
"io/ioutil"
"net/http"
"net/http/httptest"
"os"
@ -120,7 +119,7 @@ func TestConfig_Update(t *testing.T) {
http.NotFound(w, r)
return
}
b, err := ioutil.ReadFile(filePath)
b, err := os.ReadFile(filePath)
assert.NoError(t, err, tc.name)
_, err = w.Write(b)
assert.NoError(t, err, tc.name)
@ -159,11 +158,11 @@ func TestConfig_Update(t *testing.T) {
assert.True(t, ok, tc.name)
if *update {
err = ioutil.WriteFile(goldenPath, actual, 0666)
err = os.WriteFile(goldenPath, actual, 0666)
assert.NoError(t, err, tc.name)
}
expected, err := ioutil.ReadFile(goldenPath)
expected, err := os.ReadFile(goldenPath)
assert.NoError(t, err, tc.name)
assert.Equal(t, expected, actual, tc.name)

View File

@ -2,7 +2,7 @@ package oval_test
import (
"encoding/xml"
"io/ioutil"
"os"
"testing"
"github.com/kylelemons/godebug/pretty"
@ -178,7 +178,7 @@ func TestRedhatCVEJSON_UnmarshalJSON(t *testing.T) {
}
for testname, tt := range tests {
t.Run(testname, func(t *testing.T) {
xmlByte, err := ioutil.ReadFile(tt.in)
xmlByte, err := os.ReadFile(tt.in)
if err != nil {
require.NoError(t, err)
}

View File

@ -3,7 +3,6 @@ package photon_test
import (
"flag"
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"os"
@ -139,7 +138,7 @@ func TestConfig_Update(t *testing.T) {
http.NotFound(w, r)
return
}
b, err := ioutil.ReadFile(filePath)
b, err := os.ReadFile(filePath)
assert.NoError(t, err, tc.name)
_, err = w.Write(b)
assert.NoError(t, err, tc.name)
@ -182,11 +181,11 @@ func TestConfig_Update(t *testing.T) {
assert.True(t, ok, tc.name)
if *update {
err = ioutil.WriteFile(goldenPath, actual, 0666)
err = os.WriteFile(goldenPath, actual, 0666)
assert.NoError(t, err, tc.name)
}
expected, err := ioutil.ReadFile(goldenPath)
expected, err := os.ReadFile(goldenPath)
assert.NoError(t, err, tc.name)
assert.Equal(t, expected, actual, tc.name)

View File

@ -2,7 +2,7 @@ package securitydataapi_test
import (
"encoding/json"
"io/ioutil"
"os"
"reflect"
"testing"
@ -117,7 +117,7 @@ func TestRedhatCVEJSON_UnmarshalJSON(t *testing.T) {
}
for testname, tt := range tests {
t.Run(testname, func(t *testing.T) {
jsonByte, err := ioutil.ReadFile(tt.in)
jsonByte, err := os.ReadFile(tt.in)
if err != nil {
t.Fatalf("unknown error: %s", err)
}

View File

@ -2,7 +2,6 @@ package cvrf_test
import (
"flag"
"io/ioutil"
"net/http"
"net/http/httptest"
"os"
@ -107,7 +106,7 @@ func TestConfig_Update(t *testing.T) {
http.NotFound(w, r)
return
}
b, err := ioutil.ReadFile(filePath)
b, err := os.ReadFile(filePath)
assert.NoError(t, err, tc.name)
_, err = w.Write(b)
assert.NoError(t, err, tc.name)
@ -146,10 +145,10 @@ func TestConfig_Update(t *testing.T) {
goldenPath, ok := tc.goldenFiles[path]
assert.True(t, ok, tc.name)
if *update {
err = ioutil.WriteFile(goldenPath, actual, 0666)
err = os.WriteFile(goldenPath, actual, 0666)
assert.NoError(t, err, tc.name)
}
expected, err := ioutil.ReadFile(goldenPath)
expected, err := os.ReadFile(goldenPath)
assert.NoError(t, err, tc.name)
assert.Equal(t, string(expected), string(actual), tc.name)

View File

@ -2,7 +2,6 @@ package ubuntu
import (
"io"
"io/ioutil"
"log"
"os"
"path/filepath"
@ -147,7 +146,7 @@ func parse(r io.Reader) (vuln *Vulnerability, err error) {
vuln.Patches = map[Package]Statuses{}
vuln.UpstreamLinks = map[Package][]string{}
all, err := ioutil.ReadAll(r)
all, err := io.ReadAll(r)
if err != nil {
return nil, err
}

View File

@ -2,7 +2,6 @@ package utils
import (
"encoding/json"
"io/ioutil"
"os"
"path/filepath"
"time"
@ -28,13 +27,14 @@ func GetLastUpdatedDate(dist string) (time.Time, error) {
t, ok := lastUpdated[dist]
if !ok {
return time.Unix(0, 0), nil
return time.Unix(0, 0).UTC(), nil
}
return t, nil
}
func getLastUpdatedDate() (map[string]time.Time, error) {
lastUpdatedFilePath = filepath.Join(VulnListDir(), lastUpdatedFile) // update path if VulnListDir has been changed.
lastUpdated := LastUpdated{}
if _, err := os.Stat(lastUpdatedFilePath); os.IsNotExist(err) {
return lastUpdated, nil
@ -63,7 +63,7 @@ func SetLastUpdatedDate(dist string, lastUpdatedDate time.Time) error {
if err != nil {
return err
}
if err = ioutil.WriteFile(lastUpdatedFilePath, b, 0600); err != nil {
if err = os.WriteFile(lastUpdatedFilePath, b, 0600); err != nil {
return xerrors.Errorf("failed to write last updated date: %w", err)
}

View File

@ -2,7 +2,6 @@ package wolfi_test
import (
"flag"
"io/ioutil"
"net/http"
"net/http/httptest"
"net/url"
@ -99,10 +98,10 @@ func TestUpdater_Update(t *testing.T) {
goldenPath, ok := tt.goldenFiles[path]
require.True(t, ok, path)
if *update {
err = ioutil.WriteFile(goldenPath, actual, 0666)
err = os.WriteFile(goldenPath, actual, 0666)
require.NoError(t, err, goldenPath)
}
expected, err := ioutil.ReadFile(goldenPath)
expected, err := os.ReadFile(goldenPath)
assert.NoError(t, err, goldenPath)
assert.JSONEq(t, string(expected), string(actual), path)