Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
projectdiscovery
GitHub Repository: projectdiscovery/nuclei
Path: blob/dev/pkg/reporting/exporters/sarif/sarif.go
2072 views
1
package sarif
2
3
import (
4
"fmt"
5
"math"
6
"os"
7
"path"
8
"sync"
9
10
"github.com/pkg/errors"
11
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"
12
"github.com/projectdiscovery/nuclei/v3/pkg/output"
13
"github.com/projectdiscovery/sarif"
14
)
15
16
// Exporter is an exporter for nuclei sarif output format.
17
type Exporter struct {
18
sarif *sarif.Report
19
mutex *sync.Mutex
20
rulemap map[string]*int // contains rule-id && ruleIndex
21
rules []sarif.ReportingDescriptor
22
options *Options
23
}
24
25
// Options contains the configuration options for sarif exporter client
26
type Options struct {
27
// File is the file to export found sarif result to
28
File string `yaml:"file"`
29
}
30
31
// New creates a new sarif exporter integration client based on options.
32
func New(options *Options) (*Exporter, error) {
33
report := sarif.NewReport()
34
exporter := &Exporter{
35
sarif: report,
36
mutex: &sync.Mutex{},
37
rules: []sarif.ReportingDescriptor{},
38
rulemap: map[string]*int{},
39
options: options,
40
}
41
return exporter, nil
42
}
43
44
// addToolDetails adds details of static analysis tool (i.e nuclei)
45
func (exporter *Exporter) addToolDetails() {
46
driver := sarif.ToolComponent{
47
Name: "Nuclei",
48
Organization: "ProjectDiscovery",
49
Product: "Nuclei",
50
ShortDescription: &sarif.MultiformatMessageString{
51
Text: "Fast and Customizable Vulnerability Scanner",
52
},
53
FullDescription: &sarif.MultiformatMessageString{
54
Text: "Fast and customizable vulnerability scanner based on simple YAML based DSL",
55
},
56
FullName: "Nuclei " + config.Version,
57
SemanticVersion: config.Version,
58
DownloadURI: "https://github.com/projectdiscovery/nuclei/releases",
59
Rules: exporter.rules,
60
}
61
exporter.sarif.RegisterTool(driver)
62
63
reportLocation := sarif.ArtifactLocation{
64
Uri: "file:///" + exporter.options.File,
65
Description: &sarif.Message{
66
Text: "Nuclei Sarif Report",
67
},
68
}
69
70
invocation := sarif.Invocation{
71
CommandLine: os.Args[0],
72
Arguments: os.Args[1:],
73
ResponseFiles: []sarif.ArtifactLocation{reportLocation},
74
}
75
exporter.sarif.RegisterToolInvocation(invocation)
76
}
77
78
// getSeverity in terms of sarif
79
func (exporter *Exporter) getSeverity(severity string) (sarif.Level, string) {
80
switch severity {
81
case "critical":
82
return sarif.Error, "9.4"
83
case "high":
84
return sarif.Error, "8"
85
case "medium":
86
return sarif.Note, "5"
87
case "low":
88
return sarif.Note, "2"
89
case "info":
90
return sarif.None, "1"
91
}
92
93
return sarif.None, "9.5"
94
}
95
96
// Export exports a passed result event to sarif structure
97
func (exporter *Exporter) Export(event *output.ResultEvent) error {
98
exporter.mutex.Lock()
99
defer exporter.mutex.Unlock()
100
101
severity := event.Info.SeverityHolder.Severity.String()
102
resultHeader := fmt.Sprintf("%v (%v) found on %v", event.Info.Name, event.TemplateID, event.Host)
103
resultLevel, vulnRating := exporter.getSeverity(severity)
104
105
// Extra metadata if generated sarif is uploaded to GitHub security page
106
ghMeta := map[string]interface{}{}
107
ghMeta["tags"] = []string{"security"}
108
ghMeta["security-severity"] = vulnRating
109
110
// rule contain details of template
111
rule := sarif.ReportingDescriptor{
112
Id: event.TemplateID,
113
Name: event.Info.Name,
114
FullDescription: &sarif.MultiformatMessageString{
115
// Points to template URL
116
Text: event.Info.Description + "\nMore details at\n" + event.TemplateURL + "\n",
117
},
118
Properties: ghMeta,
119
}
120
121
// GitHub Uses ShortDescription as title
122
if event.Info.Description != "" {
123
rule.ShortDescription = &sarif.MultiformatMessageString{
124
Text: resultHeader,
125
}
126
}
127
128
// If rule is added
129
ruleIndex := int(math.Max(0, float64(len(exporter.rules)-1)))
130
if exporter.rulemap[rule.Id] == nil {
131
exporter.rulemap[rule.Id] = &ruleIndex
132
exporter.rules = append(exporter.rules, rule)
133
} else {
134
ruleIndex = *exporter.rulemap[rule.Id]
135
}
136
137
// vulnerability target/location
138
location := sarif.Location{
139
Message: &sarif.Message{
140
Text: path.Join(event.Host, event.Path),
141
},
142
PhysicalLocation: sarif.PhysicalLocation{
143
ArtifactLocation: sarif.ArtifactLocation{
144
// GitHub only accepts file:// protocol and local & relative files only
145
// to avoid errors // is used which also translates to file according to specification
146
Uri: "/" + event.Path,
147
Description: &sarif.Message{
148
Text: path.Join(event.Host, event.Path),
149
},
150
},
151
},
152
}
153
154
// vulnerability report/result
155
result := &sarif.Result{
156
RuleId: rule.Id,
157
RuleIndex: ruleIndex,
158
Level: resultLevel,
159
Kind: sarif.Open,
160
Message: &sarif.Message{
161
Text: resultHeader,
162
},
163
Locations: []sarif.Location{location},
164
Rule: sarif.ReportingDescriptorReference{
165
Id: rule.Id,
166
},
167
}
168
169
exporter.sarif.RegisterResult(*result)
170
171
return nil
172
173
}
174
175
// Close Writes data and closes the exporter after operation
176
func (exporter *Exporter) Close() error {
177
exporter.mutex.Lock()
178
defer exporter.mutex.Unlock()
179
180
if len(exporter.rules) == 0 {
181
// no output if there are no results
182
return nil
183
}
184
// links results and rules/templates
185
exporter.addToolDetails()
186
187
bin, err := exporter.sarif.Export()
188
if err != nil {
189
return errors.Wrap(err, "failed to generate sarif report")
190
}
191
if err := os.WriteFile(exporter.options.File, bin, 0644); err != nil {
192
return errors.Wrap(err, "failed to create sarif file")
193
}
194
195
return nil
196
}
197
198