2020-12-20 18:34:11 +00:00
|
|
|
package output
|
|
|
|
|
|
|
|
import (
|
|
|
|
"os"
|
2020-12-23 15:16:42 +00:00
|
|
|
"regexp"
|
2020-12-20 18:34:11 +00:00
|
|
|
"sync"
|
2021-01-14 07:51:21 +00:00
|
|
|
"time"
|
2020-12-20 18:34:11 +00:00
|
|
|
|
2020-12-21 06:28:33 +00:00
|
|
|
jsoniter "github.com/json-iterator/go"
|
2020-12-20 18:34:11 +00:00
|
|
|
"github.com/logrusorgru/aurora"
|
|
|
|
"github.com/pkg/errors"
|
2020-12-29 10:08:14 +00:00
|
|
|
"github.com/projectdiscovery/nuclei/v2/internal/colorizer"
|
2020-12-25 07:25:46 +00:00
|
|
|
"github.com/projectdiscovery/nuclei/v2/pkg/operators"
|
2020-12-20 18:34:11 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// 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
|
2020-12-20 18:34:11 +00:00
|
|
|
// Write writes the event to file and/or screen.
|
2020-12-25 15:03:52 +00:00
|
|
|
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)
|
2020-12-20 18:34:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// StandardWriter is a writer writing output to file and screen for results.
|
|
|
|
type StandardWriter struct {
|
2020-12-29 10:08:14 +00:00
|
|
|
json bool
|
|
|
|
noMetadata bool
|
|
|
|
aurora aurora.Aurora
|
|
|
|
outputFile *fileWriter
|
|
|
|
outputMutex *sync.Mutex
|
|
|
|
traceFile *fileWriter
|
|
|
|
traceMutex *sync.Mutex
|
|
|
|
severityColors *colorizer.Colorizer
|
2020-12-20 18:34:11 +00:00
|
|
|
}
|
|
|
|
|
2020-12-23 15:16:42 +00:00
|
|
|
var decolorizerRegex = regexp.MustCompile(`\x1B\[[0-9;]*[a-zA-Z]`)
|
|
|
|
|
2020-12-25 07:25:46 +00:00
|
|
|
// 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
|
2020-12-25 07:25:46 +00:00
|
|
|
OperatorsResult *operators.Result
|
|
|
|
}
|
|
|
|
|
2020-12-25 15:03:52 +00:00
|
|
|
// ResultEvent is a wrapped result event for a single nuclei output.
|
|
|
|
type ResultEvent struct {
|
2020-12-25 07:25:46 +00:00
|
|
|
// TemplateID is the ID of the template for the result.
|
|
|
|
TemplateID string `json:"templateID"`
|
|
|
|
// Info contains information block of the template for the result.
|
2021-02-03 19:39:29 +00:00
|
|
|
Info map[string]interface{} `json:"info,inline"`
|
2020-12-25 07:25:46 +00:00
|
|
|
// MatcherName is the name of the matcher matched if any.
|
|
|
|
MatcherName string `json:"matcher_name,omitempty"`
|
2021-01-11 15:41:35 +00:00
|
|
|
// ExtractorName is the name of the extractor matched if any.
|
|
|
|
ExtractorName string `json:"extractor_name,omitempty"`
|
2020-12-25 07:25:46 +00:00
|
|
|
// 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"`
|
|
|
|
// Matched contains the matched input in its transformed form.
|
|
|
|
Matched string `json:"matched,omitempty"`
|
|
|
|
// ExtractedResults contains the extraction result from the inputs.
|
|
|
|
ExtractedResults []string `json:"extracted_results,omitempty"`
|
|
|
|
// Request is the optional dumped request for the match.
|
|
|
|
Request string `json:"request,omitempty"`
|
|
|
|
// 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"`
|
2021-01-16 06:36:27 +00:00
|
|
|
// IP is the IP address for the found result event.
|
|
|
|
IP string `json:"ip,omitempty"`
|
2021-01-14 07:51:21 +00:00
|
|
|
// Timestamp is the time the result was found at.
|
|
|
|
Timestamp time.Time `json:"timestamp"`
|
2020-12-25 07:25:46 +00:00
|
|
|
}
|
2020-12-20 18:34:11 +00:00
|
|
|
|
|
|
|
// 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) {
|
2020-12-29 10:08:14 +00:00
|
|
|
auroraColorizer := aurora.NewAurora(colors)
|
2020-12-20 18:34:11 +00:00
|
|
|
|
|
|
|
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
|
|
|
|
}
|
2020-12-20 18:34:11 +00:00
|
|
|
writer := &StandardWriter{
|
2020-12-29 10:08:14 +00:00
|
|
|
json: json,
|
|
|
|
noMetadata: noMetadata,
|
|
|
|
aurora: auroraColorizer,
|
|
|
|
outputFile: outputFile,
|
|
|
|
outputMutex: &sync.Mutex{},
|
|
|
|
traceFile: traceOutput,
|
|
|
|
traceMutex: &sync.Mutex{},
|
|
|
|
severityColors: colorizer.New(auroraColorizer),
|
2020-12-20 18:34:11 +00:00
|
|
|
}
|
|
|
|
return writer, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Write writes the event to file and/or screen.
|
2020-12-25 15:03:52 +00:00
|
|
|
func (w *StandardWriter) Write(event *ResultEvent) error {
|
2020-12-20 18:34:11 +00:00
|
|
|
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)
|
2020-12-29 06:12:46 +00:00
|
|
|
_, _ = os.Stdout.Write([]byte("\n"))
|
2020-12-20 18:34:11 +00:00
|
|
|
if w.outputFile != nil {
|
2020-12-23 15:16:42 +00:00
|
|
|
if !w.json {
|
|
|
|
data = decolorizerRegex.ReplaceAll(data, []byte(""))
|
|
|
|
}
|
2020-12-20 18:34:11 +00:00
|
|
|
if writeErr := w.outputFile.Write(data); writeErr != nil {
|
|
|
|
return errors.Wrap(err, "could not write to output")
|
|
|
|
}
|
2020-12-29 06:12:46 +00:00
|
|
|
_ = w.outputFile.Write([]byte("\n"))
|
2020-12-20 18:34:11 +00:00
|
|
|
}
|
|
|
|
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()
|
|
|
|
_ = 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
|
|
|
|
}
|
|
|
|
|
2020-12-20 18:34:11 +00:00
|
|
|
// 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()
|
|
|
|
}
|
2020-12-20 18:34:11 +00:00
|
|
|
}
|