From 9eea8f0eaa113f8085f95cbe81c5a928bce5e25f Mon Sep 17 00:00:00 2001 From: Teppei Fukuda Date: Sat, 28 Sep 2019 02:35:47 +0300 Subject: [PATCH] Support the case when Red Hat returns the different type (#8) --- go.mod | 1 + go.sum | 2 + redhat/redhat.go | 23 +--- redhat/testdata/CVE-2009-2694.json | 32 +++++ redhat/testdata/CVE-2019-7614.json | 36 ++++++ redhat/types.go | 186 +++++++++++++++++++++++++++++ redhat/types_test.go | 134 +++++++++++++++++++++ 7 files changed, 396 insertions(+), 18 deletions(-) create mode 100644 redhat/testdata/CVE-2009-2694.json create mode 100644 redhat/testdata/CVE-2019-7614.json create mode 100644 redhat/types.go create mode 100644 redhat/types_test.go diff --git a/go.mod b/go.mod index 0dc56d4..b3920d6 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/fatih/color v1.7.0 // indirect github.com/hashicorp/go-version v1.2.0 github.com/kr/pretty v0.1.0 // indirect + github.com/kylelemons/godebug v1.1.0 github.com/mattn/go-colorable v0.1.1 // indirect github.com/mattn/go-jsonpointer v0.0.0-20180225143300-37667080efed github.com/mattn/go-runewidth v0.0.4 // indirect diff --git a/go.sum b/go.sum index 33ecf90..a34fbbe 100644 --- a/go.sum +++ b/go.sum @@ -19,6 +19,8 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/mattn/go-colorable v0.1.1 h1:G1f5SKeVxmagw/IyvzvtZE4Gybcc4Tr1tf7I8z0XgOg= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-isatty v0.0.5 h1:tHXDdz1cpzGaovsTB+TVB8q90WEokoVmfMqoVcrLUgw= diff --git a/redhat/redhat.go b/redhat/redhat.go index 9cad2bf..f5edb84 100644 --- a/redhat/redhat.go +++ b/redhat/redhat.go @@ -21,14 +21,6 @@ const ( retry = 20 // Red Hat Security Data API is unstable ) -type RedhatEntry struct { - CveID string `json:"CVE"` -} - -type RedhatCVEJSON struct { - Name string `json:"name"` -} - func Update(years []int) error { for _, year := range years { if err := update(year); err != nil { @@ -96,8 +88,8 @@ func listAllRedhatCves(after, before string, wait int) (entries []RedhatEntry, e // retrieveRedhatCveDetails returns full CVE details from RedHat API // https://access.redhat.com/documentation/en-us/red_hat_security_data_api/0.1/html-single/red_hat_security_data_api/#retrieve_a_cve -func retrieveRedhatCveDetails(urls []string) (map[string]interface{}, error) { - cves := map[string]interface{}{} +func retrieveRedhatCveDetails(urls []string) (map[string]*RedhatCVEJSON, error) { + cves := map[string]*RedhatCVEJSON{} cveJSONs, err := utils.FetchConcurrently(urls, concurrency, wait, retry) if err != nil { @@ -105,17 +97,12 @@ func retrieveRedhatCveDetails(urls []string) (map[string]interface{}, error) { } for _, cveJSON := range cveJSONs { - var cve RedhatCVEJSON - if err = json.Unmarshal(cveJSON, &cve); err != nil { + cve := &RedhatCVEJSON{} + if err = json.Unmarshal(cveJSON, cve); err != nil { log.Printf("json decode error: %s", err) continue } - var cveInterface interface{} - if err = json.Unmarshal(cveJSON, &cveInterface); err != nil { - log.Printf("json decode error: %s", err) - continue - } - cves[cve.Name] = cveInterface + cves[cve.Name] = cve } return cves, nil diff --git a/redhat/testdata/CVE-2009-2694.json b/redhat/testdata/CVE-2009-2694.json new file mode 100644 index 0000000..c75dc7a --- /dev/null +++ b/redhat/testdata/CVE-2009-2694.json @@ -0,0 +1,32 @@ +{ + "threat_severity": "Critical", + "public_date": "2009-08-18T00:00:00Z", + "bugzilla": { + "description": "\nCVE-2009-2694 pidgin: insufficient input validation in msn_slplink_process_msg()\n ", + "id": "514957", + "url": "https://bugzilla.redhat.com/show_bug.cgi?id=514957" + }, + "cvss": { + "cvss_base_score": "7.5", + "cvss_scoring_vector": "AV:N/AC:L/Au:N/C:P/I:P/A:P", + "status": "verified" + }, + "cwe": "CWE-228->CWE-119", + "details": [ + "\nThe msn_slplink_process_msg function in libpurple/protocols/msn/slplink.c in libpurple, as used in Pidgin (formerly Gaim) before 2.5.9 and Adium 1.3.5 and earlier, allows remote attackers to execute arbitrary code or cause a denial of service (memory corruption and application crash) by sending multiple crafted SLP (aka MSNSLP) messages to trigger an overwrite of an arbitrary memory location. NOTE: this issue reportedly exists because of an incomplete fix for CVE-2009-1376.\n " + ], + "affected_release": [ + { + "product_name": "Red Hat Enterprise Linux 3", + "release_date": "2009-08-18T00:00:00Z", + "advisory": "RHSA-2009:1218", + "cpe": "cpe:/o:redhat:enterprise_linux:3", + "package": "pidgin-1.5.1-4.el3" + } + ], + "name": "CVE-2009-2694", + "mitigation": { + "value": "\nUsers can lower the impact of this flaw by making sure their privacy settings only allow Pidgin to accept messages from the users on their buddy list. This will prevent exploitation of this flaw by other random MSN users.\n ", + "lang": "en:us" + } +} diff --git a/redhat/testdata/CVE-2019-7614.json b/redhat/testdata/CVE-2019-7614.json new file mode 100644 index 0000000..b462c03 --- /dev/null +++ b/redhat/testdata/CVE-2019-7614.json @@ -0,0 +1,36 @@ +{ + "bugzilla": { + "description": "\nCVE-2019-7614 elasticsearch: Race condition in response headers on systems with multiple submitting requests\n ", + "id": "1747240", + "url": "https://bugzilla.redhat.com/show_bug.cgi?id=1747240" + }, + "cvss3": { + "cvss3_base_score": "2.0", + "cvss3_scoring_vector": "CVSS:3.0/AV:A/AC:H/PR:H/UI:N/S:U/C:L/I:N/A:N", + "status": "draft" + }, + "cwe": "CWE-362", + "details": [ + "\nA race condition flaw was found in the response headers Elasticsearch versions before 7.2.1 and 6.8.2 returns to a request. On a system with multiple users submitting requests, it could be possible for an attacker to gain access to response header containing sensitive data from another user.\n " + ], + "mitigation": "\nThere is no mitigation for this issue, the flaw can only be resolved by applying updates.\n ", + "name": "CVE-2019-7614", + "package_state": [ + { + "cpe": "cpe:/a:redhat:jboss_fuse:6", + "fix_state": "Out of support scope", + "package_name": "elasticsearch", + "product_name": "Red Hat JBoss Fuse 6" + }, + { + "cpe": "cpe:/a:redhat:jboss_fuse:7", + "fix_state": "New", + "package_name": "elasticsearch", + "product_name": "Red Hat JBoss Fuse 7" + } + ], + "public_date": "2019-07-31T00:00:00", + "statement": "\nRed Hat JBoss Fuse 6: \nThis vulnerability has been rated as having a security impact of Low. After evaluation and in accordance with the criteria noted in the product support life cycle, there are no plans to address this issue in an upcoming release. Please contact Red Hat Support for further information.\n ", + "threat_severity": "Low", + "upstream_fix": "elasticsearch 7.2.1, elasticsearch 6.8.2" +} \ No newline at end of file diff --git a/redhat/types.go b/redhat/types.go new file mode 100644 index 0000000..446de18 --- /dev/null +++ b/redhat/types.go @@ -0,0 +1,186 @@ +package redhat + +import ( + "encoding/json" + + "golang.org/x/xerrors" +) + +type RedhatEntry struct { + CveID string `json:"CVE"` +} + +type RedhatCVEJSON struct { + ThreatSeverity string `json:"threat_severity"` + PublicDate string `json:"public_date"` + Bugzilla RedhatBugzilla `json:"bugzilla"` + Cvss RedhatCvss `json:"cvss"` + Cvss3 RedhatCvss3 `json:"cvss3"` + Iava string `json:"iava"` + Cwe string `json:"cwe"` + Statement string `json:"statement"` + Acknowledgement string `json:"acknowledgement"` + Mitigation string `json:"-"` + AffectedRelease []RedhatAffectedRelease `json:"-"` + PackageState []RedhatPackageState `json:"-"` + Name string `json:"name"` + DocumentDistribution string `json:"document_distribution"` + + Details []string `json:"details"` + References []string `json:"references"` +} + +func (r *RedhatCVEJSON) UnmarshalJSON(data []byte) error { + type AliasRedhatCVEJSON RedhatCVEJSON + alias := &struct { + TempMitigation interface{} `json:"mitigation"` // mitigation is string or object + TempAffectedRelease interface{} `json:"affected_release"` // affected_release is array or object + TempPackageState interface{} `json:"package_state"` // package_state is array or object + *AliasRedhatCVEJSON + }{ + AliasRedhatCVEJSON: (*AliasRedhatCVEJSON)(r), + } + + if err := json.Unmarshal(data, alias); err != nil { + return err + } + + switch alias.TempAffectedRelease.(type) { + case []interface{}: + var ar RedhatCVEJSONAffectedReleaseArray + if err := json.Unmarshal(data, &ar); err != nil { + return xerrors.Errorf("unknown affected_release type: %w", err) + } + r.AffectedRelease = ar.AffectedRelease + case map[string]interface{}: + var ar RedhatCVEJSONAffectedReleaseObject + if err := json.Unmarshal(data, &ar); err != nil { + return xerrors.Errorf("unknown affected_release type: %w", err) + } + r.AffectedRelease = []RedhatAffectedRelease{ar.AffectedRelease} + case nil: + default: + return xerrors.New("unknown affected_release type") + } + + switch alias.TempPackageState.(type) { + case []interface{}: + var ps RedhatCVEJSONPackageStateArray + if err := json.Unmarshal(data, &ps); err != nil { + return xerrors.Errorf("unknown package_state type: %w", err) + } + r.PackageState = ps.PackageState + case map[string]interface{}: + var ps RedhatCVEJSONPackageStateObject + if err := json.Unmarshal(data, &ps); err != nil { + return xerrors.Errorf("unknown package_state type: %w", err) + } + r.PackageState = []RedhatPackageState{ps.PackageState} + case nil: + default: + return xerrors.New("unknown package_state type") + } + + switch alias.TempMitigation.(type) { + case string: + r.Mitigation = alias.TempMitigation.(string) + case map[string]interface{}: + var m struct { + Mitigation RedhatCVEJSONMitigationObject + } + if err := json.Unmarshal(data, &m); err != nil { + return xerrors.Errorf("unknown package_state type: %w", err) + } + r.Mitigation = m.Mitigation.Value + case nil: + default: + return xerrors.New("unknown package_state type") + } + + return nil +} + +func (r *RedhatCVEJSON) MarshalJSON() ([]byte, error) { + type Alias RedhatCVEJSON + return json.Marshal(&struct { + TempMitigation string `json:"mitigation,omitempty"` + TempAffectedRelease interface{} `json:"affected_release,omitempty"` // affected_release is array or object + TempPackageState interface{} `json:"package_state,omitempty"` // package_state is array or object + *Alias + }{ + TempMitigation: r.Mitigation, + TempAffectedRelease: r.AffectedRelease, + TempPackageState: r.PackageState, + Alias: (*Alias)(r), + }) +} + +type RedhatCVEJSONAffectedReleaseArray struct { + AffectedRelease []RedhatAffectedRelease `json:"affected_release"` +} + +type RedhatCVEJSONAffectedReleaseObject struct { + AffectedRelease RedhatAffectedRelease `json:"affected_release"` +} + +type RedhatCVEJSONPackageStateArray struct { + PackageState []RedhatPackageState `json:"package_state"` +} + +type RedhatCVEJSONPackageStateObject struct { + PackageState RedhatPackageState `json:"package_state"` +} + +type RedhatCVEJSONMitigationObject struct { + Value string + Lang string +} + +type RedhatDetail struct { + RedhatCVEID int64 `json:",omitempty"` + Detail string +} + +type RedhatReference struct { + RedhatCVEID int64 `json:",omitempty"` + Reference string +} + +type RedhatBugzilla struct { + RedhatCVEID int64 `json:",omitempty"` + Description string `json:"description"` + + BugzillaID string `json:"id"` + URL string `json:"url"` +} + +type RedhatCvss struct { + RedhatCVEID int64 `json:",omitempty"` + CvssBaseScore string `json:"cvss_base_score"` + CvssScoringVector string `json:"cvss_scoring_vector"` + Status string `json:"status"` +} + +type RedhatCvss3 struct { + RedhatCVEID int64 `json:",omitempty"` + Cvss3BaseScore string `json:"cvss3_base_score"` + Cvss3ScoringVector string `json:"cvss3_scoring_vector"` + Status string `json:"status"` +} + +type RedhatAffectedRelease struct { + RedhatCVEID int64 `json:",omitempty"` + ProductName string `json:"product_name"` + ReleaseDate string `json:"release_date"` + Advisory string `json:"advisory"` + Package string `json:"package"` + Cpe string `json:"cpe"` +} + +type RedhatPackageState struct { + RedhatCVEID int64 `json:",omitempty"` + ProductName string `json:"product_name"` + FixState string `json:"fix_state"` + PackageName string `json:"package_name"` + Cpe string `json:"cpe"` +} diff --git a/redhat/types_test.go b/redhat/types_test.go new file mode 100644 index 0000000..e41b314 --- /dev/null +++ b/redhat/types_test.go @@ -0,0 +1,134 @@ +package redhat_test + +import ( + "encoding/json" + "io/ioutil" + "reflect" + "testing" + + "github.com/aquasecurity/vuln-list-update/redhat" + "github.com/kylelemons/godebug/pretty" +) + +func TestRedhatCVEJSON_UnmarshalJSON(t *testing.T) { + tests := map[string]struct { + in string + want *redhat.RedhatCVEJSON + }{ + "mitigation_string": { + in: "testdata/CVE-2019-7614.json", + want: &redhat.RedhatCVEJSON{ + ThreatSeverity: "Low", + PublicDate: "2019-07-31T00:00:00", + Bugzilla: redhat.RedhatBugzilla{ + RedhatCVEID: 0, + Description: "\nCVE-2019-7614 elasticsearch: Race condition in response headers on systems with multiple submitting requests\n ", + BugzillaID: "1747240", + URL: "https://bugzilla.redhat.com/show_bug.cgi?id=1747240", + }, + Cvss: redhat.RedhatCvss{ + RedhatCVEID: 0, + CvssBaseScore: "", + CvssScoringVector: "", + Status: "", + }, + Cvss3: redhat.RedhatCvss3{ + RedhatCVEID: 0, + Cvss3BaseScore: "2.0", + Cvss3ScoringVector: "CVSS:3.0/AV:A/AC:H/PR:H/UI:N/S:U/C:L/I:N/A:N", + Status: "draft", + }, + Iava: "", + Cwe: "CWE-362", + Statement: "\nRed Hat JBoss Fuse 6: \nThis vulnerability has been rated as having a security impact of Low. After evaluation and in accordance with the criteria noted in the product support life cycle, there are no plans to address this issue in an upcoming release. Please contact Red Hat Support for further information.\n ", + Acknowledgement: "", + Mitigation: "\nThere is no mitigation for this issue, the flaw can only be resolved by applying updates.\n ", + PackageState: []redhat.RedhatPackageState{ + { + RedhatCVEID: 0, + ProductName: "Red Hat JBoss Fuse 6", + FixState: "Out of support scope", + PackageName: "elasticsearch", + Cpe: "cpe:/a:redhat:jboss_fuse:6", + }, + { + RedhatCVEID: 0, + ProductName: "Red Hat JBoss Fuse 7", + FixState: "New", + PackageName: "elasticsearch", + Cpe: "cpe:/a:redhat:jboss_fuse:7", + }, + }, + //AffectedRelease: []redhat.RedhatAffectedRelease{}, + Name: "CVE-2019-7614", + DocumentDistribution: "", + Details: []string{ + "\nA race condition flaw was found in the response headers Elasticsearch versions before 7.2.1 and 6.8.2 returns to a request. On a system with multiple users submitting requests, it could be possible for an attacker to gain access to response header containing sensitive data from another user.\n ", + }, + //References: []string{}, + }, + }, + "mitigation_object": { + in: "testdata/CVE-2009-2694.json", + want: &redhat.RedhatCVEJSON{ + ThreatSeverity: "Critical", + PublicDate: "2009-08-18T00:00:00Z", + Bugzilla: redhat.RedhatBugzilla{ + RedhatCVEID: 0, + Description: "\nCVE-2009-2694 pidgin: insufficient input validation in msn_slplink_process_msg()\n ", + BugzillaID: "514957", + URL: "https://bugzilla.redhat.com/show_bug.cgi?id=514957", + }, + Cvss: redhat.RedhatCvss{ + RedhatCVEID: 0, + CvssBaseScore: "7.5", + CvssScoringVector: "AV:N/AC:L/Au:N/C:P/I:P/A:P", + Status: "verified", + }, + Cvss3: redhat.RedhatCvss3{ + RedhatCVEID: 0, + Cvss3BaseScore: "", + Cvss3ScoringVector: "", + Status: "", + }, + Iava: "", + Cwe: "CWE-228->CWE-119", + Statement: "", + Acknowledgement: "", + Mitigation: "\nUsers can lower the impact of this flaw by making sure their privacy settings only allow Pidgin to accept messages from the users on their buddy list. This will prevent exploitation of this flaw by other random MSN users.\n ", + AffectedRelease: []redhat.RedhatAffectedRelease{ + redhat.RedhatAffectedRelease{ + RedhatCVEID: 0, + ProductName: "Red Hat Enterprise Linux 3", + ReleaseDate: "2009-08-18T00:00:00Z", + Advisory: "RHSA-2009:1218", + Package: "pidgin-1.5.1-4.el3", + Cpe: "cpe:/o:redhat:enterprise_linux:3", + }, + }, + Name: "CVE-2009-2694", + DocumentDistribution: "", + Details: []string{ + "\nThe msn_slplink_process_msg function in libpurple/protocols/msn/slplink.c in libpurple, as used in Pidgin (formerly Gaim) before 2.5.9 and Adium 1.3.5 and earlier, allows remote attackers to execute arbitrary code or cause a denial of service (memory corruption and application crash) by sending multiple crafted SLP (aka MSNSLP) messages to trigger an overwrite of an arbitrary memory location. NOTE: this issue reportedly exists because of an incomplete fix for CVE-2009-1376.\n ", + }, + }, + }, + } + for testname, tt := range tests { + t.Run(testname, func(t *testing.T) { + jsonByte, err := ioutil.ReadFile(tt.in) + if err != nil { + t.Fatalf("unknown error: %s", err) + } + + got := &redhat.RedhatCVEJSON{} + err = json.Unmarshal(jsonByte, got) + if err != nil { + t.Fatalf("unknown error: %s", err) + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("[%s]\n diff: %s", testname, pretty.Compare(got, tt.want)) + } + }) + } +}