feat: update-trivy (#1316)

* feat: update-trivy

* add v2 parser

* implement v2

* refactor

* feat: add show version to future-vuls

* add test case for v2

* trivy v0.20.0

* support --list-all-pkgs

* fix lint err

* add test case for jar

* add a test case for gemspec in container

* remove v1 parser and change Library struct

* Changed the field name in the model struct LibraryScanner

* add comment

* fix comment

* fix comment

* chore

* add struct tag
This commit is contained in:
Kota Kanbe
2021-10-08 17:22:06 +09:00
committed by GitHub
parent d780a73297
commit aac5ef1438
14 changed files with 1167 additions and 5895 deletions

View File

@@ -81,6 +81,14 @@ func main() {
return
},
}
var cmdVersion = &cobra.Command{
Use: "version",
Short: "Show version",
Long: "Show version",
Run: func(cmd *cobra.Command, args []string) {
fmt.Printf("future-vuls-%s-%s\n", config.Version, config.Revision)
},
}
cmdFvulsUploader.PersistentFlags().StringVar(&serverUUID, "uuid", "", "server uuid. ENV: VULS_SERVER_UUID")
cmdFvulsUploader.PersistentFlags().StringVar(&configFile, "config", "", "config file (default is $HOME/.cobra.yaml)")
cmdFvulsUploader.PersistentFlags().BoolVarP(&stdIn, "stdin", "s", false, "input from stdin. ENV: VULS_STDIN")
@@ -92,6 +100,7 @@ func main() {
var rootCmd = &cobra.Command{Use: "future-vuls"}
rootCmd.AddCommand(cmdFvulsUploader)
rootCmd.AddCommand(cmdVersion)
if err = rootCmd.Execute(); err != nil {
fmt.Println("Failed to execute command", err)
}

View File

@@ -9,8 +9,8 @@ import (
"os"
"path/filepath"
"github.com/future-architect/vuls/config"
"github.com/future-architect/vuls/contrib/trivy/parser"
"github.com/future-architect/vuls/models"
"github.com/spf13/cobra"
)
@@ -34,45 +34,55 @@ func main() {
reader := bufio.NewReader(os.Stdin)
buf := new(bytes.Buffer)
if _, err = buf.ReadFrom(reader); err != nil {
fmt.Printf("Failed to read file. err: %+v\n", err)
os.Exit(1)
return
}
trivyJSON = buf.Bytes()
} else {
if trivyJSON, err = ioutil.ReadFile(jsonFilePath); err != nil {
fmt.Println("Failed to read file", err)
fmt.Printf("Failed to read file. err: %+v\n", err)
os.Exit(1)
return
}
}
scanResult := &models.ScanResult{
JSONVersion: models.JSONVersion,
ScannedCves: models.VulnInfos{},
}
if scanResult, err = parser.Parse(trivyJSON, scanResult); err != nil {
fmt.Println("Failed to execute command", err)
parser, err := parser.NewParser(trivyJSON)
if err != nil {
fmt.Printf("Failed to new parser. err: %+v\n", err)
os.Exit(1)
}
scanResult, err := parser.Parse(trivyJSON)
if err != nil {
fmt.Printf("Failed to parse. err: %+v\n", err)
os.Exit(1)
return
}
var resultJSON []byte
if resultJSON, err = json.MarshalIndent(scanResult, "", " "); err != nil {
fmt.Println("Failed to create json", err)
fmt.Printf("Failed to create json. err: %+v\n", err)
os.Exit(1)
return
}
fmt.Println(string(resultJSON))
return
},
}
var cmdVersion = &cobra.Command{
Use: "version",
Short: "Show version",
Long: "Show version",
Run: func(cmd *cobra.Command, args []string) {
fmt.Printf("trivy-to-vuls-%s-%s\n", config.Version, config.Revision)
},
}
cmdTrivyToVuls.Flags().BoolVarP(&stdIn, "stdin", "s", false, "input from stdin")
cmdTrivyToVuls.Flags().StringVarP(&jsonDir, "trivy-json-dir", "d", "./", "trivy json dir")
cmdTrivyToVuls.Flags().StringVarP(&jsonFileName, "trivy-json-file-name", "f", "results.json", "trivy json file name")
var rootCmd = &cobra.Command{Use: "trivy-to-vuls"}
rootCmd.AddCommand(cmdTrivyToVuls)
rootCmd.AddCommand(cmdVersion)
if err = rootCmd.Execute(); err != nil {
fmt.Println("Failed to execute command", err)
fmt.Printf("Failed to execute command. err: %+v\n", err)
os.Exit(1)
}
os.Exit(0)
}

View File

@@ -2,212 +2,32 @@ package parser
import (
"encoding/json"
"sort"
"time"
"github.com/aquasecurity/fanal/analyzer/os"
ftypes "github.com/aquasecurity/fanal/types"
"github.com/aquasecurity/trivy/pkg/report"
"github.com/aquasecurity/trivy/pkg/types"
"github.com/future-architect/vuls/constant"
v2 "github.com/future-architect/vuls/contrib/trivy/parser/v2"
"github.com/future-architect/vuls/models"
"golang.org/x/xerrors"
)
// Parse :
func Parse(vulnJSON []byte, scanResult *models.ScanResult) (result *models.ScanResult, err error) {
var trivyResults report.Results
if err = json.Unmarshal(vulnJSON, &trivyResults); err != nil {
return nil, err
}
pkgs := models.Packages{}
vulnInfos := models.VulnInfos{}
uniqueLibraryScannerPaths := map[string]models.LibraryScanner{}
for _, trivyResult := range trivyResults {
setScanResultMeta(scanResult, &trivyResult)
for _, vuln := range trivyResult.Vulnerabilities {
if _, ok := vulnInfos[vuln.VulnerabilityID]; !ok {
vulnInfos[vuln.VulnerabilityID] = models.VulnInfo{
CveID: vuln.VulnerabilityID,
Confidences: models.Confidences{
{
Score: 100,
DetectionMethod: models.TrivyMatchStr,
},
},
AffectedPackages: models.PackageFixStatuses{},
CveContents: models.CveContents{},
LibraryFixedIns: models.LibraryFixedIns{},
// VulnType : "",
}
}
vulnInfo := vulnInfos[vuln.VulnerabilityID]
var notFixedYet bool
fixState := ""
if len(vuln.FixedVersion) == 0 {
notFixedYet = true
fixState = "Affected"
}
var references models.References
for _, reference := range vuln.References {
references = append(references, models.Reference{
Source: "trivy",
Link: reference,
})
}
sort.Slice(references, func(i, j int) bool {
return references[i].Link < references[j].Link
})
var published time.Time
if vuln.PublishedDate != nil {
published = *vuln.PublishedDate
}
var lastModified time.Time
if vuln.LastModifiedDate != nil {
lastModified = *vuln.LastModifiedDate
}
vulnInfo.CveContents = models.CveContents{
models.Trivy: []models.CveContent{{
Cvss3Severity: vuln.Severity,
References: references,
Title: vuln.Title,
Summary: vuln.Description,
Published: published,
LastModified: lastModified,
}},
}
// do only if image type is Vuln
if isTrivySupportedOS(trivyResult.Type) {
pkgs[vuln.PkgName] = models.Package{
Name: vuln.PkgName,
Version: vuln.InstalledVersion,
}
vulnInfo.AffectedPackages = append(vulnInfo.AffectedPackages, models.PackageFixStatus{
Name: vuln.PkgName,
NotFixedYet: notFixedYet,
FixState: fixState,
FixedIn: vuln.FixedVersion,
})
} else {
vulnInfo.LibraryFixedIns = append(vulnInfo.LibraryFixedIns, models.LibraryFixedIn{
Key: trivyResult.Type,
Name: vuln.PkgName,
Path: trivyResult.Target,
FixedIn: vuln.FixedVersion,
})
libScanner := uniqueLibraryScannerPaths[trivyResult.Target]
libScanner.Type = trivyResult.Type
libScanner.Libs = append(libScanner.Libs, types.Library{
Name: vuln.PkgName,
Version: vuln.InstalledVersion,
})
uniqueLibraryScannerPaths[trivyResult.Target] = libScanner
}
vulnInfos[vuln.VulnerabilityID] = vulnInfo
}
}
// flatten and unique libraries
libraryScanners := make([]models.LibraryScanner, 0, len(uniqueLibraryScannerPaths))
for path, v := range uniqueLibraryScannerPaths {
uniqueLibrary := map[string]types.Library{}
for _, lib := range v.Libs {
uniqueLibrary[lib.Name+lib.Version] = lib
}
var libraries []types.Library
for _, library := range uniqueLibrary {
libraries = append(libraries, library)
}
sort.Slice(libraries, func(i, j int) bool {
return libraries[i].Name < libraries[j].Name
})
libscanner := models.LibraryScanner{
Type: v.Type,
Path: path,
Libs: libraries,
}
libraryScanners = append(libraryScanners, libscanner)
}
sort.Slice(libraryScanners, func(i, j int) bool {
return libraryScanners[i].Path < libraryScanners[j].Path
})
scanResult.ScannedCves = vulnInfos
scanResult.Packages = pkgs
scanResult.LibraryScanners = libraryScanners
return scanResult, nil
// Parser is a parser interface
type Parser interface {
Parse(vulnJSON []byte) (result *models.ScanResult, err error)
}
const trivyTarget = "trivy-target"
func setScanResultMeta(scanResult *models.ScanResult, trivyResult *report.Result) {
if isTrivySupportedOS(trivyResult.Type) {
scanResult.Family = trivyResult.Type
scanResult.ServerName = trivyResult.Target
scanResult.Optional = map[string]interface{}{
trivyTarget: trivyResult.Target,
}
} else if isTrivySupportedLib(trivyResult.Type) {
if scanResult.Family == "" {
scanResult.Family = constant.ServerTypePseudo
}
if scanResult.ServerName == "" {
scanResult.ServerName = "library scan by trivy"
}
if _, ok := scanResult.Optional[trivyTarget]; !ok {
scanResult.Optional = map[string]interface{}{
trivyTarget: trivyResult.Target,
}
}
}
scanResult.ScannedAt = time.Now()
scanResult.ScannedBy = "trivy"
scanResult.ScannedVia = "trivy"
// Report is used for judgeing the scheme version of trivy
type Report struct {
SchemaVersion int `json:",omitempty"`
}
// isTrivySupportedOS :
func isTrivySupportedOS(family string) bool {
supportedFamilies := map[string]interface{}{
os.RedHat: struct{}{},
os.Debian: struct{}{},
os.Ubuntu: struct{}{},
os.CentOS: struct{}{},
os.Fedora: struct{}{},
os.Amazon: struct{}{},
os.Oracle: struct{}{},
os.Windows: struct{}{},
os.OpenSUSE: struct{}{},
os.OpenSUSELeap: struct{}{},
os.OpenSUSETumbleweed: struct{}{},
os.SLES: struct{}{},
os.Photon: struct{}{},
os.Alpine: struct{}{},
// NewParser make a parser for the schema version of trivy
func NewParser(vulnJSON []byte) (Parser, error) {
r := Report{}
if err := json.Unmarshal(vulnJSON, &r); err != nil {
return nil, xerrors.Errorf("Failed to parse JSON. Please use the latest version of trivy, trivy-to-vuls and future-vuls")
}
_, ok := supportedFamilies[family]
return ok
}
func isTrivySupportedLib(typestr string) bool {
supportedLibs := map[string]interface{}{
ftypes.Bundler: struct{}{},
ftypes.Cargo: struct{}{},
ftypes.Composer: struct{}{},
ftypes.Npm: struct{}{},
ftypes.NuGet: struct{}{},
ftypes.Pip: struct{}{},
ftypes.Pipenv: struct{}{},
ftypes.Poetry: struct{}{},
ftypes.Yarn: struct{}{},
ftypes.Jar: struct{}{},
ftypes.GoBinary: struct{}{},
ftypes.GoMod: struct{}{},
switch r.SchemaVersion {
case 2:
return v2.ParserV2{}, nil
default:
return nil, xerrors.Errorf("Failed to parse trivy json. SchemeVersion %d is not supported yet. Please contact support", r.SchemaVersion)
}
_, ok := supportedLibs[typestr]
return ok
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,60 @@
package v2
import (
"encoding/json"
"time"
"github.com/aquasecurity/trivy/pkg/report"
"github.com/future-architect/vuls/constant"
"github.com/future-architect/vuls/contrib/trivy/pkg"
"github.com/future-architect/vuls/models"
)
// ParserV2 is a parser for scheme v2
type ParserV2 struct {
}
// Parse trivy's JSON and convert to the Vuls struct
func (p ParserV2) Parse(vulnJSON []byte) (result *models.ScanResult, err error) {
var report report.Report
if err = json.Unmarshal(vulnJSON, &report); err != nil {
return nil, err
}
scanResult, err := pkg.Convert(report.Results)
if err != nil {
return nil, err
}
setScanResultMeta(scanResult, &report)
return scanResult, nil
}
func setScanResultMeta(scanResult *models.ScanResult, report *report.Report) {
for _, r := range report.Results {
const trivyTarget = "trivy-target"
if pkg.IsTrivySupportedOS(r.Type) {
scanResult.Family = r.Type
scanResult.ServerName = r.Target
scanResult.Optional = map[string]interface{}{
trivyTarget: r.Target,
}
} else if pkg.IsTrivySupportedLib(r.Type) {
if scanResult.Family == "" {
scanResult.Family = constant.ServerTypePseudo
}
if scanResult.ServerName == "" {
scanResult.ServerName = "library scan by trivy"
}
if _, ok := scanResult.Optional[trivyTarget]; !ok {
scanResult.Optional = map[string]interface{}{
trivyTarget: r.Target,
}
}
}
scanResult.ScannedAt = time.Now()
scanResult.ScannedBy = "trivy"
scanResult.ScannedVia = "trivy"
}
}

View File

@@ -0,0 +1,725 @@
package v2
import (
"testing"
"github.com/d4l3k/messagediff"
"github.com/future-architect/vuls/models"
)
func TestParse(t *testing.T) {
cases := map[string]struct {
vulnJSON []byte
expected *models.ScanResult
}{
"image redis": {
vulnJSON: redisTrivy,
expected: redisSR,
},
"image struts": {
vulnJSON: strutsTrivy,
expected: strutsSR,
},
"image osAndLib": {
vulnJSON: osAndLibTrivy,
expected: osAndLibSR,
},
}
for testcase, v := range cases {
actual, err := ParserV2{}.Parse(v.vulnJSON)
if err != nil {
t.Errorf("%s", err)
}
diff, equal := messagediff.PrettyDiff(
v.expected,
actual,
messagediff.IgnoreStructField("ScannedAt"),
messagediff.IgnoreStructField("Title"),
messagediff.IgnoreStructField("Summary"),
messagediff.IgnoreStructField("LastModified"),
messagediff.IgnoreStructField("Published"),
)
if !equal {
t.Errorf("test: %s, diff %s", testcase, diff)
}
}
}
var redisTrivy = []byte(`
{
"SchemaVersion": 2,
"ArtifactName": "redis",
"ArtifactType": "container_image",
"Metadata": {
"OS": {
"Family": "debian",
"Name": "10.10"
},
"ImageID": "sha256:ddcca4b8a6f0367b5de2764dfe76b0a4bfa6d75237932185923705da47004347",
"DiffIDs": [
"sha256:f68ef921efae588b3dd5cc466a1ca9c94c24785f1fa9420bea15ecc2dedbe781",
"sha256:b6fc243eaea74d1a41b242da4c3ec5166db80f38c4d57a10ce8860c00d902ace",
"sha256:ec92e47b7c52dacc26df07ee13e8e81c099b5a5661ccc97b06692a9c9d01e772",
"sha256:4be6d4460d3615186717f21ffc0023b168dce48967d01934bbe31127901d3d5c",
"sha256:992463b683270e164936e9c48fa395d05a7b8b5cc0aa208e4fa81aa9158fcae1",
"sha256:0083597d42d190ddb86c35587a7b196fe18d79382520544b5f715c1e4792b19a"
],
"RepoTags": [
"redis:latest"
],
"RepoDigests": [
"redis@sha256:66ce9bc742609650afc3de7009658473ed601db4e926a5b16d239303383bacad"
],
"ImageConfig": {
"architecture": "amd64",
"container": "fa59f1c2817c9095f8f7272a4ab9b11db0332b33efb3a82c00a3d1fec8763684",
"created": "2021-08-17T14:30:06.550779326Z",
"docker_version": "20.10.7",
"history": [
{
"created": "2021-08-17T01:24:06Z",
"created_by": "/bin/sh -c #(nop) ADD file:87b4e60fe3af680c6815448374365a44e9ea461bc8ade2960b4639c25aed3ba9 in / "
},
{
"created": "2021-08-17T14:30:06Z",
"created_by": "/bin/sh -c #(nop) CMD [\"redis-server\"]",
"empty_layer": true
}
],
"os": "linux",
"rootfs": {
"type": "layers",
"diff_ids": [
"sha256:f68ef921efae588b3dd5cc466a1ca9c94c24785f1fa9420bea15ecc2dedbe781",
"sha256:b6fc243eaea74d1a41b242da4c3ec5166db80f38c4d57a10ce8860c00d902ace",
"sha256:ec92e47b7c52dacc26df07ee13e8e81c099b5a5661ccc97b06692a9c9d01e772",
"sha256:4be6d4460d3615186717f21ffc0023b168dce48967d01934bbe31127901d3d5c",
"sha256:992463b683270e164936e9c48fa395d05a7b8b5cc0aa208e4fa81aa9158fcae1",
"sha256:0083597d42d190ddb86c35587a7b196fe18d79382520544b5f715c1e4792b19a"
]
},
"config": {
"Cmd": [
"redis-server"
],
"Entrypoint": [
"docker-entrypoint.sh"
],
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"GOSU_VERSION=1.12",
"REDIS_VERSION=6.2.5",
"REDIS_DOWNLOAD_URL=http://download.redis.io/releases/redis-6.2.5.tar.gz",
"REDIS_DOWNLOAD_SHA=4b9a75709a1b74b3785e20a6c158cab94cf52298aa381eea947a678a60d551ae"
],
"Image": "sha256:befbd3fc62bffcd0115008969a014faaad07828b2c54b4bcfd2d9fc3aa2508cd",
"Volumes": {
"/data": {}
},
"WorkingDir": "/data"
}
}
},
"Results": [
{
"Target": "redis (debian 10.10)",
"Class": "os-pkgs",
"Type": "debian",
"Packages": [
{
"Name": "adduser",
"Version": "3.118",
"SrcName": "adduser",
"SrcVersion": "3.118",
"Layer": {
"DiffID": "sha256:f68ef921efae588b3dd5cc466a1ca9c94c24785f1fa9420bea15ecc2dedbe781"
}
},
{
"Name": "apt",
"Version": "1.8.2.3",
"SrcName": "apt",
"SrcVersion": "1.8.2.3",
"Layer": {
"DiffID": "sha256:f68ef921efae588b3dd5cc466a1ca9c94c24785f1fa9420bea15ecc2dedbe781"
}
},
{
"Name": "bsdutils",
"Version": "1:2.33.1-0.1",
"SrcName": "util-linux",
"SrcVersion": "2.33.1-0.1",
"Layer": {
"DiffID": "sha256:f68ef921efae588b3dd5cc466a1ca9c94c24785f1fa9420bea15ecc2dedbe781"
}
},
{
"Name": "pkgA",
"Version": "1:2.33.1-0.1",
"SrcName": "util-linux",
"SrcVersion": "2.33.1-0.1",
"Layer": {
"DiffID": "sha256:f68ef921efae588b3dd5cc466a1ca9c94c24785f1fa9420bea15ecc2dedbe781"
}
}
],
"Vulnerabilities": [
{
"VulnerabilityID": "CVE-2011-3374",
"PkgName": "apt",
"InstalledVersion": "1.8.2.3",
"Layer": {
"DiffID": "sha256:f68ef921efae588b3dd5cc466a1ca9c94c24785f1fa9420bea15ecc2dedbe781"
},
"SeveritySource": "debian",
"PrimaryURL": "https://avd.aquasec.com/nvd/cve-2011-3374",
"Description": "It was found that apt-key in apt, all versions, do not correctly validate gpg keys with the master keyring, leading to a potential man-in-the-middle attack.",
"Severity": "LOW",
"CweIDs": [
"CWE-347"
],
"CVSS": {
"nvd": {
"V2Vector": "AV:N/AC:M/Au:N/C:N/I:P/A:N",
"V3Vector": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:N",
"V2Score": 4.3,
"V3Score": 3.7
}
},
"References": [
"https://access.redhat.com/security/cve/cve-2011-3374"
],
"PublishedDate": "2019-11-26T00:15:00Z",
"LastModifiedDate": "2021-02-09T16:08:00Z"
}
]
}
]
}
`)
var redisSR = &models.ScanResult{
JSONVersion: 4,
ServerName: "redis (debian 10.10)",
Family: "debian",
ScannedBy: "trivy",
ScannedVia: "trivy",
ScannedCves: models.VulnInfos{
"CVE-2011-3374": {
CveID: "CVE-2011-3374",
Confidences: models.Confidences{
models.Confidence{
Score: 100,
DetectionMethod: "TrivyMatch",
},
},
AffectedPackages: models.PackageFixStatuses{
models.PackageFixStatus{
Name: "apt",
NotFixedYet: true,
FixState: "Affected",
FixedIn: "",
}},
CveContents: models.CveContents{
"trivy": []models.CveContent{{
Title: "",
Summary: "It was found that apt-key in apt, all versions, do not correctly validate gpg keys with the master keyring, leading to a potential man-in-the-middle attack.",
Cvss3Severity: "LOW",
References: models.References{
{Source: "trivy", Link: "https://access.redhat.com/security/cve/cve-2011-3374"},
},
}},
},
LibraryFixedIns: models.LibraryFixedIns{},
},
},
LibraryScanners: models.LibraryScanners{},
Packages: models.Packages{
"apt": models.Package{
Name: "apt",
Version: "1.8.2.3",
},
"adduser": models.Package{
Name: "adduser",
Version: "3.118",
},
"bsdutils": models.Package{
Name: "bsdutils",
Version: "1:2.33.1-0.1",
},
"pkgA": models.Package{
Name: "pkgA",
Version: "1:2.33.1-0.1",
},
},
SrcPackages: models.SrcPackages{
"util-linux": models.SrcPackage{
Name: "util-linux",
Version: "2.33.1-0.1",
BinaryNames: []string{"bsdutils", "pkgA"},
},
},
Optional: map[string]interface{}{
"trivy-target": "redis (debian 10.10)",
},
}
var strutsTrivy = []byte(`
{
"SchemaVersion": 2,
"ArtifactName": "/data/struts-1.2.7/lib",
"ArtifactType": "filesystem",
"Metadata": {
"ImageConfig": {
"architecture": "",
"created": "0001-01-01T00:00:00Z",
"os": "",
"rootfs": {
"type": "",
"diff_ids": null
},
"config": {}
}
},
"Results": [
{
"Target": "Java",
"Class": "lang-pkgs",
"Type": "jar",
"Packages": [
{
"Name": "oro:oro",
"Version": "2.0.7",
"Layer": {}
},
{
"Name": "struts:struts",
"Version": "1.2.7",
"Layer": {}
},
{
"Name": "commons-beanutils:commons-beanutils",
"Version": "1.7.0",
"Layer": {}
}
],
"Vulnerabilities": [
{
"VulnerabilityID": "CVE-2014-0114",
"PkgName": "commons-beanutils:commons-beanutils",
"InstalledVersion": "1.7.0",
"FixedVersion": "1.9.2",
"Layer": {},
"SeveritySource": "nvd",
"PrimaryURL": "https://avd.aquasec.com/nvd/cve-2014-0114",
"Title": "Apache Struts 1: Class Loader manipulation via request parameters",
"Description": "Apache Commons BeanUtils, as distributed in lib/commons-beanutils-1.8.0.jar in Apache Struts 1.x through 1.3.10 and in other products requiring commons-beanutils through 1.9.2, does not suppress the class property, which allows remote attackers to \"manipulate\" the ClassLoader and execute arbitrary code via the class parameter, as demonstrated by the passing of this parameter to the getClass method of the ActionForm object in Struts 1.",
"Severity": "HIGH",
"CweIDs": [
"CWE-20"
],
"CVSS": {
"nvd": {
"V2Vector": "AV:N/AC:L/Au:N/C:P/I:P/A:P",
"V2Score": 7.5
},
"redhat": {
"V2Vector": "AV:N/AC:L/Au:N/C:P/I:P/A:P",
"V2Score": 7.5
}
},
"References": [
"http://advisories.mageia.org/MGASA-2014-0219.html"
],
"PublishedDate": "2014-04-30T10:49:00Z",
"LastModifiedDate": "2021-01-26T18:15:00Z"
},
{
"VulnerabilityID": "CVE-2012-1007",
"PkgName": "struts:struts",
"InstalledVersion": "1.2.7",
"Layer": {},
"SeveritySource": "nvd",
"PrimaryURL": "https://avd.aquasec.com/nvd/cve-2012-1007",
"Title": "struts: multiple XSS flaws",
"Description": "Multiple cross-site scripting (XSS) vulnerabilities in Apache Struts 1.3.10 allow remote attackers to inject arbitrary web script or HTML via (1) the name parameter to struts-examples/upload/upload-submit.do, or the message parameter to (2) struts-cookbook/processSimple.do or (3) struts-cookbook/processDyna.do.",
"Severity": "MEDIUM",
"CweIDs": [
"CWE-79"
],
"CVSS": {
"nvd": {
"V2Vector": "AV:N/AC:M/Au:N/C:N/I:P/A:N",
"V2Score": 4.3
},
"redhat": {
"V2Vector": "AV:N/AC:M/Au:N/C:N/I:P/A:N",
"V2Score": 4.3
}
},
"References": [
"https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2012-1007"
],
"PublishedDate": "2012-02-07T04:09:00Z",
"LastModifiedDate": "2018-10-17T01:29:00Z"
}
]
}
]
}`)
var strutsSR = &models.ScanResult{
JSONVersion: 4,
ServerName: "library scan by trivy",
Family: "pseudo",
ScannedBy: "trivy",
ScannedVia: "trivy",
ScannedCves: models.VulnInfos{
"CVE-2014-0114": {
CveID: "CVE-2014-0114",
Confidences: models.Confidences{
models.Confidence{
Score: 100,
DetectionMethod: "TrivyMatch",
},
},
CveContents: models.CveContents{
"trivy": []models.CveContent{{
Title: "Apache Struts 1: Class Loader manipulation via request parameters",
Summary: "Apache Commons BeanUtils, as distributed in lib/commons-beanutils-1.8.0.jar in Apache Struts 1.x through 1.3.10 and in other products requiring commons-beanutils through 1.9.2, does not suppress the class property, which allows remote attackers to \"manipulate\" the ClassLoader and execute arbitrary code via the class parameter, as demonstrated by the passing of this parameter to the getClass method of the ActionForm object in Struts 1.",
Cvss3Severity: "HIGH",
References: models.References{
{Source: "trivy", Link: "http://advisories.mageia.org/MGASA-2014-0219.html"},
},
}},
},
LibraryFixedIns: models.LibraryFixedIns{
models.LibraryFixedIn{
Key: "jar",
Name: "commons-beanutils:commons-beanutils",
FixedIn: "1.9.2",
//TODO use Artifactname?
Path: "Java",
},
},
AffectedPackages: models.PackageFixStatuses{},
},
"CVE-2012-1007": {
CveID: "CVE-2012-1007",
Confidences: models.Confidences{
models.Confidence{
Score: 100,
DetectionMethod: "TrivyMatch",
},
},
CveContents: models.CveContents{
"trivy": []models.CveContent{{
Title: "struts: multiple XSS flaws",
Summary: "Multiple cross-site scripting (XSS) vulnerabilities in Apache Struts 1.3.10 allow remote attackers to inject arbitrary web script or HTML via (1) the name parameter to struts-examples/upload/upload-submit.do, or the message parameter to (2) struts-cookbook/processSimple.do or (3) struts-cookbook/processDyna.do.",
Cvss3Severity: "MEDIUM",
References: models.References{
{Source: "trivy", Link: "https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2012-1007"},
},
}},
},
LibraryFixedIns: models.LibraryFixedIns{
models.LibraryFixedIn{
Key: "jar",
Name: "struts:struts",
FixedIn: "",
//TODO use Artifactname?
Path: "Java",
},
},
AffectedPackages: models.PackageFixStatuses{},
},
},
LibraryScanners: models.LibraryScanners{
models.LibraryScanner{
Type: "jar",
LockfilePath: "Java",
Libs: []models.Library{
{
Name: "commons-beanutils:commons-beanutils",
Version: "1.7.0",
},
{
Name: "oro:oro",
Version: "2.0.7",
},
{
Name: "struts:struts",
Version: "1.2.7",
},
},
},
},
Packages: models.Packages{},
SrcPackages: models.SrcPackages{},
Optional: map[string]interface{}{
"trivy-target": "Java",
},
}
var osAndLibTrivy = []byte(`
{
"SchemaVersion": 2,
"ArtifactName": "quay.io/fluentd_elasticsearch/fluentd:v2.9.0",
"ArtifactType": "container_image",
"Metadata": {
"OS": {
"Family": "debian",
"Name": "10.2"
},
"ImageID": "sha256:5a992077baba51b97f27591a10d54d2f2723dc9c81a3fe419e261023f2554933",
"DiffIDs": [
"sha256:25165eb51d15842f870f97873e0a58409d5e860e6108e3dd829bd10e484c0065"
],
"RepoTags": [
"quay.io/fluentd_elasticsearch/fluentd:v2.9.0"
],
"RepoDigests": [
"quay.io/fluentd_elasticsearch/fluentd@sha256:54716d825ec9791ffb403ac17a1e82159c98ac6161e02b2a054595ad01aa6726"
],
"ImageConfig": {
"architecture": "amd64",
"container": "232f3fc7ddffd71dc3ff52c6c0c3a5feea2f51acffd9b53850a8fc6f1a15319a",
"created": "2020-03-04T13:59:39.161374106Z",
"docker_version": "19.03.4",
"history": [
{
"created": "2020-03-04T13:59:39.161374106Z",
"created_by": "/bin/sh -c #(nop) CMD [\"/run.sh\"]",
"empty_layer": true
}
],
"os": "linux",
"rootfs": {
"type": "layers",
"diff_ids": [
"sha256:25165eb51d15842f870f97873e0a58409d5e860e6108e3dd829bd10e484c0065"
]
},
"config": {
"Cmd": [
"/run.sh"
],
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so.2"
],
"Image": "sha256:2a538358cddc4824e9eff1531e0c63ae5e3cda85d2984c647df9b1c816b9b86b",
"ExposedPorts": {
"80/tcp": {}
}
}
}
},
"Results": [
{
"Target": "quay.io/fluentd_elasticsearch/fluentd:v2.9.0 (debian 10.2)",
"Class": "os-pkgs",
"Type": "debian",
"Packages": [
{
"Name": "libgnutls30",
"Version": "3.6.7-4",
"SrcName": "gnutls28",
"SrcVersion": "3.6.7-4",
"Layer": {
"Digest": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c",
"DiffID": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f"
}
}
],
"Vulnerabilities": [
{
"VulnerabilityID": "CVE-2021-20231",
"PkgName": "libgnutls30",
"InstalledVersion": "3.6.7-4",
"FixedVersion": "3.6.7-4+deb10u7",
"Layer": {
"Digest": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c",
"DiffID": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f"
},
"SeveritySource": "nvd",
"PrimaryURL": "https://avd.aquasec.com/nvd/cve-2021-20231",
"Title": "gnutls: Use after free in client key_share extension",
"Description": "A flaw was found in gnutls. A use after free issue in client sending key_share extension may lead to memory corruption and other consequences.",
"Severity": "CRITICAL",
"CweIDs": [
"CWE-416"
],
"CVSS": {
"nvd": {
"V2Vector": "AV:N/AC:L/Au:N/C:P/I:P/A:P",
"V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
"V2Score": 7.5,
"V3Score": 9.8
},
"redhat": {
"V3Vector": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:L",
"V3Score": 3.7
}
},
"References": [
"https://bugzilla.redhat.com/show_bug.cgi?id=1922276"
],
"PublishedDate": "2021-03-12T19:15:00Z",
"LastModifiedDate": "2021-06-01T14:07:00Z"
}
]
},
{
"Target": "Ruby",
"Class": "lang-pkgs",
"Type": "gemspec",
"Packages": [
{
"Name": "activesupport",
"Version": "6.0.2.1",
"License": "MIT",
"Layer": {
"Digest": "sha256:a8877cad19f14a7044524a145ce33170085441a7922458017db1631dcd5f7602",
"DiffID": "sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9"
},
"FilePath": "var/lib/gems/2.5.0/specifications/activesupport-6.0.2.1.gemspec"
}
],
"Vulnerabilities": [
{
"VulnerabilityID": "CVE-2020-8165",
"PkgName": "activesupport",
"PkgPath": "var/lib/gems/2.5.0/specifications/activesupport-6.0.2.1.gemspec",
"InstalledVersion": "6.0.2.1",
"FixedVersion": "6.0.3.1, 5.2.4.3",
"Layer": {
"Digest": "sha256:a8877cad19f14a7044524a145ce33170085441a7922458017db1631dcd5f7602",
"DiffID": "sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9"
},
"SeveritySource": "nvd",
"PrimaryURL": "https://avd.aquasec.com/nvd/cve-2020-8165",
"Title": "rubygem-activesupport: potentially unintended unmarshalling of user-provided objects in MemCacheStore and RedisCacheStore",
"Description": "A deserialization of untrusted data vulnernerability exists in rails \u003c 5.2.4.3, rails \u003c 6.0.3.1 that can allow an attacker to unmarshal user-provided objects in MemCacheStore and RedisCacheStore potentially resulting in an RCE.",
"Severity": "CRITICAL",
"CweIDs": [
"CWE-502"
],
"CVSS": {
"nvd": {
"V2Vector": "AV:N/AC:L/Au:N/C:P/I:P/A:P",
"V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
"V2Score": 7.5,
"V3Score": 9.8
},
"redhat": {
"V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
"V3Score": 9.8
}
},
"References": [
"https://www.debian.org/security/2020/dsa-4766"
],
"PublishedDate": "2020-06-19T18:15:00Z",
"LastModifiedDate": "2020-10-17T12:15:00Z"
}
]
}
]
}`)
var osAndLibSR = &models.ScanResult{
JSONVersion: 4,
ServerName: "quay.io/fluentd_elasticsearch/fluentd:v2.9.0 (debian 10.2)",
Family: "debian",
ScannedBy: "trivy",
ScannedVia: "trivy",
ScannedCves: models.VulnInfos{
"CVE-2021-20231": {
CveID: "CVE-2021-20231",
Confidences: models.Confidences{
models.Confidence{
Score: 100,
DetectionMethod: "TrivyMatch",
},
},
AffectedPackages: models.PackageFixStatuses{
models.PackageFixStatus{
Name: "libgnutls30",
NotFixedYet: false,
FixState: "",
FixedIn: "3.6.7-4+deb10u7",
}},
CveContents: models.CveContents{
"trivy": []models.CveContent{{
Title: "gnutls: Use after free in client key_share extension",
Summary: "A flaw was found in gnutls. A use after free issue in client sending key_share extension may lead to memory corruption and other consequences.",
Cvss3Severity: "CRITICAL",
References: models.References{
{Source: "trivy", Link: "https://bugzilla.redhat.com/show_bug.cgi?id=1922276"},
},
}},
},
LibraryFixedIns: models.LibraryFixedIns{},
},
"CVE-2020-8165": {
CveID: "CVE-2020-8165",
Confidences: models.Confidences{
models.Confidence{
Score: 100,
DetectionMethod: "TrivyMatch",
},
},
AffectedPackages: models.PackageFixStatuses{},
CveContents: models.CveContents{
"trivy": []models.CveContent{{
Title: "rubygem-activesupport: potentially unintended unmarshalling of user-provided objects in MemCacheStore and RedisCacheStore",
Summary: "A deserialization of untrusted data vulnernerability exists in rails \u003c 5.2.4.3, rails \u003c 6.0.3.1 that can allow an attacker to unmarshal user-provided objects in MemCacheStore and RedisCacheStore potentially resulting in an RCE.",
Cvss3Severity: "CRITICAL",
References: models.References{
{Source: "trivy", Link: "https://www.debian.org/security/2020/dsa-4766"},
},
}},
},
LibraryFixedIns: models.LibraryFixedIns{
models.LibraryFixedIn{
Key: "gemspec",
Name: "activesupport",
FixedIn: "6.0.3.1, 5.2.4.3",
Path: "Ruby",
},
},
},
},
LibraryScanners: models.LibraryScanners{
models.LibraryScanner{
Type: "gemspec",
LockfilePath: "Ruby",
Libs: []models.Library{
{
Name: "activesupport",
Version: "6.0.2.1",
FilePath: "var/lib/gems/2.5.0/specifications/activesupport-6.0.2.1.gemspec",
},
},
},
},
Packages: models.Packages{
"libgnutls30": models.Package{
Name: "libgnutls30",
Version: "3.6.7-4",
},
},
SrcPackages: models.SrcPackages{
"gnutls28": models.SrcPackage{
Name: "gnutls28",
Version: "3.6.7-4",
BinaryNames: []string{"libgnutls30"},
},
},
Optional: map[string]interface{}{
"trivy-target": "quay.io/fluentd_elasticsearch/fluentd:v2.9.0 (debian 10.2)",
},
}

View File

@@ -0,0 +1,226 @@
package pkg
import (
"sort"
"time"
ftypes "github.com/aquasecurity/fanal/types"
"github.com/aquasecurity/fanal/analyzer/os"
"github.com/aquasecurity/trivy/pkg/report"
"github.com/future-architect/vuls/models"
)
// Convert :
func Convert(results report.Results) (result *models.ScanResult, err error) {
scanResult := &models.ScanResult{
JSONVersion: models.JSONVersion,
ScannedCves: models.VulnInfos{},
}
pkgs := models.Packages{}
srcPkgs := models.SrcPackages{}
vulnInfos := models.VulnInfos{}
uniqueLibraryScannerPaths := map[string]models.LibraryScanner{}
for _, trivyResult := range results {
for _, vuln := range trivyResult.Vulnerabilities {
if _, ok := vulnInfos[vuln.VulnerabilityID]; !ok {
vulnInfos[vuln.VulnerabilityID] = models.VulnInfo{
CveID: vuln.VulnerabilityID,
Confidences: models.Confidences{
{
Score: 100,
DetectionMethod: models.TrivyMatchStr,
},
},
AffectedPackages: models.PackageFixStatuses{},
CveContents: models.CveContents{},
LibraryFixedIns: models.LibraryFixedIns{},
// VulnType : "",
}
}
vulnInfo := vulnInfos[vuln.VulnerabilityID]
var notFixedYet bool
fixState := ""
if len(vuln.FixedVersion) == 0 {
notFixedYet = true
fixState = "Affected"
}
var references models.References
for _, reference := range vuln.References {
references = append(references, models.Reference{
Source: "trivy",
Link: reference,
})
}
sort.Slice(references, func(i, j int) bool {
return references[i].Link < references[j].Link
})
var published time.Time
if vuln.PublishedDate != nil {
published = *vuln.PublishedDate
}
var lastModified time.Time
if vuln.LastModifiedDate != nil {
lastModified = *vuln.LastModifiedDate
}
vulnInfo.CveContents = models.CveContents{
models.Trivy: []models.CveContent{{
Cvss3Severity: vuln.Severity,
References: references,
Title: vuln.Title,
Summary: vuln.Description,
Published: published,
LastModified: lastModified,
}},
}
// do onlyIif image type is Vuln
if IsTrivySupportedOS(trivyResult.Type) {
pkgs[vuln.PkgName] = models.Package{
Name: vuln.PkgName,
Version: vuln.InstalledVersion,
}
vulnInfo.AffectedPackages = append(vulnInfo.AffectedPackages, models.PackageFixStatus{
Name: vuln.PkgName,
NotFixedYet: notFixedYet,
FixState: fixState,
FixedIn: vuln.FixedVersion,
})
} else {
vulnInfo.LibraryFixedIns = append(vulnInfo.LibraryFixedIns, models.LibraryFixedIn{
Key: trivyResult.Type,
Name: vuln.PkgName,
Path: trivyResult.Target,
FixedIn: vuln.FixedVersion,
})
libScanner := uniqueLibraryScannerPaths[trivyResult.Target]
libScanner.Type = trivyResult.Type
libScanner.Libs = append(libScanner.Libs, models.Library{
Name: vuln.PkgName,
Version: vuln.InstalledVersion,
FilePath: vuln.PkgPath,
})
uniqueLibraryScannerPaths[trivyResult.Target] = libScanner
}
vulnInfos[vuln.VulnerabilityID] = vulnInfo
}
// --list-all-pkgs flg of trivy will output all installed packages, so collect them.
if trivyResult.Class == report.ClassOSPkg {
for _, p := range trivyResult.Packages {
pkgs[p.Name] = models.Package{
Name: p.Name,
Version: p.Version,
}
if p.Name != p.SrcName {
if v, ok := srcPkgs[p.SrcName]; !ok {
srcPkgs[p.SrcName] = models.SrcPackage{
Name: p.SrcName,
Version: p.SrcVersion,
BinaryNames: []string{p.Name},
}
} else {
v.AddBinaryName(p.Name)
srcPkgs[p.SrcName] = v
}
}
}
} else if trivyResult.Class == report.ClassLangPkg {
libScanner := uniqueLibraryScannerPaths[trivyResult.Target]
libScanner.Type = trivyResult.Type
for _, p := range trivyResult.Packages {
libScanner.Libs = append(libScanner.Libs, models.Library{
Name: p.Name,
Version: p.Version,
FilePath: p.FilePath,
})
}
uniqueLibraryScannerPaths[trivyResult.Target] = libScanner
}
}
// flatten and unique libraries
libraryScanners := make([]models.LibraryScanner, 0, len(uniqueLibraryScannerPaths))
for path, v := range uniqueLibraryScannerPaths {
uniqueLibrary := map[string]models.Library{}
for _, lib := range v.Libs {
uniqueLibrary[lib.Name+lib.Version] = lib
}
var libraries []models.Library
for _, library := range uniqueLibrary {
libraries = append(libraries, library)
}
sort.Slice(libraries, func(i, j int) bool {
return libraries[i].Name < libraries[j].Name
})
libscanner := models.LibraryScanner{
Type: v.Type,
LockfilePath: path,
Libs: libraries,
}
libraryScanners = append(libraryScanners, libscanner)
}
sort.Slice(libraryScanners, func(i, j int) bool {
return libraryScanners[i].LockfilePath < libraryScanners[j].LockfilePath
})
scanResult.ScannedCves = vulnInfos
scanResult.Packages = pkgs
scanResult.SrcPackages = srcPkgs
scanResult.LibraryScanners = libraryScanners
return scanResult, nil
}
// IsTrivySupportedOS :
func IsTrivySupportedOS(family string) bool {
supportedFamilies := map[string]interface{}{
os.RedHat: struct{}{},
os.Debian: struct{}{},
os.Ubuntu: struct{}{},
os.CentOS: struct{}{},
os.Rocky: struct{}{},
os.Alma: struct{}{},
os.Fedora: struct{}{},
os.Amazon: struct{}{},
os.Oracle: struct{}{},
os.Windows: struct{}{},
os.OpenSUSE: struct{}{},
os.OpenSUSELeap: struct{}{},
os.OpenSUSETumbleweed: struct{}{},
os.SLES: struct{}{},
os.Photon: struct{}{},
os.Alpine: struct{}{},
}
_, ok := supportedFamilies[family]
return ok
}
// IsTrivySupportedLib :
func IsTrivySupportedLib(typestr string) bool {
supportedLibs := map[string]interface{}{
ftypes.Bundler: struct{}{},
ftypes.GemSpec: struct{}{},
ftypes.Cargo: struct{}{},
ftypes.Composer: struct{}{},
ftypes.Npm: struct{}{},
ftypes.NuGet: struct{}{},
ftypes.Pip: struct{}{},
ftypes.Pipenv: struct{}{},
ftypes.Poetry: struct{}{},
ftypes.PythonPkg: struct{}{},
ftypes.NodePkg: struct{}{},
ftypes.Yarn: struct{}{},
ftypes.Jar: struct{}{},
ftypes.GoBinary: struct{}{},
ftypes.GoMod: struct{}{},
}
_, ok := supportedLibs[typestr]
return ok
}