Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support for writing results as JSON. #119

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ All funds that are donated to this project will be donated to charity. A full lo
* `-l` - show the length of the response.
* `-n` - "no status" mode, disables the output of the result's status code.
* `-o <file>` - specify a file name to write the output to.
* `-oj <file>` - specify a file name to write the output as JSON.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Single-letters only for parameters (thanks to the command line parsing).

* `-p <proxy url>` - specify a proxy to use for all requests (scheme much match the URL scheme).
* `-r` - follow redirects.
* `-s <status codes>` - comma-separated set of the list of status codes to be deemed a "positive" (default: `200,204,301,302,307`).
Expand Down
15 changes: 15 additions & 0 deletions libgobuster/libgobuster.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"bufio"
"bytes"
"context"
"encoding/json"
"fmt"
"net"
"os"
Expand Down Expand Up @@ -33,6 +34,7 @@ type Gobuster struct {
context context.Context
requestsExpected int
requestsIssued int
cachedResults []Result
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bad idea. This will blow out memory big time for huge result sets. Data should be streamed out as it's captured rather that cached and then serialised in one hit.

mu *sync.RWMutex
plugin GobusterPlugin
IsWildcard bool
Expand Down Expand Up @@ -79,6 +81,12 @@ func (g *Gobuster) Results() <-chan Result {
return g.resultChan
}

// Returns our results as JSON
func (g *Gobuster) ResultsAsJson() (string, error) {
jsonResults, err := json.Marshal(g.cachedResults)
return string(jsonResults), err
}

// Errors returns a channel of errors
func (g *Gobuster) Errors() <-chan error {
return g.errorChan
Expand Down Expand Up @@ -145,13 +153,20 @@ func (g *Gobuster) worker(wordChan <-chan string, wg *sync.WaitGroup) {
continue
} else {
for _, r := range res {
g.cacheResult(r)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The result should not be cached here.

g.resultChan <- r
}
}
}
}
}

func (g *Gobuster) cacheResult(r Result) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function not necessary. It should be handled in the result handler.

g.mu.Lock()
g.cachedResults = append(g.cachedResults, r)
g.mu.Unlock()
}

func (g *Gobuster) getWordlist() (*bufio.Scanner, error) {
if g.Opts.Wordlist == "-" {
// Read directly from stdin
Expand Down
33 changes: 33 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,28 @@ func progressWorker(c context.Context, g *libgobuster.Gobuster) {
}
}

func writeJsonFile(g *libgobuster.Gobuster, jsonOutputFile string) {
var err error
var f *os.File
f, err = os.Create(jsonOutputFile)

if err != nil {
fmt.Errorf("[!] Error on creating JSON output file: %v", err)
}

jsonResults, err := g.ResultsAsJson()
if err != nil {
fmt.Errorf("[!] Error on ecoding results as JSON: %v", err)
}

if f != nil {
err = writeToFile(f, jsonResults)
if err != nil {
fmt.Errorf("[!] Error on writing to JSON output file: %v", err)
}
}
}

func writeToFile(f *os.File, output string) error {
_, err := f.WriteString(fmt.Sprintf("%s\n", output))
if err != nil {
Expand All @@ -104,12 +126,14 @@ func writeToFile(f *os.File, output string) error {

func main() {
var outputFilename string
var outputJsonFilename string
o := libgobuster.NewOptions()
flag.IntVar(&o.Threads, "t", 10, "Number of concurrent threads")
flag.StringVar(&o.Mode, "m", "dir", "Directory/File mode (dir) or DNS mode (dns)")
flag.StringVar(&o.Wordlist, "w", "", "Path to the wordlist")
flag.StringVar(&o.StatusCodes, "s", "200,204,301,302,307,403", "Positive status codes (dir mode only)")
flag.StringVar(&outputFilename, "o", "", "Output file to write results to (defaults to stdout)")
flag.StringVar(&outputJsonFilename, "oj", "", "Output file to write json results to")
flag.StringVar(&o.URL, "u", "", "The target URL or Domain")
flag.StringVar(&o.Cookies, "c", "", "Cookies to use for the requests (dir mode only)")
flag.StringVar(&o.Username, "U", "", "Username for Basic Auth (dir mode only)")
Expand All @@ -133,6 +157,10 @@ func main() {

flag.Parse()

if outputFilename == outputJsonFilename {
log.Fatal("[!] Output file name and JSON output file name must differ!")
}

// Prompt for PW if not provided
if o.Username != "" && o.Password == "" {
fmt.Printf("[?] Auth Password: ")
Expand Down Expand Up @@ -207,6 +235,11 @@ func main() {
wg.Wait()
}

if len(outputJsonFilename) > 0 {
writeJsonFile(gobuster, outputJsonFilename)
}


if !o.Quiet {
gobuster.ClearProgress()
ruler()
Expand Down