From 826ebbc21c873d1d686474c9915381f8fc1eb2ba Mon Sep 17 00:00:00 2001 From: SakiiR Date: Fri, 8 Nov 2019 15:18:27 +0100 Subject: [PATCH] Added HTML and Markdown output support (#63) * Added HTML and Markdown output support * Add HTML color code in HTML template * Added changelog entry * Fixed copy paste mistake * Changed the html report to be grepable :) * Grepable output fixed --- README.md | 12 +-- main.go | 4 +- pkg/output/file_html.go | 161 ++++++++++++++++++++++++++++++++++++++++ pkg/output/file_md.go | 51 +++++++++++++ pkg/output/stdout.go | 5 ++ 5 files changed, 226 insertions(+), 7 deletions(-) create mode 100644 pkg/output/file_html.go create mode 100644 pkg/output/file_md.go diff --git a/README.md b/README.md index 04baf82..38b5f6d 100644 --- a/README.md +++ b/README.md @@ -15,11 +15,11 @@ Heavily inspired by the great projects [gobuster](https://github.com/OJ/gobuster ## Features - - Fast! - - Allows fuzzing of HTTP header values, HTTP method, POST data, and different parts of URL, including GET parameter names and values - - Silent mode (`-s`) for clean output that's easy to use in pipes to other processes. - - Modularized architecture that allows integration with existing toolchains with reasonable effort - - Easy-to-add filters and matchers (they are interoperable) +- Fast! +- Allows fuzzing of HTTP header values, POST data, and different parts of URL, including GET parameter names and values +- Silent mode (`-s`) for clean output that's easy to use in pipes to other processes. +- Modularized architecture that allows integration with existing toolchains with reasonable effort +- Easy-to-add filters and matchers (they are interoperable) ## Example cases @@ -193,6 +193,8 @@ The only dependency of ffuf is Go 1.11. No dependencies outside of Go standard l - Changed - New CLI flag: -i, dummy flag that does nothing. for compatibility with copy as curl. - New CLI flag: -b/--cookie, cookie data for compatibility with copy as curl. + - New Output format are available: HTML and Markdown table. + - New CLI flag: -l, shows target location of redirect responses - Filtering and matching by status code, response size or word count now allow using ranges in addition to single values - The internal logging information to be discarded, and can be written to a file with the new `-debug-log` flag. diff --git a/main.go b/main.go index 9971fa4..5d4f5ae 100644 --- a/main.go +++ b/main.go @@ -85,7 +85,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, csv, ecsv") + flag.StringVar(&opts.outputFormat, "of", "json", "Output file format. Available formats: json, 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") @@ -290,7 +290,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", "csv", "ecsv"} + outputFormats := []string{"json", "html", "md", "csv", "ecsv"} found := false for _, f := range outputFormats { if f == parseOpts.outputFormat { diff --git a/pkg/output/file_html.go b/pkg/output/file_html.go new file mode 100644 index 0000000..9df0a5e --- /dev/null +++ b/pkg/output/file_html.go @@ -0,0 +1,161 @@ +package output + +import ( + "html/template" + "os" + "time" + + "github.com/ffuf/ffuf/pkg/ffuf" +) + +type htmlFileOutput struct { + CommandLine string + Time string + Results []Result +} + +const ( + htmlTemplate = ` + + + + + + FFUF Report - + + + + + + + + + +
+
+

+

FFUF Report

+
+ +
{{ .CommandLine }}
+
{{ .Time }}
+ + + +
+|result_raw|StatusCode|Input|Position|ContentLength|ContentWords| +
+ + + + + + + + + + + {{range .Results}} +
+|result_raw|{{ .StatusCode }}|{{ .Input }}|{{ .Position }}|{{ .ContentLength }}|{{ .ContentWords }}| +
+ + {{end}} + +
StatusInputPositionLengthWords
{{ .StatusCode }}{{ .Input }}{{ .Position }}{{ .ContentLength }}{{ .ContentWords }}
+ +
+

+
+
+ + + + + + + + ` +) + +// colorizeResults returns a new slice with HTMLColor attribute +func colorizeResults(results []Result) []Result { + newResults := make([]Result, 0) + + for _, r := range results { + result := r + result.HTMLColor = "black" + + s := result.StatusCode + + if s >= 200 && s <= 299 { + result.HTMLColor = "#adea9e" + } + + if s >= 300 && s <= 399 { + result.HTMLColor = "#bbbbe6" + } + + if s >= 400 && s <= 499 { + result.HTMLColor = "#d2cb7e" + } + + if s >= 500 && s <= 599 { + result.HTMLColor = "#de8dc1" + } + + newResults = append(newResults, result) + } + + return newResults +} + +func writeHTML(config *ffuf.Config, results []Result) error { + + results = colorizeResults(results) + + ti := time.Now() + + outHTML := htmlFileOutput{ + CommandLine: config.CommandLine, + Time: ti.Format(time.RFC3339), + Results: results, + } + + f, err := os.Create(config.OutputFile) + if err != nil { + return err + } + defer f.Close() + + templateName := "output.html" + t := template.New(templateName).Delims("{{", "}}") + t.Parse(htmlTemplate) + t.Execute(f, outHTML) + return nil +} diff --git a/pkg/output/file_md.go b/pkg/output/file_md.go new file mode 100644 index 0000000..cb0e4ce --- /dev/null +++ b/pkg/output/file_md.go @@ -0,0 +1,51 @@ +package output + +import ( + "html/template" + "os" + "time" + + "github.com/ffuf/ffuf/pkg/ffuf" +) + +type markdownFileOutput struct { + CommandLine string + Time string + Results []Result +} + +const ( + markdownTemplate = `# FFUF Report + + Command line : ` + "`{{.CommandLine}}`" + ` + Time: ` + "{{ .Time }}" + ` + + | Input | Position | Status Code | Content Length | Content Words | + | :---- | :------- | :---------- | :------------- | :------------ | + {{range .Results}}| {{ .Input }} | {{ .Position }} | {{ .StatusCode }} | {{ .ContentLength }} | {{ .ContentWords }} | + {{end}} + ` // The template format is not pretty but follows the markdown guide +) + +func writeMarkdown(config *ffuf.Config, res []Result) error { + + ti := time.Now() + + outHTML := htmlFileOutput{ + CommandLine: config.CommandLine, + Time: ti.Format(time.RFC3339), + Results: res, + } + + f, err := os.Create(config.OutputFile) + if err != nil { + return err + } + defer f.Close() + + templateName := "output.md" + t := template.New(templateName).Delims("{{", "}}") + t.Parse(markdownTemplate) + t.Execute(f, outHTML) + return nil +} diff --git a/pkg/output/stdout.go b/pkg/output/stdout.go index c51597e..c30dcae 100644 --- a/pkg/output/stdout.go +++ b/pkg/output/stdout.go @@ -32,6 +32,7 @@ type Result struct { StatusCode int64 `json:"status"` ContentLength int64 `json:"length"` ContentWords int64 `json:"words"` + HTMLColor string `json:"html_color"` } func NewStdoutput(conf *ffuf.Config) *Stdoutput { @@ -108,6 +109,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 == "html" { + err = writeHTML(s.config, s.Results) + } else if s.config.OutputFormat == "md" { + err = writeMarkdown(s.config, s.Results) } else if s.config.OutputFormat == "csv" { err = writeCSV(s.config, s.Results, false) } else if s.config.OutputFormat == "ecsv" {