Add CWE rule mappings (#405)

* added mappings

* added cwe to template

* link in function to template

* moved mappings and added test cases

* wording

* cleanup
This commit is contained in:
Julian Thome 2019-10-31 09:22:38 +01:00 committed by Cosmin Cojocar
parent 28c1128b73
commit 53be8dd864
6 changed files with 235 additions and 4 deletions

View file

@ -34,10 +34,50 @@ const (
High 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. // Issue is returned by a gosec rule if it discovers an issue with the scanned code.
type Issue struct { type Issue struct {
Severity Score `json:"severity"` // issue severity (how problematic it is) Severity Score `json:"severity"` // issue severity (how problematic it is)
Confidence Score `json:"confidence"` // issue confidence (how sure we are we found it) 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 RuleID string `json:"rule_id"` // Human readable explanation
What string `json:"details"` // Human readable explanation What string `json:"details"` // Human readable explanation
File string `json:"file"` // File name we found it in 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, Confidence: confidence,
Severity: severity, Severity: severity,
Code: code, Code: code,
Cwe: IssueToCWE[ruleID],
} }
} }

View file

@ -41,7 +41,7 @@ var _ = Describe("Issue", func() {
Expect(issue).ShouldNot(BeNil()) Expect(issue).ShouldNot(BeNil())
Expect(issue.Code).Should(MatchRegexp(`"bar"`)) Expect(issue.Code).Should(MatchRegexp(`"bar"`))
Expect(issue.Line).Should(Equal("2")) 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() { It("should return an error if specific context is not able to be obtained", func() {

View file

@ -18,6 +18,7 @@ import (
"encoding/csv" "encoding/csv"
"encoding/json" "encoding/json"
"encoding/xml" "encoding/xml"
"fmt"
htmlTemplate "html/template" htmlTemplate "html/template"
"io" "io"
"strconv" "strconv"
@ -56,7 +57,7 @@ Golang errors in file: [{{ $filePath }}]:
{{end}} {{end}}
{{end}} {{end}}
{{ range $index, $issue := .Issues }} {{ 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 }} > {{ $issue.Code }}
{{ end }} {{ end }}
@ -126,6 +127,7 @@ func convertToSonarIssues(rootPaths []string, data *reportInfo) (*sonarIssues, e
sonarFilePath = strings.Replace(issue.File, rootPath+"/", "", 1) sonarFilePath = strings.Replace(issue.File, rootPath+"/", "", 1)
} }
} }
if sonarFilePath == "" { if sonarFilePath == "" {
continue continue
} }
@ -154,6 +156,7 @@ func convertToSonarIssues(rootPaths []string, data *reportInfo) (*sonarIssues, e
Type: "VULNERABILITY", Type: "VULNERABILITY",
Severity: getSonarSeverity(issue.Severity.String()), Severity: getSonarSeverity(issue.Severity.String()),
EffortMinutes: SonarqubeEffortMinutes, EffortMinutes: SonarqubeEffortMinutes,
Cwe: issue.Cwe,
} }
si.SonarIssues = append(si.SonarIssues, s) si.SonarIssues = append(si.SonarIssues, s)
} }
@ -190,6 +193,7 @@ func reportCSV(w io.Writer, data *reportInfo) error {
issue.Severity.String(), issue.Severity.String(),
issue.Confidence.String(), issue.Confidence.String(),
issue.Code, issue.Code,
fmt.Sprintf("CWE-%s", issue.Cwe.ID),
}) })
if err != nil { if err != nil {
return err return err

View file

@ -1,12 +1,48 @@
package output package output
import ( import (
"bytes"
"encoding/json"
"fmt"
. "github.com/onsi/ginkgo" . "github.com/onsi/ginkgo"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
"github.com/securego/gosec" "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() { var _ = Describe("Formatter", func() {
BeforeEach(func() { BeforeEach(func() {
}) })
@ -210,4 +246,150 @@ var _ = Describe("Formatter", func() {
Expect(*issues).To(Equal(*want)) 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))
}
})
})
}) })

View file

@ -36,7 +36,8 @@ func generatePlaintext(issue *gosec.Issue) string {
return "Results:\n" + return "Results:\n" +
"[" + issue.File + ":" + issue.Line + "] - " + "[" + issue.File + ":" + issue.Line + "] - " +
issue.What + " (Confidence: " + strconv.Itoa(int(issue.Confidence)) + 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 { func groupDataByRules(data *reportInfo) map[string][]*gosec.Issue {

View file

@ -1,5 +1,7 @@
package output package output
import "github.com/securego/gosec"
type textRange struct { type textRange struct {
StartLine int `json:"startLine"` StartLine int `json:"startLine"`
EndLine int `json:"endLine"` EndLine int `json:"endLine"`
@ -15,6 +17,7 @@ type location struct {
type sonarIssue struct { type sonarIssue struct {
EngineID string `json:"engineId"` EngineID string `json:"engineId"`
RuleID string `json:"ruleId"` RuleID string `json:"ruleId"`
Cwe gosec.Cwe `json:"cwe"`
PrimaryLocation location `json:"primaryLocation"` PrimaryLocation location `json:"primaryLocation"`
Type string `json:"type"` Type string `json:"type"`
Severity string `json:"severity"` Severity string `json:"severity"`