mirror of
https://github.com/ffuf/ffuf
synced 2024-11-10 06:04:17 +00:00
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
This commit is contained in:
parent
8d057ea177
commit
826ebbc21c
5 changed files with 226 additions and 7 deletions
12
README.md
12
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.
|
||||
|
||||
|
|
4
main.go
4
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 {
|
||||
|
|
161
pkg/output/file_html.go
Normal file
161
pkg/output/file_html.go
Normal file
|
@ -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 = `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1, maximum-scale=1.0"
|
||||
/>
|
||||
<title>FFUF Report - </title>
|
||||
|
||||
<!-- CSS -->
|
||||
<link
|
||||
href="https://fonts.googleapis.com/icon?family=Material+Icons"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css"
|
||||
/>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<nav>
|
||||
<div class="nav-wrapper">
|
||||
<a href="#" class="brand-logo">FFUF</a>
|
||||
<ul id="nav-mobile" class="right hide-on-med-and-down">
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main class="section no-pad-bot" id="index-banner">
|
||||
<div class="container">
|
||||
<br /><br />
|
||||
<h1 class="header center ">FFUF Report</h1>
|
||||
<div class="row center">
|
||||
|
||||
<pre>{{ .CommandLine }}</pre>
|
||||
<pre>{{ .Time }}</pre>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<div style="display:none">
|
||||
|result_raw|StatusCode|Input|Position|ContentLength|ContentWords|
|
||||
</div>
|
||||
<tr>
|
||||
<th>Status</th>
|
||||
<th>Input</th>
|
||||
<th>Position</th>
|
||||
<th>Length</th>
|
||||
<th>Words</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{{range .Results}}
|
||||
<div style="display:none">
|
||||
|result_raw|{{ .StatusCode }}|{{ .Input }}|{{ .Position }}|{{ .ContentLength }}|{{ .ContentWords }}|
|
||||
</div>
|
||||
<tr class="result-{{ .StatusCode }}" style="background-color: {{.HTMLColor}};"><td><font color="black" class="status-code">{{ .StatusCode }}</font></td><td>{{ .Input }}</td><td>{{ .Position }}</td><td>{{ .ContentLength }}</td><td>{{ .ContentWords }}</td></tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
<br /><br />
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!--JavaScript at end of body for optimized loading-->
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js"></script>
|
||||
<style>
|
||||
body {
|
||||
display: flex;
|
||||
min-height: 100vh;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
main {
|
||||
flex: 1 0 auto;
|
||||
}
|
||||
</style>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
`
|
||||
)
|
||||
|
||||
// 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
|
||||
}
|
51
pkg/output/file_md.go
Normal file
51
pkg/output/file_md.go
Normal file
|
@ -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
|
||||
}
|
|
@ -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" {
|
||||
|
|
Loading…
Reference in a new issue