a948784f3a
Co-authored-by: chenk <hen.keinan@gmail.com>
284 lines
7.6 KiB
Go
284 lines
7.6 KiB
Go
package k8s
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
|
|
version "github.com/aquasecurity/go-version/pkg/version"
|
|
)
|
|
|
|
type MitreCVE struct {
|
|
CveMetadata CveMetadata
|
|
Containers Containers
|
|
}
|
|
|
|
type Containers struct {
|
|
Cna struct {
|
|
Affected []struct {
|
|
Product string
|
|
Vendor string
|
|
Versions []*MitreVersion
|
|
}
|
|
Descriptions []Descriptions
|
|
Metrics []struct {
|
|
CvssV3_1 struct {
|
|
VectorString string
|
|
}
|
|
CvssV3_0 struct {
|
|
VectorString string
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
type MitreVersion struct {
|
|
Status string
|
|
Version string
|
|
LessThanOrEqual string
|
|
LessThan string
|
|
VersionType string
|
|
}
|
|
|
|
type CveMetadata struct {
|
|
CveId string
|
|
}
|
|
|
|
type Descriptions struct {
|
|
Lang string
|
|
Value string
|
|
}
|
|
|
|
type Cve struct {
|
|
Description string
|
|
versions []*Version
|
|
CvssV3 Cvssv3
|
|
Package string
|
|
}
|
|
|
|
type Cvssv3 struct {
|
|
Vector string
|
|
Type string
|
|
}
|
|
|
|
type Version struct {
|
|
Introduced string `json:"introduced,omitempty"`
|
|
Fixed string `json:"fixed,omitempty"`
|
|
LastAffected string `json:"last_affected,omitempty"`
|
|
FixedIndex int `json:"-"`
|
|
}
|
|
|
|
func parseMitreCve(externalURL, mitreURL, cveID string) (*Cve, error) {
|
|
if !strings.HasPrefix(externalURL, cveList) {
|
|
// if no external url provided, return empty vulnerability to be skipped
|
|
return &Cve{}, nil
|
|
}
|
|
response, err := http.Get(fmt.Sprintf("%s/%s", mitreURL, cveID))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer response.Body.Close()
|
|
var cve MitreCVE
|
|
if err = json.NewDecoder(response.Body).Decode(&cve); err != nil {
|
|
return nil, err
|
|
}
|
|
vulnerableVersions := make([]*Version, 0)
|
|
var component string
|
|
var requireMerge bool
|
|
for _, a := range cve.Containers.Cna.Affected {
|
|
if len(component) == 0 {
|
|
component = strings.ToLower(a.Product)
|
|
}
|
|
for _, sv := range a.Versions {
|
|
if sv.Status != "affected" {
|
|
continue
|
|
}
|
|
var introduce, lastAffected, fixed string
|
|
v, ok := sanitizedVersions(sv)
|
|
if !ok {
|
|
continue
|
|
}
|
|
switch {
|
|
case len(v.LessThan) > 0:
|
|
if strings.HasSuffix(v.LessThan, ".0") {
|
|
v.Version = "0"
|
|
}
|
|
introduce, fixed = updateVersions(v.LessThan, v.Version)
|
|
case len(v.LessThanOrEqual) > 0:
|
|
introduce, lastAffected = updateVersions(v.LessThanOrEqual, v.Version)
|
|
case minorVersion(v.Version):
|
|
requireMerge = true
|
|
introduce = v.Version
|
|
default:
|
|
introduce, lastAffected = extractRangeVersions(v.Version)
|
|
}
|
|
vulnerableVersions = append(vulnerableVersions, &Version{
|
|
Introduced: introduce,
|
|
Fixed: fixed,
|
|
LastAffected: lastAffected,
|
|
})
|
|
}
|
|
}
|
|
if requireMerge {
|
|
vulnerableVersions, err = mergeVersionRange(vulnerableVersions)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
vector, vectorType := getMetrics(cve)
|
|
description := getDescription(cve.Containers.Cna.Descriptions)
|
|
return &Cve{
|
|
Description: description,
|
|
CvssV3: Cvssv3{
|
|
Vector: vector,
|
|
Type: vectorType,
|
|
},
|
|
Package: getComponentFromDescription(description, component),
|
|
versions: vulnerableVersions,
|
|
}, nil
|
|
}
|
|
|
|
func sanitizedVersions(v *MitreVersion) (*MitreVersion, bool) {
|
|
if strings.Contains(v.Version, "n/a") && len(v.LessThan) == 0 && len(v.LessThanOrEqual) == 0 {
|
|
return v, false
|
|
}
|
|
if (v.LessThanOrEqual == "unspecified" || v.LessThan == "unspecified") && len(v.Version) > 0 {
|
|
return v, false
|
|
}
|
|
// example https://cveawg.mitre.org/api/cve/CVE-2023-2727
|
|
if len(v.LessThanOrEqual) > 0 && v.LessThanOrEqual == "<=" {
|
|
v.LessThanOrEqual = v.Version
|
|
} else if len(v.LessThan) > 0 {
|
|
switch {
|
|
// example https://cveawg.mitre.org/api/cve/CVE-2019-11244
|
|
case strings.HasSuffix(strings.TrimSpace(v.LessThan), "*"):
|
|
v.Version = strings.TrimSpace(strings.ReplaceAll(v.LessThan, "*", ""))
|
|
v.LessThan = ""
|
|
}
|
|
} else if len(v.Version) > 0 {
|
|
switch {
|
|
// example https://cveawg.mitre.org/api/cve/CVE-2020-8566
|
|
case strings.HasPrefix(v.Version, "< "):
|
|
v.LessThan = strings.TrimPrefix(v.Version, "< ")
|
|
// example https://cveawg.mitre.org/api/cve/CVE-2020-8565
|
|
case strings.HasPrefix(v.Version, "<= "):
|
|
v.LessThanOrEqual = strings.TrimPrefix(v.Version, "<= ")
|
|
//example https://cveawg.mitre.org/api/cve/CVE-2019-11247
|
|
case strings.HasPrefix(strings.TrimSpace(v.Version), "prior to"):
|
|
priorToVersion := strings.TrimSpace(strings.TrimPrefix(v.Version, "prior to"))
|
|
if minorVersion(priorToVersion) {
|
|
priorToVersion = priorToVersion + ".0"
|
|
v.Version = priorToVersion
|
|
}
|
|
v.LessThan = priorToVersion
|
|
// all version is vulnerable : https://cveawg.mitre.org/api/cve/CVE-2017-1002101
|
|
case strings.HasSuffix(strings.TrimSpace(v.Version), ".x"):
|
|
v.Version = strings.TrimSpace(strings.ReplaceAll(v.Version, ".x", ""))
|
|
}
|
|
}
|
|
return &MitreVersion{
|
|
Version: trimString(v.Version, []string{"v", "V"}),
|
|
LessThanOrEqual: trimString(v.LessThanOrEqual, []string{"v", "V"}),
|
|
LessThan: trimString(v.LessThan, []string{"v", "V"}),
|
|
}, true
|
|
}
|
|
|
|
func getDescription(descriptions []Descriptions) string {
|
|
for _, d := range descriptions {
|
|
if d.Lang == "en" {
|
|
return d.Value
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
type byVersion []*Version
|
|
|
|
func (s byVersion) Len() int {
|
|
return len(s)
|
|
}
|
|
|
|
func (s byVersion) Swap(i, j int) {
|
|
s[i], s[j] = s[j], s[i]
|
|
}
|
|
|
|
func (s byVersion) Less(i, j int) bool {
|
|
v1, err := version.Parse(s[i].Introduced)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
v2, err := version.Parse(s[j].Introduced)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
return v1.LessThan(v2)
|
|
}
|
|
|
|
func mergeVersionRange(affectedVersions []*Version) ([]*Version, error) {
|
|
// this special handling is made to handle to case of conceutive vulnable minor versions.
|
|
// example: vulnerable 1.3, 1.4, 1.5, 1.6 and prior to versions 1.7.14, 1.8.9 will be form as follow:
|
|
// Introduced: 1.3.0 Fixed: 1.7.14
|
|
// Introduced: 1.8.0 Fixed: 1.8.9
|
|
// example: https://cveawg.mitre.org/api/cve/CVE-2019-11249
|
|
sort.Sort(byVersion(affectedVersions))
|
|
newAffectedVesion := make([]*Version, 0)
|
|
minorVersions := make([]*Version, 0)
|
|
for _, av := range affectedVersions {
|
|
if minorVersion(av.Introduced) {
|
|
minorVersions = append(minorVersions, av)
|
|
} else if strings.Count(av.Introduced, ".") > 1 && len(minorVersions) > 0 {
|
|
newAffectedVesion = append(newAffectedVesion, &Version{
|
|
Introduced: fmt.Sprintf("%s.0", minorVersions[0].Introduced),
|
|
LastAffected: av.LastAffected,
|
|
Fixed: av.Fixed,
|
|
})
|
|
minorVersions = minorVersions[:0]
|
|
continue
|
|
}
|
|
if len(minorVersions) == 0 {
|
|
newAffectedVesion = append(newAffectedVesion, av)
|
|
}
|
|
}
|
|
|
|
// this special handling is made to handle to case of consecutive vulnable minor versions, wheen there is no fixed version is provided.
|
|
// example: vulnerable 1.3, 1.4, 1.5, 1.6 will be form as follow:
|
|
// Introduced: 1.3.0 Fixed: 1.7.0
|
|
if len(minorVersions) > 0 {
|
|
currentVersion := fmt.Sprintf("%s.0", minorVersions[len(minorVersions)-1].Introduced)
|
|
versionParts, err := versionParts(currentVersion)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
fixed := fmt.Sprintf("%d.%d.%d", versionParts[0], versionParts[1]+1, versionParts[2])
|
|
newAffectedVesion = append(newAffectedVesion, &Version{Introduced: fmt.Sprintf("%s.0", minorVersions[0].Introduced), Fixed: fixed})
|
|
}
|
|
return newAffectedVesion, nil
|
|
}
|
|
|
|
func getMetrics(cve MitreCVE) (string, string) {
|
|
var vectorString string
|
|
for _, metric := range cve.Containers.Cna.Metrics {
|
|
vectorString = metric.CvssV3_0.VectorString
|
|
if len(vectorString) == 0 {
|
|
vectorString = metric.CvssV3_1.VectorString
|
|
}
|
|
}
|
|
return vectorString, "CVSS_V3"
|
|
}
|
|
|
|
func versionParts(version string) ([]int, error) {
|
|
parts := strings.Split(version, ".")
|
|
intParts := make([]int, 0)
|
|
for _, p := range parts {
|
|
i, err := strconv.Atoi(p)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
intParts = append(intParts, i)
|
|
}
|
|
return intParts, nil
|
|
}
|