2021-06-05 12:31:08 +00:00
|
|
|
package sarif
|
|
|
|
|
|
|
|
import (
|
2021-06-05 17:30:59 +00:00
|
|
|
"crypto/sha1"
|
|
|
|
"encoding/hex"
|
2021-06-05 12:31:08 +00:00
|
|
|
"os"
|
2021-06-05 14:36:23 +00:00
|
|
|
"path"
|
|
|
|
"strings"
|
2021-06-05 12:31:08 +00:00
|
|
|
"sync"
|
|
|
|
|
|
|
|
"github.com/owenrumney/go-sarif/sarif"
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/projectdiscovery/nuclei/v2/pkg/output"
|
|
|
|
"github.com/projectdiscovery/nuclei/v2/pkg/reporting/format"
|
|
|
|
)
|
|
|
|
|
2021-06-05 14:38:52 +00:00
|
|
|
// Exporter is an exporter for nuclei sarif output format.
|
2021-06-05 12:31:08 +00:00
|
|
|
type Exporter struct {
|
|
|
|
sarif *sarif.Report
|
|
|
|
run *sarif.Run
|
|
|
|
mutex *sync.Mutex
|
|
|
|
|
2021-06-14 11:44:16 +00:00
|
|
|
home string
|
|
|
|
options *Options
|
2021-06-05 12:31:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Options contains the configuration options for sarif exporter client
|
|
|
|
type Options struct {
|
|
|
|
// File is the file to export found sarif result to
|
|
|
|
File string `yaml:"file"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// New creates a new disk exporter integration client based on options.
|
|
|
|
func New(options *Options) (*Exporter, error) {
|
|
|
|
report, err := sarif.New(sarif.Version210)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "could not create sarif exporter")
|
|
|
|
}
|
2021-06-05 17:30:59 +00:00
|
|
|
|
2021-06-05 14:36:23 +00:00
|
|
|
home, err := os.UserHomeDir()
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "could not get home dir")
|
|
|
|
}
|
|
|
|
templatePath := path.Join(home, "nuclei-templates")
|
|
|
|
|
2021-06-05 12:31:08 +00:00
|
|
|
run := sarif.NewRun("nuclei", "https://github.com/projectdiscovery/nuclei")
|
2021-06-05 17:39:08 +00:00
|
|
|
return &Exporter{options: options, home: templatePath, sarif: report, run: run, mutex: &sync.Mutex{}}, nil
|
2021-06-05 12:31:08 +00:00
|
|
|
}
|
|
|
|
|
2021-06-05 14:38:52 +00:00
|
|
|
// Export exports a passed result event to sarif structure
|
2021-06-05 12:31:08 +00:00
|
|
|
func (i *Exporter) Export(event *output.ResultEvent) error {
|
2021-06-05 14:36:23 +00:00
|
|
|
templatePath := strings.TrimPrefix(event.TemplatePath, i.home)
|
|
|
|
|
2021-06-05 17:30:59 +00:00
|
|
|
h := sha1.New()
|
|
|
|
h.Write([]byte(event.Host))
|
|
|
|
templateID := event.TemplateID + "-" + hex.EncodeToString(h.Sum(nil))
|
|
|
|
|
|
|
|
fullDescription := format.MarkdownDescription(event)
|
2021-06-05 14:36:23 +00:00
|
|
|
sarifSeverity := getSarifSeverity(event)
|
|
|
|
|
|
|
|
var ruleName string
|
|
|
|
if s, ok := event.Info["name"]; ok {
|
|
|
|
ruleName = s.(string)
|
|
|
|
}
|
2021-06-05 12:31:08 +00:00
|
|
|
|
2021-06-05 14:36:23 +00:00
|
|
|
var templateURL string
|
|
|
|
if strings.HasPrefix(event.TemplatePath, i.home) {
|
|
|
|
templateURL = "https://github.com/projectdiscovery/nuclei-templates/blob/master" + templatePath
|
2021-06-06 10:22:13 +00:00
|
|
|
} else {
|
|
|
|
templateURL = "https://github.com/projectdiscovery/nuclei-templates"
|
2021-06-05 14:36:23 +00:00
|
|
|
}
|
2021-06-05 12:31:08 +00:00
|
|
|
|
|
|
|
var ruleDescription string
|
|
|
|
if d, ok := event.Info["description"]; ok {
|
|
|
|
ruleDescription = d.(string)
|
|
|
|
}
|
|
|
|
|
2021-06-05 14:38:52 +00:00
|
|
|
i.mutex.Lock()
|
|
|
|
defer i.mutex.Unlock()
|
|
|
|
|
2021-06-05 17:30:59 +00:00
|
|
|
_ = i.run.AddRule(templateID).
|
2021-06-05 12:31:08 +00:00
|
|
|
WithDescription(ruleName).
|
2021-06-05 17:30:59 +00:00
|
|
|
WithHelp(fullDescription).
|
2021-06-05 14:36:23 +00:00
|
|
|
WithHelpURI(templateURL).
|
2021-06-05 17:30:59 +00:00
|
|
|
WithFullDescription(sarif.NewMultiformatMessageString(ruleDescription))
|
2021-06-06 10:22:13 +00:00
|
|
|
result := i.run.AddResult(templateID).
|
2021-06-05 17:30:59 +00:00
|
|
|
WithMessage(sarif.NewMessage().WithText(event.Host)).
|
2021-06-06 10:22:13 +00:00
|
|
|
WithLevel(sarifSeverity)
|
|
|
|
|
|
|
|
// Also write file match metadata to file
|
2021-06-06 12:08:39 +00:00
|
|
|
if event.Type == "file" && (event.FileToIndexPosition != nil && len(event.FileToIndexPosition) > 0) {
|
2021-06-06 10:22:13 +00:00
|
|
|
for file, line := range event.FileToIndexPosition {
|
2021-06-06 10:42:54 +00:00
|
|
|
result.WithLocation(sarif.NewLocation().WithMessage(sarif.NewMessage().WithText(ruleName)).WithPhysicalLocation(
|
2021-06-06 10:22:13 +00:00
|
|
|
sarif.NewPhysicalLocation().
|
|
|
|
WithArtifactLocation(sarif.NewArtifactLocation().WithUri(file)).
|
2021-06-06 10:34:06 +00:00
|
|
|
WithRegion(sarif.NewRegion().WithStartColumn(1).WithStartLine(line).WithEndLine(line).WithEndColumn(32)),
|
2021-06-06 10:22:13 +00:00
|
|
|
))
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
result.WithLocation(sarif.NewLocation().WithMessage(sarif.NewMessage().WithText(event.Host)).WithPhysicalLocation(
|
2021-06-05 12:31:08 +00:00
|
|
|
sarif.NewPhysicalLocation().
|
2021-06-05 18:12:37 +00:00
|
|
|
WithArtifactLocation(sarif.NewArtifactLocation().WithUri("README.md")).
|
2021-06-05 12:31:08 +00:00
|
|
|
WithRegion(sarif.NewRegion().WithStartColumn(1).WithStartLine(1).WithEndLine(1).WithEndColumn(1)),
|
|
|
|
))
|
2021-06-06 10:22:13 +00:00
|
|
|
}
|
2021-06-05 12:31:08 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-06-05 14:36:23 +00:00
|
|
|
// getSarifSeverity returns the sarif severity
|
|
|
|
func getSarifSeverity(event *output.ResultEvent) string {
|
|
|
|
var ruleSeverity string
|
|
|
|
if s, ok := event.Info["severity"]; ok {
|
|
|
|
ruleSeverity = s.(string)
|
|
|
|
}
|
|
|
|
|
|
|
|
switch ruleSeverity {
|
|
|
|
case "info":
|
2021-06-05 17:39:08 +00:00
|
|
|
return "note"
|
2021-06-05 14:36:23 +00:00
|
|
|
case "low", "medium":
|
|
|
|
return "warning"
|
|
|
|
case "high", "critical":
|
|
|
|
return "error"
|
|
|
|
default:
|
2021-06-05 17:39:08 +00:00
|
|
|
return "note"
|
2021-06-05 14:36:23 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-05 12:31:08 +00:00
|
|
|
// Close closes the exporter after operation
|
|
|
|
func (i *Exporter) Close() error {
|
2021-06-05 14:38:52 +00:00
|
|
|
i.mutex.Lock()
|
|
|
|
defer i.mutex.Unlock()
|
|
|
|
|
2021-06-05 12:31:08 +00:00
|
|
|
i.sarif.AddRun(i.run)
|
2021-06-05 17:30:59 +00:00
|
|
|
if len(i.run.Results) == 0 {
|
|
|
|
return nil // do not write when no results
|
|
|
|
}
|
2021-06-05 12:31:08 +00:00
|
|
|
file, err := os.Create(i.options.File)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "could not create sarif output file")
|
|
|
|
}
|
|
|
|
defer file.Close()
|
|
|
|
return i.sarif.Write(file)
|
|
|
|
}
|