json-request option for request/response output in JSON matches

dev
Florian Pfitzer 2020-07-15 13:38:45 +02:00
parent 94ba9acbbf
commit b3fb45d381
5 changed files with 28 additions and 3 deletions

View File

@ -29,6 +29,7 @@ type Options struct {
UpdateTemplates bool // UpdateTemplates updates the templates installed at startup UpdateTemplates bool // UpdateTemplates updates the templates installed at startup
TemplatesDirectory string // TemplatesDirectory is the directory to use for storing templates TemplatesDirectory string // TemplatesDirectory is the directory to use for storing templates
JSON bool // JSON writes json output to files 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 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.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.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.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() flag.Parse()

View File

@ -288,6 +288,7 @@ func (r *Runner) processTemplateWithList(template *templates.Template, request i
ProxySocksURL: r.options.ProxySocksURL, ProxySocksURL: r.options.ProxySocksURL,
CustomHeaders: r.options.CustomHeaders, CustomHeaders: r.options.CustomHeaders,
JSON: r.options.JSON, JSON: r.options.JSON,
JSONRequests: r.options.JSONRequests,
CookieReuse: value.CookieReuse, CookieReuse: value.CookieReuse,
}) })
} }

View File

@ -30,6 +30,7 @@ type HTTPExecuter struct {
debug bool debug bool
Results bool Results bool
jsonOutput bool jsonOutput bool
jsonRequest bool
httpClient *retryablehttp.Client httpClient *retryablehttp.Client
template *templates.Template template *templates.Template
bulkHttpRequest *requests.BulkHTTPRequest bulkHttpRequest *requests.BulkHTTPRequest
@ -50,6 +51,7 @@ type HTTPOptions struct {
ProxySocksURL string ProxySocksURL string
Debug bool Debug bool
JSON bool JSON bool
JSONRequests bool
CustomHeaders requests.CustomHeaders CustomHeaders requests.CustomHeaders
CookieReuse bool CookieReuse bool
CookieJar *cookiejar.Jar CookieJar *cookiejar.Jar
@ -84,6 +86,7 @@ func NewHTTPExecuter(options *HTTPOptions) (*HTTPExecuter, error) {
executer := &HTTPExecuter{ executer := &HTTPExecuter{
debug: options.Debug, debug: options.Debug,
jsonOutput: options.JSON, jsonOutput: options.JSON,
jsonRequest: options.JSONRequests,
httpClient: client, httpClient: client,
template: options.Template, template: options.Template,
bulkHttpRequest: options.BulkHttpRequest, bulkHttpRequest: options.BulkHttpRequest,
@ -187,7 +190,7 @@ func (e *HTTPExecuter) handleHTTP(URL string, request *requests.HttpRequest, dyn
result.Matches[matcher.Name] = nil result.Matches[matcher.Name] = nil
// probably redundant but ensures we snapshot current payload values when matchers are valid // probably redundant but ensures we snapshot current payload values when matchers are valid
result.Meta = request.Meta result.Meta = request.Meta
e.writeOutputHTTP(request, matcher, nil) e.writeOutputHTTP(request, resp, body, matcher, nil)
e.Results = true 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 // Write a final string of output if matcher type is
// AND or if we have extractors for the mechanism too. // AND or if we have extractors for the mechanism too.
if len(e.bulkHttpRequest.Extractors) > 0 || matcherCondition == matchers.ANDCondition { if len(e.bulkHttpRequest.Extractors) > 0 || matcherCondition == matchers.ANDCondition {
e.writeOutputHTTP(request, nil, extractorResults) e.writeOutputHTTP(request, resp, body, nil, extractorResults)
e.Results = true e.Results = true
} }

View File

@ -15,6 +15,8 @@ type jsonOutput struct {
Severity string `json:"severity"` Severity string `json:"severity"`
Author string `json:"author"` Author string `json:"author"`
Description string `json:"description"` Description string `json:"description"`
Request string `json:"request,omitempty"`
Response string `json:"response,omitempty"`
} }
// unsafeToString converts byte slice to string with zero allocations // unsafeToString converts byte slice to string with zero allocations

View File

@ -1,6 +1,8 @@
package executer package executer
import ( import (
"net/http"
"net/http/httputil"
"strings" "strings"
jsoniter "github.com/json-iterator/go" jsoniter "github.com/json-iterator/go"
@ -10,7 +12,7 @@ import (
) )
// writeOutputHTTP writes http output to streams // 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() URL := req.Request.URL.String()
if e.jsonOutput { if e.jsonOutput {
@ -28,6 +30,21 @@ func (e *HTTPExecuter) writeOutputHTTP(req *requests.HttpRequest, matcher *match
if len(extractorResults) > 0 { if len(extractorResults) > 0 {
output.ExtractedResults = extractorResults 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) data, err := jsoniter.Marshal(output)
if err != nil { if err != nil {
gologger.Warningf("Could not marshal json output: %s\n", err) gologger.Warningf("Could not marshal json output: %s\n", err)