nuclei/v2/pkg/output/output.go

160 lines
3.8 KiB
Go
Raw Normal View History

package output
import (
"os"
"regexp"
"sync"
2020-12-21 06:28:33 +00:00
jsoniter "github.com/json-iterator/go"
"github.com/logrusorgru/aurora"
"github.com/pkg/errors"
)
// Writer is an interface which writes output to somewhere for nuclei events.
type Writer interface {
// Close closes the output writer interface
Close()
2020-12-21 06:34:33 +00:00
// Colorizer returns the colorizer instance for writer
Colorizer() aurora.Aurora
// Write writes the event to file and/or screen.
Write(Event) error
2020-12-21 06:28:33 +00:00
// Request writes a log the requests trace log
Request(templateID, url, requestType string, err error)
}
// StandardWriter is a writer writing output to file and screen for results.
type StandardWriter struct {
json bool
noMetadata bool
aurora aurora.Aurora
outputFile *fileWriter
2020-12-21 06:28:33 +00:00
outputMutex *sync.Mutex
traceFile *fileWriter
traceMutex *sync.Mutex
severityMap map[string]string
}
const (
fgOrange uint8 = 208
undefined string = "undefined"
)
var decolorizerRegex = regexp.MustCompile(`\x1B\[[0-9;]*[a-zA-Z]`)
// Event is a single output structure from nuclei.
type Event map[string]interface{}
// NewStandardWriter creates a new output writer based on user configurations
2020-12-21 06:28:33 +00:00
func NewStandardWriter(colors, noMetadata, json bool, file, traceFile string) (*StandardWriter, error) {
colorizer := aurora.NewAurora(colors)
var outputFile *fileWriter
if file != "" {
output, err := newFileOutputWriter(file)
if err != nil {
return nil, errors.Wrap(err, "could not create output file")
}
outputFile = output
}
2020-12-21 06:28:33 +00:00
var traceOutput *fileWriter
if traceFile != "" {
output, err := newFileOutputWriter(traceFile)
if err != nil {
return nil, errors.Wrap(err, "could not create output file")
}
traceOutput = output
}
severityMap := map[string]string{
"info": colorizer.Blue("info").String(),
"low": colorizer.Green("low").String(),
"medium": colorizer.Yellow("medium").String(),
"high": colorizer.Index(fgOrange, "high").String(),
"critical": colorizer.Red("critical").String(),
}
writer := &StandardWriter{
json: json,
noMetadata: noMetadata,
severityMap: severityMap,
aurora: colorizer,
outputFile: outputFile,
2020-12-21 06:28:33 +00:00
outputMutex: &sync.Mutex{},
traceFile: traceOutput,
traceMutex: &sync.Mutex{},
}
return writer, nil
}
// Write writes the event to file and/or screen.
func (w *StandardWriter) Write(event Event) error {
var data []byte
var err error
if w.json {
data, err = w.formatJSON(event)
} else {
data, err = w.formatScreen(event)
}
if err != nil {
return errors.Wrap(err, "could not format output")
}
_, _ = os.Stdout.Write(data)
if w.outputFile != nil {
if !w.json {
data = decolorizerRegex.ReplaceAll(data, []byte(""))
}
if writeErr := w.outputFile.Write(data); writeErr != nil {
return errors.Wrap(err, "could not write to output")
}
}
return nil
}
2020-12-21 06:28:33 +00:00
// JSONTraceRequest is a trace log request written to file
type JSONTraceRequest struct {
ID string `json:"id"`
URL string `json:"url"`
Error string `json:"error"`
Type string `json:"type"`
}
// Request writes a log the requests trace log
func (w *StandardWriter) Request(templateID, url, requestType string, err error) {
if w.traceFile == nil {
return
}
request := &JSONTraceRequest{
ID: templateID,
URL: url,
Type: requestType,
}
if err != nil {
request.Error = err.Error()
} else {
request.Error = "none"
}
data, err := jsoniter.Marshal(request)
if err != nil {
return
}
w.traceMutex.Lock()
//nolint:errcheck // We don't need to do anything here
_ = w.traceFile.Write(data)
w.traceMutex.Unlock()
}
2020-12-21 06:34:33 +00:00
// Colorizer returns the colorizer instance for writer
func (w *StandardWriter) Colorizer() aurora.Aurora {
return w.aurora
}
// Close closes the output writing interface
func (w *StandardWriter) Close() {
2020-12-21 06:28:33 +00:00
if w.outputFile != nil {
w.outputFile.Close()
}
if w.traceFile != nil {
w.traceFile.Close()
}
}