nuclei/v2/pkg/output/output.go

236 lines
6.8 KiB
Go
Raw Normal View History

package output
import (
"os"
"regexp"
"sync"
"time"
"github.com/pkg/errors"
2020-12-21 06:28:33 +00:00
jsoniter "github.com/json-iterator/go"
"github.com/logrusorgru/aurora"
2021-04-16 11:26:41 +00:00
"github.com/projectdiscovery/interactsh/pkg/server"
"github.com/projectdiscovery/nuclei/v2/internal/colorizer"
"github.com/projectdiscovery/nuclei/v2/pkg/model"
"github.com/projectdiscovery/nuclei/v2/pkg/model/types/severity"
"github.com/projectdiscovery/nuclei/v2/pkg/operators"
)
// 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(*ResultEvent) error
2021-02-02 20:39:45 +00:00
// Request logs a request in the trace log
2020-12-21 06:28:33 +00:00
Request(templateID, url, requestType string, err error)
}
// StandardWriter is a writer writing output to file and screen for results.
type StandardWriter struct {
json bool
jsonReqResp bool
noTimestamp bool
noMetadata bool
aurora aurora.Aurora
outputFile *fileWriter
outputMutex *sync.Mutex
traceFile *fileWriter
traceMutex *sync.Mutex
2021-10-30 09:39:38 +00:00
errorFile *fileWriter
errorMutex *sync.Mutex
severityColors func(severity.Severity) string
}
var decolorizerRegex = regexp.MustCompile(`\x1B\[[0-9;]*[a-zA-Z]`)
// InternalEvent is an internal output generation structure for nuclei.
type InternalEvent map[string]interface{}
// InternalWrappedEvent is a wrapped event with operators result added to it.
type InternalWrappedEvent struct {
InternalEvent InternalEvent
2020-12-25 20:39:16 +00:00
Results []*ResultEvent
OperatorsResult *operators.Result
}
// ResultEvent is a wrapped result event for a single nuclei output.
type ResultEvent struct {
// TemplateID is the ID of the template for the result.
2021-10-18 19:56:21 +00:00
TemplateID string `json:"template-id"`
2021-06-05 12:31:08 +00:00
// TemplatePath is the path of template
TemplatePath string `json:"-"`
// Info contains information block of the template for the result.
Info model.Info `json:"info,inline"`
// MatcherName is the name of the matcher matched if any.
2021-10-18 19:56:21 +00:00
MatcherName string `json:"matcher-name,omitempty"`
2021-01-11 15:41:35 +00:00
// ExtractorName is the name of the extractor matched if any.
2021-10-18 19:56:21 +00:00
ExtractorName string `json:"extractor-name,omitempty"`
// Type is the type of the result event.
Type string `json:"type"`
// Host is the host input on which match was found.
Host string `json:"host,omitempty"`
2021-03-05 13:55:09 +00:00
// Path is the path input on which match was found.
Path string `json:"path,omitempty"`
// Matched contains the matched input in its transformed form.
2021-10-18 19:56:21 +00:00
Matched string `json:"matched-at,omitempty"`
// ExtractedResults contains the extraction result from the inputs.
2021-10-18 19:56:21 +00:00
ExtractedResults []string `json:"extracted-results,omitempty"`
2021-09-07 14:31:46 +00:00
// Request is the optional, dumped request for the match.
Request string `json:"request,omitempty"`
2021-09-07 14:31:46 +00:00
// Response is the optional, dumped response for the match.
Response string `json:"response,omitempty"`
// Metadata contains any optional metadata for the event
Metadata map[string]interface{} `json:"meta,omitempty"`
// IP is the IP address for the found result event.
IP string `json:"ip,omitempty"`
// Timestamp is the time the result was found at.
Timestamp time.Time `json:"timestamp"`
2021-04-16 11:26:41 +00:00
// Interaction is the full details of interactsh interaction.
Interaction *server.Interaction `json:"interaction,omitempty"`
// CURLCommand is an optional curl command to reproduce the request
// Only applicable if the report is for HTTP.
2021-10-18 19:56:21 +00:00
CURLCommand string `json:"curl-command,omitempty"`
FileToIndexPosition map[string]int `json:"-"`
}
// NewStandardWriter creates a new output writer based on user configurations
2021-10-30 09:39:38 +00:00
func NewStandardWriter(colors, noMetadata, noTimestamp, json, jsonReqResp bool, file, traceFile string, errorFile string) (*StandardWriter, error) {
auroraColorizer := 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
}
2021-10-30 09:39:38 +00:00
var errorOutput *fileWriter
if errorFile != "" {
output, err := newFileOutputWriter(errorFile)
if err != nil {
return nil, errors.Wrap(err, "could not create error file")
}
errorOutput = output
}
writer := &StandardWriter{
json: json,
jsonReqResp: jsonReqResp,
noMetadata: noMetadata,
noTimestamp: noTimestamp,
aurora: auroraColorizer,
outputFile: outputFile,
outputMutex: &sync.Mutex{},
traceFile: traceOutput,
traceMutex: &sync.Mutex{},
2021-10-30 09:39:38 +00:00
errorFile: errorOutput,
errorMutex: &sync.Mutex{},
severityColors: colorizer.New(auroraColorizer),
}
return writer, nil
}
// Write writes the event to file and/or screen.
func (w *StandardWriter) Write(event *ResultEvent) error {
2021-03-08 06:13:23 +00:00
event.Timestamp = time.Now()
var data []byte
var err error
if w.json {
data, err = w.formatJSON(event)
} else {
2021-02-26 07:43:11 +00:00
data = w.formatScreen(event)
}
if err != nil {
return errors.Wrap(err, "could not format output")
}
2021-02-05 09:13:11 +00:00
if len(data) == 0 {
return nil
}
_, _ = os.Stdout.Write(data)
2020-12-29 06:12:46 +00:00
_, _ = os.Stdout.Write([]byte("\n"))
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
}
2021-10-30 09:46:26 +00:00
// JSONLogRequest is a trace/error log request written to file
type JSONLogRequest struct {
Template string `json:"template"`
Input string `json:"input"`
Error string `json:"error"`
Type string `json:"type"`
2020-12-21 06:28:33 +00:00
}
// Request writes a log the requests trace log
2021-10-30 09:46:26 +00:00
func (w *StandardWriter) Request(templatePath, input, requestType string, requestErr error) {
2021-10-30 09:39:38 +00:00
if w.traceFile == nil && w.errorFile == nil {
2020-12-21 06:28:33 +00:00
return
}
2021-10-30 09:46:26 +00:00
request := &JSONLogRequest{
Template: templatePath,
Input: input,
Type: requestType,
2020-12-21 06:28:33 +00:00
}
2021-10-30 09:39:38 +00:00
if requestErr != nil {
request.Error = requestErr.Error()
2020-12-21 06:28:33 +00:00
} else {
request.Error = "none"
}
data, err := jsoniter.Marshal(request)
if err != nil {
return
}
2021-10-30 09:39:38 +00:00
if w.traceFile != nil {
w.traceMutex.Lock()
_ = w.traceFile.Write(data)
w.traceMutex.Unlock()
}
if requestErr != nil && w.errorFile != nil {
w.errorMutex.Lock()
_ = w.errorFile.Write(data)
w.errorMutex.Unlock()
}
2020-12-21 06:28:33 +00:00
}
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()
}
2021-10-30 09:39:38 +00:00
if w.errorFile != nil {
w.errorFile.Close()
}
}