-
Notifications
You must be signed in to change notification settings - Fork 505
/
Copy pathcve.go
179 lines (147 loc) · 5.57 KB
/
cve.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
/*
Copyright 2021 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cve
import (
"errors"
"fmt"
"regexp"
"strings"
gocvss20 "github.com/pandatix/go-cvss/20"
gocvss30 "github.com/pandatix/go-cvss/30"
gocvss31 "github.com/pandatix/go-cvss/31"
gocvss40 "github.com/pandatix/go-cvss/40"
)
// CVE Information of a linked CVE vulnerability.
type CVE struct {
ID string `json:"id" yaml:"id"` // CVE ID, eg CVE-2019-1010260
Title string `json:"title" yaml:"title"` // Title of the vulnerability
Description string `json:"description" yaml:"description"` // Description text of the vulnerability
TrackingIssue string `json:"issue" yaml:"issue"` // Link to the vulnerability tracking issue (url, optional)
CVSSVector string `json:"vector" yaml:"vector"` // Full CVSS vector string, CVSS:3.1/AV:N/AC:H/PR:H/UI:R/S:U/C:H/I:H/A:H
CVSSScore float32 `json:"score" yaml:"score"` // Numeric CVSS score (eg 6.2)
CVSSRating string `json:"rating" yaml:"rating"` // Severity bucket (eg Medium)
CalcLink string `json:"calclink,omitempty" yaml:"calclink,omitempty"` // Link to the CVE calculator (automatic)
LinkedPRs []int `json:"pullrequests"` // List of linked PRs (to remove them from the release notes doc)
}
// ReadRawInterface populates the CVE data struct from the raw array
// as returned by the YAML parser.
func (cve *CVE) ReadRawInterface(cvedata interface{}) error {
if val, ok := cvedata.(map[interface{}]interface{})["id"].(string); ok {
cve.ID = val
}
if val, ok := cvedata.(map[interface{}]interface{})["title"].(string); ok {
cve.Title = val
}
if val, ok := cvedata.(map[interface{}]interface{})["issue"].(string); ok {
cve.TrackingIssue = val
}
if val, ok := cvedata.(map[interface{}]interface{})["vector"].(string); ok {
cve.CVSSVector = val
}
if val, ok := cvedata.(map[interface{}]interface{})["score"].(float64); ok {
cve.CVSSScore = float32(val)
}
if val, ok := cvedata.(map[interface{}]interface{})["rating"].(string); ok {
cve.CVSSRating = val
}
if val, ok := cvedata.(map[interface{}]interface{})["description"].(string); ok {
cve.Description = val
}
// Linked PRs is a list of the PR IDs
if val, ok := cvedata.(map[interface{}]interface{})["linkedPRs"].([]interface{}); ok {
cve.LinkedPRs = []int{}
for _, prid := range val {
if prid, ok := prid.(int); ok {
cve.LinkedPRs = append(cve.LinkedPRs, prid)
}
}
}
return nil
}
// Validate checks the data defined in a CVE map is complete and valid.
func (cve *CVE) Validate() (err error) {
// Verify that rating is defined and a known string
if cve.CVSSRating == "" {
return errors.New("missing CVSS rating from CVE data")
}
// Check rating is a valid string
if _, ok := map[string]bool{
"None": true, "Low": true, "Medium": true, "High": true, "Critical": true,
}[cve.CVSSRating]; !ok {
return errors.New("invalid CVSS rating")
}
// Check vector string is not empty
if cve.CVSSVector == "" {
return errors.New("string CVSS vector missing from CVE data")
}
switch {
default: // CVSS v2.0 has no prefix
_, err := gocvss20.ParseVector(cve.CVSSVector)
if err != nil {
return fmt.Errorf("parsing CVSS vector string: %w", err)
}
// FIRST ORG has no calculator for CVSS v2.0
case strings.HasPrefix(cve.CVSSVector, "CVSS:3.0"):
_, err := gocvss30.ParseVector(cve.CVSSVector)
if err != nil {
return fmt.Errorf("parsing CVSS vector string: %w", err)
}
cve.CalcLink = fmt.Sprintf(
"https://www.first.org/cvss/calculator/3.0#%s", cve.CVSSVector,
)
case strings.HasPrefix(cve.CVSSVector, "CVSS:3.1"):
_, err := gocvss31.ParseVector(cve.CVSSVector)
if err != nil {
return fmt.Errorf("parsing CVSS vector string: %w", err)
}
cve.CalcLink = fmt.Sprintf(
"https://www.first.org/cvss/calculator/3.1#%s", cve.CVSSVector,
)
case strings.HasPrefix(cve.CVSSVector, "CVSS:4.0"):
_, err := gocvss40.ParseVector(cve.CVSSVector)
if err != nil {
return fmt.Errorf("parsing CVSS vector string: %w", err)
}
cve.CalcLink = fmt.Sprintf(
"https://www.first.org/cvss/calculator/4.0#%s", cve.CVSSVector,
)
}
if cve.CVSSScore == 0 {
return errors.New("missing CVSS score from CVE data")
}
if cve.CVSSScore < 0 || cve.CVSSScore > 10 {
return errors.New("out of range CVSS score, should be 0.0 - 10.0")
}
if err := ValidateID(cve.ID); err != nil {
return fmt.Errorf("checking CVE ID: %w", err)
}
// Title and description must not be empty
if cve.Title == "" {
return errors.New("title missing from CVE data")
}
if cve.Description == "" {
return errors.New("missing CVE description from CVE data")
}
return nil
}
// ValidateID checks if a CVE IS string is valid.
func ValidateID(cveID string) error {
if cveID == "" {
return errors.New("empty CVE ID string")
}
// Verify that the CVE ID is well formed
if !regexp.MustCompile(CVEIDRegExp).MatchString(cveID) {
return errors.New("not well formed CVS ID")
}
return nil
}