├── db ├── rdb_test.go ├── redis_test.go └── util.go ├── config ├── config_test.go └── config.go ├── .dockerignore ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── FEATURE_REQUEST.md │ ├── SUPPORT_QUESTION.md │ └── BUG_REPORT.md ├── workflows │ ├── test.yml │ ├── goreleaser.yml │ ├── golangci.yml │ └── docker-build.yml ├── dependabot.yml └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── main.go ├── commands ├── version.go ├── fetch.go ├── fetchmitre.go ├── fetchnvd.go ├── fetchjvn.go ├── fetcheuvd.go ├── fetchcisco.go ├── fetchfortinet.go ├── fetchpaloalto.go ├── fetchvulncheck.go ├── root.go ├── server.go └── search.go ├── util └── util.go ├── Dockerfile ├── .revive.toml ├── .goreleaser.yml ├── fetcher ├── cisco │ └── types.go ├── util_test.go ├── euvd │ ├── types.go │ ├── euvd.go │ └── euvd_test.go ├── fortinet │ ├── types.go │ └── fortinet.go ├── jvn │ ├── jvn_test.go │ └── jvn.go ├── util.go ├── fetcher.go ├── vulncheck │ ├── vulncheck.go │ └── types.go └── nvd │ ├── nvd.go │ └── types.go ├── models ├── euvd.go ├── cisco.go ├── jvn.go ├── fortinet.go ├── models_test.go ├── paloalto.go ├── vulncheck.go ├── nvd.go └── models.go ├── GNUmakefile ├── .golangci.yml ├── log └── log.go ├── go.mod ├── server └── server.go └── LICENSE /db/rdb_test.go: -------------------------------------------------------------------------------- 1 | package db 2 | -------------------------------------------------------------------------------- /db/redis_test.go: -------------------------------------------------------------------------------- 1 | package db 2 | -------------------------------------------------------------------------------- /config/config_test.go: -------------------------------------------------------------------------------- 1 | package config 2 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .dockerignore 2 | Dockerfile 3 | vendor/ 4 | cve.sqlite3 5 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: kotakanbe 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | coverage.out 3 | vendor/ 4 | go-cve-dictionary 5 | *.sqlite3 6 | *.sqlite3-shm 7 | *.sqlite3-wal 8 | *.sqlite3-journal 9 | tags 10 | /dist/ 11 | -------------------------------------------------------------------------------- /config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | // Version ... Version 4 | var Version = "`make build` or `make install` will show the version" 5 | 6 | // Revision of Git 7 | var Revision string 8 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/FEATURE_REQUEST.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | labels: enhancement 4 | about: I have a suggestion (and might want to implement myself)! 5 | --- 6 | 7 | 10 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/SUPPORT_QUESTION.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Support Question 3 | labels: question 4 | about: If you have a question about go-cve-dictionary. 5 | --- 6 | 7 | 11 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: [pull_request] 4 | 5 | jobs: 6 | build: 7 | name: Build 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Check out code into the Go module directory 11 | uses: actions/checkout@v6 12 | - name: Set up Go 1.x 13 | uses: actions/setup-go@v6 14 | with: 15 | go-version-file: go.mod 16 | - name: Test 17 | run: make test 18 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strings" 7 | 8 | "github.com/vulsio/go-cve-dictionary/commands" 9 | ) 10 | 11 | func main() { 12 | if envArgs := os.Getenv("GO_CVE_DICTIONARY_ARGS"); 0 < len(envArgs) { 13 | commands.RootCmd.SetArgs(strings.Fields(envArgs)) 14 | } 15 | 16 | if err := commands.RootCmd.Execute(); err != nil { 17 | fmt.Fprintln(os.Stderr, err) 18 | os.Exit(1) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /commands/version.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/spf13/cobra" 7 | 8 | "github.com/vulsio/go-cve-dictionary/config" 9 | ) 10 | 11 | func init() { 12 | RootCmd.AddCommand(versionCmd) 13 | } 14 | 15 | var versionCmd = &cobra.Command{ 16 | Use: "version", 17 | Short: "Show version", 18 | Long: `Show version`, 19 | Run: func(_ *cobra.Command, _ []string) { 20 | fmt.Printf("go-cve-dictionary %s %s\n", config.Version, config.Revision) 21 | }, 22 | } 23 | -------------------------------------------------------------------------------- /commands/fetch.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "github.com/spf13/cobra" 5 | "github.com/spf13/viper" 6 | ) 7 | 8 | var fetchCmd = &cobra.Command{ 9 | Use: "fetch", 10 | Short: "Fetch Vulnerability dictionary", 11 | Long: "Fetch Vulnerability dictionary", 12 | } 13 | 14 | func init() { 15 | RootCmd.AddCommand(fetchCmd) 16 | 17 | fetchCmd.PersistentFlags().Int("batch-size", 5, "The number of batch size to insert.") 18 | _ = viper.BindPFlag("batch-size", fetchCmd.PersistentFlags().Lookup("batch-size")) 19 | } 20 | -------------------------------------------------------------------------------- /util/util.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | ) 7 | 8 | // GenWorkers generate workers 9 | func GenWorkers(num int) chan<- func() { 10 | tasks := make(chan func()) 11 | for i := 0; i < num; i++ { 12 | go func() { 13 | for f := range tasks { 14 | f() 15 | } 16 | }() 17 | } 18 | return tasks 19 | } 20 | 21 | // CacheDir return go-cve-dictionary cache directory path 22 | func CacheDir() string { 23 | cacheDir, err := os.UserCacheDir() 24 | if err != nil { 25 | cacheDir = os.TempDir() 26 | } 27 | dir := filepath.Join(cacheDir, "go-cve-dictionary") 28 | return dir 29 | } 30 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:alpine as builder 2 | 3 | RUN apk add --no-cache \ 4 | git \ 5 | make \ 6 | gcc \ 7 | musl-dev 8 | 9 | ENV REPOSITORY github.com/vulsio/go-cve-dictionary 10 | COPY . $GOPATH/src/$REPOSITORY 11 | RUN cd $GOPATH/src/$REPOSITORY && make install 12 | 13 | 14 | FROM alpine:3.22 15 | 16 | ENV LOGDIR /var/log/go-cve-dictionary 17 | ENV WORKDIR /go-cve-dictionary 18 | 19 | RUN apk add --no-cache ca-certificates git \ 20 | && mkdir -p $WORKDIR $LOGDIR 21 | 22 | COPY --from=builder /go/bin/go-cve-dictionary /usr/local/bin/ 23 | 24 | VOLUME ["$WORKDIR", "$LOGDIR"] 25 | WORKDIR $WORKDIR 26 | ENV PWD $WORKDIR 27 | 28 | ENTRYPOINT ["go-cve-dictionary"] 29 | CMD ["--help"] 30 | -------------------------------------------------------------------------------- /.revive.toml: -------------------------------------------------------------------------------- 1 | ignoreGeneratedHeader = false 2 | severity = "warning" 3 | confidence = 0.8 4 | errorCode = 0 5 | warningCode = 0 6 | 7 | [rule.blank-imports] 8 | [rule.context-as-argument] 9 | [rule.context-keys-type] 10 | [rule.dot-imports] 11 | [rule.error-return] 12 | [rule.error-strings] 13 | [rule.error-naming] 14 | [rule.exported] 15 | [rule.if-return] 16 | [rule.increment-decrement] 17 | [rule.var-naming] 18 | [rule.var-declaration] 19 | [rule.package-comments] 20 | [rule.range] 21 | [rule.receiver-naming] 22 | [rule.time-naming] 23 | [rule.unexported-return] 24 | [rule.indent-error-flow] 25 | [rule.errorf] 26 | [rule.empty-block] 27 | [rule.superfluous-else] 28 | [rule.unused-parameter] 29 | [rule.unreachable-code] 30 | [rule.redefines-builtin-id] -------------------------------------------------------------------------------- /.github/workflows/goreleaser.yml: -------------------------------------------------------------------------------- 1 | name: goreleaser 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | 8 | jobs: 9 | goreleaser: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - 13 | name: Checkout 14 | uses: actions/checkout@v6 15 | - 16 | name: Unshallow 17 | run: git fetch --prune --unshallow 18 | - 19 | name: Set up Go 20 | uses: actions/setup-go@v6 21 | with: 22 | go-version-file: go.mod 23 | - 24 | name: Run GoReleaser 25 | uses: goreleaser/goreleaser-action@v6 26 | with: 27 | distribution: goreleaser 28 | version: latest 29 | args: release --clean 30 | env: 31 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 32 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | project_name: go-cve-dictionary 2 | release: 3 | github: 4 | owner: vulsio 5 | name: go-cve-dictionary 6 | env: 7 | - CGO_ENABLED=0 8 | builds: 9 | - id: go-cve-dictionary 10 | goos: 11 | - linux 12 | - windows 13 | - darwin 14 | goarch: 15 | - amd64 16 | - arm64 17 | main: . 18 | ldflags: -s -w -X github.com/vulsio/go-cve-dictionary/config.Version={{.Version}} -X github.com/vulsio/go-cve-dictionary/config.Revision={{.Commit}} 19 | binary: go-cve-dictionary 20 | archives: 21 | - name_template: '{{ .Binary }}_{{.Version}}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}' 22 | format: tar.gz 23 | files: 24 | - LICENSE 25 | - README* 26 | snapshot: 27 | name_template: SNAPSHOT-{{ .Commit }} 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/BUG_REPORT.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | labels: bug 4 | about: If something isn't working as expected. 5 | --- 6 | 7 | # What did you do? (required. The issue will be **closed** when not provided.) 8 | 9 | 10 | # What did you expect to happen? 11 | 12 | 13 | # What happened instead? 14 | 15 | * Current Output 16 | 17 | Please re-run the command using ```-debug``` and provide the output below. 18 | 19 | # Steps to reproduce the behaviour 20 | 21 | 22 | # Configuration (**MUST** fill this out): 23 | 24 | * Go version (`go version`): 25 | 26 | * Go environment (`go env`): 27 | 28 | * go-cve-dictionary environment: 29 | 30 | Hash : ____ 31 | 32 | To check the commit hash of HEAD 33 | $ go-cve-dictionary version 34 | 35 | or 36 | 37 | $ cd $GOPATH/src/github.com/vulsio/go-cve-dictionary 38 | $ git rev-parse --short HEAD 39 | 40 | * command: 41 | 42 | -------------------------------------------------------------------------------- /fetcher/cisco/types.go: -------------------------------------------------------------------------------- 1 | package cisco 2 | 3 | type advisory struct { 4 | AdvisoryID string `json:"advisoryID"` 5 | AdvisoryTitle string `json:"advisoryTitle"` 6 | BugIDs []string `json:"bugIDs"` 7 | CsafURL string `json:"csafURL"` 8 | Cves []string `json:"cves"` 9 | CvrfURL string `json:"cvrfURL"` 10 | CvssBaseScore string `json:"cvssBaseScore"` 11 | Cwe []string `json:"cwe"` 12 | FirstPublished string `json:"firstPublished"` 13 | IpsSignatures interface{} `json:"ipsSignatures"` 14 | LastUpdated string `json:"lastUpdated"` 15 | ProductNames []string `json:"productNames"` 16 | PublicationURL string `json:"publicationURL"` 17 | Sir string `json:"sir"` 18 | Status string `json:"status"` 19 | Summary string `json:"summary"` 20 | Version string `json:"version"` 21 | } 22 | -------------------------------------------------------------------------------- /db/util.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "iter" 5 | 6 | "golang.org/x/xerrors" 7 | ) 8 | 9 | // chunk chunks the sequence into n-sized chunks 10 | // Note: slices.Chunk doesn't support iterators as of Go 1.23. 11 | // https://pkg.go.dev/slices#Chunk 12 | func chunk[T any](s iter.Seq2[T, error], n int) iter.Seq2[[]T, error] { 13 | return func(yield func([]T, error) bool) { 14 | if n < 1 { 15 | if !yield(nil, xerrors.New("cannot be less than 1")) { 16 | return 17 | } 18 | } 19 | 20 | chunk := make([]T, 0, n) 21 | for t, err := range s { 22 | if err != nil { 23 | if !yield(nil, err) { 24 | return 25 | } 26 | continue 27 | } 28 | chunk = append(chunk, t) 29 | if len(chunk) != n { 30 | continue 31 | } 32 | 33 | if !yield(chunk, nil) { 34 | return 35 | } 36 | chunk = chunk[:0] 37 | } 38 | 39 | if len(chunk) > 0 { 40 | if !yield(chunk, nil) { 41 | return 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /fetcher/util_test.go: -------------------------------------------------------------------------------- 1 | package fetcher 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | func TestParseTime(t *testing.T) { 10 | type args struct { 11 | layouts []string 12 | value string 13 | } 14 | tests := []struct { 15 | name string 16 | args args 17 | want time.Time 18 | wantErr bool 19 | }{ 20 | { 21 | name: "2025-05-02T10:15:15", 22 | args: args{ 23 | layouts: []string{"2006-01-02", "2006-01-02T15:04:05"}, 24 | value: "2025-05-02T10:15:15", 25 | }, 26 | want: time.Date(2025, 5, 2, 10, 15, 15, 0, time.UTC), 27 | }, 28 | } 29 | for _, tt := range tests { 30 | t.Run(tt.name, func(t *testing.T) { 31 | got, err := ParseTime(tt.args.layouts, tt.args.value) 32 | if (err != nil) != tt.wantErr { 33 | t.Errorf("ParseTime() error = %v, wantErr %v", err, tt.wantErr) 34 | return 35 | } 36 | if !reflect.DeepEqual(got, tt.want) { 37 | t.Errorf("ParseTime() = %v, want %v", got, tt.want) 38 | } 39 | }) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /.github/workflows/golangci.yml: -------------------------------------------------------------------------------- 1 | name: golangci-lint 2 | on: 3 | push: 4 | tags: 5 | - v* 6 | branches: 7 | - master 8 | pull_request: 9 | jobs: 10 | golangci: 11 | name: lint 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Check out code into the Go module directory 15 | uses: actions/checkout@v6 16 | - name: Set up Go 1.x 17 | uses: actions/setup-go@v6 18 | with: 19 | go-version-file: go.mod 20 | - name: golangci-lint 21 | uses: golangci/golangci-lint-action@v9 22 | with: 23 | # Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version 24 | version: latest 25 | 26 | # Optional: working directory, useful for monorepos 27 | # working-directory: somedir 28 | 29 | # Optional: golangci-lint command line arguments. 30 | # args: --issues-exit-code=0 31 | 32 | # Optional: show only new issues if it's a pull request. The default value is `false`. 33 | # only-new-issues: true 34 | -------------------------------------------------------------------------------- /fetcher/euvd/types.go: -------------------------------------------------------------------------------- 1 | package euvd 2 | 3 | type advisory struct { 4 | ID string `json:"id"` 5 | EnisaUUID string `json:"enisaUuid"` 6 | Description string `json:"description"` 7 | DatePublished string `json:"datePublished"` 8 | DateUpdated string `json:"dateUpdated"` 9 | BaseScore float64 `json:"baseScore"` 10 | BaseScoreVersion string `json:"baseScoreVersion"` 11 | BaseScoreVector string `json:"baseScoreVector"` 12 | References string `json:"references"` 13 | Aliases string `json:"aliases"` 14 | Assigner string `json:"assigner"` 15 | EPSS float64 `json:"epss"` 16 | ExploitedSince string `json:"exploitedSince,omitempty"` 17 | EnisaIDProduct []product `json:"enisaIdProduct"` 18 | EnisaIDVendor []vendor `json:"enisaIdVendor"` 19 | } 20 | 21 | type product struct { 22 | ID string `json:"id"` 23 | Product struct { 24 | Name string `json:"name"` 25 | } `json:"product"` 26 | ProductVersion string `json:"product_version,omitempty"` 27 | } 28 | 29 | type vendor struct { 30 | ID string `json:"id"` 31 | Vendor struct { 32 | Name string `json:"name"` 33 | } `json:"vendor"` 34 | } 35 | -------------------------------------------------------------------------------- /models/euvd.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import "time" 4 | 5 | const ( 6 | // EuvdType : 7 | EuvdType = "EUVD" 8 | ) 9 | 10 | // Euvd is a model of Euvd 11 | type Euvd struct { 12 | ID int64 `json:"-"` 13 | EuvdID string `gorm:"index:idx_euvds_euvdid;type:varchar(255)"` 14 | EnisaUUID string `gorm:"type:varchar(255)"` 15 | Description string `gorm:"type:text"` 16 | DatePublished time.Time 17 | DateUpdated time.Time 18 | BaseScore float64 19 | BaseScoreVersion string `gorm:"type:varchar(3)"` 20 | BaseScoreVector string `gorm:"type:varchar(255)"` 21 | References []EuvdReference 22 | Aliases []EuvdAlias 23 | Assigner string `gorm:"type:varchar(255)"` 24 | EPSS float64 25 | ExploitedSince *time.Time 26 | } 27 | 28 | // EuvdReference holds reference information about the Advisory. 29 | type EuvdReference struct { 30 | ID int64 `json:"-"` 31 | EuvdID uint `json:"-" gorm:"index:idx_euvd_references_euvd_id"` 32 | Reference `gorm:"embedded"` 33 | } 34 | 35 | // EuvdAlias holds alias information about the Advisory. 36 | type EuvdAlias struct { 37 | ID int64 `json:"-"` 38 | EuvdID uint `json:"-" gorm:"index:idx_euvd_aliases_euvd_id"` 39 | Alias string `gorm:"type:varchar(255)"` 40 | } 41 | -------------------------------------------------------------------------------- /.github/workflows/docker-build.yml: -------------------------------------------------------------------------------- 1 | name: Publish Docker image 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'master' 7 | tags: 8 | - '*' 9 | 10 | jobs: 11 | docker: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v6 16 | 17 | - name: Set up QEMU 18 | uses: docker/setup-qemu-action@v3 19 | 20 | - name: Set up Docker Buildx 21 | uses: docker/setup-buildx-action@v3 22 | 23 | - name: Login to DockerHub 24 | uses: docker/login-action@v3 25 | with: 26 | username: ${{ secrets.DOCKERHUB_USERNAME }} 27 | password: ${{ secrets.DOCKERHUB_TOKEN }} 28 | 29 | - 30 | name: Docker meta 31 | id: meta 32 | uses: docker/metadata-action@v5 33 | with: 34 | images: vuls/go-cve-dictionary 35 | tags: | 36 | type=ref,event=tag 37 | 38 | - name: Build and push 39 | uses: docker/build-push-action@v6 40 | with: 41 | push: true 42 | context: . 43 | tags: | 44 | vuls/go-cve-dictionary:latest 45 | ${{ steps.meta.outputs.tags }} 46 | secrets: | 47 | "github_token=${{ secrets.GITHUB_TOKEN }}" 48 | platforms: linux/amd64,linux/arm64 49 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "gomod" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | target-branch: "master" 13 | ignore: 14 | - dependency-name: "gorm.io/driver/mysql" 15 | - dependency-name: "gorm.io/driver/postgres" 16 | - dependency-name: "gorm.io/gorm" 17 | groups: 18 | all: 19 | patterns: 20 | - "*" 21 | exclude-patterns: 22 | - github.com/glebarez/sqlite 23 | - package-ecosystem: "github-actions" # See documentation for possible values 24 | directory: "/" # Location of package manifests 25 | schedule: 26 | interval: "weekly" 27 | target-branch: "master" 28 | groups: 29 | all: 30 | patterns: 31 | - "*" 32 | - package-ecosystem: "docker" 33 | directory: "/" 34 | schedule: 35 | interval: "weekly" 36 | target-branch: "master" 37 | groups: 38 | all: 39 | patterns: 40 | - "*" 41 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | If this Pull Request is work in progress, Add a prefix of “[WIP]” in the title. 3 | 4 | # What did you implement: 5 | 6 | Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. 7 | 8 | Fixes # (issue) 9 | 10 | ## Type of change 11 | 12 | Please delete options that are not relevant. 13 | 14 | - [ ] Bug fix (non-breaking change which fixes an issue) 15 | - [ ] New feature (non-breaking change which adds functionality) 16 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) 17 | - [ ] This change requires a documentation update 18 | 19 | # How Has This Been Tested? 20 | 21 | Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. 22 | 23 | # Checklist: 24 | You don't have to satisfy all of the following. 25 | 26 | - [ ] Write tests 27 | - [ ] Write documentation 28 | - [ ] Check that there aren't other open pull requests for the same issue/feature 29 | - [ ] Format your source code by `make fmt` 30 | - [ ] Pass the test by `make test` 31 | - [ ] Provide verification config / commands 32 | - [ ] Enable "Allow edits from maintainers" for this PR 33 | - [ ] Update the messages below 34 | 35 | ***Is this ready for review?:*** NO 36 | 37 | # Reference 38 | 39 | * https://blog.github.com/2015-01-21-how-to-write-the-perfect-pull-request/ 40 | 41 | -------------------------------------------------------------------------------- /GNUmakefile: -------------------------------------------------------------------------------- 1 | .PHONY: \ 2 | all \ 3 | build \ 4 | install \ 5 | lint \ 6 | golangci \ 7 | vet \ 8 | fmt \ 9 | fmtcheck \ 10 | pretest \ 11 | test \ 12 | cov \ 13 | clean 14 | 15 | SRCS = $(shell git ls-files '*.go') 16 | PKGS = $(shell go list ./...) 17 | VERSION := $(shell git describe --tags --abbrev=0) 18 | REVISION := $(shell git rev-parse --short HEAD) 19 | BUILDTIME := $(shell date "+%Y%m%d_%H%M%S") 20 | LDFLAGS := -X 'github.com/vulsio/go-cve-dictionary/config.Version=$(VERSION)' \ 21 | -X 'github.com/vulsio/go-cve-dictionary/config.Revision=$(REVISION)' 22 | GO := CGO_ENABLED=0 go 23 | 24 | all: build test 25 | 26 | build: main.go 27 | $(GO) build -ldflags "$(LDFLAGS)" -o go-cve-dictionary $< 28 | 29 | install: main.go 30 | $(GO) install -ldflags "$(LDFLAGS)" 31 | 32 | lint: 33 | go install github.com/mgechev/revive@latest 34 | revive -config ./.revive.toml -formatter plain $(PKGS) 35 | 36 | golangci: 37 | go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest 38 | golangci-lint run 39 | 40 | vet: 41 | echo $(PKGS) | xargs env $(GO) vet || exit; 42 | 43 | fmt: 44 | gofmt -w $(SRCS) 45 | 46 | fmtcheck: 47 | $(foreach file,$(SRCS),gofmt -d $(file);) 48 | 49 | pretest: lint vet fmtcheck 50 | 51 | test: pretest 52 | $(GO) test -cover -v ./... || exit; 53 | 54 | cov: 55 | @ go get -v github.com/axw/gocov/gocov 56 | @ go get golang.org/x/tools/cmd/cover 57 | gocov test | gocov report 58 | 59 | clean: 60 | echo $(PKGS) | xargs go clean || exit; 61 | -------------------------------------------------------------------------------- /models/cisco.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import "time" 4 | 5 | const ( 6 | // CiscoType : 7 | CiscoType = "Cisco" 8 | 9 | // CiscoExactVersionMatch : 10 | CiscoExactVersionMatch = "CiscoExactVersionMatch" 11 | // CiscoRoughVersionMatch : 12 | CiscoRoughVersionMatch = "CiscoRoughVersionMatch" 13 | // CiscoVendorProductMatch : 14 | CiscoVendorProductMatch = "CiscoVendorProductMatch" 15 | ) 16 | 17 | // Cisco is a model of Cisco 18 | type Cisco struct { 19 | ID int64 `json:"-"` 20 | AdvisoryID string `gorm:"size:255"` 21 | Title string `gorm:"size:256"` 22 | Summary string `gorm:"size:-1"` 23 | SIR string `gorm:"size:255"` 24 | CveID string `gorm:"index:idx_ciscos_cveid;size:255"` 25 | BugIDs []CiscoBugID 26 | CweIDs []CiscoCweID 27 | Affected []CiscoProduct 28 | References []CiscoReference 29 | FirstPublished time.Time 30 | LastUpdated time.Time 31 | 32 | DetectionMethod string `gorm:"-"` 33 | } 34 | 35 | // CiscoBugID : 36 | type CiscoBugID struct { 37 | ID int64 `json:"-"` 38 | CiscoID uint `json:"-" gorm:"index:idx_cisco_bug_ids_cisco_id"` 39 | BugID string `gorm:"index:idx_cisco_bugid;size:255"` 40 | } 41 | 42 | // CiscoCweID : 43 | type CiscoCweID struct { 44 | ID int64 `json:"-"` 45 | CiscoID uint `json:"-" gorm:"index:idx_cisco_cwe_ids_cisco_id"` 46 | CweID string `gorm:"index:idx_cisco_cweid;size:255"` 47 | } 48 | 49 | // CiscoProduct : 50 | type CiscoProduct struct { 51 | ID int64 `json:"-"` 52 | CiscoID uint `json:"-" gorm:"index:idx_cisco_products_cisco_id"` 53 | CpeBase `gorm:"embedded"` 54 | } 55 | 56 | // CiscoReference : 57 | type CiscoReference struct { 58 | ID int64 `json:"-"` 59 | CiscoID uint `json:"-" gorm:"index:idx_cisco_references_cisco_id"` 60 | Reference `gorm:"embedded"` 61 | } 62 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | 3 | linters: 4 | default: none 5 | enable: 6 | - errcheck 7 | - govet 8 | - ineffassign 9 | - misspell 10 | - prealloc 11 | - revive 12 | - staticcheck 13 | settings: 14 | revive: # https://golangci-lint.run/usage/linters/#revive 15 | rules: 16 | - name: blank-imports 17 | - name: context-as-argument 18 | - name: context-keys-type 19 | - name: dot-imports 20 | - name: empty-block 21 | - name: error-naming 22 | - name: error-return 23 | - name: error-strings 24 | - name: errorf 25 | - name: exported 26 | - name: if-return 27 | - name: increment-decrement 28 | - name: indent-error-flow 29 | - name: package-comments 30 | disabled: true 31 | - name: range 32 | - name: receiver-naming 33 | - name: redefines-builtin-id 34 | - name: superfluous-else 35 | - name: time-naming 36 | - name: unexported-return 37 | - name: unreachable-code 38 | - name: unused-parameter 39 | - name: var-declaration 40 | - name: var-naming 41 | arguments: 42 | - [] # AllowList 43 | - [] # DenyList 44 | - - skip-package-name-checks: true 45 | staticcheck: # https://golangci-lint.run/usage/linters/#staticcheck 46 | checks: 47 | - all 48 | - -ST1000 # at least one file in a package should have a package comment 49 | - -ST1005 # error strings should not be capitalized 50 | exclusions: 51 | rules: 52 | - source: "defer .+\\.Close\\(\\)" 53 | linters: 54 | - errcheck 55 | - source: "defer os.RemoveAll\\(.+\\)" 56 | linters: 57 | - errcheck 58 | 59 | formatters: 60 | enable: 61 | - goimports 62 | 63 | run: 64 | timeout: 10m 65 | -------------------------------------------------------------------------------- /models/jvn.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import "time" 4 | 5 | const ( 6 | // JvnType : 7 | JvnType = "JVN" 8 | 9 | // JvnVendorProductMatch : 10 | JvnVendorProductMatch = "JvnVendorProductMatch" 11 | ) 12 | 13 | // Jvn is a model of JVN 14 | type Jvn struct { 15 | ID int64 `json:"-"` 16 | CveID string `gorm:"index:idx_jvns_cveid;type:varchar(255)"` 17 | Title string `gorm:"type:varchar(255)"` 18 | Summary string `gorm:"type:text"` 19 | JvnLink string `gorm:"type:varchar(255)"` 20 | JvnID string `gorm:"type:varchar(255)"` 21 | Cvss2 JvnCvss2 22 | Cvss3 JvnCvss3 23 | Cpes []JvnCpe 24 | References []JvnReference 25 | Certs []JvnCert 26 | PublishedDate time.Time 27 | LastModifiedDate time.Time 28 | 29 | DetectionMethod string `gorm:"-"` 30 | } 31 | 32 | // JvnCvss2 has Jvn CVSS Version 2 info 33 | type JvnCvss2 struct { 34 | ID int64 `json:"-"` 35 | JvnID uint `json:"-" gorm:"index:idx_jvn_cvss2_jvn_id"` 36 | Cvss2 `gorm:"embedded"` 37 | } 38 | 39 | // JvnCvss3 has JVN CVSS3 info 40 | type JvnCvss3 struct { 41 | ID int64 `json:"-"` 42 | JVNID uint `json:"-" gorm:"index:idx_jvn_cvss3_jvn_id"` 43 | Cvss3 `gorm:"embedded"` 44 | } 45 | 46 | // JvnCpe is Child model of Jvn. 47 | // see https://www.ipa.go.jp/security/vuln/CPE.html 48 | type JvnCpe struct { 49 | ID int64 `json:"-"` 50 | JvnID uint `json:"-" gorm:"index:idx_jvn_cpes_jvn_id"` 51 | CpeBase `gorm:"embedded"` 52 | } 53 | 54 | // JvnReference is Child model of Jvn. 55 | type JvnReference struct { 56 | ID int64 `json:"-"` 57 | JvnID uint `json:"-" gorm:"index:idx_jvn_references_jvn_id"` 58 | Reference `gorm:"embedded"` 59 | } 60 | 61 | // JvnCert is Child model of Jvn. 62 | type JvnCert struct { 63 | ID int64 `json:"-"` 64 | JvnID uint `json:"-" gorm:"index:idx_jvn_certs_jvn_id"` 65 | Cert `gorm:"embedded"` 66 | } 67 | -------------------------------------------------------------------------------- /models/fortinet.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import "time" 4 | 5 | const ( 6 | // FortinetType : 7 | FortinetType = "Fortinet" 8 | 9 | // FortinetExactVersionMatch : 10 | FortinetExactVersionMatch = "FortinetExactVersionMatch" 11 | // FortinetRoughVersionMatch : 12 | FortinetRoughVersionMatch = "FortinetRoughVersionMatch" 13 | // FortinetVendorProductMatch : 14 | FortinetVendorProductMatch = "FortinetVendorProductMatch" 15 | ) 16 | 17 | // Fortinet is a model of Fortinet 18 | type Fortinet struct { 19 | ID int64 `json:"-"` 20 | AdvisoryID string `gorm:"type:varchar(255)"` 21 | CveID string `gorm:"index:idx_fortinets_cveid;type:varchar(255)"` 22 | Title string `gorm:"type:varchar(255)"` 23 | Summary string `gorm:"type:text"` 24 | Descriptions string `gorm:"type:text"` 25 | Cvss3 FortinetCvss3 26 | Cwes []FortinetCwe 27 | Cpes []FortinetCpe 28 | References []FortinetReference 29 | PublishedDate time.Time 30 | LastModifiedDate time.Time 31 | AdvisoryURL string `gorm:"type:text"` 32 | 33 | DetectionMethod string `gorm:"-"` 34 | } 35 | 36 | // FortinetCvss3 has Fortinet CVSS3 info 37 | type FortinetCvss3 struct { 38 | ID int64 `json:"-"` 39 | FortinetID uint `json:"-" gorm:"index:idx_fortinet_cvss3_fortinet_id"` 40 | Cvss3 `gorm:"embedded"` 41 | } 42 | 43 | // FortinetCwe has CweID 44 | type FortinetCwe struct { 45 | ID int64 `json:"-"` 46 | FortinetID uint `json:"-" gorm:"index:idx_fortinet_cwes_fortinet_id"` 47 | CweID string `gorm:"type:varchar(255)"` 48 | } 49 | 50 | // FortinetCpe is Child model of Fortinet. 51 | type FortinetCpe struct { 52 | ID int64 `json:"-"` 53 | FortinetID uint `json:"-" gorm:"index:idx_fortinet_cpes_fortinet_id"` 54 | CpeBase `gorm:"embedded"` 55 | } 56 | 57 | // FortinetReference holds reference information about the CVE. 58 | type FortinetReference struct { 59 | ID int64 `json:"-"` 60 | FortinetID uint `json:"-" gorm:"index:idx_fortinet_references_fortinet_id"` 61 | Reference `gorm:"embedded"` 62 | } 63 | -------------------------------------------------------------------------------- /fetcher/fortinet/types.go: -------------------------------------------------------------------------------- 1 | package fortinet 2 | 3 | import "time" 4 | 5 | type advisory struct { 6 | ID string `json:"id"` 7 | Title string `json:"title"` 8 | Summary string `json:"summary"` 9 | Description string `json:"description"` 10 | Vulnerabilities []vulnerability `json:"vulnerabilities"` 11 | References []reference `json:"references"` 12 | Published time.Time `json:"published"` 13 | Updated time.Time `json:"updated"` 14 | AdvisoryURL string `json:"advisory_url"` 15 | } 16 | 17 | type vulnerability struct { 18 | ID string `json:"id"` 19 | CVE string `json:"cve"` 20 | Definitions []definition `json:"definitions"` 21 | } 22 | 23 | type definition struct { 24 | Configurations []configuration `json:"configurations"` 25 | CVSSv2 *cvss `json:"cvssv2"` 26 | CVSSv3 *cvss `json:"cvssv3"` 27 | CWE []string `json:"cwe"` 28 | Impact string `json:"impact"` 29 | ExploitStatus string `json:"exploit_status"` 30 | } 31 | 32 | type configuration struct { 33 | Nodes []element `json:"nodes"` 34 | Children *configuration `json:"children"` 35 | } 36 | 37 | type element struct { 38 | Description string `json:"description"` 39 | CPE string `json:"cpe"` 40 | Affected expression `json:"affected"` 41 | FixedIn []string `json:"fixed_in"` 42 | } 43 | 44 | type expression struct { 45 | Eqaul *string `json:"eq"` 46 | GreaterThan *string `json:"gt"` 47 | GreaterEqaul *string `json:"ge"` 48 | LessThan *string `json:"lt"` 49 | LessEqual *string `json:"le"` 50 | } 51 | 52 | type cvss struct { 53 | BaseScore *float64 `json:"base_score"` 54 | TemporalScore *float64 `json:"temporal_score"` 55 | EnvironmentalScore *float64 `json:"environmental_score"` 56 | Vector string `json:"vector"` 57 | } 58 | 59 | type reference struct { 60 | Description string `json:"description"` 61 | URL string `json:"url"` 62 | } 63 | -------------------------------------------------------------------------------- /commands/fetchmitre.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "errors" 5 | "time" 6 | 7 | "github.com/spf13/cobra" 8 | "github.com/spf13/viper" 9 | "golang.org/x/xerrors" 10 | 11 | "github.com/vulsio/go-cve-dictionary/db" 12 | "github.com/vulsio/go-cve-dictionary/log" 13 | "github.com/vulsio/go-cve-dictionary/models" 14 | ) 15 | 16 | var fetchMitreCmd = &cobra.Command{ 17 | Use: "mitre", 18 | Short: "Fetch Vulnerability dictionary from MITRE", 19 | Long: "Fetch Vulnerability dictionary from MITRE", 20 | RunE: fetchMitre, 21 | } 22 | 23 | func init() { 24 | fetchCmd.AddCommand(fetchMitreCmd) 25 | } 26 | 27 | func fetchMitre(_ *cobra.Command, args []string) (err error) { 28 | if err := log.SetLogger(viper.GetBool("log-to-file"), viper.GetString("log-dir"), viper.GetBool("debug"), viper.GetBool("log-json")); err != nil { 29 | return xerrors.Errorf("Failed to SetLogger. err: %w", err) 30 | } 31 | 32 | driver, err := db.NewDB(viper.GetString("dbtype"), viper.GetString("dbpath"), viper.GetBool("debug-sql"), db.Option{}) 33 | if err != nil { 34 | if errors.Is(err, db.ErrDBLocked) { 35 | return xerrors.Errorf("Failed to open DB. Close DB connection before fetching. err: %w", err) 36 | } 37 | return xerrors.Errorf("Failed to open DB. err: %w", err) 38 | } 39 | 40 | fetchMeta, err := driver.GetFetchMeta() 41 | if err != nil { 42 | return xerrors.Errorf("Failed to get FetchMeta from DB. err: %w", err) 43 | } 44 | if fetchMeta.OutDated() { 45 | return xerrors.Errorf("Failed to Insert CVEs into DB. err: SchemaVersion is old. SchemaVersion: %+v", map[string]uint{"latest": models.LatestSchemaVersion, "DB": fetchMeta.SchemaVersion}) 46 | } 47 | // If the fetch fails the first time (without SchemaVersion), the DB needs to be cleaned every time, so insert SchemaVersion. 48 | if err := driver.UpsertFetchMeta(fetchMeta); err != nil { 49 | return xerrors.Errorf("Failed to upsert FetchMeta to DB. dbpath: %s, err: %w", viper.GetString("dbpath"), err) 50 | } 51 | 52 | log.Infof("Inserting MITRE into DB (%s).", driver.Name()) 53 | if err := driver.InsertMitre(args); err != nil { 54 | return xerrors.Errorf("Failed to insert. dbpath: %s, err: %w", viper.GetString("dbpath"), err) 55 | } 56 | 57 | fetchMeta.LastFetchedAt = time.Now() 58 | if err := driver.UpsertFetchMeta(fetchMeta); err != nil { 59 | return xerrors.Errorf("Failed to upsert FetchMeta to DB. dbpath: %s, err: %w", viper.GetString("dbpath"), err) 60 | } 61 | 62 | log.Infof("Finished fetching MITRE.") 63 | return nil 64 | } 65 | -------------------------------------------------------------------------------- /log/log.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path/filepath" 7 | "runtime" 8 | 9 | logger "github.com/inconshreveable/log15" 10 | "golang.org/x/xerrors" 11 | ) 12 | 13 | // GetDefaultLogDir returns default log directory 14 | func GetDefaultLogDir() string { 15 | defaultLogDir := "/var/log/go-cve-dictionary" 16 | if runtime.GOOS == "windows" { 17 | defaultLogDir = filepath.Join(os.Getenv("APPDATA"), "go-cve-dictionary") 18 | } 19 | return defaultLogDir 20 | } 21 | 22 | // SetLogger set logger 23 | func SetLogger(logToFile bool, logDir string, debug, logJSON bool) error { 24 | stderrHandler := logger.StderrHandler 25 | logFormat := logger.LogfmtFormat() 26 | if logJSON { 27 | logFormat = logger.JsonFormatEx(false, true) 28 | stderrHandler = logger.StreamHandler(os.Stderr, logFormat) 29 | } 30 | 31 | lvlHandler := logger.LvlFilterHandler(logger.LvlInfo, stderrHandler) 32 | if debug { 33 | lvlHandler = logger.LvlFilterHandler(logger.LvlDebug, stderrHandler) 34 | } 35 | 36 | var handler logger.Handler 37 | if logToFile { 38 | if _, err := os.Stat(logDir); err != nil { 39 | if os.IsNotExist(err) { 40 | if err := os.Mkdir(logDir, 0700); err != nil { 41 | return xerrors.Errorf("Failed to create log directory. err: %w", err) 42 | } 43 | } else { 44 | return xerrors.Errorf("Failed to check log directory. err: %w", err) 45 | } 46 | } 47 | 48 | logPath := filepath.Join(logDir, "go-cve-dictionary.log") 49 | if _, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644); err != nil { 50 | return xerrors.Errorf("Failed to open a log file. err: %w", err) 51 | } 52 | handler = logger.MultiHandler( 53 | logger.Must.FileHandler(logPath, logFormat), 54 | lvlHandler, 55 | ) 56 | } else { 57 | handler = lvlHandler 58 | } 59 | logger.Root().SetHandler(handler) 60 | return nil 61 | } 62 | 63 | // Debugf is wrapper function 64 | func Debugf(format string, args ...interface{}) { 65 | logger.Debug(fmt.Sprintf(format, args...)) 66 | } 67 | 68 | // Infof is wrapper function 69 | func Infof(format string, args ...interface{}) { 70 | logger.Info(fmt.Sprintf(format, args...)) 71 | } 72 | 73 | // Warnf is wrapper function 74 | func Warnf(format string, args ...interface{}) { 75 | logger.Warn(fmt.Sprintf(format, args...)) 76 | } 77 | 78 | // Errorf is wrapper function 79 | func Errorf(format string, args ...interface{}) { 80 | logger.Error(fmt.Sprintf(format, args...)) 81 | } 82 | 83 | // Fatalf is wrapper function 84 | func Fatalf(format string, args ...interface{}) { 85 | logger.Crit(fmt.Sprintf(format, args...)) 86 | } 87 | -------------------------------------------------------------------------------- /commands/fetchnvd.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "errors" 5 | "time" 6 | 7 | "github.com/spf13/cobra" 8 | "github.com/spf13/viper" 9 | "golang.org/x/xerrors" 10 | 11 | "github.com/vulsio/go-cve-dictionary/db" 12 | "github.com/vulsio/go-cve-dictionary/log" 13 | "github.com/vulsio/go-cve-dictionary/models" 14 | ) 15 | 16 | var fetchNvdCmd = &cobra.Command{ 17 | Use: "nvd", 18 | Short: "Fetch Vulnerability dictionary from NVD", 19 | Long: "Fetch Vulnerability dictionary from NVD", 20 | RunE: fetchNvd, 21 | } 22 | 23 | func init() { 24 | fetchCmd.AddCommand(fetchNvdCmd) 25 | 26 | fetchNvdCmd.PersistentFlags().Bool("full", false, "Collect large amounts of CPE relate data") 27 | _ = viper.BindPFlag("full", fetchNvdCmd.PersistentFlags().Lookup("full")) 28 | } 29 | 30 | func fetchNvd(_ *cobra.Command, args []string) (err error) { 31 | if err := log.SetLogger(viper.GetBool("log-to-file"), viper.GetString("log-dir"), viper.GetBool("debug"), viper.GetBool("log-json")); err != nil { 32 | return xerrors.Errorf("Failed to SetLogger. err: %w", err) 33 | } 34 | 35 | driver, err := db.NewDB(viper.GetString("dbtype"), viper.GetString("dbpath"), viper.GetBool("debug-sql"), db.Option{}) 36 | if err != nil { 37 | if errors.Is(err, db.ErrDBLocked) { 38 | return xerrors.Errorf("Failed to open DB. Close DB connection before fetching. err: %w", err) 39 | } 40 | return xerrors.Errorf("Failed to open DB. err: %w", err) 41 | } 42 | 43 | fetchMeta, err := driver.GetFetchMeta() 44 | if err != nil { 45 | return xerrors.Errorf("Failed to get FetchMeta from DB. err: %w", err) 46 | } 47 | if fetchMeta.OutDated() { 48 | return xerrors.Errorf("Failed to Insert CVEs into DB. err: SchemaVersion is old. SchemaVersion: %+v", map[string]uint{"latest": models.LatestSchemaVersion, "DB": fetchMeta.SchemaVersion}) 49 | } 50 | // If the fetch fails the first time (without SchemaVersion), the DB needs to be cleaned every time, so insert SchemaVersion. 51 | if err := driver.UpsertFetchMeta(fetchMeta); err != nil { 52 | return xerrors.Errorf("Failed to upsert FetchMeta to DB. dbpath: %s, err: %w", viper.GetString("dbpath"), err) 53 | } 54 | 55 | log.Infof("Inserting NVD into DB (%s).", driver.Name()) 56 | if err := driver.InsertNvd(args); err != nil { 57 | return xerrors.Errorf("Failed to insert. dbpath: %s, err: %w", viper.GetString("dbpath"), err) 58 | } 59 | 60 | fetchMeta.LastFetchedAt = time.Now() 61 | if err := driver.UpsertFetchMeta(fetchMeta); err != nil { 62 | return xerrors.Errorf("Failed to upsert FetchMeta to DB. dbpath: %s, err: %w", viper.GetString("dbpath"), err) 63 | } 64 | 65 | log.Infof("Finished fetching NVD.") 66 | return nil 67 | } 68 | -------------------------------------------------------------------------------- /commands/fetchjvn.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "errors" 5 | "time" 6 | 7 | "github.com/spf13/cobra" 8 | "github.com/spf13/viper" 9 | "golang.org/x/xerrors" 10 | 11 | "github.com/vulsio/go-cve-dictionary/db" 12 | "github.com/vulsio/go-cve-dictionary/log" 13 | "github.com/vulsio/go-cve-dictionary/models" 14 | ) 15 | 16 | var fetchJvnCmd = &cobra.Command{ 17 | Use: "jvn", 18 | Short: "Fetch Vulnerability dictionary from JVN", 19 | Long: "Fetch Vulnerability dictionary from JVN", 20 | RunE: fetchJvn, 21 | } 22 | 23 | func init() { 24 | fetchCmd.AddCommand(fetchJvnCmd) 25 | 26 | fetchJvnCmd.PersistentFlags().Bool("without-jvncert", false, "not request to jvn cert.") 27 | _ = viper.BindPFlag("without-jvncert", fetchJvnCmd.PersistentFlags().Lookup("without-jvncert")) 28 | } 29 | 30 | func fetchJvn(_ *cobra.Command, args []string) (err error) { 31 | if err := log.SetLogger(viper.GetBool("log-to-file"), viper.GetString("log-dir"), viper.GetBool("debug"), viper.GetBool("log-json")); err != nil { 32 | return xerrors.Errorf("Failed to SetLogger. err: %w", err) 33 | } 34 | 35 | driver, err := db.NewDB(viper.GetString("dbtype"), viper.GetString("dbpath"), viper.GetBool("debug-sql"), db.Option{}) 36 | if err != nil { 37 | if errors.Is(err, db.ErrDBLocked) { 38 | return xerrors.Errorf("Failed to open DB. Close DB connection before fetching. err: %w", err) 39 | } 40 | return xerrors.Errorf("Failed to open DB. err: %w", err) 41 | } 42 | 43 | fetchMeta, err := driver.GetFetchMeta() 44 | if err != nil { 45 | return xerrors.Errorf("Failed to get FetchMeta from DB. err: %w", err) 46 | } 47 | if fetchMeta.OutDated() { 48 | return xerrors.Errorf("Failed to Insert CVEs into DB. err: SchemaVersion is old. SchemaVersion: %+v", map[string]uint{"latest": models.LatestSchemaVersion, "DB": fetchMeta.SchemaVersion}) 49 | } 50 | // If the fetch fails the first time (without SchemaVersion), the DB needs to be cleaned every time, so insert SchemaVersion. 51 | if err := driver.UpsertFetchMeta(fetchMeta); err != nil { 52 | return xerrors.Errorf("Failed to upsert FetchMeta to DB. dbpath: %s, err: %w", viper.GetString("dbpath"), err) 53 | } 54 | 55 | log.Infof("Inserting JVN into DB (%s).", driver.Name()) 56 | if err := driver.InsertJvn(args); err != nil { 57 | return xerrors.Errorf("Failed to insert. dbpath: %s, err: %w", viper.GetString("dbpath"), err) 58 | } 59 | 60 | fetchMeta.LastFetchedAt = time.Now() 61 | if err := driver.UpsertFetchMeta(fetchMeta); err != nil { 62 | return xerrors.Errorf("Failed to upsert FetchMeta to DB. dbpath: %s, err: %w", viper.GetString("dbpath"), err) 63 | } 64 | 65 | log.Infof("Finished fetching JVN.") 66 | return nil 67 | } 68 | -------------------------------------------------------------------------------- /commands/fetcheuvd.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "errors" 5 | "time" 6 | 7 | "github.com/spf13/cobra" 8 | "github.com/spf13/viper" 9 | "golang.org/x/xerrors" 10 | 11 | "github.com/vulsio/go-cve-dictionary/db" 12 | "github.com/vulsio/go-cve-dictionary/fetcher/euvd" 13 | "github.com/vulsio/go-cve-dictionary/log" 14 | "github.com/vulsio/go-cve-dictionary/models" 15 | ) 16 | 17 | var fetchEUVDCmd = &cobra.Command{ 18 | Use: "euvd", 19 | Short: "Fetch Vulnerability dictionary from EUVD", 20 | Long: "Fetch Vulnerability dictionary from EUVD", 21 | RunE: fetchEUVD, 22 | } 23 | 24 | func init() { 25 | fetchCmd.AddCommand(fetchEUVDCmd) 26 | } 27 | 28 | func fetchEUVD(_ *cobra.Command, args []string) (err error) { 29 | if err := log.SetLogger(viper.GetBool("log-to-file"), viper.GetString("log-dir"), viper.GetBool("debug"), viper.GetBool("log-json")); err != nil { 30 | return xerrors.Errorf("Failed to SetLogger. err: %w", err) 31 | } 32 | 33 | driver, err := db.NewDB(viper.GetString("dbtype"), viper.GetString("dbpath"), viper.GetBool("debug-sql"), db.Option{}) 34 | if err != nil { 35 | if errors.Is(err, db.ErrDBLocked) { 36 | return xerrors.Errorf("Failed to open DB. Close DB connection before fetching. err: %w", err) 37 | } 38 | return xerrors.Errorf("Failed to open DB. err: %w", err) 39 | } 40 | 41 | fetchMeta, err := driver.GetFetchMeta() 42 | if err != nil { 43 | return xerrors.Errorf("Failed to get FetchMeta from DB. err: %w", err) 44 | } 45 | if fetchMeta.OutDated() { 46 | return xerrors.Errorf("Failed to Insert CVEs into DB. err: SchemaVersion is old. SchemaVersion: %+v", map[string]uint{"latest": models.LatestSchemaVersion, "DB": fetchMeta.SchemaVersion}) 47 | } 48 | // If the fetch fails the first time (without SchemaVersion), the DB needs to be cleaned every time, so insert SchemaVersion. 49 | if err := driver.UpsertFetchMeta(fetchMeta); err != nil { 50 | return xerrors.Errorf("Failed to upsert FetchMeta to DB. dbpath: %s, err: %w", viper.GetString("dbpath"), err) 51 | } 52 | 53 | advs, err := euvd.FetchConvert(args) 54 | if err != nil { 55 | return xerrors.Errorf("Failed to fetch from EUVD. err: %w", err) 56 | } 57 | 58 | log.Infof("Inserting EUVD into DB (%s).", driver.Name()) 59 | if err := driver.InsertEuvd(advs); err != nil { 60 | return xerrors.Errorf("Failed to insert. dbpath: %s, err: %w", viper.GetString("dbpath"), err) 61 | } 62 | 63 | fetchMeta.LastFetchedAt = time.Now() 64 | if err := driver.UpsertFetchMeta(fetchMeta); err != nil { 65 | return xerrors.Errorf("Failed to upsert FetchMeta to DB. dbpath: %s, err: %w", viper.GetString("dbpath"), err) 66 | } 67 | 68 | log.Infof("Finished fetching EUVD.") 69 | return nil 70 | } 71 | -------------------------------------------------------------------------------- /commands/fetchcisco.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "errors" 5 | "time" 6 | 7 | "github.com/spf13/cobra" 8 | "github.com/spf13/viper" 9 | "github.com/vulsio/go-cve-dictionary/db" 10 | "github.com/vulsio/go-cve-dictionary/fetcher/cisco" 11 | "github.com/vulsio/go-cve-dictionary/log" 12 | "github.com/vulsio/go-cve-dictionary/models" 13 | "golang.org/x/xerrors" 14 | ) 15 | 16 | var fetchCiscoCmd = &cobra.Command{ 17 | Use: "cisco", 18 | Short: "Fetch Vulnerability dictionary from Cisco Advisories", 19 | Long: "Fetch Vulnerability dictionary from Cisco Advisories", 20 | RunE: fetchCisco, 21 | } 22 | 23 | func init() { 24 | fetchCmd.AddCommand(fetchCiscoCmd) 25 | } 26 | 27 | func fetchCisco(_ *cobra.Command, _ []string) (err error) { 28 | if err := log.SetLogger(viper.GetBool("log-to-file"), viper.GetString("log-dir"), viper.GetBool("debug"), viper.GetBool("log-json")); err != nil { 29 | return xerrors.Errorf("Failed to SetLogger. err: %w", err) 30 | } 31 | 32 | driver, err := db.NewDB(viper.GetString("dbtype"), viper.GetString("dbpath"), viper.GetBool("debug-sql"), db.Option{}) 33 | if err != nil { 34 | if errors.Is(err, db.ErrDBLocked) { 35 | return xerrors.Errorf("Failed to open DB. Close DB connection before fetching. err: %w", err) 36 | } 37 | return xerrors.Errorf("Failed to open DB. err: %w", err) 38 | } 39 | 40 | fetchMeta, err := driver.GetFetchMeta() 41 | if err != nil { 42 | return xerrors.Errorf("Failed to get FetchMeta from DB. err: %w", err) 43 | } 44 | if fetchMeta.OutDated() { 45 | return xerrors.Errorf("Failed to Insert CVEs into DB. err: SchemaVersion is old. SchemaVersion: %+v", map[string]uint{"latest": models.LatestSchemaVersion, "DB": fetchMeta.SchemaVersion}) 46 | } 47 | // If the fetch fails the first time (without SchemaVersion), the DB needs to be cleaned every time, so insert SchemaVersion. 48 | if err := driver.UpsertFetchMeta(fetchMeta); err != nil { 49 | return xerrors.Errorf("Failed to upsert FetchMeta to DB. dbpath: %s, err: %w", viper.GetString("dbpath"), err) 50 | } 51 | 52 | advs, err := cisco.FetchConvert() 53 | if err != nil { 54 | return xerrors.Errorf("Failed to fetch from cisco. err: %w", err) 55 | } 56 | 57 | log.Infof("Inserting Cisco into DB (%s).", driver.Name()) 58 | if err := driver.InsertCisco(advs); err != nil { 59 | return xerrors.Errorf("Failed to insert. dbpath: %s, err: %w", viper.GetString("dbpath"), err) 60 | } 61 | 62 | fetchMeta.LastFetchedAt = time.Now() 63 | if err := driver.UpsertFetchMeta(fetchMeta); err != nil { 64 | return xerrors.Errorf("Failed to upsert FetchMeta to DB. dbpath: %s, err: %w", viper.GetString("dbpath"), err) 65 | } 66 | 67 | log.Infof("Finished fetching Cisco.") 68 | return nil 69 | } 70 | -------------------------------------------------------------------------------- /commands/fetchfortinet.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "errors" 5 | "time" 6 | 7 | "github.com/spf13/cobra" 8 | "github.com/spf13/viper" 9 | "golang.org/x/xerrors" 10 | 11 | "github.com/vulsio/go-cve-dictionary/db" 12 | "github.com/vulsio/go-cve-dictionary/fetcher/fortinet" 13 | "github.com/vulsio/go-cve-dictionary/log" 14 | "github.com/vulsio/go-cve-dictionary/models" 15 | ) 16 | 17 | var fetchFortinetCmd = &cobra.Command{ 18 | Use: "fortinet", 19 | Short: "Fetch Vulnerability dictionary from Fortinet Advisories", 20 | Long: "Fetch Vulnerability dictionary from Fortinet Advisories", 21 | RunE: fetchFortinet, 22 | } 23 | 24 | func init() { 25 | fetchCmd.AddCommand(fetchFortinetCmd) 26 | } 27 | 28 | func fetchFortinet(_ *cobra.Command, _ []string) (err error) { 29 | if err := log.SetLogger(viper.GetBool("log-to-file"), viper.GetString("log-dir"), viper.GetBool("debug"), viper.GetBool("log-json")); err != nil { 30 | return xerrors.Errorf("Failed to SetLogger. err: %w", err) 31 | } 32 | 33 | driver, err := db.NewDB(viper.GetString("dbtype"), viper.GetString("dbpath"), viper.GetBool("debug-sql"), db.Option{}) 34 | if err != nil { 35 | if errors.Is(err, db.ErrDBLocked) { 36 | return xerrors.Errorf("Failed to open DB. Close DB connection before fetching. err: %w", err) 37 | } 38 | return xerrors.Errorf("Failed to open DB. err: %w", err) 39 | } 40 | 41 | fetchMeta, err := driver.GetFetchMeta() 42 | if err != nil { 43 | return xerrors.Errorf("Failed to get FetchMeta from DB. err: %w", err) 44 | } 45 | if fetchMeta.OutDated() { 46 | return xerrors.Errorf("Failed to Insert CVEs into DB. err: SchemaVersion is old. SchemaVersion: %+v", map[string]uint{"latest": models.LatestSchemaVersion, "DB": fetchMeta.SchemaVersion}) 47 | } 48 | // If the fetch fails the first time (without SchemaVersion), the DB needs to be cleaned every time, so insert SchemaVersion. 49 | if err := driver.UpsertFetchMeta(fetchMeta); err != nil { 50 | return xerrors.Errorf("Failed to upsert FetchMeta to DB. dbpath: %s, err: %w", viper.GetString("dbpath"), err) 51 | } 52 | 53 | advs, err := fortinet.FetchConvert() 54 | if err != nil { 55 | return xerrors.Errorf("Failed to fetch from fortinet. err: %w", err) 56 | } 57 | 58 | log.Infof("Inserting Fortinet into DB (%s).", driver.Name()) 59 | if err := driver.InsertFortinet(advs); err != nil { 60 | return xerrors.Errorf("Failed to insert. dbpath: %s, err: %w", viper.GetString("dbpath"), err) 61 | } 62 | 63 | fetchMeta.LastFetchedAt = time.Now() 64 | if err := driver.UpsertFetchMeta(fetchMeta); err != nil { 65 | return xerrors.Errorf("Failed to upsert FetchMeta to DB. dbpath: %s, err: %w", viper.GetString("dbpath"), err) 66 | } 67 | 68 | log.Infof("Finished fetching Fortinet.") 69 | return nil 70 | } 71 | -------------------------------------------------------------------------------- /commands/fetchpaloalto.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "errors" 5 | "time" 6 | 7 | "github.com/spf13/cobra" 8 | "github.com/spf13/viper" 9 | "golang.org/x/xerrors" 10 | 11 | "github.com/vulsio/go-cve-dictionary/db" 12 | "github.com/vulsio/go-cve-dictionary/fetcher/paloalto" 13 | "github.com/vulsio/go-cve-dictionary/log" 14 | "github.com/vulsio/go-cve-dictionary/models" 15 | ) 16 | 17 | var fetchPaloaltoCmd = &cobra.Command{ 18 | Use: "paloalto", 19 | Short: "Fetch Vulnerability dictionary from Palo Alto Networks Advisories", 20 | Long: "Fetch Vulnerability dictionary from Palo Alto Networks Advisories", 21 | RunE: fetchPaloalto, 22 | } 23 | 24 | func init() { 25 | fetchCmd.AddCommand(fetchPaloaltoCmd) 26 | } 27 | 28 | func fetchPaloalto(_ *cobra.Command, _ []string) (err error) { 29 | if err := log.SetLogger(viper.GetBool("log-to-file"), viper.GetString("log-dir"), viper.GetBool("debug"), viper.GetBool("log-json")); err != nil { 30 | return xerrors.Errorf("Failed to SetLogger. err: %w", err) 31 | } 32 | 33 | driver, err := db.NewDB(viper.GetString("dbtype"), viper.GetString("dbpath"), viper.GetBool("debug-sql"), db.Option{}) 34 | if err != nil { 35 | if errors.Is(err, db.ErrDBLocked) { 36 | return xerrors.Errorf("Failed to open DB. Close DB connection before fetching. err: %w", err) 37 | } 38 | return xerrors.Errorf("Failed to open DB. err: %w", err) 39 | } 40 | 41 | fetchMeta, err := driver.GetFetchMeta() 42 | if err != nil { 43 | return xerrors.Errorf("Failed to get FetchMeta from DB. err: %w", err) 44 | } 45 | if fetchMeta.OutDated() { 46 | return xerrors.Errorf("Failed to Insert CVEs into DB. err: SchemaVersion is old. SchemaVersion: %+v", map[string]uint{"latest": models.LatestSchemaVersion, "DB": fetchMeta.SchemaVersion}) 47 | } 48 | // If the fetch fails the first time (without SchemaVersion), the DB needs to be cleaned every time, so insert SchemaVersion. 49 | if err := driver.UpsertFetchMeta(fetchMeta); err != nil { 50 | return xerrors.Errorf("Failed to upsert FetchMeta to DB. dbpath: %s, err: %w", viper.GetString("dbpath"), err) 51 | } 52 | 53 | advs, err := paloalto.FetchConvert() 54 | if err != nil { 55 | return xerrors.Errorf("Failed to fetch from paloalto. err: %w", err) 56 | } 57 | 58 | log.Infof("Inserting Paloalto into DB (%s).", driver.Name()) 59 | if err := driver.InsertPaloalto(advs); err != nil { 60 | return xerrors.Errorf("Failed to insert. dbpath: %s, err: %w", viper.GetString("dbpath"), err) 61 | } 62 | 63 | fetchMeta.LastFetchedAt = time.Now() 64 | if err := driver.UpsertFetchMeta(fetchMeta); err != nil { 65 | return xerrors.Errorf("Failed to upsert FetchMeta to DB. dbpath: %s, err: %w", viper.GetString("dbpath"), err) 66 | } 67 | 68 | log.Infof("Finished fetching Paloalto.") 69 | return nil 70 | } 71 | -------------------------------------------------------------------------------- /commands/fetchvulncheck.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "errors" 5 | "time" 6 | 7 | "github.com/spf13/cobra" 8 | "github.com/spf13/viper" 9 | "golang.org/x/xerrors" 10 | 11 | "github.com/vulsio/go-cve-dictionary/db" 12 | "github.com/vulsio/go-cve-dictionary/fetcher/vulncheck" 13 | "github.com/vulsio/go-cve-dictionary/log" 14 | "github.com/vulsio/go-cve-dictionary/models" 15 | ) 16 | 17 | var fetchVulncheckCmd = &cobra.Command{ 18 | Use: "vulncheck", 19 | Short: "Fetch Vulnerability dictionary from VulnCheck NVD++", 20 | Long: "Fetch Vulnerability dictionary from VulnCheck NVD++", 21 | RunE: fetchVulncheck, 22 | } 23 | 24 | func init() { 25 | fetchCmd.AddCommand(fetchVulncheckCmd) 26 | } 27 | 28 | func fetchVulncheck(_ *cobra.Command, args []string) (err error) { 29 | if err := log.SetLogger(viper.GetBool("log-to-file"), viper.GetString("log-dir"), viper.GetBool("debug"), viper.GetBool("log-json")); err != nil { 30 | return xerrors.Errorf("Failed to SetLogger. err: %w", err) 31 | } 32 | 33 | driver, err := db.NewDB(viper.GetString("dbtype"), viper.GetString("dbpath"), viper.GetBool("debug-sql"), db.Option{}) 34 | if err != nil { 35 | if errors.Is(err, db.ErrDBLocked) { 36 | return xerrors.Errorf("Failed to open DB. Close DB connection before fetching. err: %w", err) 37 | } 38 | return xerrors.Errorf("Failed to open DB. err: %w", err) 39 | } 40 | 41 | fetchMeta, err := driver.GetFetchMeta() 42 | if err != nil { 43 | return xerrors.Errorf("Failed to get FetchMeta from DB. err: %w", err) 44 | } 45 | if fetchMeta.OutDated() { 46 | return xerrors.Errorf("Failed to Insert CVEs into DB. err: SchemaVersion is old. SchemaVersion: %+v", map[string]uint{"latest": models.LatestSchemaVersion, "DB": fetchMeta.SchemaVersion}) 47 | } 48 | // If the fetch fails the first time (without SchemaVersion), the DB needs to be cleaned every time, so insert SchemaVersion. 49 | if err := driver.UpsertFetchMeta(fetchMeta); err != nil { 50 | return xerrors.Errorf("Failed to upsert FetchMeta to DB. dbpath: %s, err: %w", viper.GetString("dbpath"), err) 51 | } 52 | 53 | cves, err := vulncheck.FetchConvert(args) 54 | if err != nil { 55 | return xerrors.Errorf("Failed to fetch from VulnCheck NVD++. err: %w", err) 56 | } 57 | 58 | log.Infof("Inserting VulnCheck NVD++ into DB (%s).", driver.Name()) 59 | if err := driver.InsertVulncheck(cves); err != nil { 60 | return xerrors.Errorf("Failed to insert. dbpath: %s, err: %w", viper.GetString("dbpath"), err) 61 | } 62 | 63 | fetchMeta.LastFetchedAt = time.Now() 64 | if err := driver.UpsertFetchMeta(fetchMeta); err != nil { 65 | return xerrors.Errorf("Failed to upsert FetchMeta to DB. dbpath: %s, err: %w", viper.GetString("dbpath"), err) 66 | } 67 | 68 | log.Infof("Finished fetching VulnCheck NVD++.") 69 | return nil 70 | } 71 | -------------------------------------------------------------------------------- /commands/root.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path/filepath" 7 | 8 | homedir "github.com/mitchellh/go-homedir" 9 | "github.com/spf13/cobra" 10 | "github.com/spf13/viper" 11 | 12 | "github.com/vulsio/go-cve-dictionary/log" 13 | ) 14 | 15 | var cfgFile string 16 | 17 | // RootCmd represents the base command when called without any subcommands 18 | var RootCmd = &cobra.Command{ 19 | Use: "go-cve-dictionary", 20 | Short: "GO CVE Dictionary", 21 | Long: `GO CVE Dictionary`, 22 | SilenceErrors: true, 23 | SilenceUsage: true, 24 | } 25 | 26 | func init() { 27 | cobra.OnInitialize(initConfig) 28 | 29 | RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.go-cve-dictionary.yaml)") 30 | 31 | RootCmd.PersistentFlags().Bool("log-to-file", false, "output log to file") 32 | _ = viper.BindPFlag("log-to-file", RootCmd.PersistentFlags().Lookup("log-to-file")) 33 | 34 | RootCmd.PersistentFlags().String("log-dir", log.GetDefaultLogDir(), "/path/to/log") 35 | _ = viper.BindPFlag("log-dir", RootCmd.PersistentFlags().Lookup("log-dir")) 36 | 37 | RootCmd.PersistentFlags().Bool("log-json", false, "output log as JSON") 38 | _ = viper.BindPFlag("log-json", RootCmd.PersistentFlags().Lookup("log-json")) 39 | 40 | RootCmd.PersistentFlags().Bool("debug", false, "debug mode (default: false)") 41 | _ = viper.BindPFlag("debug", RootCmd.PersistentFlags().Lookup("debug")) 42 | 43 | RootCmd.PersistentFlags().Bool("debug-sql", false, "SQL debug mode") 44 | _ = viper.BindPFlag("debug-sql", RootCmd.PersistentFlags().Lookup("debug-sql")) 45 | 46 | pwd := os.Getenv("PWD") 47 | RootCmd.PersistentFlags().String("dbpath", filepath.Join(pwd, "cve.sqlite3"), "/path/to/sqlite3 or SQL connection string") 48 | _ = viper.BindPFlag("dbpath", RootCmd.PersistentFlags().Lookup("dbpath")) 49 | 50 | RootCmd.PersistentFlags().String("dbtype", "sqlite3", "Database type to store data in (sqlite3, mysql, postgres or redis supported)") 51 | _ = viper.BindPFlag("dbtype", RootCmd.PersistentFlags().Lookup("dbtype")) 52 | 53 | RootCmd.PersistentFlags().String("http-proxy", "", "http://proxy-url:port (default: empty)") 54 | _ = viper.BindPFlag("http-proxy", RootCmd.PersistentFlags().Lookup("http-proxy")) 55 | } 56 | 57 | // initConfig reads in config file and ENV variables if set. 58 | func initConfig() { 59 | if cfgFile != "" { 60 | viper.SetConfigFile(cfgFile) 61 | } else { 62 | // Find home directory. 63 | home, err := homedir.Dir() 64 | if err != nil { 65 | log.Errorf("Failed to find home directory. err: %s", err) 66 | os.Exit(1) 67 | } 68 | 69 | // Search config in home directory with name ".go-cve-dictionary" (without extension). 70 | viper.AddConfigPath(home) 71 | viper.SetConfigName(".go-cve-dictionary") 72 | } 73 | 74 | viper.AutomaticEnv() // read in environment variables that match 75 | 76 | // If a config file is found, read it in. 77 | if err := viper.ReadInConfig(); err == nil { 78 | fmt.Println("Using config file:", viper.ConfigFileUsed()) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /models/models_test.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func Test_FetchMeta(t *testing.T) { 8 | var tests = []struct { 9 | in FetchMeta 10 | outdated bool 11 | }{ 12 | { 13 | in: FetchMeta{ 14 | SchemaVersion: 1, 15 | }, 16 | outdated: true, 17 | }, 18 | { 19 | in: FetchMeta{ 20 | SchemaVersion: LatestSchemaVersion, 21 | }, 22 | outdated: false, 23 | }, 24 | } 25 | 26 | for i, tt := range tests { 27 | if aout := tt.in.OutDated(); tt.outdated != aout { 28 | t.Errorf("[%d] outdated expected: %#v\n actual: %#v\n", i, tt.outdated, aout) 29 | } 30 | } 31 | } 32 | 33 | func TestCveDetail_HasJvn(t *testing.T) { 34 | type fields struct { 35 | Nvds []Nvd 36 | Jvns []Jvn 37 | } 38 | tests := []struct { 39 | name string 40 | fields fields 41 | want bool 42 | }{ 43 | { 44 | name: "Jvn 0-slice", 45 | fields: fields{ 46 | Nvds: []Nvd{{CveID: "CVE-2020-0930"}}, 47 | Jvns: []Jvn{}, 48 | }, 49 | want: false, 50 | }, 51 | { 52 | name: "Nvd 0-slice", 53 | fields: fields{ 54 | Nvds: []Nvd{}, 55 | Jvns: []Jvn{{CveID: "CVE-2020-0930"}}, 56 | }, 57 | want: true, 58 | }, 59 | { 60 | name: "Jvn nil", 61 | fields: fields{ 62 | Nvds: []Nvd{{CveID: "CVE-2020-0930"}}, 63 | Jvns: nil, 64 | }, 65 | want: false, 66 | }, 67 | { 68 | name: "Nvd nil", 69 | fields: fields{ 70 | Nvds: nil, 71 | Jvns: []Jvn{{CveID: "CVE-2020-0930"}}, 72 | }, 73 | want: true, 74 | }, 75 | } 76 | for _, tt := range tests { 77 | t.Run(tt.name, func(t *testing.T) { 78 | c := CveDetail{ 79 | Nvds: tt.fields.Nvds, 80 | Jvns: tt.fields.Jvns, 81 | } 82 | if got := c.HasJvn(); got != tt.want { 83 | t.Errorf("CveDetail.hasJvn() = %v, want %v", got, tt.want) 84 | } 85 | }) 86 | } 87 | } 88 | 89 | func TestCveDetail_HasNvd(t *testing.T) { 90 | type fields struct { 91 | Nvds []Nvd 92 | Jvns []Jvn 93 | } 94 | tests := []struct { 95 | name string 96 | fields fields 97 | want bool 98 | }{ 99 | { 100 | name: "Jvn 0-slice", 101 | fields: fields{ 102 | Nvds: []Nvd{{CveID: "CVE-2020-0930"}}, 103 | Jvns: []Jvn{}, 104 | }, 105 | want: true, 106 | }, 107 | { 108 | name: "Nvd 0-slice", 109 | fields: fields{ 110 | Nvds: []Nvd{}, 111 | Jvns: []Jvn{{CveID: "CVE-2020-0930"}}, 112 | }, 113 | want: false, 114 | }, 115 | { 116 | name: "Jvn nil", 117 | fields: fields{ 118 | Nvds: []Nvd{{CveID: "CVE-2020-0930"}}, 119 | Jvns: nil, 120 | }, 121 | want: true, 122 | }, 123 | { 124 | name: "Nvd nil", 125 | fields: fields{ 126 | Nvds: nil, 127 | Jvns: []Jvn{{CveID: "CVE-2020-0930"}}, 128 | }, 129 | want: false, 130 | }, 131 | } 132 | for _, tt := range tests { 133 | t.Run(tt.name, func(t *testing.T) { 134 | c := CveDetail{ 135 | Nvds: tt.fields.Nvds, 136 | Jvns: tt.fields.Jvns, 137 | } 138 | if got := c.HasNvd(); got != tt.want { 139 | t.Errorf("CveDetail.hasNvd() = %v, want %v", got, tt.want) 140 | } 141 | }) 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /fetcher/jvn/jvn_test.go: -------------------------------------------------------------------------------- 1 | package jvn 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | "time" 7 | 8 | "github.com/vulsio/go-cve-dictionary/models" 9 | ) 10 | 11 | func TestDistributeCvesByYear(t *testing.T) { 12 | type args struct { 13 | uniqMap map[string]map[string]models.Jvn 14 | cves map[string]models.Jvn 15 | } 16 | var tests = []struct { 17 | in args 18 | expected map[string]map[string]models.Jvn 19 | }{ 20 | { 21 | in: args{ 22 | uniqMap: map[string]map[string]models.Jvn{}, 23 | cves: map[string]models.Jvn{ 24 | "JVNDB-2021-0001#CVE-2021-0001": {JvnID: "JVNDB-2021-0001", CveID: "CVE-2021-0001"}, 25 | "JVNDB-2021-0001#CVE-2020-0001": {JvnID: "JVNDB-2021-0001", CveID: "CVE-2020-0001"}, 26 | "JVNDB-2020-0001#CVE-2020-0001": {JvnID: "JVNDB-2020-0001", CveID: "CVE-2020-0001"}, 27 | }, 28 | }, 29 | expected: map[string]map[string]models.Jvn{ 30 | "2020": { 31 | "JVNDB-2020-0001#CVE-2020-0001": {JvnID: "JVNDB-2020-0001", CveID: "CVE-2020-0001"}, 32 | }, 33 | "2021": { 34 | "JVNDB-2021-0001#CVE-2021-0001": {JvnID: "JVNDB-2021-0001", CveID: "CVE-2021-0001"}, 35 | "JVNDB-2021-0001#CVE-2020-0001": {JvnID: "JVNDB-2021-0001", CveID: "CVE-2020-0001"}, 36 | }, 37 | }, 38 | }, 39 | { 40 | in: args{ 41 | uniqMap: map[string]map[string]models.Jvn{ 42 | "2021": { 43 | "JVNDB-2021-0001#CVE-2021-0001": {JvnID: "JVNDB-2021-0001", CveID: "CVE-2021-0001", LastModifiedDate: time.Date(2021, time.September, 3, 0, 0, 0, 0, time.UTC)}, 44 | }, 45 | }, 46 | cves: map[string]models.Jvn{ 47 | "JVNDB-2021-0001#CVE-2021-0001": {JvnID: "JVNDB-2021-0001", CveID: "CVE-2021-0001", LastModifiedDate: time.Date(2021, time.September, 4, 0, 0, 0, 0, time.UTC)}, 48 | }, 49 | }, 50 | expected: map[string]map[string]models.Jvn{ 51 | "2021": { 52 | "JVNDB-2021-0001#CVE-2021-0001": {JvnID: "JVNDB-2021-0001", CveID: "CVE-2021-0001", LastModifiedDate: time.Date(2021, time.September, 4, 0, 0, 0, 0, time.UTC)}, 53 | }, 54 | }, 55 | }, 56 | } 57 | 58 | for i, tt := range tests { 59 | distributeCvesByYear(tt.in.uniqMap, tt.in.cves) 60 | if !reflect.DeepEqual(tt.in.uniqMap, tt.expected) { 61 | t.Errorf("[%d] expected: %v\n actual: %v\n", i, tt.expected, tt.in.uniqMap) 62 | } 63 | } 64 | } 65 | 66 | func TestGetCveIDs(t *testing.T) { 67 | var tests = []struct { 68 | in Item 69 | expected []string 70 | }{ 71 | { 72 | in: Item{ 73 | Identifier: "success", 74 | References: []references{{ 75 | Source: "NVD", 76 | ID: "CVE-0000-0001", 77 | }}, 78 | }, 79 | expected: []string{"CVE-0000-0001"}, 80 | }, 81 | { 82 | in: Item{ 83 | Identifier: "extra space", 84 | References: []references{{ 85 | Source: "NVD", 86 | ID: "CVE-0000-0002 ", 87 | }}, 88 | }, 89 | expected: []string{"CVE-0000-0002"}, 90 | }, 91 | { 92 | in: Item{ 93 | Identifier: "invalid CVE-ID", 94 | References: []references{{ 95 | Source: "NVD", 96 | ID: "CCVE-0000-0003", 97 | }}, 98 | }, 99 | expected: []string{}, 100 | }, 101 | } 102 | 103 | for i, tt := range tests { 104 | if got := getCveIDs(tt.in); !reflect.DeepEqual(got, tt.expected) { 105 | t.Errorf("[%d] expected: %v\n actual: %v\n", i, tt.expected, got) 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /fetcher/util.go: -------------------------------------------------------------------------------- 1 | package fetcher 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "strings" 7 | "time" 8 | 9 | version "github.com/MaineK00n/go-cisco-version/asa" 10 | "github.com/knqyf263/go-cpe/common" 11 | "github.com/knqyf263/go-cpe/naming" 12 | "golang.org/x/xerrors" 13 | 14 | log "github.com/vulsio/go-cve-dictionary/log" 15 | "github.com/vulsio/go-cve-dictionary/models" 16 | ) 17 | 18 | // ParseCpeURI parses cpe22uri and set to models.CpeBase 19 | func ParseCpeURI(uri string) (*models.CpeBase, error) { 20 | var wfn common.WellFormedName 21 | var err error 22 | if strings.HasPrefix(uri, "cpe:/") { 23 | val := strings.TrimPrefix(uri, "cpe:/") 24 | if strings.Contains(val, "/") { 25 | uri = "cpe:/" + strings.ReplaceAll(val, "/", `\/`) 26 | } 27 | wfn, err = naming.UnbindURI(uri) 28 | if err != nil { 29 | return nil, err 30 | } 31 | } else { 32 | wfn, err = naming.UnbindFS(uri) 33 | if err != nil { 34 | return nil, err 35 | } 36 | } 37 | 38 | return &models.CpeBase{ 39 | URI: naming.BindToURI(wfn), 40 | FormattedString: naming.BindToFS(wfn), 41 | WellFormedName: wfn.String(), 42 | CpeWFN: models.CpeWFN{ 43 | Part: fmt.Sprintf("%s", wfn.Get(common.AttributePart)), 44 | Vendor: fmt.Sprintf("%s", wfn.Get(common.AttributeVendor)), 45 | Product: fmt.Sprintf("%s", wfn.Get(common.AttributeProduct)), 46 | Version: fmt.Sprintf("%s", wfn.Get(common.AttributeVersion)), 47 | Update: fmt.Sprintf("%s", wfn.Get(common.AttributeUpdate)), 48 | Edition: fmt.Sprintf("%s", wfn.Get(common.AttributeEdition)), 49 | Language: fmt.Sprintf("%s", wfn.Get(common.AttributeLanguage)), 50 | SoftwareEdition: fmt.Sprintf("%s", wfn.Get(common.AttributeSwEdition)), 51 | TargetSW: fmt.Sprintf("%s", wfn.Get(common.AttributeTargetSw)), 52 | TargetHW: fmt.Sprintf("%s", wfn.Get(common.AttributeTargetHw)), 53 | Other: fmt.Sprintf("%s", wfn.Get(common.AttributeOther)), 54 | }, 55 | }, nil 56 | } 57 | 58 | // CheckIfVersionParsable checks if the version fields in CpeBase are parsable 59 | func CheckIfVersionParsable(cpeBase *models.CpeBase) bool { 60 | if cpeBase.Version != "ANY" && cpeBase.Version != "NA" { 61 | vers := []string{cpeBase.VersionStartExcluding, 62 | cpeBase.VersionStartIncluding, 63 | cpeBase.VersionEndIncluding, 64 | cpeBase.VersionEndExcluding} 65 | for _, v := range vers { 66 | if v == "" { 67 | continue 68 | } 69 | v := strings.ReplaceAll(v, `\`, "") 70 | if _, err := version.NewVersion(v); err != nil { 71 | return false 72 | } 73 | } 74 | } 75 | return true 76 | } 77 | 78 | // StringToFloat cast string to float64 79 | func StringToFloat(str string) float64 { 80 | if len(str) == 0 { 81 | return 0 82 | } 83 | var f float64 84 | var ignorableError error 85 | if f, ignorableError = strconv.ParseFloat(str, 64); ignorableError != nil { 86 | log.Errorf("Failed to cast CVSS score. score: %s, err; %s", 87 | str, 88 | ignorableError, 89 | ) 90 | f = 0 91 | } 92 | return f 93 | } 94 | 95 | // ParseTime parses a time string with multiple layouts 96 | func ParseTime(layouts []string, value string) (time.Time, error) { 97 | for _, layout := range layouts { 98 | t, err := time.Parse(layout, value) 99 | if err == nil { 100 | return t, nil 101 | } 102 | } 103 | return time.Time{}, xerrors.Errorf("Failed to parse %q with layouts %q", value, layouts) 104 | } 105 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/vulsio/go-cve-dictionary 2 | 3 | go 1.24.0 4 | 5 | require ( 6 | github.com/MaineK00n/go-cisco-version v0.0.0-20250611084427-015c6492ef23 7 | github.com/MaineK00n/go-paloalto-version v0.0.0-20250522233912-78724c69edda 8 | github.com/PuerkitoBio/goquery v1.11.0 9 | github.com/cenkalti/backoff v2.2.1+incompatible 10 | github.com/cheggaaa/pb/v3 v3.1.7 11 | github.com/glebarez/sqlite v1.11.0 12 | github.com/go-redis/redis/v8 v8.11.5 13 | github.com/google/go-cmp v0.7.0 14 | github.com/hashicorp/go-version v1.8.0 15 | github.com/inconshreveable/log15 v3.0.0-testing.5+incompatible 16 | github.com/klauspost/compress v1.18.2 17 | github.com/knqyf263/go-cpe v0.0.0-20201213041631-54f6ab28673f 18 | github.com/knqyf263/go-rpm-version v0.0.0-20170716094938-74609b86c936 19 | github.com/labstack/echo/v4 v4.13.4 20 | github.com/mitchellh/go-homedir v1.1.0 21 | github.com/opencontainers/image-spec v1.1.1 22 | github.com/spf13/cobra v1.10.2 23 | github.com/spf13/viper v1.21.0 24 | golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 25 | gorm.io/driver/mysql v1.5.5 26 | gorm.io/driver/postgres v1.5.7 27 | gorm.io/gorm v1.25.7 28 | oras.land/oras-go/v2 v2.6.0 29 | ) 30 | 31 | require ( 32 | github.com/VividCortex/ewma v1.2.0 // indirect 33 | github.com/andybalholm/cascadia v1.3.3 // indirect 34 | github.com/cespare/xxhash/v2 v2.3.0 // indirect 35 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect 36 | github.com/dustin/go-humanize v1.0.1 // indirect 37 | github.com/fatih/color v1.18.0 // indirect 38 | github.com/fsnotify/fsnotify v1.9.0 // indirect 39 | github.com/glebarez/go-sqlite v1.21.2 // indirect 40 | github.com/go-sql-driver/mysql v1.7.1 // indirect 41 | github.com/go-stack/stack v1.8.0 // indirect 42 | github.com/go-viper/mapstructure/v2 v2.4.0 // indirect 43 | github.com/google/uuid v1.6.0 // indirect 44 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 45 | github.com/jackc/pgpassfile v1.0.0 // indirect 46 | github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect 47 | github.com/jackc/pgx/v5 v5.5.4 // indirect 48 | github.com/jackc/puddle/v2 v2.2.1 // indirect 49 | github.com/jinzhu/inflection v1.0.0 // indirect 50 | github.com/jinzhu/now v1.1.5 // indirect 51 | github.com/labstack/gommon v0.4.2 // indirect 52 | github.com/mattn/go-colorable v0.1.14 // indirect 53 | github.com/mattn/go-isatty v0.0.20 // indirect 54 | github.com/mattn/go-runewidth v0.0.16 // indirect 55 | github.com/opencontainers/go-digest v1.0.0 // indirect 56 | github.com/pelletier/go-toml/v2 v2.2.4 // indirect 57 | github.com/pkg/errors v0.9.1 // indirect 58 | github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect 59 | github.com/rivo/uniseg v0.4.7 // indirect 60 | github.com/sagikazarmark/locafero v0.11.0 // indirect 61 | github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect 62 | github.com/spf13/afero v1.15.0 // indirect 63 | github.com/spf13/cast v1.10.0 // indirect 64 | github.com/spf13/pflag v1.0.10 // indirect 65 | github.com/subosito/gotenv v1.6.0 // indirect 66 | github.com/valyala/bytebufferpool v1.0.0 // indirect 67 | github.com/valyala/fasttemplate v1.2.2 // indirect 68 | go.yaml.in/yaml/v3 v3.0.4 // indirect 69 | golang.org/x/crypto v0.45.0 // indirect 70 | golang.org/x/net v0.47.0 // indirect 71 | golang.org/x/sync v0.18.0 // indirect 72 | golang.org/x/sys v0.38.0 // indirect 73 | golang.org/x/term v0.37.0 // indirect 74 | golang.org/x/text v0.31.0 // indirect 75 | golang.org/x/time v0.11.0 // indirect 76 | modernc.org/libc v1.22.5 // indirect 77 | modernc.org/mathutil v1.5.0 // indirect 78 | modernc.org/memory v1.5.0 // indirect 79 | modernc.org/sqlite v1.23.1 // indirect 80 | ) 81 | -------------------------------------------------------------------------------- /models/paloalto.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import "time" 4 | 5 | const ( 6 | // PaloaltoType : 7 | PaloaltoType = "Paloalto" 8 | 9 | // PaloaltoExactVersionMatch : 10 | PaloaltoExactVersionMatch = "PaloaltoExactVersionMatch" 11 | // PaloaltoRoughVersionMatch : 12 | PaloaltoRoughVersionMatch = "PaloaltoRoughVersionMatch" 13 | // PaloaltoVendorProductMatch : 14 | PaloaltoVendorProductMatch = "PaloaltoVendorProductMatch" 15 | ) 16 | 17 | // Paloalto is a model of Paloalto 18 | type Paloalto struct { 19 | ID int64 `json:"-"` 20 | AdvisoryID string `gorm:"type:varchar(255)"` 21 | CveID string `gorm:"index:idx_paloaltos_cveid;type:varchar(255)"` 22 | Title string `gorm:"type:varchar(256)"` 23 | Descriptions []PaloaltoDescription 24 | Affected []PaloaltoProduct 25 | ProblemTypes []PaloaltoProblemType 26 | Impacts []PaloaltoImpact 27 | CVSSv3 []PaloaltoCVSS3 28 | CVSSv40 []PaloaltoCVSS40 29 | Workarounds []PaloaltoWorkaround 30 | Solutions []PaloaltoSolution 31 | Exploits []PaloaltoExploit 32 | Configurations []PaloaltoConfiguration 33 | References []PaloaltoReference 34 | DatePublic *time.Time 35 | 36 | DetectionMethod string `gorm:"-"` 37 | } 38 | 39 | // PaloaltoDescription : 40 | type PaloaltoDescription struct { 41 | ID int64 `json:"-"` 42 | PaloaltoID uint `json:"-" gorm:"index:idx_paloalto_descriptions_paloalto_id"` 43 | Description string `gorm:"type:text"` 44 | } 45 | 46 | // PaloaltoProduct : 47 | type PaloaltoProduct struct { 48 | ID int64 `json:"-"` 49 | PaloaltoID uint `json:"-" gorm:"index:idx_paloalto_affected_paloalto_id"` 50 | CpeBase `gorm:"embedded"` 51 | } 52 | 53 | // PaloaltoProblemType : 54 | type PaloaltoProblemType struct { 55 | ID int64 `json:"-"` 56 | PaloaltoID uint `json:"-" gorm:"index:idx_paloalto_problem_types_paloalto_id"` 57 | CweID string `gorm:"type:varchar(255)"` 58 | } 59 | 60 | // PaloaltoImpact : 61 | type PaloaltoImpact struct { 62 | ID int64 `json:"-"` 63 | PaloaltoID uint `json:"-" gorm:"index:idx_paloalto_impacts_paloalto_id"` 64 | CapecID string `gorm:"type:varchar(11)"` 65 | } 66 | 67 | // PaloaltoCVSS3 : 68 | type PaloaltoCVSS3 struct { 69 | ID int64 `json:"-"` 70 | PaloaltoID uint `json:"-" gorm:"index:idx_paloalto_cvss3_paloalto_id"` 71 | Cvss3 `gorm:"embedded"` 72 | } 73 | 74 | // PaloaltoCVSS40 : 75 | type PaloaltoCVSS40 struct { 76 | ID int64 `json:"-"` 77 | PaloaltoID uint `json:"-" gorm:"index:idx_paloalto_cvss40_paloalto_id"` 78 | Cvss40 `gorm:"embedded"` 79 | } 80 | 81 | // PaloaltoWorkaround : 82 | type PaloaltoWorkaround struct { 83 | ID int64 `json:"-"` 84 | PaloaltoID uint `json:"-" gorm:"index:idx_paloalto_workarounds_paloalto_id"` 85 | Workaround string `gorm:"type:text"` 86 | } 87 | 88 | // PaloaltoSolution : 89 | type PaloaltoSolution struct { 90 | ID int64 `json:"-"` 91 | PaloaltoID uint `json:"-" gorm:"index:idx_paloalto_solutions_paloalto_id"` 92 | Solution string `gorm:"type:text"` 93 | } 94 | 95 | // PaloaltoExploit : 96 | type PaloaltoExploit struct { 97 | ID int64 `json:"-"` 98 | PaloaltoID uint `json:"-" gorm:"index:idx_paloalto_exploits_paloalto_id"` 99 | Exploit string `gorm:"type:text"` 100 | } 101 | 102 | // PaloaltoConfiguration : 103 | type PaloaltoConfiguration struct { 104 | ID int64 `json:"-"` 105 | PaloaltoID uint `json:"-" gorm:"index:idx_paloalto_configurations_paloalto_id"` 106 | Configuration string `gorm:"type:text"` 107 | } 108 | 109 | // PaloaltoReference : 110 | type PaloaltoReference struct { 111 | ID int64 `json:"-"` 112 | PaloaltoID uint `json:"-" gorm:"index:idx_paloalto_references_paloalto_id"` 113 | Reference `gorm:"embedded"` 114 | } 115 | -------------------------------------------------------------------------------- /models/vulncheck.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import "time" 4 | 5 | const ( 6 | // VulncheckType : 7 | VulncheckType = "VulnCheck" 8 | 9 | // VulncheckExactVersionMatch : 10 | VulncheckExactVersionMatch = "VulnCheckExactVersionMatch" 11 | // VulncheckRoughVersionMatch : 12 | VulncheckRoughVersionMatch = "VulnCheckRoughVersionMatch" 13 | // VulncheckVendorProductMatch : 14 | VulncheckVendorProductMatch = "VulnCheckVendorProductMatch" 15 | ) 16 | 17 | // Vulncheck is a struct of VulnCheck JSON 18 | type Vulncheck struct { 19 | ID int64 `json:"-"` 20 | CveID string `gorm:"index:idx_vulnchecks_cveid;type:varchar(255)"` 21 | Descriptions []VulncheckDescription 22 | Cvss2 []VulncheckCvss2Extra 23 | Cvss3 []VulncheckCvss3 24 | Cvss40 []VulncheckCvss40 25 | Cwes []VulncheckCwe 26 | Cpes []VulncheckCpe 27 | References []VulncheckReference 28 | Certs []VulncheckCert 29 | PublishedDate time.Time 30 | LastModifiedDate time.Time 31 | 32 | DetectionMethod string `gorm:"-"` 33 | } 34 | 35 | // VulncheckDescription has description of the CVE 36 | type VulncheckDescription struct { 37 | ID int64 `json:"-"` 38 | VulncheckID uint `json:"-" gorm:"index:idx_vulncheck_descriptions_vulncheck_id"` 39 | Lang string `gorm:"type:varchar(255)"` 40 | Value string `gorm:"type:text"` 41 | } 42 | 43 | // VulncheckCvss2Extra has Vulncheck extra CVSS V2 info 44 | type VulncheckCvss2Extra struct { 45 | ID int64 `json:"-"` 46 | VulncheckID uint `json:"-" gorm:"index:idx_vulncheck_cvss2_extra_vulncheck_id"` 47 | Source string `gorm:"type:text"` 48 | Type string `gorm:"type:varchar(255)"` 49 | Cvss2 `gorm:"embedded"` 50 | ExploitabilityScore float64 51 | ImpactScore float64 52 | ObtainAllPrivilege bool 53 | ObtainUserPrivilege bool 54 | ObtainOtherPrivilege bool 55 | UserInteractionRequired bool 56 | } 57 | 58 | // VulncheckCvss3 has Vulncheck CVSS3 info 59 | type VulncheckCvss3 struct { 60 | ID int64 `json:"-"` 61 | VulncheckID uint `json:"-" gorm:"index:idx_vulncheck_cvss3_vulncheck_id"` 62 | Source string `gorm:"type:text"` 63 | Type string `gorm:"type:varchar(255)"` 64 | Cvss3 `gorm:"embedded"` 65 | } 66 | 67 | // VulncheckCvss40 has Vulncheck CVSS40 info 68 | type VulncheckCvss40 struct { 69 | ID int64 `json:"-"` 70 | VulncheckID uint `json:"-" gorm:"index:idx_vulncheck_cvss40_vulncheck_id"` 71 | Source string `gorm:"type:text"` 72 | Type string `gorm:"type:varchar(255)"` 73 | Cvss40 `gorm:"embedded"` 74 | } 75 | 76 | // VulncheckCwe has CweID 77 | type VulncheckCwe struct { 78 | ID int64 `json:"-"` 79 | VulncheckID uint `json:"-" gorm:"index:idx_vulncheck_cwes_vulncheck_id"` 80 | Source string `gorm:"type:text"` 81 | Type string `gorm:"type:varchar(255)"` 82 | CweID string `gorm:"type:varchar(255)"` 83 | } 84 | 85 | // VulncheckCpe is Child model of Vulncheck. 86 | type VulncheckCpe struct { 87 | ID int64 `json:"-"` 88 | VulncheckID uint `json:"-" gorm:"index:idx_vulncheck_cpes_vulncheck_id"` 89 | CpeBase `gorm:"embedded"` 90 | } 91 | 92 | // VulncheckReference holds reference information about the CVE. 93 | type VulncheckReference struct { 94 | ID int64 `json:"-"` 95 | VulncheckID uint `json:"-" gorm:"index:idx_vulncheck_references_vulncheck_id"` 96 | Reference `gorm:"embedded"` 97 | } 98 | 99 | // VulncheckCert is Child model of Vulncheck. 100 | type VulncheckCert struct { 101 | ID int64 `json:"-"` 102 | VulncheckID uint `json:"-" gorm:"index:idx_vulncheck_certs_vulncheck_id"` 103 | Cert `gorm:"embedded"` 104 | } 105 | -------------------------------------------------------------------------------- /models/nvd.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import "time" 4 | 5 | const ( 6 | // NvdType : 7 | NvdType = "NVD" 8 | 9 | // NvdExactVersionMatch : 10 | NvdExactVersionMatch = "NvdExactVersionMatch" 11 | // NvdRoughVersionMatch : 12 | NvdRoughVersionMatch = "NvdRoughVersionMatch" 13 | // NvdVendorProductMatch : 14 | NvdVendorProductMatch = "NvdVendorProductMatch" 15 | ) 16 | 17 | // Nvd is a struct of NVD JSON 18 | // https://scap.nist.gov/schema/nvd/feed/0.1/nvd_cve_feed_json_0.1_beta.schema 19 | type Nvd struct { 20 | ID int64 `json:"-"` 21 | CveID string `gorm:"index:idx_nvds_cveid;type:varchar(255)"` 22 | Descriptions []NvdDescription 23 | Cvss2 []NvdCvss2Extra 24 | Cvss3 []NvdCvss3 25 | Cvss40 []NvdCvss40 26 | Cwes []NvdCwe 27 | Cpes []NvdCpe 28 | References []NvdReference 29 | Certs []NvdCert 30 | PublishedDate time.Time 31 | LastModifiedDate time.Time 32 | 33 | DetectionMethod string `gorm:"-"` 34 | } 35 | 36 | // NvdDescription has description of the CVE 37 | type NvdDescription struct { 38 | ID int64 `json:"-"` 39 | NvdID uint `json:"-" gorm:"index:idx_nvd_descriptions_nvd_id"` 40 | Lang string `gorm:"type:varchar(255)"` 41 | Value string `gorm:"type:text"` 42 | } 43 | 44 | // NvdCvss2Extra has Nvd extra CVSS V2 info 45 | type NvdCvss2Extra struct { 46 | ID int64 `json:"-"` 47 | NvdID uint `json:"-" gorm:"index:idx_nvd_cvss2_extra_nvd_id"` 48 | Source string `gorm:"type:text"` 49 | Type string `gorm:"type:varchar(255)"` 50 | Cvss2 `gorm:"embedded"` 51 | ExploitabilityScore float64 52 | ImpactScore float64 53 | ObtainAllPrivilege bool 54 | ObtainUserPrivilege bool 55 | ObtainOtherPrivilege bool 56 | UserInteractionRequired bool 57 | } 58 | 59 | // NvdCvss3 has Nvd CVSS3 info 60 | type NvdCvss3 struct { 61 | ID int64 `json:"-"` 62 | NvdID uint `json:"-" gorm:"index:idx_nvd_cvss3_nvd_id"` 63 | Source string `gorm:"type:text"` 64 | Type string `gorm:"type:varchar(255)"` 65 | Cvss3 `gorm:"embedded"` 66 | } 67 | 68 | // NvdCvss40 has Nvd CVSS40 info 69 | type NvdCvss40 struct { 70 | ID int64 `json:"-"` 71 | NvdID uint `json:"-" gorm:"index:idx_nvd_cvss40_nvd_id"` 72 | Source string `gorm:"type:text"` 73 | Type string `gorm:"type:varchar(255)"` 74 | Cvss40 `gorm:"embedded"` 75 | } 76 | 77 | // NvdCwe has CweID 78 | type NvdCwe struct { 79 | ID int64 `json:"-"` 80 | NvdID uint `json:"-" gorm:"index:idx_nvd_cwes_nvd_id"` 81 | Source string `gorm:"type:text"` 82 | Type string `gorm:"type:varchar(255)"` 83 | CweID string `gorm:"type:varchar(255)"` 84 | } 85 | 86 | // NvdCpe is Child model of Nvd. 87 | // see https://www.ipa.go.jp/security/vuln/CPE.html 88 | // In NVD, 89 | // configurations>nodes>cpe>vulnerable: true 90 | type NvdCpe struct { 91 | ID int64 `json:"-"` 92 | NvdID uint `json:"-" gorm:"index:idx_nvd_cpes_nvd_id"` 93 | CpeBase `gorm:"embedded"` 94 | EnvCpes []NvdEnvCpe 95 | } 96 | 97 | // NvdEnvCpe is a Environmental CPE 98 | // Only NVD has this information. 99 | // configurations>nodes>cpe>vulnerable: false 100 | type NvdEnvCpe struct { 101 | ID int64 `json:"-"` 102 | NvdCpeID uint `json:"-" gorm:"index:idx_nvd_env_cpes_nvd_cpe_id"` 103 | CpeBase `gorm:"embedded"` 104 | } 105 | 106 | // NvdReference holds reference information about the CVE. 107 | type NvdReference struct { 108 | ID int64 `json:"-"` 109 | NvdID uint `json:"-" gorm:"index:idx_nvd_references_nvd_id"` 110 | Reference `gorm:"embedded"` 111 | } 112 | 113 | // NvdCert is Child model of Nvd. 114 | type NvdCert struct { 115 | ID int64 `json:"-"` 116 | NvdID uint `json:"-" gorm:"index:idx_nvd_certs_nvd_id"` 117 | Cert `gorm:"embedded"` 118 | } 119 | -------------------------------------------------------------------------------- /commands/server.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/spf13/cobra" 7 | "github.com/spf13/viper" 8 | "golang.org/x/xerrors" 9 | 10 | "github.com/vulsio/go-cve-dictionary/db" 11 | "github.com/vulsio/go-cve-dictionary/log" 12 | "github.com/vulsio/go-cve-dictionary/models" 13 | "github.com/vulsio/go-cve-dictionary/server" 14 | ) 15 | 16 | // serverCmd is Subcommand for CVE dictionary HTTP Server 17 | var serverCmd = &cobra.Command{ 18 | Use: "server", 19 | Short: "Start CVE dictionary HTTP Server", 20 | Long: `Start CVE dictionary HTTP Server`, 21 | RunE: executeServer, 22 | } 23 | 24 | func init() { 25 | RootCmd.AddCommand(serverCmd) 26 | 27 | serverCmd.PersistentFlags().String("bind", "127.0.0.1", "HTTP server bind to IP address") 28 | _ = viper.BindPFlag("bind", serverCmd.PersistentFlags().Lookup("bind")) 29 | 30 | serverCmd.PersistentFlags().String("port", "1323", "HTTP server port number") 31 | _ = viper.BindPFlag("port", serverCmd.PersistentFlags().Lookup("port")) 32 | } 33 | 34 | func executeServer(_ *cobra.Command, _ []string) (err error) { 35 | if err := log.SetLogger(viper.GetBool("log-to-file"), viper.GetString("log-dir"), viper.GetBool("debug"), viper.GetBool("log-json")); err != nil { 36 | return xerrors.Errorf("Failed to SetLogger. err: %w", err) 37 | } 38 | 39 | driver, err := db.NewDB(viper.GetString("dbtype"), viper.GetString("dbpath"), viper.GetBool("debug-sql"), db.Option{}) 40 | if err != nil { 41 | if errors.Is(err, db.ErrDBLocked) { 42 | return xerrors.Errorf("Failed to open DB. Close DB connection before fetching. err: %w", err) 43 | } 44 | return xerrors.Errorf("Failed to open DB. err: %w", err) 45 | } 46 | 47 | fetchMeta, err := driver.GetFetchMeta() 48 | if err != nil { 49 | return xerrors.Errorf("Failed to get FetchMeta from DB. err: %w", err) 50 | } 51 | if fetchMeta.OutDated() { 52 | return xerrors.Errorf("Failed to start server. err: SchemaVersion is old. SchemaVersion: %+v", map[string]uint{"latest": models.LatestSchemaVersion, "DB": fetchMeta.SchemaVersion}) 53 | } 54 | 55 | count := 0 56 | nvdCount, err := driver.CountNvd() 57 | if err != nil { 58 | log.Errorf("Failed to count NVD table: %s", err) 59 | return err 60 | } 61 | count += nvdCount 62 | 63 | vulncheckCount, err := driver.CountVulncheck() 64 | if err != nil { 65 | log.Errorf("Failed to count VulnCheck table: %s", err) 66 | return err 67 | } 68 | count += vulncheckCount 69 | 70 | jvnCount, err := driver.CountJvn() 71 | if err != nil { 72 | log.Errorf("Failed to count JVN table: %s", err) 73 | return err 74 | } 75 | count += jvnCount 76 | 77 | euvdCount, err := driver.CountEuvd() 78 | if err != nil { 79 | log.Errorf("Failed to count EUVD table: %s", err) 80 | return err 81 | } 82 | count += euvdCount 83 | 84 | fortinetCount, err := driver.CountFortinet() 85 | if err != nil { 86 | log.Errorf("Failed to count Fortinet table: %s", err) 87 | return err 88 | } 89 | count += fortinetCount 90 | 91 | mitreCount, err := driver.CountMitre() 92 | if err != nil { 93 | log.Errorf("Failed to count MITRE table: %s", err) 94 | return err 95 | } 96 | count += mitreCount 97 | 98 | paloaltoCount, err := driver.CountPaloalto() 99 | if err != nil { 100 | log.Errorf("Failed to count Paloalto table: %s", err) 101 | return err 102 | } 103 | count += paloaltoCount 104 | 105 | ciscoCount, err := driver.CountCisco() 106 | if err != nil { 107 | log.Errorf("Failed to count Cisco table: %s", err) 108 | return err 109 | } 110 | count += ciscoCount 111 | 112 | if count == 0 { 113 | log.Infof("No Vulnerability data found. Run the below command to fetch data from NVD, VulnCheck, JVN, EUVD, Fortinet, MITRE, Paloalto, Cisco") 114 | log.Infof("") 115 | log.Infof(" go-cve-dictionary fetch nvd") 116 | log.Infof(" go-cve-dictionary fetch vulncheck") 117 | log.Infof(" go-cve-dictionary fetch jvn") 118 | log.Infof(" go-cve-dictionary fetch euvd") 119 | log.Infof(" go-cve-dictionary fetch fortinet") 120 | log.Infof(" go-cve-dictionary fetch mitre") 121 | log.Infof(" go-cve-dictionary fetch paloalto") 122 | log.Infof(" go-cve-dictionary fetch cisco") 123 | log.Infof("") 124 | return nil 125 | } 126 | 127 | log.Infof("Starting HTTP Server...") 128 | if err = server.Start(viper.GetBool("log-to-file"), viper.GetString("log-dir"), driver); err != nil { 129 | return xerrors.Errorf("Failed to start server. err: %w", err) 130 | } 131 | return nil 132 | } 133 | -------------------------------------------------------------------------------- /fetcher/euvd/euvd.go: -------------------------------------------------------------------------------- 1 | package euvd 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "io/fs" 7 | "iter" 8 | "os" 9 | "path/filepath" 10 | "slices" 11 | "strings" 12 | "time" 13 | 14 | "golang.org/x/xerrors" 15 | 16 | "github.com/vulsio/go-cve-dictionary/fetcher" 17 | "github.com/vulsio/go-cve-dictionary/models" 18 | ) 19 | 20 | // FetchConvert Fetch CVE vulnerability information from EUVD 21 | func FetchConvert(years []string) (iter.Seq2[models.Euvd, error], error) { 22 | dir, err := fetcher.FetchFromGHCR("vuls-data-raw-enisa-euvd-list") 23 | if err != nil { 24 | return nil, xerrors.Errorf("Failed to fetch euvd. err: %w", err) 25 | } 26 | 27 | return func(yield func(models.Euvd, error) bool) { 28 | defer os.RemoveAll(dir) 29 | 30 | yieldErr := errors.New("yield error") 31 | if err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error { 32 | if err != nil { 33 | return err 34 | } 35 | 36 | if d.IsDir() || filepath.Ext(path) != ".json" { 37 | return nil 38 | } 39 | 40 | if len(years) > 0 && !slices.Contains(years, filepath.Base(filepath.Dir(path))) { 41 | return nil 42 | } 43 | 44 | f, err := os.Open(path) 45 | if err != nil { 46 | return xerrors.Errorf("Failed to open %s. err: %w", path, err) 47 | } 48 | defer f.Close() 49 | 50 | var a advisory 51 | if err := json.NewDecoder(f).Decode(&a); err != nil { 52 | return xerrors.Errorf("Failed to decode %s. err: %w", path, err) 53 | } 54 | 55 | converted, err := convert(a) 56 | if err != nil { 57 | return xerrors.Errorf("Failed to convert %s to models.EUVD. err: %w", path, err) 58 | } 59 | 60 | if !yield(converted, nil) { 61 | return yieldErr 62 | } 63 | 64 | return nil 65 | }); err != nil { 66 | if errors.Is(err, yieldErr) { 67 | return 68 | } 69 | if !yield(models.Euvd{}, xerrors.Errorf("Failed to walk directory. err: %w", err)) { 70 | return 71 | } 72 | } 73 | }, nil 74 | } 75 | 76 | func convert(a advisory) (models.Euvd, error) { 77 | published, err := func() (time.Time, error) { 78 | if a.DatePublished == "" { 79 | return time.Date(1000, time.January, 1, 0, 0, 0, 0, time.UTC), nil 80 | } 81 | t, err := fetcher.ParseTime([]string{"Jan 2, 2006, 3:04:05 PM"}, a.DatePublished) 82 | if err != nil { 83 | return time.Time{}, xerrors.Errorf("Failed to parse Time. err: %w", err) 84 | } 85 | return t, nil 86 | }() 87 | if err != nil { 88 | return models.Euvd{}, xerrors.Errorf("Failed to parse DatePublished. err: %w", err) 89 | } 90 | updated, err := func() (time.Time, error) { 91 | if a.DateUpdated == "" { 92 | return time.Date(1000, time.January, 1, 0, 0, 0, 0, time.UTC), nil 93 | } 94 | t, err := fetcher.ParseTime([]string{"Jan 2, 2006, 3:04:05 PM"}, a.DateUpdated) 95 | if err != nil { 96 | return time.Time{}, xerrors.Errorf("Failed to parse Time. err: %w", err) 97 | } 98 | return t, nil 99 | }() 100 | if err != nil { 101 | return models.Euvd{}, xerrors.Errorf("Failed to parse DateUpdated. err: %w", err) 102 | } 103 | exploited, err := func() (*time.Time, error) { 104 | if a.ExploitedSince == "" { 105 | return nil, nil 106 | } 107 | t, err := fetcher.ParseTime([]string{"Jan 2, 2006, 3:04:05 PM"}, a.ExploitedSince) 108 | if err != nil { 109 | return nil, xerrors.Errorf("Failed to parse Time. err: %w", err) 110 | } 111 | return &t, nil 112 | }() 113 | if err != nil { 114 | return models.Euvd{}, xerrors.Errorf("Failed to parse ExploitedSince. err: %w", err) 115 | } 116 | 117 | return models.Euvd{ 118 | EuvdID: a.ID, 119 | EnisaUUID: a.EnisaUUID, 120 | Description: a.Description, 121 | DatePublished: published, 122 | DateUpdated: updated, 123 | BaseScore: a.BaseScore, 124 | BaseScoreVersion: a.BaseScoreVersion, 125 | BaseScoreVector: a.BaseScoreVector, 126 | References: func() []models.EuvdReference { 127 | var rs []models.EuvdReference 128 | for r := range strings.SplitSeq(a.References, "\n") { 129 | if r != "" { 130 | rs = append(rs, models.EuvdReference{ 131 | Reference: models.Reference{ 132 | Link: r, 133 | Name: r, 134 | }, 135 | }) 136 | } 137 | } 138 | return rs 139 | }(), 140 | Aliases: func() []models.EuvdAlias { 141 | var as []models.EuvdAlias 142 | for a := range strings.SplitSeq(a.Aliases, "\n") { 143 | if a != "" { 144 | as = append(as, models.EuvdAlias{ 145 | Alias: a, 146 | }) 147 | } 148 | } 149 | return as 150 | }(), 151 | Assigner: a.Assigner, 152 | EPSS: a.EPSS, 153 | ExploitedSince: exploited, 154 | }, nil 155 | } 156 | -------------------------------------------------------------------------------- /fetcher/euvd/euvd_test.go: -------------------------------------------------------------------------------- 1 | package euvd 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | "time" 7 | 8 | "github.com/vulsio/go-cve-dictionary/models" 9 | ) 10 | 11 | func Test_convert(t *testing.T) { 12 | type args struct { 13 | a advisory 14 | } 15 | tests := []struct { 16 | name string 17 | args args 18 | want models.Euvd 19 | wantErr bool 20 | }{ 21 | { 22 | name: "happy", 23 | args: args{ 24 | a: advisory{ 25 | ID: "EUVD-2025-197896", 26 | EnisaUUID: "39fc6ba0-3055-36d7-b1af-e36504adc285", 27 | Description: "Type Confusion in V8 in Google Chrome prior to 142.0.7444.175 allowed a remote attacker to potentially exploit heap corruption via a crafted HTML page. (Chromium security severity: High)", 28 | DatePublished: "Nov 17, 2025, 11:03:38 PM", 29 | DateUpdated: "Nov 20, 2025, 4:55:20 AM", 30 | BaseScore: 8.8, 31 | BaseScoreVersion: "3.1", 32 | BaseScoreVector: "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H", 33 | References: "https://nvd.nist.gov/vuln/detail/CVE-2025-13223\nhttps://chromereleases.googleblog.com/2025/11/stable-channel-update-for-desktop_17.html\nhttps://issues.chromium.org/issues/460017370\nhttps://www.cisa.gov/known-exploited-vulnerabilities-catalog?field_cve=CVE-2025-13223\n", 34 | Aliases: "CVE-2025-13223\nGHSA-fvx3-7348-92qj\n", 35 | Assigner: "Chrome", 36 | EPSS: 16.09, 37 | ExploitedSince: "Nov 19, 2025, 12:00:00 AM", 38 | EnisaIDProduct: []product{ 39 | { 40 | ID: "31e9543a-c977-34ab-aed8-fb1998cd919f", 41 | Product: struct { 42 | Name string "json:\"name\"" 43 | }{ 44 | Name: "Chrome", 45 | }, 46 | ProductVersion: "0 <142.0.7444.175", 47 | }, 48 | { 49 | ID: "9d0d9ce2-bd26-3828-91bf-10e38520abdd", 50 | Product: struct { 51 | Name string "json:\"name\"" 52 | }{ 53 | Name: "Chrome", 54 | }, 55 | ProductVersion: "142.0.7444.175 <142.0.7444.175", 56 | }, 57 | }, 58 | EnisaIDVendor: []vendor{ 59 | { 60 | ID: "9e98bd11-aaad-3e41-b9c8-b2ea9ab64447", 61 | Vendor: struct { 62 | Name string "json:\"name\"" 63 | }{ 64 | Name: "Google", 65 | }, 66 | }, 67 | }, 68 | }, 69 | }, 70 | want: models.Euvd{ 71 | EuvdID: "EUVD-2025-197896", 72 | EnisaUUID: "39fc6ba0-3055-36d7-b1af-e36504adc285", 73 | Description: "Type Confusion in V8 in Google Chrome prior to 142.0.7444.175 allowed a remote attacker to potentially exploit heap corruption via a crafted HTML page. (Chromium security severity: High)", 74 | DatePublished: time.Date(2025, 11, 17, 23, 03, 38, 0, time.UTC), 75 | DateUpdated: time.Date(2025, 11, 20, 4, 55, 20, 0, time.UTC), 76 | BaseScore: 8.8, 77 | BaseScoreVersion: "3.1", 78 | BaseScoreVector: "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H", 79 | References: []models.EuvdReference{ 80 | { 81 | Reference: models.Reference{ 82 | Link: "https://nvd.nist.gov/vuln/detail/CVE-2025-13223", 83 | Name: "https://nvd.nist.gov/vuln/detail/CVE-2025-13223", 84 | }, 85 | }, 86 | { 87 | Reference: models.Reference{ 88 | Link: "https://chromereleases.googleblog.com/2025/11/stable-channel-update-for-desktop_17.html", 89 | Name: "https://chromereleases.googleblog.com/2025/11/stable-channel-update-for-desktop_17.html", 90 | }, 91 | }, 92 | { 93 | Reference: models.Reference{ 94 | Link: "https://issues.chromium.org/issues/460017370", 95 | Name: "https://issues.chromium.org/issues/460017370", 96 | }, 97 | }, 98 | { 99 | Reference: models.Reference{ 100 | Link: "https://www.cisa.gov/known-exploited-vulnerabilities-catalog?field_cve=CVE-2025-13223", 101 | Name: "https://www.cisa.gov/known-exploited-vulnerabilities-catalog?field_cve=CVE-2025-13223", 102 | }, 103 | }, 104 | }, 105 | Aliases: []models.EuvdAlias{ 106 | { 107 | Alias: "CVE-2025-13223", 108 | }, 109 | { 110 | Alias: "GHSA-fvx3-7348-92qj", 111 | }, 112 | }, 113 | Assigner: "Chrome", 114 | EPSS: 16.09, 115 | ExploitedSince: func() *time.Time { t := time.Date(2025, 11, 19, 00, 0, 0, 0, time.UTC); return &t }(), 116 | }, 117 | }, 118 | } 119 | for _, tt := range tests { 120 | t.Run(tt.name, func(t *testing.T) { 121 | got, err := convert(tt.args.a) 122 | if (err != nil) != tt.wantErr { 123 | t.Errorf("convert() error = %v, wantErr %v", err, tt.wantErr) 124 | return 125 | } 126 | if !reflect.DeepEqual(got, tt.want) { 127 | t.Errorf("convert() = %v, want %v", got, tt.want) 128 | } 129 | }) 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /models/models.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "time" 5 | 6 | "gorm.io/gorm" 7 | ) 8 | 9 | // LatestSchemaVersion manages the Schema version used in the latest go-cve-dictionary. 10 | const LatestSchemaVersion = 3 11 | 12 | // FetchMeta has meta information about fetched CVE data 13 | type FetchMeta struct { 14 | gorm.Model `json:"-"` 15 | GoCVEDictRevision string 16 | SchemaVersion uint 17 | LastFetchedAt time.Time 18 | } 19 | 20 | // OutDated checks whether last fetched feed is out dated 21 | func (f FetchMeta) OutDated() bool { 22 | return f.SchemaVersion != LatestSchemaVersion 23 | } 24 | 25 | // CveDetail : 26 | type CveDetail struct { 27 | CveID string 28 | Nvds []Nvd 29 | Vulnchecks []Vulncheck 30 | Jvns []Jvn 31 | Euvds []Euvd 32 | Fortinets []Fortinet 33 | Mitres []Mitre 34 | Paloaltos []Paloalto 35 | Ciscos []Cisco 36 | } 37 | 38 | // HasNvd returns true if NVD contents 39 | func (c CveDetail) HasNvd() bool { 40 | return len(c.Nvds) != 0 41 | } 42 | 43 | // HasVulncheck returns true if VulnCheck contents 44 | func (c CveDetail) HasVulncheck() bool { 45 | return len(c.Vulnchecks) != 0 46 | } 47 | 48 | // HasJvn returns true if JVN contents 49 | func (c CveDetail) HasJvn() bool { 50 | return len(c.Jvns) != 0 51 | } 52 | 53 | // HasEuvd returns true if EUVD contents 54 | func (c CveDetail) HasEuvd() bool { 55 | return len(c.Euvds) != 0 56 | } 57 | 58 | // HasFortinet returns true if Fortinet contents 59 | func (c CveDetail) HasFortinet() bool { 60 | return len(c.Fortinets) != 0 61 | } 62 | 63 | // HasMitre returns true if Mitre contents 64 | func (c CveDetail) HasMitre() bool { 65 | return len(c.Mitres) != 0 66 | } 67 | 68 | // HasPaloalto returns true if Paloalto contents 69 | func (c CveDetail) HasPaloalto() bool { 70 | return len(c.Paloaltos) != 0 71 | } 72 | 73 | // HasCisco returns true if Cisco contents 74 | func (c CveDetail) HasCisco() bool { 75 | return len(c.Ciscos) != 0 76 | } 77 | 78 | // CveIDs : 79 | type CveIDs struct { 80 | Nvd []string 81 | Vulncheck []string 82 | Jvn []string 83 | Fortinet []string 84 | Paloalto []string 85 | Cisco []string 86 | } 87 | 88 | // Cvss2 has CVSS Version 2 info 89 | type Cvss2 struct { 90 | VectorString string `gorm:"type:varchar(255)"` 91 | AccessVector string `gorm:"type:varchar(255)"` 92 | AccessComplexity string `gorm:"type:varchar(255)"` 93 | Authentication string `gorm:"type:varchar(255)"` 94 | ConfidentialityImpact string `gorm:"type:varchar(255)"` 95 | IntegrityImpact string `gorm:"type:varchar(255)"` 96 | AvailabilityImpact string `gorm:"type:varchar(255)"` 97 | BaseScore float64 98 | Severity string `gorm:"type:varchar(255)"` 99 | } 100 | 101 | // Cvss3 has CVSS Version 3 info 102 | type Cvss3 struct { 103 | VectorString string `gorm:"type:varchar(255)"` 104 | AttackVector string `gorm:"type:varchar(255)"` 105 | AttackComplexity string `gorm:"type:varchar(255)"` 106 | PrivilegesRequired string `gorm:"type:varchar(255)"` 107 | UserInteraction string `gorm:"type:varchar(255)"` 108 | Scope string `gorm:"type:varchar(255)"` 109 | ConfidentialityImpact string `gorm:"type:varchar(255)"` 110 | IntegrityImpact string `gorm:"type:varchar(255)"` 111 | AvailabilityImpact string `gorm:"type:varchar(255)"` 112 | BaseScore float64 113 | BaseSeverity string `gorm:"type:varchar(255)"` 114 | ExploitabilityScore float64 115 | ImpactScore float64 116 | } 117 | 118 | // Cvss40 has CVSS Version 4.0 info 119 | type Cvss40 struct { 120 | VectorString string `gorm:"type:varchar(255)"` 121 | BaseScore float64 `json:"baseScore"` 122 | BaseSeverity string `gorm:"type:varchar(255)"` 123 | ThreatScore *float64 124 | ThreatSeverity *string `gorm:"type:varchar(255)"` 125 | EnvironmentalScore *float64 126 | EnvironmentalSeverity *string `gorm:"type:varchar(255)"` 127 | } 128 | 129 | // CpeBase has common args of Cpe and EnvCpe 130 | type CpeBase struct { 131 | URI string `gorm:"index;type:varchar(255)"` 132 | FormattedString string `gorm:"index;type:varchar(255)"` 133 | WellFormedName string `gorm:"type:text"` 134 | CpeWFN `gorm:"embedded"` 135 | VersionStartExcluding string `gorm:"type:varchar(255)"` 136 | VersionStartIncluding string `gorm:"type:varchar(255)"` 137 | VersionEndExcluding string `gorm:"type:varchar(255)"` 138 | VersionEndIncluding string `gorm:"type:varchar(255)"` 139 | } 140 | 141 | // CpeWFN has CPE Well Formed name information 142 | type CpeWFN struct { 143 | Part string `gorm:"index;type:varchar(255)"` 144 | Vendor string `gorm:"index;type:varchar(255)"` 145 | Product string `gorm:"index;type:varchar(255)"` 146 | Version string `gorm:"type:varchar(255)"` 147 | Update string `gorm:"type:varchar(255)"` 148 | Edition string `gorm:"type:varchar(255)"` 149 | Language string `gorm:"type:varchar(255)"` 150 | SoftwareEdition string `gorm:"type:varchar(255)"` 151 | TargetSW string `gorm:"type:varchar(255)"` 152 | TargetHW string `gorm:"type:varchar(255)"` 153 | Other string `gorm:"type:varchar(255)"` 154 | } 155 | 156 | // Reference holds reference information about the CVE. 157 | type Reference struct { 158 | Link string `gorm:"type:text"` 159 | Source string `gorm:"type:varchar(255)"` 160 | Tags string `gorm:"type:varchar(255)"` 161 | Name string `gorm:"type:text"` 162 | } 163 | 164 | // Cert holds CERT alerts. 165 | type Cert struct { 166 | Title string `gorm:"type:text"` 167 | Link string `gorm:"type:text"` 168 | } 169 | -------------------------------------------------------------------------------- /server/server.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "os" 7 | "path/filepath" 8 | "sort" 9 | 10 | "github.com/labstack/echo/v4" 11 | "github.com/labstack/echo/v4/middleware" 12 | "github.com/spf13/viper" 13 | "golang.org/x/xerrors" 14 | 15 | "github.com/vulsio/go-cve-dictionary/db" 16 | "github.com/vulsio/go-cve-dictionary/log" 17 | ) 18 | 19 | // Start starts CVE dictionary HTTP Server. 20 | func Start(logToFile bool, logDir string, driver db.DB) error { 21 | e := echo.New() 22 | e.Debug = viper.GetBool("debug") 23 | 24 | // Middleware 25 | e.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{Output: os.Stderr})) 26 | e.Use(middleware.Recover()) 27 | 28 | // setup access logger 29 | if logToFile { 30 | logPath := filepath.Join(logDir, "access.log") 31 | f, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) 32 | if err != nil { 33 | return xerrors.Errorf("Failed to open a log file: %s", err) 34 | } 35 | defer f.Close() 36 | e.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{Output: f})) 37 | } 38 | 39 | // Routes 40 | e.GET("/health", health()) 41 | e.GET("/cves/:id", getCve(driver)) 42 | e.POST("/cves", getCveMulti(driver)) 43 | e.GET("/cves/ids", getCveIDs(driver)) 44 | e.GET("/advisories/jvn", getAdvisoriesJvn(driver)) 45 | e.GET("/advisories/euvd", getAdvisoriesEuvd(driver)) 46 | e.GET("/advisories/fortinet", getAdvisoriesFortinet(driver)) 47 | e.GET("/advisories/paloalto", getAdvisoriesPaloalto(driver)) 48 | e.GET("/advisories/cisco", getAdvisoriesCisco(driver)) 49 | e.POST("/cpes", getCveByCpeName(driver)) 50 | e.POST("/cpes/ids", getCveIDsByCpeName(driver)) 51 | 52 | bindURL := fmt.Sprintf("%s:%s", viper.GetString("bind"), viper.GetString("port")) 53 | log.Infof("Listening on %s", bindURL) 54 | 55 | return e.Start(bindURL) 56 | } 57 | 58 | // Handler 59 | func health() echo.HandlerFunc { 60 | return func(c echo.Context) error { 61 | return c.String(http.StatusOK, "") 62 | } 63 | } 64 | 65 | // Handler 66 | func getCve(driver db.DB) echo.HandlerFunc { 67 | return func(c echo.Context) error { 68 | cveid := c.Param("id") 69 | cveDetail, err := driver.Get(cveid) 70 | if err != nil { 71 | log.Errorf("%s", err) 72 | return err 73 | } 74 | return c.JSON(http.StatusOK, &cveDetail) 75 | } 76 | } 77 | 78 | func getCveMulti(driver db.DB) echo.HandlerFunc { 79 | return func(c echo.Context) error { 80 | var cveIDs []string 81 | if err := c.Bind(&cveIDs); err != nil { 82 | log.Errorf("%s", err) 83 | return err 84 | } 85 | 86 | cveDetails, err := driver.GetMulti(cveIDs) 87 | if err != nil { 88 | log.Errorf("%s", err) 89 | return err 90 | } 91 | return c.JSON(http.StatusOK, &cveDetails) 92 | } 93 | } 94 | 95 | type cpeName struct { 96 | Name string `form:"name"` 97 | } 98 | 99 | func getCveByCpeName(driver db.DB) echo.HandlerFunc { 100 | return func(c echo.Context) error { 101 | cpe := cpeName{} 102 | err := c.Bind(&cpe) 103 | if err != nil { 104 | log.Errorf("%s", err) 105 | return err 106 | } 107 | cveDetails, err := driver.GetByCpeURI(cpe.Name) 108 | if err != nil { 109 | log.Errorf("%s", err) 110 | return err 111 | } 112 | 113 | sort.Slice(cveDetails, func(i, j int) bool { 114 | return cveDetails[i].CveID < cveDetails[j].CveID 115 | }) 116 | return c.JSON(http.StatusOK, &cveDetails) 117 | } 118 | } 119 | 120 | func getCveIDs(driver db.DB) echo.HandlerFunc { 121 | return func(c echo.Context) error { 122 | cveIDs, err := driver.GetCveIDs() 123 | if err != nil { 124 | log.Errorf("%s", err) 125 | return err 126 | } 127 | return c.JSON(http.StatusOK, &cveIDs) 128 | } 129 | } 130 | 131 | func getCveIDsByCpeName(driver db.DB) echo.HandlerFunc { 132 | return func(c echo.Context) error { 133 | cpe := cpeName{} 134 | err := c.Bind(&cpe) 135 | if err != nil { 136 | log.Errorf("%s", err) 137 | return err 138 | } 139 | cveids, err := driver.GetCveIDsByCpeURI(cpe.Name) 140 | if err != nil { 141 | log.Errorf("%s", err) 142 | return err 143 | } 144 | cveIDs := map[string][]string{"NVD": cveids.Nvd, "VulnCheck": cveids.Vulncheck, "JVN": cveids.Jvn, "Fortinet": cveids.Fortinet, "Paloalto": cveids.Paloalto, "Cisco": cveids.Cisco} 145 | return c.JSON(http.StatusOK, &cveIDs) 146 | } 147 | } 148 | 149 | func getAdvisoriesJvn(driver db.DB) echo.HandlerFunc { 150 | return func(c echo.Context) error { 151 | m, err := driver.GetAdvisoriesJvn() 152 | if err != nil { 153 | log.Errorf("%s", err) 154 | return err 155 | } 156 | return c.JSON(http.StatusOK, &m) 157 | } 158 | } 159 | 160 | func getAdvisoriesEuvd(driver db.DB) echo.HandlerFunc { 161 | return func(c echo.Context) error { 162 | m, err := driver.GetAdvisoriesEuvd() 163 | if err != nil { 164 | log.Errorf("%s", err) 165 | return err 166 | } 167 | return c.JSON(http.StatusOK, &m) 168 | } 169 | } 170 | 171 | func getAdvisoriesFortinet(driver db.DB) echo.HandlerFunc { 172 | return func(c echo.Context) error { 173 | m, err := driver.GetAdvisoriesFortinet() 174 | if err != nil { 175 | log.Errorf("%s", err) 176 | return err 177 | } 178 | return c.JSON(http.StatusOK, &m) 179 | } 180 | } 181 | 182 | func getAdvisoriesPaloalto(driver db.DB) echo.HandlerFunc { 183 | return func(c echo.Context) error { 184 | m, err := driver.GetAdvisoriesPaloalto() 185 | if err != nil { 186 | log.Errorf("%s", err) 187 | return err 188 | } 189 | return c.JSON(http.StatusOK, &m) 190 | } 191 | } 192 | 193 | func getAdvisoriesCisco(driver db.DB) echo.HandlerFunc { 194 | return func(c echo.Context) error { 195 | m, err := driver.GetAdvisoriesCisco() 196 | if err != nil { 197 | log.Errorf("%s", err) 198 | return err 199 | } 200 | return c.JSON(http.StatusOK, &m) 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /fetcher/fetcher.go: -------------------------------------------------------------------------------- 1 | package fetcher 2 | 3 | import ( 4 | "archive/tar" 5 | "bytes" 6 | "compress/gzip" 7 | "context" 8 | "encoding/json" 9 | "fmt" 10 | "io" 11 | "net/http" 12 | "net/url" 13 | "os" 14 | "os/exec" 15 | "path/filepath" 16 | "time" 17 | 18 | "github.com/cenkalti/backoff" 19 | "github.com/klauspost/compress/zstd" 20 | ocispec "github.com/opencontainers/image-spec/specs-go/v1" 21 | "github.com/spf13/viper" 22 | "golang.org/x/xerrors" 23 | "oras.land/oras-go/v2" 24 | "oras.land/oras-go/v2/registry/remote" 25 | "oras.land/oras-go/v2/registry/remote/auth" 26 | "oras.land/oras-go/v2/registry/remote/retry" 27 | 28 | "github.com/vulsio/go-cve-dictionary/log" 29 | ) 30 | 31 | // FetchFeedFile fetches vulnerability feed file concurrently 32 | func FetchFeedFile(urlstr string, gzip bool) (body []byte, err error) { 33 | log.Infof("Fetching... %s", urlstr) 34 | 35 | count, retryMax := 0, 20 36 | f := func() (err error) { 37 | if body, err = fetchFile(urlstr, gzip); err != nil { 38 | count++ 39 | if count == retryMax { 40 | return nil 41 | } 42 | return xerrors.Errorf("HTTP GET error, url: %s, err: %w", urlstr, err) 43 | } 44 | return nil 45 | } 46 | notify := func(err error, t time.Duration) { 47 | log.Warnf("Failed to HTTP GET. retrying in %s seconds. err: %+v", t, err) 48 | } 49 | err = backoff.RetryNotify(f, backoff.NewExponentialBackOff(), notify) 50 | if err != nil { 51 | return nil, xerrors.Errorf("Failed to fetch file: %w", err) 52 | } 53 | 54 | if count == retryMax { 55 | return nil, xerrors.Errorf("Failed to fetch file. Retry count exceeded: %d", retryMax) 56 | } 57 | 58 | return body, nil 59 | } 60 | 61 | func fetchFile(urlstr string, isGzip bool) (body []byte, err error) { 62 | var proxyURL *url.URL 63 | httpClient := &http.Client{ 64 | Timeout: time.Duration(180 * time.Second), 65 | } 66 | if viper.GetString("http-proxy") != "" { 67 | if proxyURL, err = url.Parse(viper.GetString("http-proxy")); err != nil { 68 | return nil, xerrors.Errorf("Failed to parse proxy url: %w", err) 69 | } 70 | httpClient = &http.Client{ 71 | Transport: &http.Transport{ 72 | Proxy: http.ProxyURL(proxyURL), 73 | }, 74 | Timeout: time.Duration(180 * time.Second), 75 | } 76 | } 77 | req, err := http.NewRequest("GET", urlstr, nil) 78 | if err != nil { 79 | return nil, xerrors.Errorf("Failed to new request. url: %s, err: %w", urlstr, err) 80 | } 81 | req.Header.Set("User-Agent", "curl/7.58.0") 82 | resp, err := httpClient.Do(req) 83 | if err != nil { 84 | return nil, xerrors.Errorf("Failed to GET. url: %s, err: %w", urlstr, err) 85 | } 86 | 87 | defer resp.Body.Close() 88 | buf, err := io.ReadAll(resp.Body) 89 | if err != nil { 90 | return nil, xerrors.Errorf("Failed to read body. url: %s, err: %w", urlstr, err) 91 | } 92 | 93 | if isGzip { 94 | reader, err := gzip.NewReader(bytes.NewReader(buf)) 95 | if err != nil { 96 | return nil, xerrors.Errorf("Failed to decompress NVD feedfile. url: %s, err: %w", urlstr, err) 97 | } 98 | defer reader.Close() 99 | 100 | bytes, err := io.ReadAll(reader) 101 | if err != nil { 102 | return nil, xerrors.Errorf("Failed to Read NVD feedfile. url: %s, err: %w", urlstr, err) 103 | } 104 | return bytes, nil 105 | } 106 | 107 | return buf, nil 108 | } 109 | 110 | // FetchFromGHCR fetches from "ghcr.io/vulsio/vuls-data-db:" to "/tmp/go-cve-dictionary*/" 111 | func FetchFromGHCR(tag string) (string, error) { 112 | dir, err := os.MkdirTemp("", "go-cve-dictionary") 113 | if err != nil { 114 | return "", xerrors.Errorf("Failed to create temp directory. err: %w", err) 115 | } 116 | 117 | ctx := context.TODO() 118 | repo, err := remote.NewRepository(fmt.Sprintf("ghcr.io/vulsio/vuls-data-db:%s", tag)) 119 | if err != nil { 120 | return "", xerrors.Errorf("Failed to create client for %s. err: %w", fmt.Sprintf("ghcr.io/vulsio/vuls-data-db:%s", tag), err) 121 | } 122 | if viper.GetString("http-proxy") != "" { 123 | proxyURL, err := url.Parse(viper.GetString("http-proxy")) 124 | if err != nil { 125 | return "", xerrors.Errorf("Failed to parse proxy url: %w", err) 126 | } 127 | 128 | trans := http.DefaultTransport.(*http.Transport).Clone() 129 | trans.Proxy = http.ProxyURL(proxyURL) 130 | 131 | repo.Client = &auth.Client{ 132 | Client: &http.Client{ 133 | Transport: retry.NewTransport(trans), 134 | }, 135 | Header: http.Header{ 136 | "User-Agent": {"oras-go"}, 137 | }, 138 | Cache: auth.DefaultCache, 139 | } 140 | } 141 | _, r, err := oras.Fetch(ctx, repo, repo.Reference.Reference, oras.DefaultFetchOptions) 142 | if err != nil { 143 | return "", xerrors.Errorf("Failed to fetch manifest. err: %w", err) 144 | } 145 | defer r.Close() 146 | 147 | var manifest ocispec.Manifest 148 | if err := json.NewDecoder(r).Decode(&manifest); err != nil { 149 | return "", xerrors.Errorf("Failed to decode manifest. err: %w", err) 150 | } 151 | 152 | l := func() *ocispec.Descriptor { 153 | for _, l := range manifest.Layers { 154 | if l.MediaType == "application/vnd.vulsio.vuls-data-db.dotgit.layer.v1.tar+zstd" { 155 | return &l 156 | } 157 | } 158 | return nil 159 | }() 160 | if l == nil { 161 | return "", xerrors.Errorf("Failed to find digest and filename from layers, actual layers: %#v", manifest.Layers) 162 | } 163 | 164 | r, err = repo.Fetch(ctx, *l) 165 | if err != nil { 166 | return "", xerrors.Errorf("Failed to fetch content. err: %w", err) 167 | } 168 | defer r.Close() 169 | 170 | zr, err := zstd.NewReader(r) 171 | if err != nil { 172 | return "", xerrors.Errorf("Failed to new zstd reader. err: %w", err) 173 | } 174 | defer zr.Close() 175 | 176 | tr := tar.NewReader(zr) 177 | for { 178 | hdr, err := tr.Next() 179 | if err == io.EOF { 180 | break 181 | } 182 | if err != nil { 183 | return "", xerrors.Errorf("Failed to next tar reader. err: %w", err) 184 | } 185 | 186 | p := filepath.Join(dir, hdr.Name) 187 | 188 | switch hdr.Typeflag { 189 | case tar.TypeDir: 190 | if err := os.MkdirAll(p, 0755); err != nil { 191 | return "", xerrors.Errorf("Failed to mkdir %s. err: %w", p, err) 192 | } 193 | case tar.TypeReg: 194 | if err := os.MkdirAll(filepath.Dir(p), 0755); err != nil { 195 | return "", xerrors.Errorf("Failed to mkdir %s. err: %w", p, err) 196 | } 197 | 198 | if err := func() error { 199 | f, err := os.Create(p) 200 | if err != nil { 201 | return xerrors.Errorf("Failed to create %s. err: %w", p, err) 202 | } 203 | defer f.Close() 204 | 205 | if _, err := io.Copy(f, tr); err != nil { 206 | return xerrors.Errorf("Failed to copy to %s. err: %w", p, err) 207 | } 208 | 209 | return nil 210 | }(); err != nil { 211 | return "", xerrors.Errorf("Failed to create %s. err: %w", p, err) 212 | } 213 | } 214 | } 215 | 216 | cmd := exec.Command("git", "-C", filepath.Join(dir, tag), "restore", ".") 217 | if err := cmd.Run(); err != nil { 218 | return "", xerrors.Errorf("Failed to exec %q. err: %w", cmd.String(), err) 219 | } 220 | 221 | return dir, nil 222 | } 223 | -------------------------------------------------------------------------------- /fetcher/vulncheck/vulncheck.go: -------------------------------------------------------------------------------- 1 | package vulncheck 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "io/fs" 8 | "iter" 9 | "os" 10 | "path/filepath" 11 | "slices" 12 | "strings" 13 | 14 | "golang.org/x/xerrors" 15 | 16 | "github.com/vulsio/go-cve-dictionary/fetcher" 17 | "github.com/vulsio/go-cve-dictionary/log" 18 | "github.com/vulsio/go-cve-dictionary/models" 19 | ) 20 | 21 | // FetchConvert Fetch CVE vulnerability information from VulnCheck NVD++ 22 | func FetchConvert(years []string) (iter.Seq2[models.Vulncheck, error], error) { 23 | dir, err := fetcher.FetchFromGHCR("vuls-data-raw-vulncheck-nist-nvd2") 24 | if err != nil { 25 | return nil, xerrors.Errorf("Failed to fetch vulncheck nist-nvd2. err: %w", err) 26 | } 27 | 28 | return func(yield func(models.Vulncheck, error) bool) { 29 | defer os.RemoveAll(dir) 30 | 31 | yieldErr := errors.New("yield error") 32 | if err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error { 33 | if err != nil { 34 | return err 35 | } 36 | 37 | if d.IsDir() || filepath.Ext(path) != ".json" { 38 | return nil 39 | } 40 | 41 | if len(years) > 0 && !slices.Contains(years, filepath.Base(filepath.Dir(path))) { 42 | return nil 43 | } 44 | 45 | f, err := os.Open(path) 46 | if err != nil { 47 | return xerrors.Errorf("Failed to open %s. err: %w", path, err) 48 | } 49 | defer f.Close() 50 | 51 | var v cve 52 | if err := json.NewDecoder(f).Decode(&v); err != nil { 53 | return xerrors.Errorf("Failed to decode %s. err: %w", path, err) 54 | } 55 | 56 | converted, err := convert(v) 57 | if err != nil { 58 | return xerrors.Errorf("Failed to convert %s to models.Vulncheck. err: %w", path, err) 59 | } 60 | 61 | if !yield(converted, nil) { 62 | return yieldErr 63 | } 64 | 65 | return nil 66 | }); err != nil { 67 | if errors.Is(err, yieldErr) { 68 | return 69 | } 70 | if !yield(models.Vulncheck{}, xerrors.Errorf("Failed to walk directory. err: %w", err)) { 71 | return 72 | } 73 | } 74 | }, nil 75 | } 76 | 77 | func convert(v cve) (models.Vulncheck, error) { 78 | c2 := make([]models.VulncheckCvss2Extra, 0, len(v.Metrics.CVSSMetricV2)) 79 | for _, v2 := range v.Metrics.CVSSMetricV2 { 80 | c2 = append(c2, models.VulncheckCvss2Extra{ 81 | Source: v2.Source, 82 | Type: v2.Type, 83 | Cvss2: models.Cvss2{ 84 | VectorString: v2.CvssData.VectorString, 85 | AccessVector: v2.CvssData.AccessVector, 86 | AccessComplexity: v2.CvssData.AccessComplexity, 87 | Authentication: v2.CvssData.Authentication, 88 | ConfidentialityImpact: v2.CvssData.ConfidentialityImpact, 89 | IntegrityImpact: v2.CvssData.IntegrityImpact, 90 | AvailabilityImpact: v2.CvssData.AvailabilityImpact, 91 | BaseScore: v2.CvssData.BaseScore, 92 | Severity: v2.BaseSeverity, 93 | }, 94 | ExploitabilityScore: v2.ExploitabilityScore, 95 | ImpactScore: v2.ImpactScore, 96 | ObtainAllPrivilege: v2.ObtainAllPrivilege, 97 | ObtainUserPrivilege: v2.ObtainUserPrivilege, 98 | ObtainOtherPrivilege: v2.ObtainOtherPrivilege, 99 | UserInteractionRequired: v2.UserInteractionRequired, 100 | }) 101 | } 102 | 103 | c3 := make([]models.VulncheckCvss3, 0, len(v.Metrics.CVSSMetricV30)+len(v.Metrics.CVSSMetricV31)) 104 | for _, v30 := range v.Metrics.CVSSMetricV30 { 105 | c3 = append(c3, models.VulncheckCvss3{ 106 | Source: v30.Source, 107 | Type: v30.Type, 108 | Cvss3: models.Cvss3{ 109 | VectorString: v30.CVSSData.VectorString, 110 | AttackVector: v30.CVSSData.AttackVector, 111 | AttackComplexity: v30.CVSSData.AttackComplexity, 112 | PrivilegesRequired: v30.CVSSData.PrivilegesRequired, 113 | UserInteraction: v30.CVSSData.UserInteraction, 114 | Scope: v30.CVSSData.Scope, 115 | ConfidentialityImpact: v30.CVSSData.ConfidentialityImpact, 116 | IntegrityImpact: v30.CVSSData.IntegrityImpact, 117 | AvailabilityImpact: v30.CVSSData.AvailabilityImpact, 118 | BaseScore: v30.CVSSData.BaseScore, 119 | BaseSeverity: v30.CVSSData.BaseSeverity, 120 | ExploitabilityScore: v30.ExploitabilityScore, 121 | ImpactScore: v30.ImpactScore, 122 | }, 123 | }) 124 | } 125 | for _, v31 := range v.Metrics.CVSSMetricV31 { 126 | c3 = append(c3, models.VulncheckCvss3{ 127 | Source: v31.Source, 128 | Type: v31.Type, 129 | Cvss3: models.Cvss3{ 130 | VectorString: v31.CVSSData.VectorString, 131 | AttackVector: v31.CVSSData.AttackVector, 132 | AttackComplexity: v31.CVSSData.AttackComplexity, 133 | PrivilegesRequired: v31.CVSSData.PrivilegesRequired, 134 | UserInteraction: v31.CVSSData.UserInteraction, 135 | Scope: v31.CVSSData.Scope, 136 | ConfidentialityImpact: v31.CVSSData.ConfidentialityImpact, 137 | IntegrityImpact: v31.CVSSData.IntegrityImpact, 138 | AvailabilityImpact: v31.CVSSData.AvailabilityImpact, 139 | BaseScore: v31.CVSSData.BaseScore, 140 | BaseSeverity: v31.CVSSData.BaseSeverity, 141 | ExploitabilityScore: func() float64 { 142 | if v31.ExploitabilityScore != nil { 143 | return *v31.ExploitabilityScore 144 | } 145 | return 0 146 | }(), 147 | ImpactScore: func() float64 { 148 | if v31.ImpactScore != nil { 149 | return *v31.ImpactScore 150 | } 151 | return 0 152 | }(), 153 | }, 154 | }) 155 | } 156 | c40 := make([]models.VulncheckCvss40, 0, len(v.Metrics.CVSSMetricV40)) 157 | for _, v40 := range v.Metrics.CVSSMetricV40 { 158 | c40 = append(c40, models.VulncheckCvss40{ 159 | Source: v40.Source, 160 | Type: v40.Type, 161 | Cvss40: models.Cvss40{ 162 | VectorString: v40.CVSSData.VectorString, 163 | BaseScore: v40.CVSSData.BaseScore, 164 | BaseSeverity: v40.CVSSData.BaseSeverity, 165 | ThreatScore: v40.CVSSData.ThreatScore, 166 | ThreatSeverity: v40.CVSSData.ThreatSeverity, 167 | EnvironmentalScore: v40.CVSSData.EnvironmentalScore, 168 | EnvironmentalSeverity: v40.CVSSData.EnvironmentalSeverity, 169 | }, 170 | }) 171 | } 172 | 173 | var cpes []models.VulncheckCpe 174 | for _, conf := range v.VCConfigurations { 175 | if conf.Negate { 176 | continue 177 | } 178 | 179 | for _, node := range conf.Nodes { 180 | if node.Negate { 181 | continue 182 | } 183 | 184 | for _, cpe := range node.CPEMatch { 185 | if !cpe.Vulnerable { 186 | continue 187 | } 188 | 189 | cpeBase, err := fetcher.ParseCpeURI(cpe.Criteria) 190 | if err != nil { 191 | log.Infof("Failed to parse CpeURI %s: %s", cpe.Criteria, err) 192 | continue 193 | } 194 | cpeBase.VersionStartExcluding = cpe.VersionStartExcluding 195 | cpeBase.VersionStartIncluding = cpe.VersionStartIncluding 196 | cpeBase.VersionEndExcluding = cpe.VersionEndExcluding 197 | cpeBase.VersionEndIncluding = cpe.VersionEndIncluding 198 | if !fetcher.CheckIfVersionParsable(cpeBase) { 199 | log.Infof("Failed to parse CPE version fields. %s: %+v", v.ID, cpeBase) 200 | continue 201 | } 202 | cpes = append(cpes, models.VulncheckCpe{CpeBase: *cpeBase}) 203 | } 204 | } 205 | } 206 | 207 | publish, err := fetcher.ParseTime([]string{"2006-01-02T15:04:05.999999999"}, v.Published) 208 | if err != nil { 209 | return models.Vulncheck{}, xerrors.Errorf("Failed to parse Time. err: %w", err) 210 | } 211 | modified, err := fetcher.ParseTime([]string{"2006-01-02T15:04:05.999999999"}, v.LastModified) 212 | if err != nil { 213 | return models.Vulncheck{}, xerrors.Errorf("Failed to parse Time. err: %w", err) 214 | } 215 | 216 | return models.Vulncheck{ 217 | CveID: v.ID, 218 | Descriptions: func() []models.VulncheckDescription { 219 | ds := make([]models.VulncheckDescription, 0, len(v.Descriptions)) 220 | for _, d := range v.Descriptions { 221 | ds = append(ds, models.VulncheckDescription{ 222 | Lang: d.Lang, 223 | Value: d.Value, 224 | }) 225 | } 226 | return ds 227 | }(), 228 | Cvss2: c2, 229 | Cvss3: c3, 230 | Cvss40: c40, 231 | Cwes: func() []models.VulncheckCwe { 232 | var cs []models.VulncheckCwe 233 | for _, w := range v.Weaknesses { 234 | for _, d := range w.Description { 235 | if !slices.ContainsFunc(cs, func(e models.VulncheckCwe) bool { 236 | return e.Source == w.Source && e.Type == w.Type && e.CweID == d.Value 237 | }) { 238 | cs = append(cs, models.VulncheckCwe{ 239 | Source: w.Source, 240 | Type: w.Type, 241 | CweID: d.Value, 242 | }) 243 | } 244 | } 245 | } 246 | return cs 247 | }(), 248 | Cpes: cpes, 249 | References: func() []models.VulncheckReference { 250 | rs := make([]models.VulncheckReference, 0, len(v.References)) 251 | for _, r := range v.References { 252 | rs = append(rs, models.VulncheckReference{ 253 | Reference: models.Reference{ 254 | Link: r.URL, 255 | Name: r.URL, 256 | Source: r.Source, 257 | Tags: strings.Join(r.Tags, ","), 258 | }, 259 | }) 260 | } 261 | return rs 262 | }(), 263 | Certs: func() []models.VulncheckCert { 264 | var cs []models.VulncheckCert 265 | for _, r := range v.References { 266 | if !strings.HasPrefix(r.URL, "http") { 267 | continue 268 | } 269 | if strings.Contains(r.URL, "us-cert") { 270 | ss := strings.Split(r.URL, "/") 271 | title := fmt.Sprintf("US-CERT-%s", ss[len(ss)-1]) 272 | cs = append(cs, models.VulncheckCert{ 273 | Cert: models.Cert{ 274 | Link: r.URL, 275 | Title: title, 276 | }, 277 | }) 278 | } 279 | } 280 | return cs 281 | }(), 282 | PublishedDate: publish, 283 | LastModifiedDate: modified, 284 | }, nil 285 | } 286 | -------------------------------------------------------------------------------- /fetcher/fortinet/fortinet.go: -------------------------------------------------------------------------------- 1 | package fortinet 2 | 3 | import ( 4 | "archive/zip" 5 | "bytes" 6 | "encoding/json" 7 | "fmt" 8 | "maps" 9 | "slices" 10 | "strings" 11 | 12 | "github.com/knqyf263/go-cpe/common" 13 | "github.com/knqyf263/go-cpe/naming" 14 | "golang.org/x/xerrors" 15 | 16 | "github.com/vulsio/go-cve-dictionary/fetcher" 17 | "github.com/vulsio/go-cve-dictionary/log" 18 | "github.com/vulsio/go-cve-dictionary/models" 19 | ) 20 | 21 | // FetchConvert Fetch CVE vulnerability information from Fortinet 22 | func FetchConvert() ([]models.Fortinet, error) { 23 | bs, err := fetcher.FetchFeedFile("https://github.com/vulsio/vuls-data-raw-fortinet/archive/refs/heads/main.zip", false) 24 | if err != nil { 25 | return nil, xerrors.Errorf("Failed to fetch vulsio/vuls-data-raw-fortinet. err: %w", err) 26 | } 27 | 28 | r, err := zip.NewReader(bytes.NewReader(bs), int64(len(bs))) 29 | if err != nil { 30 | return nil, xerrors.Errorf("Failed to create zip reader. err: %w", err) 31 | } 32 | 33 | var advs []models.Fortinet 34 | for _, zf := range r.File { 35 | if zf.FileInfo().IsDir() { 36 | continue 37 | } 38 | 39 | as, err := func() ([]models.Fortinet, error) { 40 | f, err := zf.Open() 41 | if err != nil { 42 | return nil, xerrors.Errorf("Failed to open. err: %w", err) 43 | } 44 | defer f.Close() 45 | 46 | var a advisory 47 | if err := json.NewDecoder(f).Decode(&a); err != nil { 48 | return nil, xerrors.Errorf("Failed to decode json. err: %w", err) 49 | } 50 | 51 | converted, err := convert(a) 52 | if err != nil { 53 | return nil, xerrors.Errorf("Failed to convert advisory. err: %w", err) 54 | } 55 | 56 | return converted, nil 57 | }() 58 | if err != nil { 59 | return nil, xerrors.Errorf("Failed to convert %s. err: %w", zf.Name, err) 60 | } 61 | 62 | advs = append(advs, as...) 63 | } 64 | 65 | return advs, nil 66 | } 67 | 68 | func convert(a advisory) ([]models.Fortinet, error) { 69 | converted := []models.Fortinet{} 70 | for _, v := range a.Vulnerabilities { 71 | if v.CVE == "" { 72 | continue 73 | } 74 | 75 | cs := models.Fortinet{ 76 | AdvisoryID: a.ID, 77 | CveID: v.CVE, 78 | Title: a.Title, 79 | Summary: a.Summary, 80 | Descriptions: a.Description, 81 | PublishedDate: a.Published, 82 | LastModifiedDate: a.Updated, 83 | AdvisoryURL: a.AdvisoryURL, 84 | } 85 | 86 | for _, d := range v.Definitions { 87 | m := map[models.FortinetCpe]struct{}{} 88 | for _, c := range d.Configurations { 89 | cpes, err := cpeWalk(c) 90 | if err != nil { 91 | return nil, xerrors.Errorf("Failed to cpe walk. err: %w", err) 92 | } 93 | for _, cpe := range cpes { 94 | m[cpe] = struct{}{} 95 | } 96 | } 97 | cs.Cpes = slices.Collect(maps.Keys(m)) 98 | 99 | if d.CVSSv3 != nil { 100 | parsed := parseCvss3VectorStr(d.CVSSv3.Vector) 101 | cs.Cvss3 = models.FortinetCvss3{ 102 | Cvss3: models.Cvss3{ 103 | VectorString: d.CVSSv3.Vector, 104 | AttackVector: parsed.AttackVector, 105 | AttackComplexity: parsed.AttackComplexity, 106 | PrivilegesRequired: parsed.PrivilegesRequired, 107 | UserInteraction: parsed.UserInteraction, 108 | Scope: parsed.Scope, 109 | ConfidentialityImpact: parsed.ConfidentialityImpact, 110 | IntegrityImpact: parsed.IntegrityImpact, 111 | AvailabilityImpact: parsed.AvailabilityImpact, 112 | }, 113 | } 114 | if d.CVSSv3.BaseScore != nil { 115 | cs.Cvss3.BaseScore = *d.CVSSv3.BaseScore 116 | } 117 | } 118 | 119 | for _, cwe := range d.CWE { 120 | cs.Cwes = append(cs.Cwes, models.FortinetCwe{ 121 | CweID: cwe, 122 | }) 123 | } 124 | } 125 | 126 | for _, r := range a.References { 127 | cs.References = append(cs.References, models.FortinetReference{ 128 | Reference: models.Reference{ 129 | Name: r.Description, 130 | Link: r.URL, 131 | }, 132 | }) 133 | } 134 | 135 | converted = append(converted, cs) 136 | } 137 | return converted, nil 138 | } 139 | 140 | func cpeWalk(conf configuration) ([]models.FortinetCpe, error) { 141 | cpes := []models.FortinetCpe{} 142 | 143 | for _, n := range conf.Nodes { 144 | if strings.HasSuffix(n.Description, ":") || ((n.Affected.Eqaul != nil && *n.Affected.Eqaul == "") && (n.Affected.GreaterThan != nil && *n.Affected.GreaterThan == "") && (n.Affected.GreaterEqaul != nil && *n.Affected.GreaterEqaul == "") && (n.Affected.LessThan != nil && *n.Affected.LessThan == "") && (n.Affected.LessEqual != nil && *n.Affected.LessEqual == "")) { 145 | continue 146 | } 147 | 148 | wfn, err := naming.UnbindFS(n.CPE) 149 | if err != nil { 150 | return nil, xerrors.Errorf("Failed to unbind a formatted string to WFN. err: %w", err) 151 | } 152 | 153 | if n.Affected.Eqaul != nil { 154 | // n.Affected.Eqaul: 4.2.0, 5.0.0:beta1, ANY, NA 155 | for i, v := range strings.Split(*n.Affected.Eqaul, ":") { 156 | switch i { 157 | case 0: 158 | switch v { 159 | case "ANY", "NA": 160 | lv, err := common.NewLogicalValue(v) 161 | if err != nil { 162 | return nil, xerrors.Errorf("Failed to new Logicalvalue. err: %w", err) 163 | } 164 | if err := wfn.Set(common.AttributeVersion, lv); err != nil { 165 | return nil, xerrors.Errorf("Failed to set version to WFN. err: %w", err) 166 | } 167 | default: 168 | if err := wfn.Set(common.AttributeVersion, strings.NewReplacer(".", "\\.", "-", "\\-").Replace(v)); err != nil { 169 | return nil, xerrors.Errorf("Failed to set version to WFN. err: %w", err) 170 | } 171 | } 172 | case 1: 173 | switch v { 174 | case "ANY", "NA": 175 | lv, err := common.NewLogicalValue(v) 176 | if err != nil { 177 | return nil, xerrors.Errorf("Failed to new Logicalvalue. err: %w", err) 178 | } 179 | if err := wfn.Set(common.AttributeUpdate, lv); err != nil { 180 | return nil, xerrors.Errorf("Failed to set update to WFN. err: %w", err) 181 | } 182 | default: 183 | if err := wfn.Set(common.AttributeUpdate, v); err != nil { 184 | return nil, xerrors.Errorf("Failed to set update to WFN. err: %w", err) 185 | } 186 | } 187 | } 188 | } 189 | } 190 | 191 | cpeBase := models.CpeBase{ 192 | URI: naming.BindToURI(wfn), 193 | FormattedString: naming.BindToFS(wfn), 194 | WellFormedName: wfn.String(), 195 | CpeWFN: models.CpeWFN{ 196 | Part: fmt.Sprintf("%s", wfn.Get(common.AttributePart)), 197 | Vendor: fmt.Sprintf("%s", wfn.Get(common.AttributeVendor)), 198 | Product: fmt.Sprintf("%s", wfn.Get(common.AttributeProduct)), 199 | Version: fmt.Sprintf("%s", wfn.Get(common.AttributeVersion)), 200 | Update: fmt.Sprintf("%s", wfn.Get(common.AttributeUpdate)), 201 | Edition: fmt.Sprintf("%s", wfn.Get(common.AttributeEdition)), 202 | Language: fmt.Sprintf("%s", wfn.Get(common.AttributeLanguage)), 203 | SoftwareEdition: fmt.Sprintf("%s", wfn.Get(common.AttributeSwEdition)), 204 | TargetSW: fmt.Sprintf("%s", wfn.Get(common.AttributeTargetSw)), 205 | TargetHW: fmt.Sprintf("%s", wfn.Get(common.AttributeTargetHw)), 206 | Other: fmt.Sprintf("%s", wfn.Get(common.AttributeOther)), 207 | }, 208 | } 209 | 210 | if n.Affected.GreaterThan != nil { 211 | cpeBase.VersionStartExcluding = *n.Affected.GreaterThan 212 | } 213 | if n.Affected.GreaterEqaul != nil { 214 | cpeBase.VersionStartIncluding = *n.Affected.GreaterEqaul 215 | } 216 | if n.Affected.LessThan != nil { 217 | cpeBase.VersionEndExcluding = *n.Affected.LessThan 218 | } 219 | if n.Affected.LessEqual != nil { 220 | cpeBase.VersionEndIncluding = *n.Affected.LessEqual 221 | } 222 | 223 | cpes = append(cpes, models.FortinetCpe{ 224 | CpeBase: cpeBase, 225 | }) 226 | } 227 | 228 | if conf.Children != nil { 229 | cs, err := cpeWalk(*conf.Children) 230 | if err != nil { 231 | return nil, xerrors.Errorf("Failed to cpewalk. err: %w", err) 232 | } 233 | cpes = append(cpes, cs...) 234 | } 235 | 236 | return cpes, nil 237 | } 238 | 239 | func parseCvss3VectorStr(vector string) models.Cvss3 { 240 | cvss := models.Cvss3{VectorString: vector} 241 | for _, s := range strings.Split(strings.TrimPrefix(vector, "CVSS:3.1/"), "/") { 242 | m, v, ok := strings.Cut(s, ":") 243 | if !ok { 244 | continue 245 | } 246 | switch m { 247 | case "AV": 248 | switch v { 249 | case "N": 250 | cvss.AttackVector = "NETWORK" 251 | case "A": 252 | cvss.AttackVector = "ADJACENT_NETWORK" 253 | case "L": 254 | cvss.AttackVector = "LOCAL" 255 | case "P": 256 | cvss.AttackVector = "PHYSICAL" 257 | default: 258 | log.Warnf("%s is unknown Attack Vector", v) 259 | } 260 | case "AC": 261 | switch v { 262 | case "L": 263 | cvss.AttackComplexity = "LOW" 264 | case "H": 265 | cvss.AttackComplexity = "HIGH" 266 | default: 267 | log.Warnf("%s is unknown Attack Complexity", v) 268 | } 269 | case "PR": 270 | switch v { 271 | case "N": 272 | cvss.PrivilegesRequired = "NONE" 273 | case "L": 274 | cvss.PrivilegesRequired = "LOW" 275 | case "H": 276 | cvss.PrivilegesRequired = "HIGH" 277 | default: 278 | log.Warnf("%s is unknown Privileges Required", v) 279 | } 280 | case "UI": 281 | switch v { 282 | case "N": 283 | cvss.UserInteraction = "NONE" 284 | case "R": 285 | cvss.UserInteraction = "REQUIRED" 286 | default: 287 | log.Warnf("%s is unknown User Interaction", v) 288 | } 289 | case "S": 290 | switch v { 291 | case "U": 292 | cvss.Scope = "UNCHANGED" 293 | case "C": 294 | cvss.Scope = "CHANGED" 295 | default: 296 | log.Warnf("%s is unknown Scope", v) 297 | } 298 | case "C": 299 | switch v { 300 | case "N": 301 | cvss.ConfidentialityImpact = "NONE" 302 | case "L": 303 | cvss.ConfidentialityImpact = "LOW" 304 | case "H": 305 | cvss.ConfidentialityImpact = "HIGH" 306 | default: 307 | log.Warnf("%s is unknown Confidentiality", v) 308 | } 309 | case "I": 310 | switch v { 311 | case "N": 312 | cvss.IntegrityImpact = "NONE" 313 | case "L": 314 | cvss.IntegrityImpact = "LOW" 315 | case "H": 316 | cvss.IntegrityImpact = "HIGH" 317 | default: 318 | log.Warnf("%s is unknown Integrity", v) 319 | } 320 | case "A": 321 | switch v { 322 | case "N": 323 | cvss.AvailabilityImpact = "NONE" 324 | case "L": 325 | cvss.AvailabilityImpact = "LOW" 326 | case "H": 327 | cvss.AvailabilityImpact = "HIGH" 328 | default: 329 | log.Warnf("%s is unknown Availability", v) 330 | } 331 | case "E", "RL", "RC", "CR", "IR", "AR", "MAV", "MAC", "MPR", "MUI", "MS", "MC", "MI", "MA": 332 | default: 333 | log.Warnf("%s is unknown metrics", m) 334 | } 335 | } 336 | 337 | return cvss 338 | } 339 | -------------------------------------------------------------------------------- /fetcher/nvd/nvd.go: -------------------------------------------------------------------------------- 1 | package nvd 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io/fs" 7 | "os" 8 | "path/filepath" 9 | "runtime" 10 | "slices" 11 | "strings" 12 | "time" 13 | 14 | "github.com/spf13/viper" 15 | "golang.org/x/xerrors" 16 | 17 | "github.com/vulsio/go-cve-dictionary/fetcher" 18 | "github.com/vulsio/go-cve-dictionary/log" 19 | "github.com/vulsio/go-cve-dictionary/models" 20 | "github.com/vulsio/go-cve-dictionary/util" 21 | ) 22 | 23 | // Fetch Fetch CVE vulnerability information from NVD 24 | func Fetch() (string, error) { 25 | dir, err := fetcher.FetchFromGHCR("vuls-data-raw-nvd-api-cve") 26 | if err != nil { 27 | return "", xerrors.Errorf("Failed to fetch vuls-data-raw-nvd-api-cve. err: %w", err) 28 | } 29 | return dir, nil 30 | } 31 | 32 | // Convert convert /vuls-data-raw-nvd-api-cve//CVE--\d{4,}.json to []models.Nvd 33 | func Convert(tempDir, year string) ([]models.Nvd, error) { 34 | var ps []string 35 | if err := filepath.WalkDir(filepath.Join(tempDir, "vuls-data-raw-nvd-api-cve", year), func(path string, d fs.DirEntry, err error) error { 36 | if err != nil { 37 | return err 38 | } 39 | if d.IsDir() { 40 | return nil 41 | } 42 | ps = append(ps, path) 43 | return nil 44 | }); err != nil { 45 | return nil, xerrors.Errorf("Failed to walk %s. err: %w", filepath.Join(tempDir, year), err) 46 | } 47 | 48 | reqChan := make(chan string, len(ps)) 49 | resChan := make(chan *models.Nvd, len(ps)) 50 | errChan := make(chan error, len(ps)) 51 | defer close(reqChan) 52 | defer close(resChan) 53 | defer close(errChan) 54 | 55 | go func() { 56 | for _, p := range ps { 57 | reqChan <- p 58 | } 59 | }() 60 | 61 | concurrency := runtime.NumCPU() + 2 62 | tasks := util.GenWorkers(concurrency) 63 | for range ps { 64 | tasks <- func() { 65 | req := <-reqChan 66 | cve, err := convertToModel(req) 67 | if err != nil { 68 | errChan <- err 69 | return 70 | } 71 | resChan <- cve 72 | } 73 | } 74 | 75 | cves := []models.Nvd{} 76 | errs := []error{} 77 | timeout := time.After(10 * 60 * time.Second) 78 | for range ps { 79 | select { 80 | case res := <-resChan: 81 | if res != nil { 82 | cves = append(cves, *res) 83 | } 84 | case err := <-errChan: 85 | errs = append(errs, err) 86 | case <-timeout: 87 | return nil, fmt.Errorf("Timeout Fetching") 88 | } 89 | } 90 | if 0 < len(errs) { 91 | return nil, xerrors.Errorf("%w", errs) 92 | } 93 | return cves, nil 94 | } 95 | 96 | // convertToModel converts Nvd JSON to model structure. 97 | func convertToModel(cvePath string) (*models.Nvd, error) { 98 | f, err := os.Open(cvePath) 99 | if err != nil { 100 | return nil, xerrors.Errorf("Failed to open %s. err: %w", cvePath, err) 101 | } 102 | defer f.Close() 103 | 104 | var item cve 105 | if err := json.NewDecoder(f).Decode(&item); err != nil { 106 | return nil, xerrors.Errorf("Failed to decode %s. err: %w", cvePath, err) 107 | } 108 | 109 | if item.VulnStatus == "Rejected" { 110 | return nil, nil 111 | } 112 | 113 | // Description 114 | descs := []models.NvdDescription{} 115 | for _, desc := range item.Descriptions { 116 | descs = append(descs, models.NvdDescription{ 117 | Lang: desc.Lang, 118 | Value: desc.Value, 119 | }) 120 | } 121 | 122 | //References 123 | refs := []models.NvdReference{} 124 | for _, r := range item.References { 125 | refs = append(refs, models.NvdReference{ 126 | Reference: models.Reference{ 127 | Link: r.URL, 128 | Name: r.URL, 129 | Source: r.Source, 130 | Tags: strings.Join(r.Tags, ","), 131 | }, 132 | }) 133 | } 134 | 135 | // Certs 136 | certs := []models.NvdCert{} 137 | for _, ref := range item.References { 138 | if !strings.HasPrefix(ref.URL, "http") { 139 | continue 140 | } 141 | if strings.Contains(ref.URL, "us-cert") { 142 | ss := strings.Split(ref.URL, "/") 143 | title := fmt.Sprintf("US-CERT-%s", ss[len(ss)-1]) 144 | certs = append(certs, models.NvdCert{ 145 | Cert: models.Cert{ 146 | Link: ref.URL, 147 | Title: title, 148 | }, 149 | }) 150 | } 151 | } 152 | 153 | // Cwes 154 | cwes := []models.NvdCwe{} 155 | for _, weak := range item.Weaknesses { 156 | for _, desc := range weak.Description { 157 | if !slices.ContainsFunc(cwes, func(e models.NvdCwe) bool { 158 | return e.Source == weak.Source && e.Type == weak.Type && e.CweID == desc.Value 159 | }) { 160 | cwes = append(cwes, models.NvdCwe{ 161 | Source: weak.Source, 162 | Type: weak.Type, 163 | CweID: desc.Value, 164 | }) 165 | } 166 | } 167 | } 168 | 169 | full := viper.GetBool("full") 170 | cpes := []models.NvdCpe{} 171 | for _, conf := range item.Configurations { 172 | if conf.Negate { 173 | continue 174 | } 175 | 176 | var ( 177 | nodeCpes []models.NvdCpe 178 | nodeEnvCpes []models.NvdEnvCpe 179 | ) 180 | for _, node := range conf.Nodes { 181 | if node.Negate { 182 | continue 183 | } 184 | 185 | for _, cpe := range node.CPEMatch { 186 | if cpe.Vulnerable { 187 | cpeBase, err := fetcher.ParseCpeURI(cpe.Criteria) 188 | if err != nil { 189 | log.Infof("Failed to parse CpeURI %s: %s", cpe.Criteria, err) 190 | continue 191 | } 192 | cpeBase.VersionStartExcluding = cpe.VersionStartExcluding 193 | cpeBase.VersionStartIncluding = cpe.VersionStartIncluding 194 | cpeBase.VersionEndExcluding = cpe.VersionEndExcluding 195 | cpeBase.VersionEndIncluding = cpe.VersionEndIncluding 196 | nodeCpes = append(nodeCpes, models.NvdCpe{ 197 | CpeBase: *cpeBase, 198 | EnvCpes: []models.NvdEnvCpe{}, 199 | }) 200 | if !fetcher.CheckIfVersionParsable(cpeBase) { 201 | return nil, fmt.Errorf("Version parse err. Please add a issue on [GitHub](https://github.com/vulsio/go-cve-dictionary/issues/new). Title: %s, Content: %v", item.ID, item) 202 | } 203 | } else { 204 | if !full || conf.Operator != "AND" { 205 | continue 206 | } 207 | cpeBase, err := fetcher.ParseCpeURI(cpe.Criteria) 208 | if err != nil { 209 | log.Infof("Failed to parse CpeURI %s: %s", cpe.Criteria, err) 210 | continue 211 | } 212 | cpeBase.VersionStartExcluding = cpe.VersionStartExcluding 213 | cpeBase.VersionStartIncluding = cpe.VersionStartIncluding 214 | cpeBase.VersionEndExcluding = cpe.VersionEndExcluding 215 | cpeBase.VersionEndIncluding = cpe.VersionEndIncluding 216 | nodeEnvCpes = append(nodeEnvCpes, models.NvdEnvCpe{ 217 | CpeBase: *cpeBase, 218 | }) 219 | if !fetcher.CheckIfVersionParsable(cpeBase) { 220 | return nil, fmt.Errorf("Version parse err. Please add a issue on [GitHub](https://github.com/vulsio/go-cve-dictionary/issues/new). Title: %s, Content: %v", item.ID, item) 221 | } 222 | } 223 | } 224 | } 225 | for _, nodeCpe := range nodeCpes { 226 | nodeCpe.EnvCpes = append(nodeCpe.EnvCpes, nodeEnvCpes...) 227 | cpes = append(cpes, nodeCpe) 228 | } 229 | } 230 | 231 | c2 := make([]models.NvdCvss2Extra, 0, len(item.Metrics.CVSSMetricV2)) 232 | for _, v2 := range item.Metrics.CVSSMetricV2 { 233 | c2 = append(c2, models.NvdCvss2Extra{ 234 | Source: v2.Source, 235 | Type: v2.Type, 236 | Cvss2: models.Cvss2{ 237 | VectorString: v2.CvssData.VectorString, 238 | AccessVector: v2.CvssData.AccessVector, 239 | AccessComplexity: v2.CvssData.AccessComplexity, 240 | Authentication: v2.CvssData.Authentication, 241 | ConfidentialityImpact: v2.CvssData.ConfidentialityImpact, 242 | IntegrityImpact: v2.CvssData.IntegrityImpact, 243 | AvailabilityImpact: v2.CvssData.AvailabilityImpact, 244 | BaseScore: v2.CvssData.BaseScore, 245 | Severity: v2.BaseSeverity, 246 | }, 247 | ExploitabilityScore: v2.ExploitabilityScore, 248 | ImpactScore: v2.ImpactScore, 249 | ObtainAllPrivilege: v2.ObtainAllPrivilege, 250 | ObtainUserPrivilege: v2.ObtainUserPrivilege, 251 | ObtainOtherPrivilege: v2.ObtainOtherPrivilege, 252 | UserInteractionRequired: v2.UserInteractionRequired, 253 | }) 254 | } 255 | 256 | c3 := make([]models.NvdCvss3, 0, len(item.Metrics.CVSSMetricV30)+len(item.Metrics.CVSSMetricV31)) 257 | for _, v30 := range item.Metrics.CVSSMetricV30 { 258 | c3 = append(c3, models.NvdCvss3{ 259 | Source: v30.Source, 260 | Type: v30.Type, 261 | Cvss3: models.Cvss3{ 262 | VectorString: v30.CVSSData.VectorString, 263 | AttackVector: v30.CVSSData.AttackVector, 264 | AttackComplexity: v30.CVSSData.AttackComplexity, 265 | PrivilegesRequired: v30.CVSSData.PrivilegesRequired, 266 | UserInteraction: v30.CVSSData.UserInteraction, 267 | Scope: v30.CVSSData.Scope, 268 | ConfidentialityImpact: v30.CVSSData.ConfidentialityImpact, 269 | IntegrityImpact: v30.CVSSData.IntegrityImpact, 270 | AvailabilityImpact: v30.CVSSData.AvailabilityImpact, 271 | BaseScore: v30.CVSSData.BaseScore, 272 | BaseSeverity: v30.CVSSData.BaseSeverity, 273 | ExploitabilityScore: v30.ExploitabilityScore, 274 | ImpactScore: v30.ImpactScore, 275 | }, 276 | }) 277 | } 278 | for _, v31 := range item.Metrics.CVSSMetricV31 { 279 | c3 = append(c3, models.NvdCvss3{ 280 | Source: v31.Source, 281 | Type: v31.Type, 282 | Cvss3: models.Cvss3{ 283 | VectorString: v31.CVSSData.VectorString, 284 | AttackVector: v31.CVSSData.AttackVector, 285 | AttackComplexity: v31.CVSSData.AttackComplexity, 286 | PrivilegesRequired: v31.CVSSData.PrivilegesRequired, 287 | UserInteraction: v31.CVSSData.UserInteraction, 288 | Scope: v31.CVSSData.Scope, 289 | ConfidentialityImpact: v31.CVSSData.ConfidentialityImpact, 290 | IntegrityImpact: v31.CVSSData.IntegrityImpact, 291 | AvailabilityImpact: v31.CVSSData.AvailabilityImpact, 292 | BaseScore: v31.CVSSData.BaseScore, 293 | BaseSeverity: v31.CVSSData.BaseSeverity, 294 | ExploitabilityScore: func() float64 { 295 | if v31.ExploitabilityScore != nil { 296 | return *v31.ExploitabilityScore 297 | } 298 | return 0 299 | }(), 300 | ImpactScore: func() float64 { 301 | if v31.ImpactScore != nil { 302 | return *v31.ImpactScore 303 | } 304 | return 0 305 | }(), 306 | }, 307 | }) 308 | } 309 | c40 := make([]models.NvdCvss40, 0, len(item.Metrics.CVSSMetricV40)) 310 | for _, v40 := range item.Metrics.CVSSMetricV40 { 311 | c40 = append(c40, models.NvdCvss40{ 312 | Source: v40.Source, 313 | Type: v40.Type, 314 | Cvss40: models.Cvss40{ 315 | VectorString: v40.CVSSData.VectorString, 316 | BaseScore: v40.CVSSData.BaseScore, 317 | BaseSeverity: v40.CVSSData.BaseSeverity, 318 | ThreatScore: v40.CVSSData.ThreatScore, 319 | ThreatSeverity: v40.CVSSData.ThreatSeverity, 320 | EnvironmentalScore: v40.CVSSData.EnvironmentalScore, 321 | EnvironmentalSeverity: v40.CVSSData.EnvironmentalSeverity, 322 | }, 323 | }) 324 | } 325 | 326 | publish, err := fetcher.ParseTime([]string{"2006-01-02T15:04:05.000"}, item.Published) 327 | if err != nil { 328 | return nil, xerrors.Errorf("Failed to parse NVD Time. err: %w", err) 329 | } 330 | modified, err := fetcher.ParseTime([]string{"2006-01-02T15:04:05.000"}, item.LastModified) 331 | if err != nil { 332 | return nil, xerrors.Errorf("Failed to parse NVD Time. err: %w", err) 333 | } 334 | 335 | return &models.Nvd{ 336 | CveID: item.ID, 337 | Descriptions: descs, 338 | Cvss2: c2, 339 | Cvss3: c3, 340 | Cvss40: c40, 341 | Cwes: cwes, 342 | Cpes: cpes, 343 | References: refs, 344 | Certs: certs, 345 | PublishedDate: publish, 346 | LastModifiedDate: modified, 347 | }, nil 348 | } 349 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2016 kotakanbe 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /fetcher/jvn/jvn.go: -------------------------------------------------------------------------------- 1 | package jvn 2 | 3 | import ( 4 | "encoding/xml" 5 | "fmt" 6 | "net/http" 7 | "net/url" 8 | "regexp" 9 | "runtime" 10 | "strings" 11 | "time" 12 | 13 | "github.com/PuerkitoBio/goquery" 14 | "github.com/spf13/viper" 15 | "github.com/vulsio/go-cve-dictionary/fetcher" 16 | "github.com/vulsio/go-cve-dictionary/log" 17 | "github.com/vulsio/go-cve-dictionary/models" 18 | "github.com/vulsio/go-cve-dictionary/util" 19 | "golang.org/x/xerrors" 20 | ) 21 | 22 | // Meta ... https://jvndb.jvn.jp/ja/feed/checksum.txt 23 | type Meta struct { 24 | URL string `json:"url"` 25 | Hash string `json:"sha256"` 26 | LastModified string `json:"lastModified"` 27 | } 28 | 29 | type rdf struct { 30 | Items []Item `xml:"item"` 31 | } 32 | 33 | // Item ... http://jvndb.jvn.jp/apis/getVulnOverviewList_api.html 34 | type Item struct { 35 | About string `xml:"about,attr"` 36 | Title string `xml:"title"` 37 | Link string `xml:"link"` 38 | Description string `xml:"description"` 39 | Publisher string `xml:"publisher"` 40 | Identifier string `xml:"identifier"` 41 | References []references `xml:"references"` 42 | Cpes []cpe `xml:"cpe"` 43 | Cvsses []Cvss `xml:"cvss"` 44 | Date string `xml:"date"` 45 | Issued string `xml:"issued"` 46 | Modified string `xml:"modified"` 47 | } 48 | 49 | type cpe struct { 50 | Version string `xml:"version,attr"` // cpe:/a:mysql:mysql 51 | Vendor string `xml:"vendor,attr"` 52 | Product string `xml:"product,attr"` 53 | Value string `xml:",chardata"` 54 | } 55 | 56 | type references struct { 57 | ID string `xml:"id,attr"` 58 | Source string `xml:"source,attr"` 59 | Title string `xml:"title,attr"` 60 | URL string `xml:",chardata"` 61 | } 62 | 63 | // Cvss ... CVSS 64 | type Cvss struct { 65 | Score string `xml:"score,attr"` 66 | Severity string `xml:"severity,attr"` 67 | Vector string `xml:"vector,attr"` 68 | Version string `xml:"version,attr"` 69 | } 70 | 71 | // FetchConvert Fetch CVE vulnerability information from JVN 72 | func FetchConvert(uniqCve map[string]map[string]models.Jvn, years []string) error { 73 | for _, y := range years { 74 | items, err := fetch(y) 75 | if err != nil { 76 | return xerrors.Errorf("Failed to fetch. err: %w", err) 77 | } 78 | 79 | cves, err := convert(items) 80 | if err != nil { 81 | return xerrors.Errorf("Failed to convert. err: %w", err) 82 | } 83 | distributeCvesByYear(uniqCve, cves) 84 | } 85 | return nil 86 | } 87 | 88 | func fetch(year string) ([]Item, error) { 89 | u := func() string { 90 | switch year { 91 | case "modified": 92 | return "https://jvndb.jvn.jp/ja/rss/jvndb.rdf" 93 | case "recent": 94 | return "https://jvndb.jvn.jp/ja/rss/jvndb_new.rdf" 95 | default: 96 | return fmt.Sprintf("https://jvndb.jvn.jp/ja/rss/years/jvndb_%s.rdf", year) 97 | } 98 | }() 99 | 100 | body, err := fetcher.FetchFeedFile(u, false) 101 | if err != nil { 102 | return nil, xerrors.Errorf("Failed to FetchFeedFiles. err: %w", err) 103 | } 104 | 105 | var rdf rdf 106 | items := []Item{} 107 | if err := xml.Unmarshal([]byte(body), &rdf); err != nil { 108 | return nil, xerrors.Errorf("Failed to unmarshal. url: %s, err: %w", u, err) 109 | } 110 | for i, item := range rdf.Items { 111 | if !strings.Contains(item.Description, "** 未確定 **") && !strings.Contains(item.Description, "** サポート外 **") && !strings.Contains(item.Description, "** 削除 **") { 112 | items = append(items, rdf.Items[i]) 113 | } 114 | } 115 | 116 | return items, nil 117 | } 118 | 119 | func convert(items []Item) (map[string]models.Jvn, error) { 120 | reqChan := make(chan Item, len(items)) 121 | resChan := make(chan []models.Jvn, len(items)) 122 | errChan := make(chan error) 123 | defer close(reqChan) 124 | defer close(resChan) 125 | defer close(errChan) 126 | 127 | go func() { 128 | for _, item := range items { 129 | reqChan <- item 130 | } 131 | }() 132 | 133 | concurrency := runtime.NumCPU() + 2 134 | tasks := util.GenWorkers(concurrency) 135 | for range items { 136 | tasks <- func() { 137 | req := <-reqChan 138 | cves, err := convertToModel(&req) 139 | if err != nil { 140 | errChan <- err 141 | return 142 | } 143 | resChan <- cves 144 | } 145 | } 146 | 147 | cves := map[string]models.Jvn{} 148 | errs := []error{} 149 | timeout := time.After(10 * 60 * time.Second) 150 | for range items { 151 | select { 152 | case res := <-resChan: 153 | for _, cve := range res { 154 | uniqJVNID := fmt.Sprintf("%s#%s", cve.JvnID, cve.CveID) 155 | cves[uniqJVNID] = cve 156 | } 157 | case err := <-errChan: 158 | errs = append(errs, err) 159 | case <-timeout: 160 | return nil, fmt.Errorf("Timeout Fetching") 161 | } 162 | } 163 | if 0 < len(errs) { 164 | return nil, xerrors.Errorf("%w", errs) 165 | } 166 | return cves, nil 167 | } 168 | 169 | // convertJvn converts Jvn structure(got from JVN) to model structure. 170 | func convertToModel(item *Item) (cves []models.Jvn, err error) { 171 | var cvss2, cvss3 Cvss 172 | for _, cvss := range item.Cvsses { 173 | if strings.HasPrefix(cvss.Version, "2") { 174 | cvss2 = cvss 175 | } else if strings.HasPrefix(cvss.Version, "3") { 176 | cvss3 = cvss 177 | } else { 178 | log.Warnf("Unknown CVSS version format: %s", cvss.Version) 179 | } 180 | } 181 | 182 | // References 183 | refs, links := []models.JvnReference{}, []string{} 184 | for _, r := range item.References { 185 | ref := models.JvnReference{ 186 | Reference: models.Reference{ 187 | Link: r.URL, 188 | Name: r.Title, 189 | Source: r.Source, 190 | }, 191 | } 192 | refs = append(refs, ref) 193 | 194 | if ref.Source == "JPCERT-AT" { 195 | links = append(links, r.URL) 196 | } 197 | } 198 | 199 | certs, err := collectCertLinks(links) 200 | if err != nil { 201 | return nil, 202 | xerrors.Errorf("Failed to collect links. err: %w", err) 203 | } 204 | 205 | // Cpes 206 | cpes := []models.JvnCpe{} 207 | for _, c := range item.Cpes { 208 | cpeBase, err := fetcher.ParseCpeURI(c.Value) 209 | if err != nil { 210 | // logging only 211 | log.Warnf("Failed to parse CPE URI: %s, %s", c.Value, err) 212 | continue 213 | } 214 | cpes = append(cpes, models.JvnCpe{ 215 | CpeBase: *cpeBase, 216 | }) 217 | } 218 | 219 | publish, err := fetcher.ParseTime([]string{"2006-01-02T15:04-07:00"}, item.Issued) 220 | if err != nil { 221 | return nil, err 222 | } 223 | modified, err := fetcher.ParseTime([]string{"2006-01-02T15:04-07:00"}, item.Modified) 224 | if err != nil { 225 | return nil, err 226 | } 227 | 228 | cveIDs := getCveIDs(*item) 229 | if len(cveIDs) == 0 { 230 | log.Debugf("No CveIDs in references. JvnID: %s, Link: %s", 231 | item.Identifier, item.Link) 232 | // ignore this item 233 | return nil, nil 234 | } 235 | 236 | for _, cveID := range cveIDs { 237 | v2elems := parseCvss2VectorStr(cvss2.Vector) 238 | v3elems := parseCvss3VectorStr(cvss3.Vector) 239 | cve := models.Jvn{ 240 | CveID: cveID, 241 | Title: strings.ReplaceAll(item.Title, "\r", ""), 242 | Summary: strings.ReplaceAll(item.Description, "\r", ""), 243 | JvnLink: item.Link, 244 | JvnID: item.Identifier, 245 | 246 | Cvss2: models.JvnCvss2{ 247 | Cvss2: models.Cvss2{ 248 | BaseScore: fetcher.StringToFloat(cvss2.Score), 249 | Severity: cvss2.Severity, 250 | VectorString: cvss2.Vector, 251 | AccessVector: v2elems[0], 252 | AccessComplexity: v2elems[1], 253 | Authentication: v2elems[2], 254 | ConfidentialityImpact: v2elems[3], 255 | IntegrityImpact: v2elems[4], 256 | AvailabilityImpact: v2elems[5], 257 | }, 258 | }, 259 | 260 | Cvss3: models.JvnCvss3{ 261 | Cvss3: models.Cvss3{ 262 | BaseScore: fetcher.StringToFloat(cvss3.Score), 263 | BaseSeverity: cvss3.Severity, 264 | VectorString: cvss3.Vector, 265 | AttackVector: v3elems[0], 266 | AttackComplexity: v3elems[1], 267 | PrivilegesRequired: v3elems[2], 268 | UserInteraction: v3elems[3], 269 | Scope: v3elems[4], 270 | ConfidentialityImpact: v3elems[5], 271 | IntegrityImpact: v3elems[6], 272 | AvailabilityImpact: v3elems[7], 273 | }, 274 | }, 275 | 276 | References: append([]models.JvnReference{}, refs...), 277 | Cpes: append([]models.JvnCpe{}, cpes...), 278 | Certs: append([]models.JvnCert{}, certs...), 279 | 280 | PublishedDate: publish, 281 | LastModifiedDate: modified, 282 | } 283 | cves = append(cves, cve) 284 | } 285 | return 286 | } 287 | 288 | func collectCertLinks(links []string) (certs []models.JvnCert, err error) { 289 | var proxyURL *url.URL 290 | httpClient := &http.Client{} 291 | if viper.GetString("http-proxy") != "" { 292 | if proxyURL, err = url.Parse(viper.GetString("http-proxy")); err != nil { 293 | return nil, xerrors.Errorf("failed to parse proxy url: %w", err) 294 | } 295 | httpClient = &http.Client{Transport: &http.Transport{Proxy: http.ProxyURL(proxyURL)}} 296 | } 297 | 298 | certs = []models.JvnCert{} 299 | for _, link := range links { 300 | title := "" 301 | if !viper.GetBool("without-jvncert") { 302 | if strings.HasSuffix(link, ".html") { 303 | res, err := httpClient.Get(link) 304 | if err != nil { 305 | return nil, xerrors.Errorf("Failed to get %s: err: %w", link, err) 306 | } 307 | defer res.Body.Close() 308 | 309 | doc, err := goquery.NewDocumentFromReader(res.Body) 310 | if err != nil { 311 | return nil, xerrors.Errorf("Failed to NewDocumentFromReader. err: %w", err) 312 | } 313 | title = doc.Find("title").Text() 314 | } 315 | } 316 | certs = append(certs, models.JvnCert{ 317 | Cert: models.Cert{ 318 | Title: title, 319 | Link: link, 320 | }, 321 | }) 322 | } 323 | 324 | return certs, nil 325 | } 326 | 327 | var cvss2VectorMap = map[string]string{ 328 | "AV:L": "LOCAL", 329 | "AV:A": "ADJACENT_NETWORK", 330 | "AV:N": "NETWORK", 331 | 332 | "AC:L": "LOW", 333 | "AC:M": "MEDIUM", 334 | "AC:H": "HIGH", 335 | 336 | "Au:M": "MULTIPLE", 337 | "Au:S": "SINGLE", 338 | "Au:N": "NONE", 339 | 340 | "C:N": "NONE", 341 | "C:P": "PARTIAL", 342 | "C:C": "COMPLETE", 343 | 344 | "I:N": "NONE", 345 | "I:P": "PARTIAL", 346 | "I:C": "COMPLETE", 347 | 348 | "A:N": "NONE", 349 | "A:P": "PARTIAL", 350 | "A:C": "COMPLETE", 351 | } 352 | 353 | func parseCvss2VectorStr(str string) (elems []string) { 354 | if len(str) == 0 { 355 | return []string{"", "", "", "", "", ""} 356 | } 357 | for _, s := range strings.Split(str, "/") { 358 | elems = append(elems, cvss2VectorMap[s]) 359 | } 360 | return 361 | } 362 | 363 | var cvss3VectorMap = map[string]string{ 364 | "AV:N": "NETWORK", 365 | "AV:A": "ADJACENT_NETWORK", 366 | "AV:L": "LOCAL", 367 | "AV:P": "PHYSICAL", 368 | 369 | "AC:L": "LOW", 370 | "AC:H": "HIGH", 371 | 372 | "PR:N": "NONE", 373 | "PR:L": "LOW", 374 | "PR:H": "HIGH", 375 | 376 | "UI:N": "NONE", 377 | "UI:R": "REQUIRED", 378 | 379 | "S:U": "UNCHANGED", 380 | "S:C": "CHANGED", 381 | 382 | "C:N": "NONE", 383 | "C:L": "LOW", 384 | "C:H": "HIGH", 385 | 386 | "I:N": "NONE", 387 | "I:L": "LOW", 388 | "I:H": "HIGH", 389 | 390 | "A:N": "NONE", 391 | "A:L": "LOW", 392 | "A:H": "HIGH", 393 | } 394 | 395 | func parseCvss3VectorStr(str string) (elems []string) { 396 | if len(str) == 0 { 397 | return []string{"", "", "", "", "", "", "", ""} 398 | } 399 | str = strings.TrimPrefix(str, "CVSS:3.0/") 400 | for _, s := range strings.Split(str, "/") { 401 | elems = append(elems, cvss3VectorMap[s]) 402 | } 403 | return 404 | } 405 | 406 | var cveIDPattern = regexp.MustCompile(`^CVE-[0-9]{4}-[0-9]{4,}$`) 407 | 408 | func getCveIDs(item Item) []string { 409 | cveIDsMap := map[string]bool{} 410 | for _, ref := range item.References { 411 | switch ref.Source { 412 | case "NVD", "CVE": 413 | if cveIDPattern.MatchString(ref.ID) { 414 | cveIDsMap[ref.ID] = true 415 | } else { 416 | id := strings.TrimSpace(ref.ID) 417 | if cveIDPattern.MatchString(id) { 418 | log.Warnf("CVE-ID with extra space. Please email JVNDB (isec-jvndb@ipa.go.jp) to fix the rdf file with the following information. RDF data(Identifier: %s, Reference Source: %s, ID: %s)", item.Identifier, ref.Source, ref.ID) 419 | cveIDsMap[id] = true 420 | } else { 421 | log.Warnf("Failed to get CVE-ID. Invalid CVE-ID. Please email JVNDB (isec-jvndb@ipa.go.jp) to fix the rdf file with the following information. RDF data(Identifier: %s, Reference Source: %s, ID: %s)", item.Identifier, ref.Source, ref.ID) 422 | } 423 | } 424 | } 425 | } 426 | cveIDs := []string{} 427 | for cveID := range cveIDsMap { 428 | cveIDs = append(cveIDs, cveID) 429 | } 430 | return cveIDs 431 | } 432 | 433 | func distributeCvesByYear(uniqCves map[string]map[string]models.Jvn, cves map[string]models.Jvn) { 434 | for uniqJVNID, cve := range cves { 435 | y := strings.Split(cve.JvnID, "-")[1] 436 | if _, ok := uniqCves[y]; !ok { 437 | uniqCves[y] = map[string]models.Jvn{} 438 | } 439 | if destCve, ok := uniqCves[y][uniqJVNID]; !ok { 440 | uniqCves[y][uniqJVNID] = cve 441 | } else { 442 | if cve.LastModifiedDate.After(destCve.LastModifiedDate) { 443 | uniqCves[y][uniqJVNID] = cve 444 | } 445 | } 446 | } 447 | } 448 | -------------------------------------------------------------------------------- /commands/search.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "os" 7 | 8 | "github.com/spf13/cobra" 9 | "github.com/spf13/viper" 10 | "golang.org/x/xerrors" 11 | 12 | "github.com/vulsio/go-cve-dictionary/db" 13 | "github.com/vulsio/go-cve-dictionary/log" 14 | "github.com/vulsio/go-cve-dictionary/models" 15 | ) 16 | 17 | var searchCmd = &cobra.Command{ 18 | Use: "search", 19 | Short: "Search for Vulnerability in the dictionary", 20 | Long: "Search for Vulnerability in the dictionary", 21 | } 22 | 23 | var searchCVECmd = &cobra.Command{ 24 | Use: "cve", 25 | Short: "Search for Vulnerability in the dictionary by CVEID", 26 | Long: "Search for Vulnerability in the dictionary by CVEID", 27 | RunE: searchCVE, 28 | } 29 | 30 | var searchAdvisoryCmd = &cobra.Command{ 31 | Use: "advisories", 32 | Short: "Search for Advisories", 33 | Long: "Search for Advisories", 34 | ValidArgs: []string{"jvn", "euvd", "fortinet", "paloalto", "cisco"}, 35 | Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs), 36 | RunE: searchAdvisories, 37 | } 38 | 39 | var searchCPECmd = &cobra.Command{ 40 | Use: "cpe", 41 | Short: "Search for Vulnerability in the dictionary by CPE", 42 | Long: "Search for Vulnerability in the dictionary by CPE", 43 | Args: cobra.ExactArgs(1), 44 | RunE: searchCPE, 45 | } 46 | 47 | func searchCVE(_ *cobra.Command, args []string) error { 48 | if err := log.SetLogger(viper.GetBool("log-to-file"), viper.GetString("log-dir"), viper.GetBool("debug"), viper.GetBool("log-json")); err != nil { 49 | return xerrors.Errorf("Failed to SetLogger. err: %w", err) 50 | } 51 | 52 | driver, err := db.NewDB(viper.GetString("dbtype"), viper.GetString("dbpath"), viper.GetBool("debug-sql"), db.Option{}) 53 | if err != nil { 54 | if errors.Is(err, db.ErrDBLocked) { 55 | return xerrors.Errorf("Failed to open DB. Close DB connection before fetching. err: %w", err) 56 | } 57 | return xerrors.Errorf("Failed to open DB. err: %w", err) 58 | } 59 | 60 | fetchMeta, err := driver.GetFetchMeta() 61 | if err != nil { 62 | return xerrors.Errorf("Failed to get FetchMeta from DB. err: %w", err) 63 | } 64 | if fetchMeta.OutDated() { 65 | return xerrors.Errorf("Failed to start server. err: SchemaVersion is old. SchemaVersion: %+v", map[string]uint{"latest": models.LatestSchemaVersion, "DB": fetchMeta.SchemaVersion}) 66 | } 67 | 68 | count := 0 69 | nvdCount, err := driver.CountNvd() 70 | if err != nil { 71 | log.Errorf("Failed to count NVD table: %s", err) 72 | return err 73 | } 74 | count += nvdCount 75 | 76 | vulncheckCount, err := driver.CountVulncheck() 77 | if err != nil { 78 | log.Errorf("Failed to count VulnCheck table: %s", err) 79 | return err 80 | } 81 | count += vulncheckCount 82 | 83 | jvnCount, err := driver.CountJvn() 84 | if err != nil { 85 | log.Errorf("Failed to count JVN table: %s", err) 86 | return err 87 | } 88 | count += jvnCount 89 | 90 | euvdCount, err := driver.CountEuvd() 91 | if err != nil { 92 | log.Errorf("Failed to count EUVD table: %s", err) 93 | return err 94 | } 95 | count += euvdCount 96 | 97 | fortinetCount, err := driver.CountFortinet() 98 | if err != nil { 99 | log.Errorf("Failed to count Fortinet table: %s", err) 100 | return err 101 | } 102 | count += fortinetCount 103 | 104 | mitreCount, err := driver.CountMitre() 105 | if err != nil { 106 | log.Errorf("Failed to count MITRE table: %s", err) 107 | return err 108 | } 109 | count += mitreCount 110 | 111 | paloaltoCount, err := driver.CountPaloalto() 112 | if err != nil { 113 | log.Errorf("Failed to count Paloalto table: %s", err) 114 | return err 115 | } 116 | count += paloaltoCount 117 | 118 | ciscoCount, err := driver.CountCisco() 119 | if err != nil { 120 | log.Errorf("Failed to count Cisco table: %s", err) 121 | return err 122 | } 123 | count += ciscoCount 124 | 125 | if count == 0 { 126 | log.Infof("No Vulnerability data found. Run the below command to fetch data from NVD, VulnCheck, JVN, EUVD, Fortinet, MITRE, Paloalto, Cisco") 127 | log.Infof("") 128 | log.Infof(" go-cve-dictionary fetch nvd") 129 | log.Infof(" go-cve-dictionary fetch vulncheck") 130 | log.Infof(" go-cve-dictionary fetch jvn") 131 | log.Infof(" go-cve-dictionary fetch euvd") 132 | log.Infof(" go-cve-dictionary fetch fortinet") 133 | log.Infof(" go-cve-dictionary fetch mitre") 134 | log.Infof(" go-cve-dictionary fetch paloalto") 135 | log.Infof(" go-cve-dictionary fetch cisco") 136 | log.Infof("") 137 | return nil 138 | } 139 | 140 | enc := json.NewEncoder(os.Stdout) 141 | enc.SetIndent("", " ") 142 | switch len(args) { 143 | case 0: 144 | cveids, err := driver.GetCveIDs() 145 | if err != nil { 146 | return xerrors.Errorf("Failed to get All CVEIDs. err: %w", err) 147 | } 148 | if err := enc.Encode(cveids); err != nil { 149 | return xerrors.Errorf("Failed to encode All CVEIDs. err: %w", err) 150 | } 151 | case 1: 152 | d, err := driver.Get(args[0]) 153 | if err != nil { 154 | return xerrors.Errorf("Failed to get CVEDetail by CVEID. err: %w", err) 155 | } 156 | if err := enc.Encode(d); err != nil { 157 | return xerrors.Errorf("Failed to encode CVEDetail by CVEID. err: %w", err) 158 | } 159 | default: 160 | ds, err := driver.GetMulti(args) 161 | if err != nil { 162 | return xerrors.Errorf("Failed to get CVEDetails by CVEIDs. err: %w", err) 163 | } 164 | if err := enc.Encode(ds); err != nil { 165 | return xerrors.Errorf("Failed to encode CVEDetails by CVEIDs. err: %w", err) 166 | } 167 | } 168 | 169 | return nil 170 | } 171 | 172 | func searchAdvisories(_ *cobra.Command, args []string) error { 173 | if err := log.SetLogger(viper.GetBool("log-to-file"), viper.GetString("log-dir"), viper.GetBool("debug"), viper.GetBool("log-json")); err != nil { 174 | return xerrors.Errorf("Failed to SetLogger. err: %w", err) 175 | } 176 | 177 | driver, err := db.NewDB(viper.GetString("dbtype"), viper.GetString("dbpath"), viper.GetBool("debug-sql"), db.Option{}) 178 | if err != nil { 179 | if errors.Is(err, db.ErrDBLocked) { 180 | return xerrors.Errorf("Failed to open DB. Close DB connection before fetching. err: %w", err) 181 | } 182 | return xerrors.Errorf("Failed to open DB. err: %w", err) 183 | } 184 | 185 | fetchMeta, err := driver.GetFetchMeta() 186 | if err != nil { 187 | return xerrors.Errorf("Failed to get FetchMeta from DB. err: %w", err) 188 | } 189 | if fetchMeta.OutDated() { 190 | return xerrors.Errorf("Failed to start server. err: SchemaVersion is old. SchemaVersion: %+v", map[string]uint{"latest": models.LatestSchemaVersion, "DB": fetchMeta.SchemaVersion}) 191 | } 192 | 193 | count := 0 194 | jvnCount, err := driver.CountJvn() 195 | if err != nil { 196 | log.Errorf("Failed to count JVN table: %s", err) 197 | return err 198 | } 199 | count += jvnCount 200 | 201 | euvdCount, err := driver.CountEuvd() 202 | if err != nil { 203 | log.Errorf("Failed to count EUVD table: %s", err) 204 | return err 205 | } 206 | count += euvdCount 207 | 208 | fortinetCount, err := driver.CountFortinet() 209 | if err != nil { 210 | log.Errorf("Failed to count Fortinet table: %s", err) 211 | return err 212 | } 213 | count += fortinetCount 214 | 215 | paloaltoCount, err := driver.CountPaloalto() 216 | if err != nil { 217 | log.Errorf("Failed to count Paloalto table: %s", err) 218 | return err 219 | } 220 | count += paloaltoCount 221 | 222 | ciscoCount, err := driver.CountCisco() 223 | if err != nil { 224 | log.Errorf("Failed to count Cisco table: %s", err) 225 | return err 226 | } 227 | count += ciscoCount 228 | 229 | if count == 0 { 230 | log.Infof("No Advisory data found. Run the below command to fetch data from JVN, EUVD, Fortinet, Paloalto, Cisco") 231 | log.Infof("") 232 | log.Infof(" go-cve-dictionary fetch jvn") 233 | log.Infof(" go-cve-dictionary fetch euvd") 234 | log.Infof(" go-cve-dictionary fetch fortinet") 235 | log.Infof(" go-cve-dictionary fetch paloalto") 236 | log.Infof(" go-cve-dictionary fetch cisco") 237 | log.Infof("") 238 | return nil 239 | } 240 | 241 | enc := json.NewEncoder(os.Stdout) 242 | enc.SetIndent("", " ") 243 | switch args[0] { 244 | case "jvn": 245 | m, err := driver.GetAdvisoriesJvn() 246 | if err != nil { 247 | return xerrors.Errorf("Failed to Get JVN Advisories. err: %w", err) 248 | } 249 | if err := enc.Encode(m); err != nil { 250 | return xerrors.Errorf("Failed to encode All Advisories. err: %w", err) 251 | } 252 | case "euvd": 253 | m, err := driver.GetAdvisoriesEuvd() 254 | if err != nil { 255 | return xerrors.Errorf("Failed to Get EUVD Advisories. err: %w", err) 256 | } 257 | if err := enc.Encode(m); err != nil { 258 | return xerrors.Errorf("Failed to encode All Advisories. err: %w", err) 259 | } 260 | case "fortinet": 261 | m, err := driver.GetAdvisoriesFortinet() 262 | if err != nil { 263 | return xerrors.Errorf("Failed to Get Fortinet Advisories. err: %w", err) 264 | } 265 | if err := enc.Encode(m); err != nil { 266 | return xerrors.Errorf("Failed to encode All Advisories. err: %w", err) 267 | } 268 | case "paloalto": 269 | m, err := driver.GetAdvisoriesPaloalto() 270 | if err != nil { 271 | return xerrors.Errorf("Failed to Get Paloalto Advisories. err: %w", err) 272 | } 273 | if err := enc.Encode(m); err != nil { 274 | return xerrors.Errorf("Failed to encode All Advisories. err: %w", err) 275 | } 276 | case "cisco": 277 | m, err := driver.GetAdvisoriesCisco() 278 | if err != nil { 279 | return xerrors.Errorf("Failed to Get Cisco Advisories. err: %w", err) 280 | } 281 | if err := enc.Encode(m); err != nil { 282 | return xerrors.Errorf("Failed to encode All Advisories. err: %w", err) 283 | } 284 | default: 285 | return xerrors.Errorf("not support type: %s", args[0]) 286 | } 287 | 288 | return nil 289 | } 290 | 291 | func searchCPE(_ *cobra.Command, args []string) error { 292 | if err := log.SetLogger(viper.GetBool("log-to-file"), viper.GetString("log-dir"), viper.GetBool("debug"), viper.GetBool("log-json")); err != nil { 293 | return xerrors.Errorf("Failed to SetLogger. err: %w", err) 294 | } 295 | 296 | driver, err := db.NewDB(viper.GetString("dbtype"), viper.GetString("dbpath"), viper.GetBool("debug-sql"), db.Option{}) 297 | if err != nil { 298 | if errors.Is(err, db.ErrDBLocked) { 299 | return xerrors.Errorf("Failed to open DB. Close DB connection before fetching. err: %w", err) 300 | } 301 | return xerrors.Errorf("Failed to open DB. err: %w", err) 302 | } 303 | 304 | fetchMeta, err := driver.GetFetchMeta() 305 | if err != nil { 306 | return xerrors.Errorf("Failed to get FetchMeta from DB. err: %w", err) 307 | } 308 | if fetchMeta.OutDated() { 309 | return xerrors.Errorf("Failed to start server. err: SchemaVersion is old. SchemaVersion: %+v", map[string]uint{"latest": models.LatestSchemaVersion, "DB": fetchMeta.SchemaVersion}) 310 | } 311 | 312 | count := 0 313 | nvdCount, err := driver.CountNvd() 314 | if err != nil { 315 | log.Errorf("Failed to count NVD table: %s", err) 316 | return err 317 | } 318 | count += nvdCount 319 | 320 | vulncheckCount, err := driver.CountVulncheck() 321 | if err != nil { 322 | log.Errorf("Failed to count VulnCheck table: %s", err) 323 | return err 324 | } 325 | count += vulncheckCount 326 | 327 | jvnCount, err := driver.CountJvn() 328 | if err != nil { 329 | log.Errorf("Failed to count JVN table: %s", err) 330 | return err 331 | } 332 | count += jvnCount 333 | 334 | fortinetCount, err := driver.CountFortinet() 335 | if err != nil { 336 | log.Errorf("Failed to count Fortinet table: %s", err) 337 | return err 338 | } 339 | count += fortinetCount 340 | 341 | paloaltoCount, err := driver.CountPaloalto() 342 | if err != nil { 343 | log.Errorf("Failed to count Paloalto table: %s", err) 344 | return err 345 | } 346 | count += paloaltoCount 347 | 348 | ciscoCount, err := driver.CountCisco() 349 | if err != nil { 350 | log.Errorf("Failed to count Cisco table: %s", err) 351 | return err 352 | } 353 | count += ciscoCount 354 | 355 | if count == 0 { 356 | log.Infof("No Vulnerability data found. Run the below command to fetch data from NVD, VulnCheck, JVN, Fortinet, Paloalto, Cisco") 357 | log.Infof("") 358 | log.Infof(" go-cve-dictionary fetch nvd") 359 | log.Infof(" go-cve-dictionary fetch vulncheck") 360 | log.Infof(" go-cve-dictionary fetch jvn") 361 | log.Infof(" go-cve-dictionary fetch fortinet") 362 | log.Infof(" go-cve-dictionary fetch paloalto") 363 | log.Infof(" go-cve-dictionary fetch cisco") 364 | log.Infof("") 365 | return nil 366 | } 367 | 368 | enc := json.NewEncoder(os.Stdout) 369 | enc.SetIndent("", " ") 370 | if viper.GetBool("cveid-only") { 371 | cveids, err := driver.GetCveIDsByCpeURI(args[0]) 372 | if err != nil { 373 | return xerrors.Errorf("Failed to Get CVEIDs by CPE URI. err: %w", err) 374 | } 375 | if err := enc.Encode(map[string][]string{"NVD": cveids.Nvd, "VulnCheck": cveids.Vulncheck, "JVN": cveids.Jvn, "Fortinet": cveids.Fortinet, "Paloalto": cveids.Paloalto, "Cisco": cveids.Cisco}); err != nil { 376 | return xerrors.Errorf("Failed to encode CVEIDs by CPE URI. err: %w", err) 377 | } 378 | return nil 379 | } 380 | cveDetails, err := driver.GetByCpeURI(args[0]) 381 | if err != nil { 382 | return xerrors.Errorf("Failed to Get CVEDetails by CPE URI. err: %w", err) 383 | } 384 | if err := enc.Encode(cveDetails); err != nil { 385 | return xerrors.Errorf("Failed to encode CVEDetails by CPE URI. err: %w", err) 386 | } 387 | 388 | return nil 389 | } 390 | 391 | func init() { 392 | RootCmd.AddCommand(searchCmd) 393 | searchCmd.AddCommand(searchCVECmd, searchAdvisoryCmd, searchCPECmd) 394 | 395 | searchCPECmd.PersistentFlags().Bool("cveid-only", false, "show only CVEID in search results") 396 | _ = viper.BindPFlag("cveid-only", searchCPECmd.PersistentFlags().Lookup("cveid-only")) 397 | } 398 | -------------------------------------------------------------------------------- /fetcher/vulncheck/types.go: -------------------------------------------------------------------------------- 1 | package vulncheck 2 | 3 | type cve struct { 4 | ID string `json:"id"` 5 | SourceIdentifier string `json:"sourceIdentifier,omitempty"` 6 | VulnStatus string `json:"vulnStatus,omitempty"` 7 | Published string `json:"published"` 8 | LastModified string `json:"lastModified"` 9 | EvaluatorComment string `json:"evaluatorComment,omitempty"` 10 | EvaluatorSolution string `json:"evaluatorSolution,omitempty"` 11 | EvaluatorImpact string `json:"evaluatorImpact,omitempty"` 12 | CISAExploitAdd string `json:"cisaExploitAdd,omitempty"` 13 | CISAActionDue string `json:"cisaActionDue,omitempty"` 14 | CISARequiredAction string `json:"cisaRequiredAction,omitempty"` 15 | CISAVulnerabilityName string `json:"cisaVulnerabilityName,omitempty"` 16 | CVETags []cveTags `json:"cveTags,omitempty"` 17 | Descriptions []langString `json:"descriptions"` 18 | References []reference `json:"references"` 19 | Metrics metrics `json:"metrics,omitempty"` 20 | Weaknesses []weakness `json:"weaknesses,omitempty"` 21 | Configurations []config `json:"configurations,omitempty"` 22 | VendorComments []vendorComment `json:"vendorComments,omitempty"` 23 | VCConfigurations []config `json:"vcConfigurations,omitempty"` 24 | VCVulnerableCPEs []string `json:"vcVulnerableCPEs,omitempty"` 25 | } 26 | 27 | type cveTags struct { 28 | SourceIdentifier string `json:"sourceIdentifier,omitempty"` 29 | Tags []string `json:"tags,omitempty"` 30 | } 31 | 32 | type langString struct { 33 | Lang string `json:"lang"` 34 | Value string `json:"value"` 35 | } 36 | 37 | type reference struct { 38 | Source string `json:"source,omitempty"` 39 | Tags []string `json:"tags,omitempty"` 40 | URL string `json:"url"` 41 | } 42 | 43 | type metrics struct { 44 | CVSSMetricV2 []cvssMetricV2 `json:"cvssMetricV2,omitempty"` 45 | CVSSMetricV30 []cvssMetricV30 `json:"cvssMetricV30,omitempty"` 46 | CVSSMetricV31 []cvssMetricV31 `json:"cvssMetricV31,omitempty"` 47 | CVSSMetricV40 []cvssMetricV40 `json:"cvssMetricV40,omitempty"` 48 | } 49 | 50 | type cvssMetricV2 struct { 51 | Source string `json:"source"` 52 | Type string `json:"type"` 53 | CvssData cvssV20 `json:"cvssData"` 54 | BaseSeverity string `json:"baseSeverity,omitempty"` 55 | ExploitabilityScore float64 `json:"exploitabilityScore,omitempty"` 56 | ImpactScore float64 `json:"impactScore,omitempty"` 57 | ACInsufInfo bool `json:"acInsufInfo,omitempty"` 58 | ObtainAllPrivilege bool `json:"obtainAllPrivilege,omitempty"` 59 | ObtainUserPrivilege bool `json:"obtainUserPrivilege,omitempty"` 60 | ObtainOtherPrivilege bool `json:"obtainOtherPrivilege,omitempty"` 61 | UserInteractionRequired bool `json:"userInteractionRequired,omitempty"` 62 | } 63 | 64 | type config struct { 65 | Operator string `json:"operator,omitempty"` 66 | Negate bool `json:"negate,omitempty"` 67 | Nodes []node `json:"nodes"` 68 | } 69 | 70 | type node struct { 71 | Operator string `json:"operator"` 72 | Negate bool `json:"negate,omitempty"` 73 | CPEMatch []cpeMatch `json:"cpeMatch"` 74 | } 75 | 76 | type cpeMatch struct { 77 | Vulnerable bool `json:"vulnerable"` 78 | Criteria string `json:"criteria"` 79 | MatchCriteriaID string `json:"matchCriteriaId"` 80 | VersionStartExcluding string `json:"versionStartExcluding,omitempty"` 81 | VersionStartIncluding string `json:"versionStartIncluding,omitempty"` 82 | VersionEndExcluding string `json:"versionEndExcluding,omitempty"` 83 | VersionEndIncluding string `json:"versionEndIncluding,omitempty"` 84 | } 85 | 86 | type cvssMetricV30 struct { 87 | Source string `json:"source"` 88 | Type string `json:"type"` 89 | CVSSData cvssV30 `json:"cvssData"` 90 | ExploitabilityScore float64 `json:"exploitabilityScore,omitempty"` 91 | ImpactScore float64 `json:"impactScore,omitempty"` 92 | } 93 | 94 | type cvssMetricV31 struct { 95 | Source string `json:"source"` 96 | Type string `json:"type"` 97 | CVSSData cvssV31 `json:"cvssData"` 98 | ExploitabilityScore *float64 `json:"exploitabilityScore,omitempty"` 99 | ImpactScore *float64 `json:"impactScore,omitempty"` 100 | } 101 | 102 | type cvssMetricV40 struct { 103 | Source string `json:"source"` 104 | Type string `json:"type"` 105 | CVSSData cvssV40 `json:"cvssData"` 106 | } 107 | 108 | type vendorComment struct { 109 | Organization string `json:"organization"` 110 | Comment string `json:"comment"` 111 | LastModified string `json:"lastModified"` 112 | } 113 | 114 | type weakness struct { 115 | Source string `json:"source"` 116 | Type string `json:"type"` 117 | Description []langString `json:"description"` 118 | } 119 | 120 | type cvssV40 struct { 121 | Version string `json:"version"` 122 | VectorString string `json:"vectorString"` 123 | BaseScore float64 `json:"baseScore"` 124 | BaseSeverity string `json:"baseSeverity"` 125 | AttackVector *string `json:"attackVector,omitempty"` 126 | AttackComplexity *string `json:"attackComplexity,omitempty"` 127 | AttackRequirements *string `json:"attackRequirements,omitempty"` 128 | PrivilegesRequired *string `json:"privilegesRequired,omitempty"` 129 | UserInteraction *string `json:"userInteraction,omitempty"` 130 | VulnerableSystemConfidentiality *string `json:"vulnerableSystemConfidentiality,omitempty"` 131 | VulnerableSystemIntegrity *string `json:"vulnerableSystemIntegrity,omitempty"` 132 | VulnerableSystemAvailability *string `json:"vulnerableSystemAvailability,omitempty"` 133 | SubsequentSystemConfidentiality *string `json:"subsequentSystemConfidentiality,omitempty"` 134 | SubsequentSystemIntegrity *string `json:"subsequentSystemIntegrity,omitempty"` 135 | SubsequentSystemAvailability *string `json:"subsequentSystemAvailability,omitempty"` 136 | ExploitMaturity *string `json:"exploitMaturity,omitempty"` 137 | ConfidentialityRequirement *string `json:"confidentialityRequirements,omitempty"` 138 | IntegrityRequirement *string `json:"integrityRequirements,omitempty"` 139 | AvailabilityRequirement *string `json:"availabilityRequirements,omitempty"` 140 | ModifiedAttackVector *string `json:"modifiedAttackVector,omitempty"` 141 | ModifiedAttackComplexity *string `json:"modifiedAttackComplexity,omitempty"` 142 | ModifiedAttackRequirements *string `json:"modifiedAttackRequirements,omitempty"` 143 | ModifiedPrivilegesRequired *string `json:"modifiedPrivilegesRequired,omitempty"` 144 | ModifiedUserInteraction *string `json:"modifiedUserInteraction,omitempty"` 145 | ModifiedVulnerableSystemConfidentiality *string `json:"modifiedVulnerableSystemConfidentiality,omitempty"` 146 | ModifiedVulnerableSystemIntegrity *string `json:"modifiedVulnerableSystemIntegrity,omitempty"` 147 | ModifiedVulnerableSystemAvailability *string `json:"modifiedVulnerableSystemAvailability,omitempty"` 148 | ModifiedSubsequentSystemConfidentiality *string `json:"modifiedSubsequentSystemConfidentiality,omitempty"` 149 | ModifiedSubsequentSystemIntegrity *string `json:"modifiedSubsequentSystemIntegrity,omitempty"` 150 | ModifiedSubsequentSystemAvailability *string `json:"modifiedSubsequentSystemAvailability,omitempty"` 151 | Safety *string `json:"safety,omitempty"` 152 | Automatable *string `json:"automatable,omitempty"` 153 | ProviderUrgency *string `json:"providerUrgency,omitempty"` 154 | Recovery *string `json:"recovery,omitempty"` 155 | ValueDensity *string `json:"valueDensity,omitempty"` 156 | VulnerabilityResponseEffort *string `json:"vulnerabilityResponseEffort,omitempty"` 157 | ThreatScore *float64 `json:"threatScore,omitempty"` 158 | ThreatSeverity *string `json:"threatSeverity,omitempty"` 159 | EnvironmentalScore *float64 `json:"environmentalScore,omitempty"` 160 | EnvironmentalSeverity *string `json:"environmentalSeverity,omitempty"` 161 | } 162 | 163 | type cvssV31 struct { 164 | Version string `json:"version"` 165 | VectorString string `json:"vectorString"` 166 | AttackVector string `json:"attackVector,omitempty"` 167 | AttackComplexity string `json:"attackComplexity,omitempty"` 168 | PrivilegesRequired string `json:"privilegesRequired,omitempty"` 169 | UserInteraction string `json:"userInteraction,omitempty"` 170 | Scope string `json:"scope,omitempty"` 171 | ConfidentialityImpact string `json:"confidentialityImpact,omitempty"` 172 | IntegrityImpact string `json:"integrityImpact,omitempty"` 173 | AvailabilityImpact string `json:"availabilityImpact,omitempty"` 174 | BaseScore float64 `json:"baseScore"` 175 | BaseSeverity string `json:"baseSeverity"` 176 | ExploitCodeMaturity string `json:"exploitCodeMaturity,omitempty"` 177 | RemediationLevel string `json:"remediationLevel,omitempty"` 178 | ReportConfidence string `json:"reportConfidence,omitempty"` 179 | TemporalScore float64 `json:"temporalScore,omitempty"` 180 | TemporalSeverity string `json:"temporalSeverity,omitempty"` 181 | ConfidentialityRequirement string `json:"confidentialityRequirement,omitempty"` 182 | IntegrityRequirement string `json:"integrityRequirement,omitempty"` 183 | AvailabilityRequirement string `json:"availabilityRequirement,omitempty"` 184 | ModifiedAttackVector string `json:"modifiedAttackVector,omitempty"` 185 | ModifiedAttackComplexity string `json:"modifiedAttackComplexity,omitempty"` 186 | ModifiedPrivilegesRequired string `json:"modifiedPrivilegesRequired,omitempty"` 187 | ModifiedUserInteraction string `json:"modifiedUserInteraction,omitempty"` 188 | ModifiedScope string `json:"modifiedScope,omitempty"` 189 | ModifiedConfidentialityImpact string `json:"modifiedConfidentialityImpact,omitempty"` 190 | ModifiedIntegrityImpact string `json:"modifiedIntegrityImpact,omitempty"` 191 | ModifiedAvailabilityImpact string `json:"modifiedAvailabilityImpact,omitempty"` 192 | EnvironmentalScore float64 `json:"environmentalScore,omitempty"` 193 | EnvironmentalSeverity string `json:"environmentalSeverity,omitempty"` 194 | } 195 | 196 | type cvssV30 struct { 197 | Version string `json:"version"` 198 | VectorString string `json:"vectorString"` 199 | AttackVector string `json:"attackVector,omitempty"` 200 | AttackComplexity string `json:"attackComplexity,omitempty"` 201 | PrivilegesRequired string `json:"privilegesRequired,omitempty"` 202 | UserInteraction string `json:"userInteraction,omitempty"` 203 | Scope string `json:"scope,omitempty"` 204 | ConfidentialityImpact string `json:"confidentialityImpact,omitempty"` 205 | IntegrityImpact string `json:"integrityImpact,omitempty"` 206 | AvailabilityImpact string `json:"availabilityImpact,omitempty"` 207 | BaseScore float64 `json:"baseScore"` 208 | BaseSeverity string `json:"baseSeverity"` 209 | ExploitCodeMaturity string `json:"exploitCodeMaturity,omitempty"` 210 | RemediationLevel string `json:"remediationLevel,omitempty"` 211 | ReportConfidence string `json:"reportConfidence,omitempty"` 212 | TemporalScore *float64 `json:"temporalScore,omitempty"` 213 | TemporalSeverity string `json:"temporalSeverity,omitempty"` 214 | ConfidentialityRequirement string `json:"confidentialityRequirement,omitempty"` 215 | IntegrityRequirement string `json:"integrityRequirement,omitempty"` 216 | AvailabilityRequirement string `json:"availabilityRequirement,omitempty"` 217 | ModifiedAttackVector string `json:"modifiedAttackVector,omitempty"` 218 | ModifiedAttackComplexity string `json:"modifiedAttackComplexity,omitempty"` 219 | ModifiedPrivilegesRequired string `json:"modifiedPrivilegesRequired,omitempty"` 220 | ModifiedUserInteraction string `json:"modifiedUserInteraction,omitempty"` 221 | ModifiedScope string `json:"modifiedScope,omitempty"` 222 | ModifiedConfidentialityImpact string `json:"modifiedConfidentialityImpact,omitempty"` 223 | ModifiedIntegrityImpact string `json:"modifiedIntegrityImpact,omitempty"` 224 | ModifiedAvailabilityImpact string `json:"modifiedAvailabilityImpact,omitempty"` 225 | EnvironmentalScore *float64 `json:"environmentalScore,omitempty"` 226 | EnvironmentalSeverity string `json:"environmentalSeverity,omitempty"` 227 | } 228 | 229 | type cvssV20 struct { 230 | Version string `json:"version"` 231 | VectorString string `json:"vectorString"` 232 | AccessVector string `json:"accessVector,omitempty"` 233 | AccessComplexity string `json:"accessComplexity,omitempty"` 234 | Authentication string `json:"authentication,omitempty"` 235 | ConfidentialityImpact string `json:"confidentialityImpact,omitempty"` 236 | IntegrityImpact string `json:"integrityImpact,omitempty"` 237 | AvailabilityImpact string `json:"availabilityImpact,omitempty"` 238 | BaseScore float64 `json:"baseScore"` 239 | Exploitability string `json:"exploitability,omitempty"` 240 | RemediationLevel string `json:"remediationLevel,omitempty"` 241 | ReportConfidence string `json:"reportConfidence,omitempty"` 242 | TemporalScore float64 `json:"temporalScore,omitempty"` 243 | CollateralDamagePotential string `json:"collateralDamagePotential,omitempty"` 244 | TargetDistribution string `json:"targetDistribution,omitempty"` 245 | ConfidentialityRequirement string `json:"confidentialityRequirement,omitempty"` 246 | IntegrityRequirement string `json:"integrityRequirement,omitempty"` 247 | AvailabilityRequirement string `json:"availabilityRequirement,omitempty"` 248 | EnvironmentalScore float64 `json:"environmentalScore,omitempty"` 249 | } 250 | -------------------------------------------------------------------------------- /fetcher/nvd/types.go: -------------------------------------------------------------------------------- 1 | package nvd 2 | 3 | // https://github.com/MaineK00n/vuls-data-update/blob/38e5f8203f3ba90ce565e4a8eb650c17412ea88d/pkg/fetch/nvd/api/cve/types.go#L18 4 | type cve struct { 5 | ID string `json:"id"` 6 | SourceIdentifier string `json:"sourceIdentifier,omitempty"` 7 | VulnStatus string `json:"vulnStatus,omitempty"` 8 | Published string `json:"published"` 9 | LastModified string `json:"lastModified"` 10 | EvaluatorComment string `json:"evaluatorComment,omitempty"` 11 | EvaluatorSolution string `json:"evaluatorSolution,omitempty"` 12 | EvaluatorImpact string `json:"evaluatorImpact,omitempty"` 13 | CISAExploitAdd string `json:"cisaExploitAdd,omitempty"` 14 | CISAActionDue string `json:"cisaActionDue,omitempty"` 15 | CISARequiredAction string `json:"cisaRequiredAction,omitempty"` 16 | CISAVulnerabilityName string `json:"cisaVulnerabilityName,omitempty"` 17 | Descriptions []struct { 18 | Lang string `json:"lang"` 19 | Value string `json:"value"` 20 | } `json:"descriptions"` 21 | References []struct { 22 | Source string `json:"source,omitempty"` 23 | Tags []string `json:"tags,omitempty"` 24 | URL string `json:"url"` 25 | } `json:"references"` 26 | Metrics struct { 27 | CVSSMetricV2 []struct { 28 | Source string `json:"source"` 29 | Type string `json:"type"` 30 | CvssData struct { 31 | Version string `json:"version"` 32 | VectorString string `json:"vectorString"` 33 | AccessVector string `json:"accessVector,omitempty"` 34 | AccessComplexity string `json:"accessComplexity,omitempty"` 35 | Authentication string `json:"authentication,omitempty"` 36 | ConfidentialityImpact string `json:"confidentialityImpact,omitempty"` 37 | IntegrityImpact string `json:"integrityImpact,omitempty"` 38 | AvailabilityImpact string `json:"availabilityImpact,omitempty"` 39 | BaseScore float64 `json:"baseScore"` 40 | Exploitability string `json:"exploitability,omitempty"` 41 | RemediationLevel string `json:"remediationLevel,omitempty"` 42 | ReportConfidence string `json:"reportConfidence,omitempty"` 43 | TemporalScore float64 `json:"temporalScore,omitempty"` 44 | CollateralDamagePotential string `json:"collateralDamagePotential,omitempty"` 45 | TargetDistribution string `json:"targetDistribution,omitempty"` 46 | ConfidentialityRequirement string `json:"confidentialityRequirement,omitempty"` 47 | IntegrityRequirement string `json:"integrityRequirement,omitempty"` 48 | AvailabilityRequirement string `json:"availabilityRequirement,omitempty"` 49 | EnvironmentalScore float64 `json:"environmentalScore,omitempty"` 50 | } `json:"cvssData"` 51 | BaseSeverity string `json:"baseSeverity,omitempty"` 52 | ExploitabilityScore float64 `json:"exploitabilityScore,omitempty"` 53 | ImpactScore float64 `json:"impactScore,omitempty"` 54 | ACInsufInfo bool `json:"acInsufInfo,omitempty"` 55 | ObtainAllPrivilege bool `json:"obtainAllPrivilege,omitempty"` 56 | ObtainUserPrivilege bool `json:"obtainUserPrivilege,omitempty"` 57 | ObtainOtherPrivilege bool `json:"obtainOtherPrivilege,omitempty"` 58 | UserInteractionRequired bool `json:"userInteractionRequired,omitempty"` 59 | } `json:"cvssMetricV2,omitempty"` 60 | CVSSMetricV30 []struct { 61 | Source string `json:"source"` 62 | Type string `json:"type"` 63 | CVSSData struct { 64 | Version string `json:"version"` 65 | VectorString string `json:"vectorString"` 66 | AttackVector string `json:"attackVector,omitempty"` 67 | AttackComplexity string `json:"attackComplexity,omitempty"` 68 | PrivilegesRequired string `json:"privilegesRequired,omitempty"` 69 | UserInteraction string `json:"userInteraction,omitempty"` 70 | Scope string `json:"scope,omitempty"` 71 | ConfidentialityImpact string `json:"confidentialityImpact,omitempty"` 72 | IntegrityImpact string `json:"integrityImpact,omitempty"` 73 | AvailabilityImpact string `json:"availabilityImpact,omitempty"` 74 | BaseScore float64 `json:"baseScore"` 75 | BaseSeverity string `json:"baseSeverity"` 76 | ExploitCodeMaturity string `json:"exploitCodeMaturity,omitempty"` 77 | RemediationLevel string `json:"remediationLevel,omitempty"` 78 | ReportConfidence string `json:"reportConfidence,omitempty"` 79 | TemporalScore *float64 `json:"temporalScore,omitempty"` 80 | TemporalSeverity string `json:"temporalSeverity,omitempty"` 81 | ConfidentialityRequirement string `json:"confidentialityRequirement,omitempty"` 82 | IntegrityRequirement string `json:"integrityRequirement,omitempty"` 83 | AvailabilityRequirement string `json:"availabilityRequirement,omitempty"` 84 | ModifiedAttackVector string `json:"modifiedAttackVector,omitempty"` 85 | ModifiedAttackComplexity string `json:"modifiedAttackComplexity,omitempty"` 86 | ModifiedPrivilegesRequired string `json:"modifiedPrivilegesRequired,omitempty"` 87 | ModifiedUserInteraction string `json:"modifiedUserInteraction,omitempty"` 88 | ModifiedScope string `json:"modifiedScope,omitempty"` 89 | ModifiedConfidentialityImpact string `json:"modifiedConfidentialityImpact,omitempty"` 90 | ModifiedIntegrityImpact string `json:"modifiedIntegrityImpact,omitempty"` 91 | ModifiedAvailabilityImpact string `json:"modifiedAvailabilityImpact,omitempty"` 92 | EnvironmentalScore *float64 `json:"environmentalScore,omitempty"` 93 | EnvironmentalSeverity string `json:"environmentalSeverity,omitempty"` 94 | } `json:"cvssData"` 95 | ExploitabilityScore float64 `json:"exploitabilityScore,omitempty"` 96 | ImpactScore float64 `json:"impactScore,omitempty"` 97 | } `json:"cvssMetricV30,omitempty"` 98 | CVSSMetricV31 []struct { 99 | Source string `json:"source"` 100 | Type string `json:"type"` 101 | CVSSData struct { 102 | Version string `json:"version"` 103 | VectorString string `json:"vectorString"` 104 | AttackVector string `json:"attackVector,omitempty"` 105 | AttackComplexity string `json:"attackComplexity,omitempty"` 106 | PrivilegesRequired string `json:"privilegesRequired,omitempty"` 107 | UserInteraction string `json:"userInteraction,omitempty"` 108 | Scope string `json:"scope,omitempty"` 109 | ConfidentialityImpact string `json:"confidentialityImpact,omitempty"` 110 | IntegrityImpact string `json:"integrityImpact,omitempty"` 111 | AvailabilityImpact string `json:"availabilityImpact,omitempty"` 112 | BaseScore float64 `json:"baseScore"` 113 | BaseSeverity string `json:"baseSeverity"` 114 | ExploitCodeMaturity string `json:"exploitCodeMaturity,omitempty"` 115 | RemediationLevel string `json:"remediationLevel,omitempty"` 116 | ReportConfidence string `json:"reportConfidence,omitempty"` 117 | TemporalScore float64 `json:"temporalScore,omitempty"` 118 | TemporalSeverity string `json:"temporalSeverity,omitempty"` 119 | ConfidentialityRequirement string `json:"confidentialityRequirement,omitempty"` 120 | IntegrityRequirement string `json:"integrityRequirement,omitempty"` 121 | AvailabilityRequirement string `json:"availabilityRequirement,omitempty"` 122 | ModifiedAttackVector string `json:"modifiedAttackVector,omitempty"` 123 | ModifiedAttackComplexity string `json:"modifiedAttackComplexity,omitempty"` 124 | ModifiedPrivilegesRequired string `json:"modifiedPrivilegesRequired,omitempty"` 125 | ModifiedUserInteraction string `json:"modifiedUserInteraction,omitempty"` 126 | ModifiedScope string `json:"modifiedScope,omitempty"` 127 | ModifiedConfidentialityImpact string `json:"modifiedConfidentialityImpact,omitempty"` 128 | ModifiedIntegrityImpact string `json:"modifiedIntegrityImpact,omitempty"` 129 | ModifiedAvailabilityImpact string `json:"modifiedAvailabilityImpact,omitempty"` 130 | EnvironmentalScore float64 `json:"environmentalScore,omitempty"` 131 | EnvironmentalSeverity string `json:"environmentalSeverity,omitempty"` 132 | } `json:"cvssData"` 133 | ExploitabilityScore *float64 `json:"exploitabilityScore,omitempty"` 134 | ImpactScore *float64 `json:"impactScore,omitempty"` 135 | } `json:"cvssMetricV31,omitempty"` 136 | CVSSMetricV40 []struct { 137 | Source string `json:"source"` 138 | Type string `json:"type"` 139 | CVSSData struct { 140 | Version string `json:"version"` 141 | VectorString string `json:"vectorString"` 142 | BaseScore float64 `json:"baseScore"` 143 | BaseSeverity string `json:"baseSeverity"` 144 | AttackVector *string `json:"attackVector,omitempty"` 145 | AttackComplexity *string `json:"attackComplexity,omitempty"` 146 | AttackRequirements *string `json:"attackRequirements,omitempty"` 147 | PrivilegesRequired *string `json:"privilegesRequired,omitempty"` 148 | UserInteraction *string `json:"userInteraction,omitempty"` 149 | VulnerableSystemConfidentiality *string `json:"vulnerableSystemConfidentiality,omitempty"` // schema property: vulnConfidentialityImpact 150 | VulnerableSystemIntegrity *string `json:"vulnerableSystemIntegrity,omitempty"` // schema property: vulnIntegrityImpact 151 | VulnerableSystemAvailability *string `json:"vulnerableSystemAvailability,omitempty"` // schema property: vulnAvailabilityImpact 152 | SubsequentSystemConfidentiality *string `json:"subsequentSystemConfidentiality,omitempty"` // schema property: subConfidentialityImpact 153 | SubsequentSystemIntegrity *string `json:"subsequentSystemIntegrity,omitempty"` // schema property: subIntegrityImpact 154 | SubsequentSystemAvailability *string `json:"subsequentSystemAvailability,omitempty"` // schema property: subAvailabilityImpact 155 | ExploitMaturity *string `json:"exploitMaturity,omitempty"` 156 | ConfidentialityRequirement *string `json:"confidentialityRequirements,omitempty"` 157 | IntegrityRequirement *string `json:"integrityRequirements,omitempty"` 158 | AvailabilityRequirement *string `json:"availabilityRequirements,omitempty"` 159 | ModifiedAttackVector *string `json:"modifiedAttackVector,omitempty"` 160 | ModifiedAttackComplexity *string `json:"modifiedAttackComplexity,omitempty"` 161 | ModifiedAttackRequirements *string `json:"modifiedAttackRequirements,omitempty"` 162 | ModifiedPrivilegesRequired *string `json:"modifiedPrivilegesRequired,omitempty"` 163 | ModifiedUserInteraction *string `json:"modifiedUserInteraction,omitempty"` 164 | ModifiedVulnerableSystemConfidentiality *string `json:"modifiedVulnerableSystemConfidentiality,omitempty"` // schema property: modifiedVulnConfidentialityImpact 165 | ModifiedVulnerableSystemIntegrity *string `json:"modifiedVulnerableSystemIntegrity,omitempty"` // schema property: modifiedVulnIntegrityImpact 166 | ModifiedVulnerableSystemAvailability *string `json:"modifiedVulnerableSystemAvailability,omitempty"` // schema property: modifiedVulnAvailabilityImpact 167 | ModifiedSubsequentSystemConfidentiality *string `json:"modifiedSubsequentSystemConfidentiality,omitempty"` // schema property: modifiedSubConfidentialityImpact 168 | ModifiedSubsequentSystemIntegrity *string `json:"modifiedSubsequentSystemIntegrity,omitempty"` // schema property: modifiedSubIntegrityImpact 169 | ModifiedSubsequentSystemAvailability *string `json:"modifiedSubsequentSystemAvailability,omitempty"` // schema property: modifiedSubAvailabilityImpact 170 | Safety *string `json:"safety,omitempty"` // schema property: Safety 171 | Automatable *string `json:"automatable,omitempty"` // schema property: Automatable 172 | ProviderUrgency *string `json:"providerUrgency,omitempty"` 173 | Recovery *string `json:"recovery,omitempty"` // schema property: Recovery 174 | ValueDensity *string `json:"valueDensity,omitempty"` 175 | VulnerabilityResponseEffort *string `json:"vulnerabilityResponseEffort,omitempty"` 176 | ThreatScore *float64 `json:"threatScore,omitempty"` 177 | ThreatSeverity *string `json:"threatSeverity,omitempty"` 178 | EnvironmentalScore *float64 `json:"environmentalScore,omitempty"` 179 | EnvironmentalSeverity *string `json:"environmentalSeverity,omitempty"` 180 | } `json:"cvssData"` 181 | } `json:"cvssMetricV40,omitempty"` 182 | } `json:"metrics,omitempty"` 183 | Weaknesses []struct { 184 | Source string `json:"source"` 185 | Type string `json:"type"` 186 | Description []struct { 187 | Lang string `json:"lang"` 188 | Value string `json:"value"` 189 | } `json:"description"` 190 | } `json:"weaknesses,omitempty"` 191 | Configurations []struct { 192 | Operator string `json:"operator,omitempty"` 193 | Negate bool `json:"negate,omitempty"` 194 | Nodes []struct { 195 | Operator string `json:"operator"` 196 | Negate bool `json:"negate,omitempty"` 197 | CPEMatch []struct { 198 | Vulnerable bool `json:"vulnerable"` 199 | Criteria string `json:"criteria"` 200 | MatchCriteriaID string `json:"matchCriteriaId"` 201 | VersionStartExcluding string `json:"versionStartExcluding,omitempty"` 202 | VersionStartIncluding string `json:"versionStartIncluding,omitempty"` 203 | VersionEndExcluding string `json:"versionEndExcluding,omitempty"` 204 | VersionEndIncluding string `json:"versionEndIncluding,omitempty"` 205 | } `json:"cpeMatch"` 206 | } `json:"nodes"` 207 | } `json:"configurations,omitempty"` 208 | VendorComments []struct { 209 | Organization string `json:"organization"` 210 | Comment string `json:"comment"` 211 | LastModified string `json:"lastModified"` 212 | } `json:"vendorComments,omitempty"` 213 | } 214 | --------------------------------------------------------------------------------