143 lines
4.2 KiB
Go
Raw Normal View History

2021-06-05 18:01:08 +05:30
package sarif
import (
2021-06-05 23:00:59 +05:30
"crypto/sha1"
"encoding/hex"
2021-06-05 18:01:08 +05:30
"os"
"path/filepath"
2021-06-05 20:06:23 +05:30
"strings"
2021-06-05 18:01:08 +05:30
"sync"
"github.com/owenrumney/go-sarif/sarif"
"github.com/pkg/errors"
"github.com/projectdiscovery/nuclei/v2/pkg/model/types/severity"
2021-06-05 18:01:08 +05:30
"github.com/projectdiscovery/nuclei/v2/pkg/output"
"github.com/projectdiscovery/nuclei/v2/pkg/reporting/format"
"github.com/projectdiscovery/nuclei/v2/pkg/utils"
2021-06-05 18:01:08 +05:30
)
2021-06-05 20:08:52 +05:30
// Exporter is an exporter for nuclei sarif output format.
2021-06-05 18:01:08 +05:30
type Exporter struct {
sarif *sarif.Report
run *sarif.Run
mutex *sync.Mutex
2021-06-14 17:14:16 +05:30
home string
options *Options
2021-06-05 18:01:08 +05:30
}
// 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"`
}
2021-09-19 16:26:47 +05:30
// New creates a new sarif exporter integration client based on options.
2021-06-05 18:01:08 +05:30
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 23:00:59 +05:30
2021-06-05 20:06:23 +05:30
home, err := os.UserHomeDir()
if err != nil {
return nil, errors.Wrap(err, "could not get home dir")
}
templatePath := filepath.Join(home, "nuclei-templates")
2021-06-05 20:06:23 +05:30
2021-06-05 18:01:08 +05:30
run := sarif.NewRun("nuclei", "https://github.com/projectdiscovery/nuclei")
return &Exporter{options: options, home: templatePath, sarif: report, run: run, mutex: &sync.Mutex{}}, nil
2021-06-05 18:01:08 +05:30
}
2021-06-05 20:08:52 +05:30
// Export exports a passed result event to sarif structure
func (exporter *Exporter) Export(event *output.ResultEvent) error {
templatePath := strings.TrimPrefix(event.TemplatePath, exporter.home)
2021-06-05 20:06:23 +05:30
2021-06-05 23:00:59 +05:30
h := sha1.New()
2021-08-24 13:35:01 +05:30
_, _ = h.Write([]byte(event.Host))
2021-06-05 23:00:59 +05:30
templateID := event.TemplateID + "-" + hex.EncodeToString(h.Sum(nil))
fullDescription := format.MarkdownDescription(event)
2021-06-05 20:06:23 +05:30
sarifSeverity := getSarifSeverity(event)
var ruleName string
if utils.IsNotBlank(event.Info.Name) {
ruleName = event.Info.Name
2021-06-05 20:06:23 +05:30
}
2021-06-05 18:01:08 +05:30
2021-06-05 20:06:23 +05:30
var templateURL string
if strings.HasPrefix(event.TemplatePath, exporter.home) {
2021-06-05 20:06:23 +05:30
templateURL = "https://github.com/projectdiscovery/nuclei-templates/blob/master" + templatePath
} else {
templateURL = "https://github.com/projectdiscovery/nuclei-templates"
2021-06-05 20:06:23 +05:30
}
2021-06-05 18:01:08 +05:30
var ruleDescription string
if utils.IsNotBlank(event.Info.Description) {
ruleDescription = event.Info.Description
2021-06-05 18:01:08 +05:30
}
exporter.mutex.Lock()
defer exporter.mutex.Unlock()
2021-06-05 20:08:52 +05:30
_ = exporter.run.AddRule(templateID).
2021-06-05 18:01:08 +05:30
WithDescription(ruleName).
2021-06-05 23:00:59 +05:30
WithHelp(fullDescription).
2021-06-05 20:06:23 +05:30
WithHelpURI(templateURL).
2021-06-05 23:00:59 +05:30
WithFullDescription(sarif.NewMultiformatMessageString(ruleDescription))
result := exporter.run.AddResult(templateID).
2021-06-05 23:00:59 +05:30
WithMessage(sarif.NewMessage().WithText(event.Host)).
WithLevel(sarifSeverity)
// Also write file match metadata to file
2021-06-06 17:38:39 +05:30
if event.Type == "file" && (event.FileToIndexPosition != nil && len(event.FileToIndexPosition) > 0) {
for file, line := range event.FileToIndexPosition {
2021-06-06 16:12:54 +05:30
result.WithLocation(sarif.NewLocation().WithMessage(sarif.NewMessage().WithText(ruleName)).WithPhysicalLocation(
sarif.NewPhysicalLocation().
WithArtifactLocation(sarif.NewArtifactLocation().WithUri(file)).
2021-06-06 16:04:06 +05:30
WithRegion(sarif.NewRegion().WithStartColumn(1).WithStartLine(line).WithEndLine(line).WithEndColumn(32)),
))
}
} else {
result.WithLocation(sarif.NewLocation().WithMessage(sarif.NewMessage().WithText(event.Host)).WithPhysicalLocation(
2021-06-05 18:01:08 +05:30
sarif.NewPhysicalLocation().
WithArtifactLocation(sarif.NewArtifactLocation().WithUri("README.md")).
2021-06-05 18:01:08 +05:30
WithRegion(sarif.NewRegion().WithStartColumn(1).WithStartLine(1).WithEndLine(1).WithEndColumn(1)),
))
}
2021-06-05 18:01:08 +05:30
return nil
}
2021-06-05 20:06:23 +05:30
// getSarifSeverity returns the sarif severity
func getSarifSeverity(event *output.ResultEvent) string {
switch event.Info.SeverityHolder.Severity {
case severity.Info:
return "note"
case severity.Low, severity.Medium:
2021-06-05 20:06:23 +05:30
return "warning"
case severity.High, severity.Critical:
2021-06-05 20:06:23 +05:30
return "error"
default:
return "note"
2021-06-05 20:06:23 +05:30
}
}
2021-06-05 18:01:08 +05:30
// Close closes the exporter after operation
func (exporter *Exporter) Close() error {
exporter.mutex.Lock()
defer exporter.mutex.Unlock()
2021-06-05 20:08:52 +05:30
exporter.sarif.AddRun(exporter.run)
if len(exporter.run.Results) == 0 {
2021-06-05 23:00:59 +05:30
return nil // do not write when no results
}
file, err := os.Create(exporter.options.File)
2021-06-05 18:01:08 +05:30
if err != nil {
return errors.Wrap(err, "could not create sarif output file")
}
defer file.Close()
return exporter.sarif.Write(file)
2021-06-05 18:01:08 +05:30
}