From 53be8dd8644ee48802114178cff6eb7e29757414 Mon Sep 17 00:00:00 2001 From: Julian Thome Date: Thu, 31 Oct 2019 09:22:38 +0100 Subject: [PATCH] Add CWE rule mappings (#405) * added mappings * added cwe to template * link in function to template * moved mappings and added test cases * wording * cleanup --- issue.go | 41 +++++++++ issue_test.go | 2 +- output/formatter.go | 6 +- output/formatter_test.go | 184 ++++++++++++++++++++++++++++++++++++- output/junit_xml_format.go | 3 +- output/sonarqube_format.go | 3 + 6 files changed, 235 insertions(+), 4 deletions(-) diff --git a/issue.go b/issue.go index 9f0454e..d54d981 100644 --- a/issue.go +++ b/issue.go @@ -34,10 +34,50 @@ const ( High ) +// Cwe id and url +type Cwe struct { + ID string + URL string +} + +// GetCwe creates a cwe object for a given RuleID +func GetCwe(id string) Cwe { + return Cwe{ID: id, URL: fmt.Sprintf("https://cwe.mitre.org/data/definitions/%s.html", id)} +} + +// IssueToCWE maps gosec rules to CWEs +var IssueToCWE = map[string]Cwe{ + "G101": GetCwe("798"), + "G102": GetCwe("200"), + "G103": GetCwe("242"), + "G104": GetCwe("703"), + "G106": GetCwe("322"), + "G107": GetCwe("88"), + "G201": GetCwe("89"), + "G202": GetCwe("89"), + "G203": GetCwe("79"), + "G204": GetCwe("78"), + "G301": GetCwe("276"), + "G302": GetCwe("276"), + "G303": GetCwe("377"), + "G304": GetCwe("22"), + "G305": GetCwe("22"), + "G401": GetCwe("326"), + "G402": GetCwe("295"), + "G403": GetCwe("310"), + "G404": GetCwe("338"), + "G501": GetCwe("327"), + "G502": GetCwe("327"), + "G503": GetCwe("327"), + "G504": GetCwe("327"), + "G505": GetCwe("327"), +} + // Issue is returned by a gosec rule if it discovers an issue with the scanned code. type Issue struct { Severity Score `json:"severity"` // issue severity (how problematic it is) Confidence Score `json:"confidence"` // issue confidence (how sure we are we found it) + Cwe Cwe `json:"cwe"` // Cwe associated with RuleID RuleID string `json:"rule_id"` // Human readable explanation What string `json:"details"` // Human readable explanation File string `json:"file"` // File name we found it in @@ -121,5 +161,6 @@ func NewIssue(ctx *Context, node ast.Node, ruleID, desc string, severity Score, Confidence: confidence, Severity: severity, Code: code, + Cwe: IssueToCWE[ruleID], } } diff --git a/issue_test.go b/issue_test.go index 68c4a5f..25a0ac2 100644 --- a/issue_test.go +++ b/issue_test.go @@ -41,7 +41,7 @@ var _ = Describe("Issue", func() { Expect(issue).ShouldNot(BeNil()) Expect(issue.Code).Should(MatchRegexp(`"bar"`)) Expect(issue.Line).Should(Equal("2")) - + Expect(issue.Cwe.ID).Should(Equal("")) }) It("should return an error if specific context is not able to be obtained", func() { diff --git a/output/formatter.go b/output/formatter.go index 428661a..6b6a092 100644 --- a/output/formatter.go +++ b/output/formatter.go @@ -18,6 +18,7 @@ import ( "encoding/csv" "encoding/json" "encoding/xml" + "fmt" htmlTemplate "html/template" "io" "strconv" @@ -56,7 +57,7 @@ Golang errors in file: [{{ $filePath }}]: {{end}} {{end}} {{ range $index, $issue := .Issues }} -[{{ $issue.File }}:{{ $issue.Line }}] - {{ $issue.RuleID }}: {{ $issue.What }} (Confidence: {{ $issue.Confidence}}, Severity: {{ $issue.Severity }}) +[{{ $issue.File }}:{{ $issue.Line }}] - {{ $issue.RuleID }} (CWE-{{ $issue.Cwe.ID }}): {{ $issue.What }} (Confidence: {{ $issue.Confidence}}, Severity: {{ $issue.Severity }}) > {{ $issue.Code }} {{ end }} @@ -126,6 +127,7 @@ func convertToSonarIssues(rootPaths []string, data *reportInfo) (*sonarIssues, e sonarFilePath = strings.Replace(issue.File, rootPath+"/", "", 1) } } + if sonarFilePath == "" { continue } @@ -154,6 +156,7 @@ func convertToSonarIssues(rootPaths []string, data *reportInfo) (*sonarIssues, e Type: "VULNERABILITY", Severity: getSonarSeverity(issue.Severity.String()), EffortMinutes: SonarqubeEffortMinutes, + Cwe: issue.Cwe, } si.SonarIssues = append(si.SonarIssues, s) } @@ -190,6 +193,7 @@ func reportCSV(w io.Writer, data *reportInfo) error { issue.Severity.String(), issue.Confidence.String(), issue.Code, + fmt.Sprintf("CWE-%s", issue.Cwe.ID), }) if err != nil { return err diff --git a/output/formatter_test.go b/output/formatter_test.go index d196087..520ce36 100644 --- a/output/formatter_test.go +++ b/output/formatter_test.go @@ -1,12 +1,48 @@ package output import ( + "bytes" + "encoding/json" + "fmt" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" - "github.com/securego/gosec" + "gopkg.in/yaml.v2" + "strings" ) +func createIssue(ruleID string, cwe gosec.Cwe) gosec.Issue { + return gosec.Issue{ + File: "/home/src/project/test.go", + Line: "1", + RuleID: ruleID, + What: "test", + Confidence: gosec.High, + Severity: gosec.High, + Code: "testcode", + Cwe: cwe, + } +} + +func createReportInfo(rule string, cwe gosec.Cwe) reportInfo { + issue := createIssue(rule, cwe) + metrics := gosec.Metrics{} + return reportInfo{ + Errors: map[string][]gosec.Error{}, + Issues: []*gosec.Issue{ + &issue, + }, + Stats: &metrics, + } +} + +func stripString(str string) string { + ret := strings.Replace(str, "\n", "", -1) + ret = strings.Replace(ret, " ", "", -1) + ret = strings.Replace(ret, "\t", "", -1) + return ret +} + var _ = Describe("Formatter", func() { BeforeEach(func() { }) @@ -210,4 +246,150 @@ var _ = Describe("Formatter", func() { Expect(*issues).To(Equal(*want)) }) }) + Context("When using different report formats", func() { + + grules := []string{"G101", "G102", "G103", "G104", "G106", + "G107", "G201", "G202", "G203", "G204", "G301", + "G302", "G303", "G304", "G305", "G401", "G402", + "G403", "G404", "G501", "G502", "G503", "G504", "G505"} + + It("csv formatted report should contain the CWE mapping", func() { + for _, rule := range grules { + cwe := gosec.IssueToCWE[rule] + issue := createIssue(rule, cwe) + error := map[string][]gosec.Error{} + + buf := new(bytes.Buffer) + CreateReport(buf, "csv", []string{}, []*gosec.Issue{&issue}, &gosec.Metrics{}, error) + pattern := "/home/src/project/test.go,1,test,HIGH,HIGH,testcode,CWE-%s\n" + expect := fmt.Sprintf(pattern, cwe.ID) + Expect(string(buf.Bytes())).To(Equal(expect)) + } + }) + It("xml formatted report should contain the CWE mapping", func() { + for _, rule := range grules { + cwe := gosec.IssueToCWE[rule] + issue := createIssue(rule, cwe) + error := map[string][]gosec.Error{} + + buf := new(bytes.Buffer) + CreateReport(buf, "xml", []string{}, []*gosec.Issue{&issue}, &gosec.Metrics{NumFiles: 0, NumLines: 0, NumNosec: 0, NumFound: 0}, error) + pattern := "Results:\n\n\n[/home/src/project/test.go:1] - %s (CWE-%s): test (Confidence: HIGH, Severity: HIGH)\n > testcode\n\n\nSummary:\n Files: 0\n Lines: 0\n Nosec: 0\n Issues: 0\n\n" + expect := fmt.Sprintf(pattern, rule, cwe.ID) + Expect(string(buf.Bytes())).To(Equal(expect)) + } + }) + It("json formatted report should contain the CWE mapping", func() { + for _, rule := range grules { + cwe := gosec.IssueToCWE[rule] + issue := createIssue(rule, cwe) + error := map[string][]gosec.Error{} + + data := createReportInfo(rule, cwe) + + expect := new(bytes.Buffer) + enc := json.NewEncoder(expect) + enc.Encode(data) + + buf := new(bytes.Buffer) + CreateReport(buf, "json", []string{}, []*gosec.Issue{&issue}, &gosec.Metrics{}, error) + result := stripString(buf.String()) + expectation := stripString(expect.String()) + Expect(result).To(Equal(expectation)) + } + }) + It("html formatted report should contain the CWE mapping", func() { + for _, rule := range grules { + cwe := gosec.IssueToCWE[rule] + issue := createIssue(rule, cwe) + error := map[string][]gosec.Error{} + + data := createReportInfo(rule, cwe) + + expect := new(bytes.Buffer) + enc := json.NewEncoder(expect) + enc.Encode(data) + + buf := new(bytes.Buffer) + CreateReport(buf, "html", []string{}, []*gosec.Issue{&issue}, &gosec.Metrics{}, error) + result := stripString(buf.String()) + expectation := stripString(expect.String()) + Expect(result).To(ContainSubstring(expectation)) + } + }) + It("yaml formatted report should contain the CWE mapping", func() { + for _, rule := range grules { + cwe := gosec.IssueToCWE[rule] + issue := createIssue(rule, cwe) + error := map[string][]gosec.Error{} + + data := createReportInfo(rule, cwe) + + expect := new(bytes.Buffer) + enc := yaml.NewEncoder(expect) + enc.Encode(data) + + buf := new(bytes.Buffer) + CreateReport(buf, "yaml", []string{}, []*gosec.Issue{&issue}, &gosec.Metrics{}, error) + result := stripString(buf.String()) + expectation := stripString(expect.String()) + Expect(result).To(ContainSubstring(expectation)) + } + }) + It("junit-xml formatted report should contain the CWE mapping", func() { + for _, rule := range grules { + cwe := gosec.IssueToCWE[rule] + issue := createIssue(rule, cwe) + error := map[string][]gosec.Error{} + + data := createReportInfo(rule, cwe) + + expect := new(bytes.Buffer) + enc := yaml.NewEncoder(expect) + enc.Encode(data) + + buf := new(bytes.Buffer) + CreateReport(buf, "junit-xml", []string{}, []*gosec.Issue{&issue}, &gosec.Metrics{}, error) + expectation := stripString(fmt.Sprintf("[/home/src/project/test.go:1] - test (Confidence: 2, Severity: 2, CWE: %s)", cwe.ID)) + result := stripString(buf.String()) + Expect(result).To(ContainSubstring(expectation)) + } + }) + It("text formatted report should contain the CWE mapping", func() { + for _, rule := range grules { + cwe := gosec.IssueToCWE[rule] + issue := createIssue(rule, cwe) + error := map[string][]gosec.Error{} + + data := createReportInfo(rule, cwe) + + expect := new(bytes.Buffer) + enc := yaml.NewEncoder(expect) + enc.Encode(data) + + buf := new(bytes.Buffer) + CreateReport(buf, "text", []string{}, []*gosec.Issue{&issue}, &gosec.Metrics{}, error) + expectation := stripString(fmt.Sprintf("[/home/src/project/test.go:1] - %s (CWE-%s): test (Confidence: HIGH, Severity: HIGH)", rule, cwe.ID)) + result := stripString(buf.String()) + Expect(result).To(ContainSubstring(expectation)) + } + }) + It("sonarqube formatted report should contain the CWE mapping", func() { + for _, rule := range grules { + cwe := gosec.IssueToCWE[rule] + issue := createIssue(rule, cwe) + error := map[string][]gosec.Error{} + buf := new(bytes.Buffer) + CreateReport(buf, "sonarqube", []string{"/home/src/project"}, []*gosec.Issue{&issue}, &gosec.Metrics{}, error) + result := stripString(buf.String()) + + expect := new(bytes.Buffer) + enc := json.NewEncoder(expect) + enc.Encode(cwe) + + expectation := stripString(expect.String()) + Expect(result).To(ContainSubstring(expectation)) + } + }) + }) }) diff --git a/output/junit_xml_format.go b/output/junit_xml_format.go index 547d5b2..0279383 100644 --- a/output/junit_xml_format.go +++ b/output/junit_xml_format.go @@ -36,7 +36,8 @@ func generatePlaintext(issue *gosec.Issue) string { return "Results:\n" + "[" + issue.File + ":" + issue.Line + "] - " + issue.What + " (Confidence: " + strconv.Itoa(int(issue.Confidence)) + - ", Severity: " + strconv.Itoa(int(issue.Severity)) + ")\n" + "> " + htmlLib.EscapeString(issue.Code) + ", Severity: " + strconv.Itoa(int(issue.Severity)) + + ", CWE: " + issue.Cwe.ID + ")\n" + "> " + htmlLib.EscapeString(issue.Code) } func groupDataByRules(data *reportInfo) map[string][]*gosec.Issue { diff --git a/output/sonarqube_format.go b/output/sonarqube_format.go index bede4fb..9435f96 100644 --- a/output/sonarqube_format.go +++ b/output/sonarqube_format.go @@ -1,5 +1,7 @@ package output +import "github.com/securego/gosec" + type textRange struct { StartLine int `json:"startLine"` EndLine int `json:"endLine"` @@ -15,6 +17,7 @@ type location struct { type sonarIssue struct { EngineID string `json:"engineId"` RuleID string `json:"ruleId"` + Cwe gosec.Cwe `json:"cwe"` PrimaryLocation location `json:"primaryLocation"` Type string `json:"type"` Severity string `json:"severity"`