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 a CLI flag to specify TLS SNI value
- 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
- Changed
- Fixed an issue where output file was created regardless of `-or`

View file

@ -25,6 +25,7 @@
* [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)

View file

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

View file

@ -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",

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.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.")

View file

@ -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"`
@ -79,6 +80,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

View file

@ -49,6 +49,7 @@ type GeneralOptions struct {
Colors bool
ConfigFile string `toml:"-"`
Delay string
Json bool
MaxTime int
MaxTimeJob int
Noninteractive bool
@ -113,6 +114,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
@ -449,6 +451,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
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
@ -483,6 +486,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()
}

View file

@ -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,16 @@ 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.Fprint(os.Stderr, TERMINAL_CLEAR_LINE)
fmt.Println(string(resBytes))
}
}
func (s *Stdoutput) colorize(status int64) string {
if !s.config.Colors {
return ""