Fix JSON output regression and Stdout race condition issues (#94)
* Fix json output regression and improve stdout printing * Add changelog entry
This commit is contained in:
parent
ac141e5e34
commit
7aad9c6051
@ -155,7 +155,7 @@ Usage of ./ffuf:
|
|||||||
-o string
|
-o string
|
||||||
Write output to file
|
Write output to file
|
||||||
-of string
|
-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
|
-p delay
|
||||||
Seconds of delay between requests, or a range of random delay. For example "0.1" or "0.1-2.0"
|
Seconds of delay between requests, or a range of random delay. For example "0.1" or "0.1-2.0"
|
||||||
-r Follow redirects
|
-r Follow redirects
|
||||||
@ -193,8 +193,10 @@ The only dependency of ffuf is Go 1.11. No dependencies outside of Go standard l
|
|||||||
- master
|
- master
|
||||||
- New
|
- New
|
||||||
- Added a new flag to select a multi wordlist operation mode: `--mode`, possible values: `clusterbomb` and `pitchfork`.
|
- 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
|
- Changed
|
||||||
- Fixed a bug in the default multi wordlist mode
|
- 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
|
- v0.11
|
||||||
|
|
||||||
|
|||||||
4
main.go
4
main.go
@ -92,7 +92,7 @@ func main() {
|
|||||||
flag.StringVar(&opts.proxyURL, "x", "", "HTTP Proxy URL")
|
flag.StringVar(&opts.proxyURL, "x", "", "HTTP Proxy URL")
|
||||||
flag.StringVar(&conf.Method, "X", "GET", "HTTP method to use")
|
flag.StringVar(&conf.Method, "X", "GET", "HTTP method to use")
|
||||||
flag.StringVar(&conf.OutputFile, "o", "", "Write output to file")
|
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.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.Quiet, "s", false, "Do not print additional information (silent mode)")
|
||||||
flag.BoolVar(&conf.StopOn403, "sf", false, "Stop when > 95% of responses return 403 Forbidden")
|
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
|
//Check the output file format option
|
||||||
if conf.OutputFile != "" {
|
if conf.OutputFile != "" {
|
||||||
//No need to check / error out if output file isn't defined
|
//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
|
found := false
|
||||||
for _, f := range outputFormats {
|
for _, f := range outputFormats {
|
||||||
if f == parseOpts.outputFormat {
|
if f == parseOpts.outputFormat {
|
||||||
|
|||||||
@ -8,19 +8,68 @@ import (
|
|||||||
"github.com/ffuf/ffuf/pkg/ffuf"
|
"github.com/ffuf/ffuf/pkg/ffuf"
|
||||||
)
|
)
|
||||||
|
|
||||||
type jsonFileOutput struct {
|
type ejsonFileOutput struct {
|
||||||
CommandLine string `json:"commandline"`
|
CommandLine string `json:"commandline"`
|
||||||
Time string `json:"time"`
|
Time string `json:"time"`
|
||||||
Results []Result `json:"results"`
|
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()
|
t := time.Now()
|
||||||
outJSON := jsonFileOutput{
|
outJSON := ejsonFileOutput{
|
||||||
CommandLine: config.CommandLine,
|
CommandLine: config.CommandLine,
|
||||||
Time: t.Format(time.RFC3339),
|
Time: t.Format(time.RFC3339),
|
||||||
Results: res,
|
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)
|
outBytes, err := json.Marshal(outJSON)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@ -110,6 +110,8 @@ func (s *Stdoutput) Finalize() error {
|
|||||||
if s.config.OutputFile != "" {
|
if s.config.OutputFile != "" {
|
||||||
if s.config.OutputFormat == "json" {
|
if s.config.OutputFormat == "json" {
|
||||||
err = writeJSON(s.config, s.Results)
|
err = writeJSON(s.config, s.Results)
|
||||||
|
} else if s.config.OutputFormat == "ejson" {
|
||||||
|
err = writeEJSON(s.config, s.Results)
|
||||||
} else if s.config.OutputFormat == "html" {
|
} else if s.config.OutputFormat == "html" {
|
||||||
err = writeHTML(s.config, s.Results)
|
err = writeHTML(s.config, s.Results)
|
||||||
} else if s.config.OutputFormat == "md" {
|
} else if s.config.OutputFormat == "md" {
|
||||||
@ -192,19 +194,20 @@ func (s *Stdoutput) resultQuiet(resp ffuf.Response) {
|
|||||||
|
|
||||||
func (s *Stdoutput) resultMultiline(resp ffuf.Response) {
|
func (s *Stdoutput) resultMultiline(resp ffuf.Response) {
|
||||||
var res_hdr, res_str string
|
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))
|
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 {
|
for k, v := range resp.Request.Input {
|
||||||
if inSlice(k, s.config.CommandKeywords) {
|
if inSlice(k, s.config.CommandKeywords) {
|
||||||
// If we're using external command for input, display the position instead of input
|
// 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 {
|
} else {
|
||||||
// Wordlist input
|
// 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) {
|
func (s *Stdoutput) resultNormal(resp ffuf.Response) {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user