diff --git a/v2/internal/runner/options.go b/v2/internal/runner/options.go index 5f252ca6..fa45169f 100644 --- a/v2/internal/runner/options.go +++ b/v2/internal/runner/options.go @@ -29,6 +29,7 @@ type Options struct { UpdateTemplates bool // UpdateTemplates updates the templates installed at startup TemplatesDirectory string // TemplatesDirectory is the directory to use for storing templates JSON bool // JSON writes json output to files + JSONRequests bool // write requests/responses for matches in JSON output Stdin bool // Stdin specifies whether stdin input was given to the process } @@ -66,6 +67,7 @@ func ParseOptions() *Options { flag.BoolVar(&options.UpdateTemplates, "update-templates", false, "Update Templates updates the installed templates (optional)") flag.StringVar(&options.TemplatesDirectory, "update-directory", "", "Directory to use for storing nuclei-templates") flag.BoolVar(&options.JSON, "json", false, "Write json output to files") + flag.BoolVar(&options.JSONRequests, "json-requests", false, "Write requests/responses for matches in JSON output") flag.Parse() diff --git a/v2/internal/runner/runner.go b/v2/internal/runner/runner.go index c35c85aa..5a29fe2d 100644 --- a/v2/internal/runner/runner.go +++ b/v2/internal/runner/runner.go @@ -288,6 +288,7 @@ func (r *Runner) processTemplateWithList(template *templates.Template, request i ProxySocksURL: r.options.ProxySocksURL, CustomHeaders: r.options.CustomHeaders, JSON: r.options.JSON, + JSONRequests: r.options.JSONRequests, CookieReuse: value.CookieReuse, }) } diff --git a/v2/pkg/executer/executer_http.go b/v2/pkg/executer/executer_http.go index 6e550db4..b4a5d934 100644 --- a/v2/pkg/executer/executer_http.go +++ b/v2/pkg/executer/executer_http.go @@ -30,6 +30,7 @@ type HTTPExecuter struct { debug bool Results bool jsonOutput bool + jsonRequest bool httpClient *retryablehttp.Client template *templates.Template bulkHttpRequest *requests.BulkHTTPRequest @@ -50,6 +51,7 @@ type HTTPOptions struct { ProxySocksURL string Debug bool JSON bool + JSONRequests bool CustomHeaders requests.CustomHeaders CookieReuse bool CookieJar *cookiejar.Jar @@ -84,6 +86,7 @@ func NewHTTPExecuter(options *HTTPOptions) (*HTTPExecuter, error) { executer := &HTTPExecuter{ debug: options.Debug, jsonOutput: options.JSON, + jsonRequest: options.JSONRequests, httpClient: client, template: options.Template, bulkHttpRequest: options.BulkHttpRequest, @@ -187,7 +190,7 @@ func (e *HTTPExecuter) handleHTTP(URL string, request *requests.HttpRequest, dyn result.Matches[matcher.Name] = nil // probably redundant but ensures we snapshot current payload values when matchers are valid result.Meta = request.Meta - e.writeOutputHTTP(request, matcher, nil) + e.writeOutputHTTP(request, resp, body, matcher, nil) e.Results = true } } @@ -211,7 +214,7 @@ func (e *HTTPExecuter) handleHTTP(URL string, request *requests.HttpRequest, dyn // Write a final string of output if matcher type is // AND or if we have extractors for the mechanism too. if len(e.bulkHttpRequest.Extractors) > 0 || matcherCondition == matchers.ANDCondition { - e.writeOutputHTTP(request, nil, extractorResults) + e.writeOutputHTTP(request, resp, body, nil, extractorResults) e.Results = true } diff --git a/v2/pkg/executer/http_utils.go b/v2/pkg/executer/http_utils.go index 8863d132..3dc2b843 100644 --- a/v2/pkg/executer/http_utils.go +++ b/v2/pkg/executer/http_utils.go @@ -15,6 +15,8 @@ type jsonOutput struct { Severity string `json:"severity"` Author string `json:"author"` Description string `json:"description"` + Request string `json:"request,omitempty"` + Response string `json:"response,omitempty"` } // unsafeToString converts byte slice to string with zero allocations diff --git a/v2/pkg/executer/output_http.go b/v2/pkg/executer/output_http.go index 066bde06..78b9e843 100644 --- a/v2/pkg/executer/output_http.go +++ b/v2/pkg/executer/output_http.go @@ -1,6 +1,8 @@ package executer import ( + "net/http" + "net/http/httputil" "strings" jsoniter "github.com/json-iterator/go" @@ -10,7 +12,7 @@ import ( ) // writeOutputHTTP writes http output to streams -func (e *HTTPExecuter) writeOutputHTTP(req *requests.HttpRequest, matcher *matchers.Matcher, extractorResults []string) { +func (e *HTTPExecuter) writeOutputHTTP(req *requests.HttpRequest, resp *http.Response, body string, matcher *matchers.Matcher, extractorResults []string) { URL := req.Request.URL.String() if e.jsonOutput { @@ -28,6 +30,21 @@ func (e *HTTPExecuter) writeOutputHTTP(req *requests.HttpRequest, matcher *match if len(extractorResults) > 0 { output.ExtractedResults = extractorResults } + if e.jsonRequest { + dumpedRequest, err := httputil.DumpRequest(req.Request.Request, true) + if err != nil { + gologger.Warningf("could not dump request: %s\n", err) + } else { + output.Request = string(dumpedRequest) + } + dumpedResponse, err := httputil.DumpResponse(resp, false) + if err != nil { + gologger.Warningf("could not dump response: %s\n", err) + } else { + output.Response = string(dumpedResponse) + body + } + + } data, err := jsoniter.Marshal(output) if err != nil { gologger.Warningf("Could not marshal json output: %s\n", err)