diff --git a/cli/gobuster.go b/cli/gobuster.go index 4ebfac95..3aa71279 100644 --- a/cli/gobuster.go +++ b/cli/gobuster.go @@ -52,7 +52,7 @@ func resultWorker(g *libgobuster.Gobuster, filename string, wg *sync.WaitGroup, defer f.Close() } - for r := range g.Results() { + for r := range g.Progress.ResultChan { s, err := r.ResultToString() if err != nil { g.LogError.Fatal(err) @@ -81,7 +81,7 @@ func resultWorker(g *libgobuster.Gobuster, filename string, wg *sync.WaitGroup, func errorWorker(g *libgobuster.Gobuster, wg *sync.WaitGroup, output *outputType) { defer wg.Done() - for e := range g.Errors() { + for e := range g.Progress.ErrorChan { if !g.Opts.Quiet && !g.Opts.NoError { output.Mu.Lock() s := fmt.Sprintf("[!] %s\n", e.Error()) @@ -109,16 +109,17 @@ func progressWorker(ctx context.Context, g *libgobuster.Gobuster, wg *sync.WaitG select { case <-tick.C: if !g.Opts.Quiet && !g.Opts.NoProgress { - g.RequestsCountMutex.RLock() + requestsIssued := g.Progress.RequestsIssued() + requestsExpected := g.Progress.RequestsExpected() output.Mu.Lock() var charsWritten int if g.Opts.Wordlist == "-" { - s := fmt.Sprintf("\rProgress: %d", g.RequestsIssued) + s := fmt.Sprintf("\rProgress: %d", requestsIssued) s = rightPad(s, " ", output.MaxCharsWritten) charsWritten, _ = fmt.Fprint(os.Stderr, s) // only print status if we already read in the wordlist - } else if g.RequestsExpected > 0 { - s := fmt.Sprintf("\rProgress: %d / %d (%3.2f%%)", g.RequestsIssued, g.RequestsExpected, float32(g.RequestsIssued)*100.0/float32(g.RequestsExpected)) + } else if requestsExpected > 0 { + s := fmt.Sprintf("\rProgress: %d / %d (%3.2f%%)", requestsIssued, requestsExpected, float32(requestsIssued)*100.0/float32(requestsExpected)) s = rightPad(s, " ", output.MaxCharsWritten) charsWritten, _ = fmt.Fprint(os.Stderr, s) } @@ -127,7 +128,6 @@ func progressWorker(ctx context.Context, g *libgobuster.Gobuster, wg *sync.WaitG } output.Mu.Unlock() - g.RequestsCountMutex.RUnlock() } case <-ctx.Done(): return diff --git a/gobusterdir/gobusterdir.go b/gobusterdir/gobusterdir.go index 5cea81f4..1594e1e7 100644 --- a/gobusterdir/gobusterdir.go +++ b/gobusterdir/gobusterdir.go @@ -164,7 +164,7 @@ func (d *GobusterDir) AdditionalWords(word string) []string { } // ProcessWord is the process implementation of gobusterdir -func (d *GobusterDir) ProcessWord(ctx context.Context, word string, resChannel chan<- libgobuster.Result) error { +func (d *GobusterDir) ProcessWord(ctx context.Context, word string, progress *libgobuster.Progress) error { suffix := "" if d.options.UseSlash { suffix = "/" @@ -212,7 +212,7 @@ func (d *GobusterDir) ProcessWord(ctx context.Context, word string, resChannel c } if (resultStatus && !helper.SliceContains(d.options.ExcludeLength, int(size))) || d.globalopts.Verbose { - resChannel <- Result{ + progress.ResultChan <- Result{ URL: d.options.URL, Path: entity, Verbose: d.globalopts.Verbose, diff --git a/gobusterdns/gobusterdns.go b/gobusterdns/gobusterdns.go index f7048f63..ace28e1e 100644 --- a/gobusterdns/gobusterdns.go +++ b/gobusterdns/gobusterdns.go @@ -102,7 +102,7 @@ func (d *GobusterDNS) PreRun(ctx context.Context) error { } // ProcessWord is the process implementation of gobusterdns -func (d *GobusterDNS) ProcessWord(ctx context.Context, word string, resChannel chan<- libgobuster.Result) error { +func (d *GobusterDNS) ProcessWord(ctx context.Context, word string, progress *libgobuster.Progress) error { subdomain := fmt.Sprintf("%s.%s", word, d.options.Domain) ips, err := d.dnsLookup(ctx, subdomain) if err == nil { @@ -121,10 +121,10 @@ func (d *GobusterDNS) ProcessWord(ctx context.Context, word string, resChannel c result.CNAME = cname } } - resChannel <- result + progress.ResultChan <- result } } else if d.globalopts.Verbose { - resChannel <- Result{ + progress.ResultChan <- Result{ Subdomain: subdomain, Found: false, ShowIPs: d.options.ShowIPs, diff --git a/gobusterfuzz/gobusterfuzz.go b/gobusterfuzz/gobusterfuzz.go index 06d654e9..b7a9ff97 100644 --- a/gobusterfuzz/gobusterfuzz.go +++ b/gobusterfuzz/gobusterfuzz.go @@ -84,7 +84,7 @@ func (d *GobusterFuzz) PreRun(ctx context.Context) error { } // ProcessWord is the process implementation of gobusterfuzz -func (d *GobusterFuzz) ProcessWord(ctx context.Context, word string, resChannel chan<- libgobuster.Result) error { +func (d *GobusterFuzz) ProcessWord(ctx context.Context, word string, progress *libgobuster.Progress) error { url := strings.ReplaceAll(d.options.URL, "FUZZ", word) tries := 1 @@ -124,7 +124,7 @@ func (d *GobusterFuzz) ProcessWord(ctx context.Context, word string, resChannel } if resultStatus || d.globalopts.Verbose { - resChannel <- Result{ + progress.ResultChan <- Result{ Verbose: d.globalopts.Verbose, Found: resultStatus, Path: url, diff --git a/gobusters3/gobusters3.go b/gobusters3/gobusters3.go index 3da43061..f93de55f 100644 --- a/gobusters3/gobusters3.go +++ b/gobusters3/gobusters3.go @@ -74,7 +74,7 @@ func (s *GobusterS3) PreRun(ctx context.Context) error { } // ProcessWord is the process implementation of GobusterS3 -func (s *GobusterS3) ProcessWord(ctx context.Context, word string, resChannel chan<- libgobuster.Result) error { +func (s *GobusterS3) ProcessWord(ctx context.Context, word string, progress *libgobuster.Progress) error { // only check for valid bucket names if !s.isValidBucketName(word) { return nil @@ -156,7 +156,7 @@ func (s *GobusterS3) ProcessWord(ctx context.Context, word string, resChannel ch } } - resChannel <- Result{ + progress.ResultChan <- Result{ Found: found, BucketName: word, Status: extraStr, diff --git a/gobustervhost/gobustervhost.go b/gobustervhost/gobustervhost.go index a3c60f20..cbe47cb9 100644 --- a/gobustervhost/gobustervhost.go +++ b/gobustervhost/gobustervhost.go @@ -106,7 +106,7 @@ func (v *GobusterVhost) PreRun(ctx context.Context) error { } // ProcessWord is the process implementation of gobusterdir -func (v *GobusterVhost) ProcessWord(ctx context.Context, word string, resChannel chan<- libgobuster.Result) error { +func (v *GobusterVhost) ProcessWord(ctx context.Context, word string, progress *libgobuster.Progress) error { var subdomain string if v.options.AppendDomain { subdomain = fmt.Sprintf("%s.%s", word, v.domain) @@ -148,7 +148,7 @@ func (v *GobusterVhost) ProcessWord(ctx context.Context, word string, resChannel if found { resultStatus = true } - resChannel <- Result{ + progress.ResultChan <- Result{ Found: resultStatus, Vhost: subdomain, StatusCode: *statusCode, diff --git a/libgobuster/interfaces.go b/libgobuster/interfaces.go index a082aeb7..28d7e2b8 100644 --- a/libgobuster/interfaces.go +++ b/libgobuster/interfaces.go @@ -6,7 +6,7 @@ import "context" type GobusterPlugin interface { Name() string PreRun(context.Context) error - ProcessWord(context.Context, string, chan<- Result) error + ProcessWord(context.Context, string, *Progress) error AdditionalWords(string) []string GetConfigString() (string, error) } diff --git a/libgobuster/libgobuster.go b/libgobuster/libgobuster.go index 933162e4..2036bf6a 100644 --- a/libgobuster/libgobuster.go +++ b/libgobuster/libgobuster.go @@ -25,15 +25,11 @@ type ResultToStringFunc func(*Gobuster, *Result) (*string, error) // Gobuster is the main object when creating a new run type Gobuster struct { - Opts *Options - RequestsExpected int - RequestsIssued int - RequestsCountMutex *sync.RWMutex - plugin GobusterPlugin - resultChan chan Result - errorChan chan error - LogInfo *log.Logger - LogError *log.Logger + Opts *Options + plugin GobusterPlugin + LogInfo *log.Logger + LogError *log.Logger + Progress *Progress } // NewGobuster returns a new Gobuster object @@ -41,31 +37,13 @@ func NewGobuster(opts *Options, plugin GobusterPlugin) (*Gobuster, error) { var g Gobuster g.Opts = opts g.plugin = plugin - g.RequestsCountMutex = new(sync.RWMutex) - g.resultChan = make(chan Result) - g.errorChan = make(chan error) g.LogInfo = log.New(os.Stdout, "", log.LstdFlags) g.LogError = log.New(os.Stderr, "\r[ERROR] ", log.LstdFlags) + g.Progress = NewProgress() return &g, nil } -// Results returns a channel of Results -func (g *Gobuster) Results() <-chan Result { - return g.resultChan -} - -// Errors returns a channel of errors -func (g *Gobuster) Errors() <-chan error { - return g.errorChan -} - -func (g *Gobuster) incrementRequests() { - g.RequestsCountMutex.Lock() - g.RequestsIssued++ - g.RequestsCountMutex.Unlock() -} - func (g *Gobuster) worker(ctx context.Context, wordChan <-chan string, wg *sync.WaitGroup) { defer wg.Done() for { @@ -77,7 +55,7 @@ func (g *Gobuster) worker(ctx context.Context, wordChan <-chan string, wg *sync. if !ok { return } - g.incrementRequests() + g.Progress.incrementRequests() wordCleaned := strings.TrimSpace(word) // Skip "comment" (starts with #), as well as empty lines @@ -86,10 +64,10 @@ func (g *Gobuster) worker(ctx context.Context, wordChan <-chan string, wg *sync. } // Mode-specific processing - err := g.plugin.ProcessWord(ctx, wordCleaned, g.resultChan) + err := g.plugin.ProcessWord(ctx, wordCleaned, g.Progress) if err != nil { // do not exit and continue - g.errorChan <- err + g.Progress.ErrorChan <- err continue } @@ -117,17 +95,16 @@ func (g *Gobuster) getWordlist() (*bufio.Scanner, error) { return nil, fmt.Errorf("failed to get number of lines: %w", err) } - g.RequestsIssued = 0 - // calcutate expected requests - g.RequestsExpected = lines + g.Progress.IncrementTotalRequests(lines) // call the function once with a dummy entry to receive the number // of custom words per wordlist word customWordsLen := len(g.plugin.AdditionalWords("dummy")) if customWordsLen > 0 { - // + 1 for the initial word - g.RequestsExpected *= (customWordsLen + 1) + origExpected := g.Progress.RequestsExpected() + inc := origExpected * customWordsLen + g.Progress.IncrementTotalRequests(inc) } // rewind wordlist @@ -141,8 +118,8 @@ func (g *Gobuster) getWordlist() (*bufio.Scanner, error) { // Run the busting of the website with the given // set of settings from the command line. func (g *Gobuster) Run(ctx context.Context) error { - defer close(g.resultChan) - defer close(g.errorChan) + defer close(g.Progress.ResultChan) + defer close(g.Progress.ErrorChan) if err := g.plugin.PreRun(ctx); err != nil { return err diff --git a/libgobuster/progress.go b/libgobuster/progress.go new file mode 100644 index 00000000..0a8e19f9 --- /dev/null +++ b/libgobuster/progress.go @@ -0,0 +1,46 @@ +package libgobuster + +import "sync" + +type Progress struct { + requestsExpectedMutex *sync.RWMutex + requestsExpected int + requestsCountMutex *sync.RWMutex + requestsIssued int + ResultChan chan Result + ErrorChan chan error +} + +func NewProgress() *Progress { + var p Progress + p.requestsIssued = 0 + p.requestsExpectedMutex = new(sync.RWMutex) + p.requestsCountMutex = new(sync.RWMutex) + p.ResultChan = make(chan Result) + p.ErrorChan = make(chan error) + return &p +} + +func (p *Progress) RequestsExpected() int { + p.requestsExpectedMutex.RLock() + defer p.requestsExpectedMutex.RUnlock() + return p.requestsExpected +} + +func (p *Progress) RequestsIssued() int { + p.requestsCountMutex.RLock() + defer p.requestsCountMutex.RUnlock() + return p.requestsIssued +} + +func (p *Progress) incrementRequests() { + p.requestsCountMutex.Lock() + p.requestsIssued++ + p.requestsCountMutex.Unlock() +} + +func (p *Progress) IncrementTotalRequests(by int) { + p.requestsCountMutex.Lock() + p.requestsExpected += by + p.requestsCountMutex.Unlock() +}