diff --git a/README.md b/README.md index 373d8aa4..596d9ce3 100644 --- a/README.md +++ b/README.md @@ -99,7 +99,7 @@ To define the test case for ffuf, use the keyword `FUZZ` anywhere in the URL (`- -o string Write output to file -of string - Output file format. Available formats: json (default "json") + Output file format. Available formats: json, 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" -s Do not print additional information (silent mode) diff --git a/main.go b/main.go index 57a78d52..9a4f11f8 100644 --- a/main.go +++ b/main.go @@ -67,7 +67,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") + flag.StringVar(&opts.outputFormat, "of", "json", "Output file format. Available formats: json, csv, ecsv") flag.BoolVar(&conf.Quiet, "s", false, "Do not print additional information (silent mode)") flag.BoolVar(&conf.StopOn403, "sf", false, "Stop when > 90% of responses return 403 Forbidden") flag.IntVar(&conf.Threads, "t", 40, "Number of concurrent threads.") @@ -186,7 +186,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"} + outputFormats := []string{"json", "csv", "ecsv"} found := false for _, f := range outputFormats { if f == parseOpts.outputFormat { diff --git a/pkg/output/file_csv.go b/pkg/output/file_csv.go new file mode 100644 index 00000000..57e3ca4b --- /dev/null +++ b/pkg/output/file_csv.go @@ -0,0 +1,51 @@ +package output + +import ( + "encoding/base64" + "encoding/csv" + "os" + "strconv" + + "github.com/ffuf/ffuf/pkg/ffuf" +) + +var header = []string{"input", "status_code", "content_length", "content_words"} + +func writeCSV(config *ffuf.Config, res []Result, encode bool) error { + f, err := os.Create(config.OutputFile) + if err != nil { + return err + } + defer f.Close() + + w := csv.NewWriter(f) + defer w.Flush() + + if err := w.Write(header); err != nil { + return err + } + for _, r := range res { + if encode { + r.Input = base64encode(r.Input) + } + + err := w.Write(toCSV(r)) + if err != nil { + return err + } + } + return nil +} + +func base64encode(in string) string { + return base64.StdEncoding.EncodeToString([]byte(in)) +} + +func toCSV(r Result) []string { + return []string{ + r.Input, + strconv.FormatInt(r.StatusCode, 10), + strconv.FormatInt(r.ContentLength, 10), + strconv.FormatInt(r.ContentWords, 10), + } +} diff --git a/pkg/output/stdout.go b/pkg/output/stdout.go index 1424656f..8a897d08 100644 --- a/pkg/output/stdout.go +++ b/pkg/output/stdout.go @@ -90,6 +90,10 @@ 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 == "csv" { + err = writeCSV(s.config, s.Results, false) + } else if s.config.OutputFormat == "ecsv" { + err = writeCSV(s.config, s.Results, true) } if err != nil { s.Error(fmt.Sprintf("%s", err))