diff --git a/README.md b/README.md index 7e538bc..e5e8690 100644 --- a/README.md +++ b/README.md @@ -155,7 +155,7 @@ Usage of ./ffuf: -o string Write output to file -of string - Output file format. Available formats: json, html, md, csv, ecsv (default "json") + Output file format. Available formats: json, ejson, html, md, csv, ecsv (default "json") -p delay Seconds of delay between requests, or a range of random delay. For example "0.1" or "0.1-2.0" -r Follow redirects @@ -193,8 +193,10 @@ The only dependency of ffuf is Go 1.11. No dependencies outside of Go standard l - master - New - Added a new flag to select a multi wordlist operation mode: `--mode`, possible values: `clusterbomb` and `pitchfork`. + - Added a new output file format eJSON, for always base64 encoding the input data. - Changed - Fixed a bug in the default multi wordlist mode + - Fixed JSON output regression, where all the input data was always encoded in base64 - v0.11 diff --git a/main.go b/main.go index 1a6484e..cf87bbf 100644 --- a/main.go +++ b/main.go @@ -92,7 +92,7 @@ func main() { flag.StringVar(&opts.proxyURL, "x", "", "HTTP Proxy URL") flag.StringVar(&conf.Method, "X", "GET", "HTTP method to use") flag.StringVar(&conf.OutputFile, "o", "", "Write output to file") - flag.StringVar(&opts.outputFormat, "of", "json", "Output file format. Available formats: json, html, md, csv, ecsv") + flag.StringVar(&opts.outputFormat, "of", "json", "Output file format. Available formats: json, ejson, html, md, csv, ecsv") flag.BoolVar(&conf.ShowRedirectLocation, "l", false, "Show target location of redirect responses") flag.BoolVar(&conf.Quiet, "s", false, "Do not print additional information (silent mode)") flag.BoolVar(&conf.StopOn403, "sf", false, "Stop when > 95% of responses return 403 Forbidden") @@ -333,7 +333,7 @@ func prepareConfig(parseOpts *cliOptions, conf *ffuf.Config) error { //Check the output file format option if conf.OutputFile != "" { //No need to check / error out if output file isn't defined - outputFormats := []string{"json", "html", "md", "csv", "ecsv"} + outputFormats := []string{"json", "ejson", "html", "md", "csv", "ecsv"} found := false for _, f := range outputFormats { if f == parseOpts.outputFormat { diff --git a/pkg/output/file_json.go b/pkg/output/file_json.go index 2313e54..286fc97 100644 --- a/pkg/output/file_json.go +++ b/pkg/output/file_json.go @@ -8,19 +8,68 @@ import ( "github.com/ffuf/ffuf/pkg/ffuf" ) -type jsonFileOutput struct { +type ejsonFileOutput struct { CommandLine string `json:"commandline"` Time string `json:"time"` Results []Result `json:"results"` } -func writeJSON(config *ffuf.Config, res []Result) error { +type JsonResult struct { + Input map[string]string `json:"input"` + Position int `json:"position"` + StatusCode int64 `json:"status"` + ContentLength int64 `json:"length"` + ContentWords int64 `json:"words"` + ContentLines int64 `json:"lines"` +} + +type jsonFileOutput struct { + CommandLine string `json:"commandline"` + Time string `json:"time"` + Results []JsonResult `json:"results"` +} + +func writeEJSON(config *ffuf.Config, res []Result) error { t := time.Now() - outJSON := jsonFileOutput{ + outJSON := ejsonFileOutput{ CommandLine: config.CommandLine, Time: t.Format(time.RFC3339), Results: res, } + + outBytes, err := json.Marshal(outJSON) + if err != nil { + return err + } + err = ioutil.WriteFile(config.OutputFile, outBytes, 0644) + if err != nil { + return err + } + return nil +} + +func writeJSON(config *ffuf.Config, res []Result) error { + t := time.Now() + jsonRes := make([]JsonResult, 0) + for _, r := range res { + strinput := make(map[string]string) + for k, v := range r.Input { + strinput[k] = string(v) + } + jsonRes = append(jsonRes, JsonResult{ + Input: strinput, + Position: r.Position, + StatusCode: r.StatusCode, + ContentLength: r.ContentLength, + ContentWords: r.ContentWords, + ContentLines: r.ContentLines, + }) + } + outJSON := jsonFileOutput{ + CommandLine: config.CommandLine, + Time: t.Format(time.RFC3339), + Results: jsonRes, + } outBytes, err := json.Marshal(outJSON) if err != nil { return err diff --git a/pkg/output/stdout.go b/pkg/output/stdout.go index e659c9e..c8aecf7 100644 --- a/pkg/output/stdout.go +++ b/pkg/output/stdout.go @@ -110,6 +110,8 @@ func (s *Stdoutput) Finalize() error { if s.config.OutputFile != "" { if s.config.OutputFormat == "json" { err = writeJSON(s.config, s.Results) + } else if s.config.OutputFormat == "ejson" { + err = writeEJSON(s.config, s.Results) } else if s.config.OutputFormat == "html" { err = writeHTML(s.config, s.Results) } else if s.config.OutputFormat == "md" { @@ -192,19 +194,20 @@ func (s *Stdoutput) resultQuiet(resp ffuf.Response) { func (s *Stdoutput) resultMultiline(resp ffuf.Response) { var res_hdr, res_str string - res_str = "%s * %s: %s\n" + res_str = "%s%s * %s: %s\n" res_hdr = fmt.Sprintf("%s[Status: %d, Size: %d, Words: %d, Lines: %d%s]", TERMINAL_CLEAR_LINE, resp.StatusCode, resp.ContentLength, resp.ContentWords, resp.ContentLines, s.addRedirectLocation(resp)) - fmt.Println(s.colorize(res_hdr, resp.StatusCode)) + res_hdr = s.colorize(res_hdr, resp.StatusCode) + reslines := "" for k, v := range resp.Request.Input { if inSlice(k, s.config.CommandKeywords) { // If we're using external command for input, display the position instead of input - fmt.Printf(res_str, TERMINAL_CLEAR_LINE, k, strconv.Itoa(resp.Request.Position)) + reslines = fmt.Sprintf(res_str, reslines, TERMINAL_CLEAR_LINE, k, strconv.Itoa(resp.Request.Position)) } else { // Wordlist input - fmt.Printf(res_str, TERMINAL_CLEAR_LINE, k, v) + reslines = fmt.Sprintf(res_str, reslines, TERMINAL_CLEAR_LINE, k, v) } } - + fmt.Printf("%s\n%s", res_hdr, reslines) } func (s *Stdoutput) resultNormal(resp ffuf.Response) {