Add -json option (#509)

* Add -json option

Prints newline-delimited JSON output to STDOUT

* sort

* 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.
This commit is contained in:
Justin Steven 2022-03-07 01:39:33 +11:00 committed by GitHub
parent 9aeae16a08
commit 4c1a75498b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 36 additions and 9 deletions

View File

@ -4,6 +4,7 @@
- Added response time logging and filtering - Added response time logging and filtering
- Added a CLI flag to specify TLS SNI value - Added a CLI flag to specify TLS SNI value
- Added full line colors - Added full line colors
- Added `-json` to emit newline delimited JSON output
- Added 500 Internal Server Error to list of status codes matched by default - Added 500 Internal Server Error to list of status codes matched by default
- Changed - Changed
- Fixed an issue where output file was created regardless of `-or` - Fixed an issue where output file was created regardless of `-or`

View File

@ -25,6 +25,7 @@
* [jimen0](https://github.com/jimen0) * [jimen0](https://github.com/jimen0)
* [joohoi](https://github.com/joohoi) * [joohoi](https://github.com/joohoi)
* [jsgv](https://github.com/jsgv) * [jsgv](https://github.com/jsgv)
* [justinsteven](https://github.com/justinsteven)
* [jvesiluoma](https://github.com/jvesiluoma) * [jvesiluoma](https://github.com/jvesiluoma)
* [Kiblyn11](https://github.com/Kiblyn11) * [Kiblyn11](https://github.com/Kiblyn11)
* [lc](https://github.com/lc) * [lc](https://github.com/lc)

View File

@ -39,6 +39,7 @@
stoponerrors = false stoponerrors = false
threads = 40 threads = 40
verbose = false verbose = false
json = false
[input] [input]
dirsearchcompat = false dirsearchcompat = false

View File

@ -61,7 +61,7 @@ func Usage() {
Description: "", Description: "",
Flags: make([]UsageFlag, 0), Flags: make([]UsageFlag, 0),
Hidden: false, 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{ u_compat := UsageSection{
Name: "COMPATIBILITY OPTIONS", Name: "COMPATIBILITY OPTIONS",

View File

@ -63,6 +63,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.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.AutoCalibration, "ac", opts.General.AutoCalibration, "Automatically calibrate filtering options")
flag.BoolVar(&opts.General.Colors, "c", opts.General.Colors, "Colorize output.") 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.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.Quiet, "s", opts.General.Quiet, "Do not print additional information (silent mode)")
flag.BoolVar(&opts.General.ShowVersion, "V", opts.General.ShowVersion, "Show version information.") flag.BoolVar(&opts.General.ShowVersion, "V", opts.General.ShowVersion, "Show version information.")

View File

@ -26,6 +26,7 @@ type Config struct {
InputNum int `json:"cmd_inputnum"` InputNum int `json:"cmd_inputnum"`
InputProviders []InputProviderConfig `json:"inputproviders"` InputProviders []InputProviderConfig `json:"inputproviders"`
InputShell string `json:"inputshell"` InputShell string `json:"inputshell"`
Json bool `json:"json"`
Matchers map[string]FilterProvider `json:"matchers"` Matchers map[string]FilterProvider `json:"matchers"`
MaxTime int `json:"maxtime"` MaxTime int `json:"maxtime"`
MaxTimeJob int `json:"maxtime_job"` MaxTimeJob int `json:"maxtime_job"`
@ -79,6 +80,7 @@ func NewConfig(ctx context.Context, cancel context.CancelFunc) Config {
conf.InputNum = 0 conf.InputNum = 0
conf.InputShell = "" conf.InputShell = ""
conf.InputProviders = make([]InputProviderConfig, 0) conf.InputProviders = make([]InputProviderConfig, 0)
conf.Json = false
conf.Matchers = make(map[string]FilterProvider) conf.Matchers = make(map[string]FilterProvider)
conf.MaxTime = 0 conf.MaxTime = 0
conf.MaxTimeJob = 0 conf.MaxTimeJob = 0

View File

@ -49,6 +49,7 @@ type GeneralOptions struct {
Colors bool Colors bool
ConfigFile string `toml:"-"` ConfigFile string `toml:"-"`
Delay string Delay string
Json bool
MaxTime int MaxTime int
MaxTimeJob int MaxTimeJob int
Noninteractive bool Noninteractive bool
@ -113,6 +114,7 @@ func NewConfigOptions() *ConfigOptions {
c.General.AutoCalibration = false c.General.AutoCalibration = false
c.General.Colors = false c.General.Colors = false
c.General.Delay = "" c.General.Delay = ""
c.General.Json = false
c.General.MaxTime = 0 c.General.MaxTime = 0
c.General.MaxTimeJob = 0 c.General.MaxTimeJob = 0
c.General.Noninteractive = false c.General.Noninteractive = false
@ -449,6 +451,7 @@ func ConfigFromOptions(parseOpts *ConfigOptions, ctx context.Context, cancel con
conf.MaxTimeJob = parseOpts.General.MaxTimeJob conf.MaxTimeJob = parseOpts.General.MaxTimeJob
conf.Noninteractive = parseOpts.General.Noninteractive conf.Noninteractive = parseOpts.General.Noninteractive
conf.Verbose = parseOpts.General.Verbose conf.Verbose = parseOpts.General.Verbose
conf.Json = parseOpts.General.Json
conf.Http2 = parseOpts.HTTP.Http2 conf.Http2 = parseOpts.HTTP.Http2
// Handle copy as curl situation where POST method is implied by --data flag. If method is set to anything but GET, NOOP // Handle copy as curl situation where POST method is implied by --data flag. If method is set to anything but GET, NOOP
@ -483,6 +486,12 @@ func ConfigFromOptions(parseOpts *ConfigOptions, ctx context.Context, cancel con
errs.Add(fmt.Errorf(errmsg)) 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() return &conf, errs.ErrorOrNil()
} }

View File

@ -2,6 +2,7 @@ package output
import ( import (
"crypto/md5" "crypto/md5"
"encoding/json"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os" "os"
@ -359,15 +360,16 @@ func (s *Stdoutput) writeResultToFile(resp ffuf.Response) string {
} }
func (s *Stdoutput) PrintResult(res ffuf.Result) { func (s *Stdoutput) PrintResult(res ffuf.Result) {
if s.config.Quiet { switch {
case s.config.Quiet:
s.resultQuiet(res) s.resultQuiet(res)
} else { case s.config.Json:
if len(res.Input) > 1 || s.config.Verbose || len(s.config.OutputDirectory) > 0 { s.resultJson(res)
// Print a multi-line result (when using multiple input keywords and wordlists) case len(res.Input) > 1 || s.config.Verbose || len(s.config.OutputDirectory) > 0:
s.resultMultiline(res) // Print a multi-line result (when using multiple input keywords and wordlists)
} else { s.resultMultiline(res)
s.resultNormal(res) default:
} s.resultNormal(res)
} }
} }
@ -431,6 +433,16 @@ func (s *Stdoutput) resultNormal(res ffuf.Result) {
fmt.Println(resnormal) fmt.Println(resnormal)
} }
func (s *Stdoutput) resultJson(res ffuf.Result) {
resBytes, err := json.Marshal(res)
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 { func (s *Stdoutput) colorize(status int64) string {
if !s.config.Colors { if !s.config.Colors {
return "" return ""