diff --git a/v2/cmd/nuclei/main.go b/v2/cmd/nuclei/main.go index d2f8ebb9f..da5a1ddbf 100644 --- a/v2/cmd/nuclei/main.go +++ b/v2/cmd/nuclei/main.go @@ -139,6 +139,7 @@ on extensive configurability, massive extensibility and ease of use.`) flagSet.StringVarP(&options.ProxyURL, "proxy-url", "proxy", "", "URL of the HTTP proxy server"), flagSet.StringVar(&options.ProxySocksURL, "proxy-socks-url", "", "URL of the SOCKS proxy server"), flagSet.StringVarP(&options.TraceLogFile, "trace-log", "tlog", "", "file to write sent requests trace log"), + flagSet.StringVarP(&options.ErrorLogFile, "error-log", "elog", "", "file to write sent requests error log"), flagSet.BoolVar(&options.Version, "version", false, "show nuclei version"), flagSet.BoolVarP(&options.Verbose, "verbose", "v", false, "show verbose output"), flagSet.BoolVar(&options.VerboseVerbose, "vv", false, "display templates loaded for scan"), diff --git a/v2/internal/runner/runner.go b/v2/internal/runner/runner.go index b3f806952..ca74f6a49 100644 --- a/v2/internal/runner/runner.go +++ b/v2/internal/runner/runner.go @@ -215,7 +215,7 @@ func New(options *types.Options) (*Runner, error) { } // Create the output file if asked - outputWriter, err := output.NewStandardWriter(!options.NoColor, options.NoMeta, options.NoTimestamp, options.JSON, options.JSONRequests, options.Output, options.TraceLogFile) + outputWriter, err := output.NewStandardWriter(!options.NoColor, options.NoMeta, options.NoTimestamp, options.JSON, options.JSONRequests, options.Output, options.TraceLogFile, options.ErrorLogFile) if err != nil { return nil, errors.Wrap(err, "could not create output file") } diff --git a/v2/pkg/output/output.go b/v2/pkg/output/output.go index b90be29c3..572045f5a 100644 --- a/v2/pkg/output/output.go +++ b/v2/pkg/output/output.go @@ -41,6 +41,8 @@ type StandardWriter struct { outputMutex *sync.Mutex traceFile *fileWriter traceMutex *sync.Mutex + errorFile *fileWriter + errorMutex *sync.Mutex severityColors func(severity.Severity) string } @@ -97,7 +99,7 @@ type ResultEvent struct { } // NewStandardWriter creates a new output writer based on user configurations -func NewStandardWriter(colors, noMetadata, noTimestamp, json, jsonReqResp bool, file, traceFile string) (*StandardWriter, error) { +func NewStandardWriter(colors, noMetadata, noTimestamp, json, jsonReqResp bool, file, traceFile string, errorFile string) (*StandardWriter, error) { auroraColorizer := aurora.NewAurora(colors) var outputFile *fileWriter @@ -116,6 +118,14 @@ func NewStandardWriter(colors, noMetadata, noTimestamp, json, jsonReqResp bool, } traceOutput = output } + 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, @@ -126,6 +136,8 @@ func NewStandardWriter(colors, noMetadata, noTimestamp, json, jsonReqResp bool, outputMutex: &sync.Mutex{}, traceFile: traceOutput, traceMutex: &sync.Mutex{}, + errorFile: errorOutput, + errorMutex: &sync.Mutex{}, severityColors: colorizer.New(auroraColorizer), } return writer, nil @@ -171,8 +183,8 @@ type JSONTraceRequest struct { } // Request writes a log the requests trace log -func (w *StandardWriter) Request(templateID, url, requestType string, err error) { - if w.traceFile == nil { +func (w *StandardWriter) Request(templateID, url, requestType string, requestErr error) { + if w.traceFile == nil && w.errorFile == nil { return } request := &JSONTraceRequest{ @@ -180,8 +192,8 @@ func (w *StandardWriter) Request(templateID, url, requestType string, err error) URL: url, Type: requestType, } - if err != nil { - request.Error = err.Error() + if requestErr != nil { + request.Error = requestErr.Error() } else { request.Error = "none" } @@ -190,9 +202,18 @@ func (w *StandardWriter) Request(templateID, url, requestType string, err error) if err != nil { return } - w.traceMutex.Lock() - _ = w.traceFile.Write(data) - w.traceMutex.Unlock() + + 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() + } } // Colorizer returns the colorizer instance for writer @@ -208,4 +229,7 @@ func (w *StandardWriter) Close() { if w.traceFile != nil { w.traceFile.Close() } + if w.errorFile != nil { + w.errorFile.Close() + } } diff --git a/v2/pkg/types/types.go b/v2/pkg/types/types.go index d258f4245..dcab9eae2 100644 --- a/v2/pkg/types/types.go +++ b/v2/pkg/types/types.go @@ -57,6 +57,8 @@ type Options struct { TemplatesDirectory string // TraceLogFile specifies a file to write with the trace of all requests TraceLogFile string + // ErrorLogFile specifies a file to write with the errors of all requests + ErrorLogFile string // ReportingDB is the db for report storage as well as deduplication ReportingDB string // ReportingConfig is the config file for nuclei reporting module