From 0c28d135a41ca98309df5121ab2ae9148d232098 Mon Sep 17 00:00:00 2001 From: Justin Steven Date: Mon, 17 Jan 2022 15:53:18 +1000 Subject: [PATCH 1/3] Add -json option Prints newline-delimited JSON output to STDOUT --- CHANGELOG.md | 1 + CONTRIBUTORS.md | 1 + ffufrc.example | 1 + help.go | 2 +- main.go | 1 + pkg/ffuf/config.go | 2 ++ pkg/ffuf/optionsparser.go | 9 +++++++++ pkg/output/stdout.go | 23 +++++++++++++++-------- 8 files changed, 31 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b750349..0139c19b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - Added response time logging and filtering - Added a CLI flag to specify TLS SNI value - Added full line colors + - Added `-json` to emit newline delimited JSON output - Changed - Fixed an issue where output file was created regardless of `-or` - Fixed an issue where output (often a lot of it) would be printed after entering interactive mode diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 4c2f8b02..6cc92cb0 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -18,6 +18,7 @@ * [helpermika](https://github.com/helpermika) * [Ice3man543](https://github.com/Ice3man543) * [JamTookTheBait](https://github.com/JamTookTheBait) +* [justinsteven](https://github.com/justinsteven) * [jimen0](https://github.com/jimen0) * [joohoi](https://github.com/joohoi) * [jsgv](https://github.com/jsgv) diff --git a/ffufrc.example b/ffufrc.example index eb3d05be..5698f721 100644 --- a/ffufrc.example +++ b/ffufrc.example @@ -39,6 +39,7 @@ stoponerrors = false threads = 40 verbose = false + json = false [input] dirsearchcompat = false diff --git a/help.go b/help.go index f235e28a..f632dea0 100644 --- a/help.go +++ b/help.go @@ -61,7 +61,7 @@ func Usage() { Description: "", Flags: make([]UsageFlag, 0), Hidden: false, - ExpectedFlags: []string{"ac", "acc", "c", "config", "maxtime", "maxtime-job", "noninteractive", "p", "rate", "s", "sa", "se", "sf", "t", "v", "V"}, + ExpectedFlags: []string{"ac", "acc", "c", "config", "json", "maxtime", "maxtime-job", "noninteractive", "p", "rate", "s", "sa", "se", "sf", "t", "v", "V"}, } u_compat := UsageSection{ Name: "COMPATIBILITY OPTIONS", diff --git a/main.go b/main.go index 4e2dd9c1..d8a41ff4 100644 --- a/main.go +++ b/main.go @@ -62,6 +62,7 @@ func ParseFlags(opts *ffuf.ConfigOptions) *ffuf.ConfigOptions { flag.BoolVar(&opts.Output.OutputSkipEmptyFile, "or", opts.Output.OutputSkipEmptyFile, "Don't create the output file if we don't have results") flag.BoolVar(&opts.General.AutoCalibration, "ac", opts.General.AutoCalibration, "Automatically calibrate filtering options") flag.BoolVar(&opts.General.Colors, "c", opts.General.Colors, "Colorize output.") + flag.BoolVar(&opts.General.Json, "json", opts.General.Json, "JSON output, printing newline-delimited JSON records") flag.BoolVar(&opts.General.Noninteractive, "noninteractive", opts.General.Noninteractive, "Disable the interactive console functionality") flag.BoolVar(&opts.General.Quiet, "s", opts.General.Quiet, "Do not print additional information (silent mode)") flag.BoolVar(&opts.General.ShowVersion, "V", opts.General.ShowVersion, "Show version information.") diff --git a/pkg/ffuf/config.go b/pkg/ffuf/config.go index 9dc666c0..27539cfa 100644 --- a/pkg/ffuf/config.go +++ b/pkg/ffuf/config.go @@ -26,6 +26,7 @@ type Config struct { InputNum int `json:"cmd_inputnum"` InputProviders []InputProviderConfig `json:"inputproviders"` InputShell string `json:"inputshell"` + Json bool `json:"json"` Matchers map[string]FilterProvider `json:"matchers"` MaxTime int `json:"maxtime"` MaxTimeJob int `json:"maxtime_job"` @@ -77,6 +78,7 @@ func NewConfig(ctx context.Context, cancel context.CancelFunc) Config { conf.InputNum = 0 conf.InputShell = "" conf.InputProviders = make([]InputProviderConfig, 0) + conf.Json = false conf.Matchers = make(map[string]FilterProvider) conf.MaxTime = 0 conf.MaxTimeJob = 0 diff --git a/pkg/ffuf/optionsparser.go b/pkg/ffuf/optionsparser.go index 272c0931..cf6c1c9e 100644 --- a/pkg/ffuf/optionsparser.go +++ b/pkg/ffuf/optionsparser.go @@ -48,6 +48,7 @@ type GeneralOptions struct { Colors bool ConfigFile string `toml:"-"` Delay string + Json bool MaxTime int MaxTimeJob int Noninteractive bool @@ -112,6 +113,7 @@ func NewConfigOptions() *ConfigOptions { c.General.AutoCalibration = false c.General.Colors = false c.General.Delay = "" + c.General.Json = false c.General.MaxTime = 0 c.General.MaxTimeJob = 0 c.General.Noninteractive = false @@ -410,6 +412,7 @@ func ConfigFromOptions(parseOpts *ConfigOptions, ctx context.Context, cancel con conf.MaxTimeJob = parseOpts.General.MaxTimeJob conf.Noninteractive = parseOpts.General.Noninteractive conf.Verbose = parseOpts.General.Verbose + conf.Json = parseOpts.General.Json // Handle copy as curl situation where POST method is implied by --data flag. If method is set to anything but GET, NOOP if len(conf.Data) > 0 && @@ -436,6 +439,12 @@ func ConfigFromOptions(parseOpts *ConfigOptions, ctx context.Context, cancel con errs.Add(fmt.Errorf(errmsg)) } } + + // Make verbose mutually exclusive with json + if parseOpts.General.Verbose && parseOpts.General.Json { + errs.Add(fmt.Errorf("Cannot have -json and -v")) + } + return &conf, errs.ErrorOrNil() } diff --git a/pkg/output/stdout.go b/pkg/output/stdout.go index 0c68dccf..83b3dfb5 100644 --- a/pkg/output/stdout.go +++ b/pkg/output/stdout.go @@ -2,6 +2,7 @@ package output import ( "crypto/md5" + "encoding/json" "fmt" "io/ioutil" "os" @@ -359,15 +360,16 @@ func (s *Stdoutput) writeResultToFile(resp ffuf.Response) string { } func (s *Stdoutput) PrintResult(res ffuf.Result) { - if s.config.Quiet { + switch { + case s.config.Quiet: s.resultQuiet(res) - } else { - if len(res.Input) > 1 || s.config.Verbose || len(s.config.OutputDirectory) > 0 { - // Print a multi-line result (when using multiple input keywords and wordlists) - s.resultMultiline(res) - } else { - s.resultNormal(res) - } + case s.config.Json: + s.resultJson(res) + case len(res.Input) > 1 || s.config.Verbose || len(s.config.OutputDirectory) > 0: + // Print a multi-line result (when using multiple input keywords and wordlists) + s.resultMultiline(res) + default: + s.resultNormal(res) } } @@ -431,6 +433,11 @@ func (s *Stdoutput) resultNormal(res ffuf.Result) { fmt.Println(resnormal) } +func (s *Stdoutput) resultJson(res ffuf.Result) { + resBytes, err := json.Marshal(res) + if err != nil {s.Error(err.Error())} else {fmt.Println(string(resBytes))} +} + func (s *Stdoutput) colorize(status int64) string { if !s.config.Colors { return "" From 6b6fcc2e64e1d4a5f20f0d4d3f77088b827d0195 Mon Sep 17 00:00:00 2001 From: Justin Steven Date: Sun, 23 Jan 2022 09:52:14 +1000 Subject: [PATCH 2/3] sort --- CONTRIBUTORS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index febfabd9..6f4ff404 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -21,10 +21,10 @@ * [helpermika](https://github.com/helpermika) * [Ice3man543](https://github.com/Ice3man543) * [JamTookTheBait](https://github.com/JamTookTheBait) -* [justinsteven](https://github.com/justinsteven) * [jimen0](https://github.com/jimen0) * [joohoi](https://github.com/joohoi) * [jsgv](https://github.com/jsgv) +* [justinsteven](https://github.com/justinsteven) * [jvesiluoma](https://github.com/jvesiluoma) * [Kiblyn11](https://github.com/Kiblyn11) * [lc](https://github.com/lc) From 83d35efde479ab617279db9df999de679d9033fb Mon Sep 17 00:00:00 2001 From: Justin Steven Date: Sun, 23 Jan 2022 10:15:13 +1000 Subject: [PATCH 3/3] Clear terminal line via STDERR foreach JSON result For each JSON result being printed, prepend it with a TERMINAL_CLEAR_LINE via STDERR. This clears the progress line (which is also being emitted via STDERR) and leaves us with a clean stream of JSON lines in the terminal. --- pkg/output/stdout.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pkg/output/stdout.go b/pkg/output/stdout.go index 83b3dfb5..76b2c3b3 100644 --- a/pkg/output/stdout.go +++ b/pkg/output/stdout.go @@ -435,7 +435,12 @@ func (s *Stdoutput) resultNormal(res ffuf.Result) { func (s *Stdoutput) resultJson(res ffuf.Result) { resBytes, err := json.Marshal(res) - if err != nil {s.Error(err.Error())} else {fmt.Println(string(resBytes))} + if err != nil { + s.Error(err.Error()) + } else { + fmt.Fprint(os.Stderr, TERMINAL_CLEAR_LINE) + fmt.Println(string(resBytes)) + } } func (s *Stdoutput) colorize(status int64) string {