1
1
Fork 0
mirror of https://github.com/OJ/gobuster.git synced 2024-05-18 02:16:02 +02:00

reduce code reuse

This commit is contained in:
Christian Mehlmauer 2019-04-13 12:48:26 +02:00
parent 16409fad4a
commit 9b6813f4c4
No known key found for this signature in database
GPG Key ID: DCF54A05D6E62591
21 changed files with 646 additions and 443 deletions

1
.gitignore vendored
View File

@ -27,3 +27,4 @@ _testmain.go
gobuster
build
v3

View File

@ -1,22 +1,14 @@
package cmd
import (
"context"
"fmt"
"log"
"os"
"os/signal"
"regexp"
"strconv"
"strings"
"syscall"
"time"
"github.com/OJ/gobuster/v3/cli"
"github.com/OJ/gobuster/v3/gobusterdir"
"github.com/OJ/gobuster/v3/helper"
"github.com/OJ/gobuster/v3/libgobuster"
"github.com/spf13/cobra"
"golang.org/x/crypto/ssh/terminal"
)
var cmdDir *cobra.Command
@ -26,32 +18,13 @@ func runDir(cmd *cobra.Command, args []string) error {
if err != nil {
return fmt.Errorf("error on parsing arguments: %v", err)
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
plugin, err := gobusterdir.NewGobusterDir(ctx, globalopts, pluginopts)
plugin, err := gobusterdir.NewGobusterDir(mainContext, globalopts, pluginopts)
if err != nil {
return fmt.Errorf("error on creating gobusterdir: %v", err)
}
signalChan := make(chan os.Signal, 1)
signal.Notify(signalChan, os.Interrupt)
defer func() {
signal.Stop(signalChan)
cancel()
}()
go func() {
select {
case <-signalChan:
// caught CTRL+C
if !globalopts.Quiet {
fmt.Println("\n[!] Keyboard interrupt detected, terminating.")
}
cancel()
case <-ctx.Done():
}
}()
if err := cli.Gobuster(ctx, globalopts, plugin); err != nil {
if err := cli.Gobuster(mainContext, globalopts, plugin); err != nil {
return fmt.Errorf("error on running goubster: %v", err)
}
return nil
@ -65,54 +38,19 @@ func parseDirOptions() (*libgobuster.Options, *gobusterdir.OptionsDir, error) {
plugin := gobusterdir.NewOptionsDir()
plugin.URL, err = cmdDir.Flags().GetString("url")
httpOpts, err := parseCommonHTTPOptions(cmdDir)
if err != nil {
return nil, nil, fmt.Errorf("invalid value for url: %v", err)
}
if !strings.HasPrefix(plugin.URL, "http") {
// check to see if a port was specified
re := regexp.MustCompile(`^[^/]+:(\d+)`)
match := re.FindStringSubmatch(plugin.URL)
if len(match) < 2 {
// no port, default to http on 80
plugin.URL = fmt.Sprintf("http://%s", plugin.URL)
} else {
port, err2 := strconv.Atoi(match[1])
if err2 != nil || (port != 80 && port != 443) {
return nil, nil, fmt.Errorf("url scheme not specified")
} else if port == 80 {
plugin.URL = fmt.Sprintf("http://%s", plugin.URL)
} else {
plugin.URL = fmt.Sprintf("https://%s", plugin.URL)
}
}
}
plugin.StatusCodes, err = cmdDir.Flags().GetString("statuscodes")
if err != nil {
return nil, nil, fmt.Errorf("invalid value for statuscodes: %v", err)
}
if err = plugin.ParseStatusCodes(); err != nil {
return nil, nil, fmt.Errorf("invalid value for statuscodes: %v", err)
}
plugin.Cookies, err = cmdDir.Flags().GetString("cookies")
if err != nil {
return nil, nil, fmt.Errorf("invalid value for cookies: %v", err)
}
plugin.Username, err = cmdDir.Flags().GetString("username")
if err != nil {
return nil, nil, fmt.Errorf("invalid value for username: %v", err)
}
plugin.Password, err = cmdDir.Flags().GetString("password")
if err != nil {
return nil, nil, fmt.Errorf("invalid value for password: %v", err)
return nil, nil, err
}
plugin.Password = httpOpts.Password
plugin.URL = httpOpts.URL
plugin.UserAgent = httpOpts.UserAgent
plugin.Username = httpOpts.Username
plugin.Proxy = httpOpts.Proxy
plugin.Cookies = httpOpts.Cookies
plugin.Timeout = httpOpts.Timeout
plugin.FollowRedirect = httpOpts.FollowRedirect
plugin.InsecureSSL = httpOpts.InsecureSSL
plugin.Extensions, err = cmdDir.Flags().GetString("extensions")
if err != nil {
@ -120,29 +58,27 @@ func parseDirOptions() (*libgobuster.Options, *gobusterdir.OptionsDir, error) {
}
if plugin.Extensions != "" {
if err = plugin.ParseExtensions(); err != nil {
ret, err := helper.ParseExtensions(plugin.Extensions)
if err != nil {
return nil, nil, fmt.Errorf("invalid value for extensions: %v", err)
}
plugin.ExtensionsParsed = ret
}
plugin.UserAgent, err = cmdDir.Flags().GetString("useragent")
plugin.StatusCodes, err = cmdDir.Flags().GetString("statuscodes")
if err != nil {
return nil, nil, fmt.Errorf("invalid value for useragent: %v", err)
return nil, nil, fmt.Errorf("invalid value for statuscodes: %v", err)
}
plugin.Proxy, err = cmdDir.Flags().GetString("proxy")
ret, err := helper.ParseStatusCodes(plugin.StatusCodes)
if err != nil {
return nil, nil, fmt.Errorf("invalid value for proxy: %v", err)
return nil, nil, fmt.Errorf("invalid value for statuscodes: %v", err)
}
plugin.StatusCodesParsed = ret
plugin.Timeout, err = cmdDir.Flags().GetDuration("timeout")
plugin.UseSlash, err = cmdDir.Flags().GetBool("addslash")
if err != nil {
return nil, nil, fmt.Errorf("invalid value for timeout: %v", err)
}
plugin.FollowRedirect, err = cmdDir.Flags().GetBool("followredirect")
if err != nil {
return nil, nil, fmt.Errorf("invalid value for followredirect: %v", err)
return nil, nil, fmt.Errorf("invalid value for addslash: %v", err)
}
plugin.Expanded, err = cmdDir.Flags().GetBool("expanded")
@ -160,38 +96,11 @@ func parseDirOptions() (*libgobuster.Options, *gobusterdir.OptionsDir, error) {
return nil, nil, fmt.Errorf("invalid value for includelength: %v", err)
}
plugin.InsecureSSL, err = cmdDir.Flags().GetBool("insecuressl")
if err != nil {
return nil, nil, fmt.Errorf("invalid value for insecuressl: %v", err)
}
plugin.WildcardForced, err = cmdDir.Flags().GetBool("wildcard")
if err != nil {
return nil, nil, fmt.Errorf("invalid value for wildcard: %v", err)
}
plugin.UseSlash, err = cmdDir.Flags().GetBool("addslash")
if err != nil {
return nil, nil, fmt.Errorf("invalid value for addslash: %v", err)
}
// Prompt for PW if not provided
if plugin.Username != "" && plugin.Password == "" {
fmt.Printf("[?] Auth Password: ")
passBytes, err := terminal.ReadPassword(int(syscall.Stdin))
// print a newline to simulate the newline that was entered
// this means that formatting/printing after doesn't look bad.
fmt.Println("")
if err != nil {
return nil, nil, fmt.Errorf("username given but reading of password failed")
}
plugin.Password = string(passBytes)
}
// if it's still empty bail out
if plugin.Username != "" && plugin.Password == "" {
return nil, nil, fmt.Errorf("username was provided but password is missing")
}
return globalopts, plugin, nil
}
@ -202,27 +111,17 @@ func init() {
RunE: runDir,
}
cmdDir.Flags().StringP("url", "u", "", "The target URL")
if err := addCommonHTTPOptions(cmdDir); err != nil {
log.Fatalf("%v", err)
}
cmdDir.Flags().StringP("statuscodes", "s", "200,204,301,302,307,401,403", "Positive status codes")
cmdDir.Flags().StringP("cookies", "c", "", "Cookies to use for the requests")
cmdDir.Flags().StringP("username", "U", "", "Username for Basic Auth")
cmdDir.Flags().StringP("password", "P", "", "Password for Basic Auth")
cmdDir.Flags().StringP("extensions", "x", "", "File extension(s) to search for")
cmdDir.Flags().StringP("useragent", "a", libgobuster.DefaultUserAgent(), "Set the User-Agent string")
cmdDir.Flags().StringP("proxy", "p", "", "Proxy to use for requests [http(s)://host:port]")
cmdDir.Flags().DurationP("timeout", "", 10*time.Second, "HTTP Timeout")
cmdDir.Flags().BoolP("followredirect", "r", false, "Follow redirects")
cmdDir.Flags().BoolP("expanded", "e", false, "Expanded mode, print full URLs")
cmdDir.Flags().BoolP("nostatus", "n", false, "Don't print status codes")
cmdDir.Flags().BoolP("includelength", "l", false, "Include the length of the body in the output")
cmdDir.Flags().BoolP("insecuressl", "k", false, "Skip SSL certificate verification")
cmdDir.Flags().BoolP("addslash", "f", false, "Apped / to each request")
cmdDir.Flags().BoolP("wildcard", "", false, "Force continued operation when wildcard found")
if err := cmdDir.MarkFlagRequired("url"); err != nil {
log.Fatalf("error on marking flag as required: %v", err)
}
cmdDir.PersistentPreRun = func(cmd *cobra.Command, args []string) {
configureGlobalOptions()
}

93
cli/cmd/dir_test.go Normal file
View File

@ -0,0 +1,93 @@
package cmd
import (
"context"
"fmt"
"io/ioutil"
"log"
"net/http"
"net/http/httptest"
"os"
"testing"
"time"
"github.com/OJ/gobuster/v3/cli"
"github.com/OJ/gobuster/v3/gobusterdir"
"github.com/OJ/gobuster/v3/helper"
"github.com/OJ/gobuster/v3/libgobuster"
)
func httpServer(b *testing.B, content string) *httptest.Server {
b.Helper()
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, content)
}))
return ts
}
func BenchmarkDirMode(b *testing.B) {
h := httpServer(b, "test")
defer h.Close()
pluginopts := gobusterdir.NewOptionsDir()
pluginopts.URL = h.URL
pluginopts.Timeout = 10 * time.Second
pluginopts.WildcardForced = true
pluginopts.Extensions = ".php,.csv"
tmpExt, err := helper.ParseExtensions(pluginopts.Extensions)
if err != nil {
b.Fatalf("could not parse extensions: %v", err)
}
pluginopts.ExtensionsParsed = tmpExt
pluginopts.StatusCodes = "200,204,301,302,307,401,403"
tmpStat, err := helper.ParseStatusCodes(pluginopts.StatusCodes)
if err != nil {
b.Fatalf("could not parse status codes: %v", err)
}
pluginopts.StatusCodesParsed = tmpStat
wordlist, err := ioutil.TempFile("", "")
if err != nil {
b.Fatalf("could not create tempfile: %v", err)
}
defer os.Remove(wordlist.Name())
for w := 0; w < 1000; w++ {
wordlist.WriteString(fmt.Sprintf("%d\n", w))
}
wordlist.Close()
globalopts := libgobuster.Options{
Threads: 10,
Wordlist: wordlist.Name(),
NoProgress: true,
}
ctx := context.Background()
oldStdout := os.Stdout
oldStderr := os.Stderr
defer func(out, err *os.File) { os.Stdout = out; os.Stderr = err }(oldStdout, oldStderr)
devnull, err := os.Open(os.DevNull)
if err != nil {
b.Fatalf("could not get devnull %v", err)
}
defer devnull.Close()
log.SetFlags(0)
log.SetOutput(ioutil.Discard)
// Run the real benchmark
for x := 0; x < b.N; x++ {
os.Stdout = devnull
os.Stderr = devnull
plugin, err := gobusterdir.NewGobusterDir(ctx, &globalopts, pluginopts)
if err != nil {
b.Fatalf("error on creating gobusterdir: %v", err)
}
if err := cli.Gobuster(ctx, &globalopts, plugin); err != nil {
b.Fatalf("error on running goubster: %v", err)
}
os.Stdout = oldStdout
os.Stderr = oldStderr
}
}

View File

@ -1,11 +1,8 @@
package cmd
import (
"context"
"fmt"
"log"
"os"
"os/signal"
"runtime"
"time"
@ -22,32 +19,13 @@ func runDNS(cmd *cobra.Command, args []string) error {
if err != nil {
return fmt.Errorf("error on parsing arguments: %v", err)
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
plugin, err := gobusterdns.NewGobusterDNS(globalopts, pluginopts)
if err != nil {
return fmt.Errorf("Error on creating gobusterdns: %v", err)
}
signalChan := make(chan os.Signal, 1)
signal.Notify(signalChan, os.Interrupt)
defer func() {
signal.Stop(signalChan)
cancel()
}()
go func() {
select {
case <-signalChan:
// caught CTRL+C
if !globalopts.Quiet {
fmt.Println("\n[!] Keyboard interrupt detected, terminating.")
}
cancel()
case <-ctx.Done():
}
}()
if err := cli.Gobuster(ctx, globalopts, plugin); err != nil {
if err := cli.Gobuster(mainContext, globalopts, plugin); err != nil {
return fmt.Errorf("error on running goubster: %v", err)
}
return nil

121
cli/cmd/http.go Normal file
View File

@ -0,0 +1,121 @@
package cmd
import (
"fmt"
"regexp"
"strconv"
"strings"
"syscall"
"time"
"github.com/OJ/gobuster/v3/libgobuster"
"github.com/spf13/cobra"
"golang.org/x/crypto/ssh/terminal"
)
func addCommonHTTPOptions(cmd *cobra.Command) error {
cmd.Flags().StringP("url", "u", "", "The target URL")
cmd.Flags().StringP("cookies", "c", "", "Cookies to use for the requests")
cmd.Flags().StringP("username", "U", "", "Username for Basic Auth")
cmd.Flags().StringP("password", "P", "", "Password for Basic Auth")
cmd.Flags().StringP("useragent", "a", libgobuster.DefaultUserAgent(), "Set the User-Agent string")
cmd.Flags().StringP("proxy", "p", "", "Proxy to use for requests [http(s)://host:port]")
cmd.Flags().DurationP("timeout", "", 10*time.Second, "HTTP Timeout")
cmd.Flags().BoolP("followredirect", "r", false, "Follow redirects")
cmd.Flags().BoolP("insecuressl", "k", false, "Skip SSL certificate verification")
if err := cmdDir.MarkFlagRequired("url"); err != nil {
return fmt.Errorf("error on marking flag as required: %v", err)
}
return nil
}
func parseCommonHTTPOptions(cmd *cobra.Command) (libgobuster.OptionsHTTP, error) {
options := libgobuster.OptionsHTTP{}
var err error
options.URL, err = cmd.Flags().GetString("url")
if err != nil {
return options, fmt.Errorf("invalid value for url: %v", err)
}
if !strings.HasPrefix(options.URL, "http") {
// check to see if a port was specified
re := regexp.MustCompile(`^[^/]+:(\d+)`)
match := re.FindStringSubmatch(options.URL)
if len(match) < 2 {
// no port, default to http on 80
options.URL = fmt.Sprintf("http://%s", options.URL)
} else {
port, err2 := strconv.Atoi(match[1])
if err2 != nil || (port != 80 && port != 443) {
return options, fmt.Errorf("url scheme not specified")
} else if port == 80 {
options.URL = fmt.Sprintf("http://%s", options.URL)
} else {
options.URL = fmt.Sprintf("https://%s", options.URL)
}
}
}
options.Cookies, err = cmd.Flags().GetString("cookies")
if err != nil {
return options, fmt.Errorf("invalid value for cookies: %v", err)
}
options.Username, err = cmd.Flags().GetString("username")
if err != nil {
return options, fmt.Errorf("invalid value for username: %v", err)
}
options.Password, err = cmd.Flags().GetString("password")
if err != nil {
return options, fmt.Errorf("invalid value for password: %v", err)
}
options.UserAgent, err = cmd.Flags().GetString("useragent")
if err != nil {
return options, fmt.Errorf("invalid value for useragent: %v", err)
}
options.Proxy, err = cmd.Flags().GetString("proxy")
if err != nil {
return options, fmt.Errorf("invalid value for proxy: %v", err)
}
options.Timeout, err = cmd.Flags().GetDuration("timeout")
if err != nil {
return options, fmt.Errorf("invalid value for timeout: %v", err)
}
options.FollowRedirect, err = cmd.Flags().GetBool("followredirect")
if err != nil {
return options, fmt.Errorf("invalid value for followredirect: %v", err)
}
options.InsecureSSL, err = cmd.Flags().GetBool("insecuressl")
if err != nil {
return options, fmt.Errorf("invalid value for insecuressl: %v", err)
}
// Prompt for PW if not provided
if options.Username != "" && options.Password == "" {
fmt.Printf("[?] Auth Password: ")
passBytes, err := terminal.ReadPassword(int(syscall.Stdin))
// print a newline to simulate the newline that was entered
// this means that formatting/printing after doesn't look bad.
fmt.Println("")
if err != nil {
return options, fmt.Errorf("username given but reading of password failed")
}
options.Password = string(passBytes)
}
// if it's still empty bail out
if options.Username != "" && options.Password == "" {
return options, fmt.Errorf("username was provided but password is missing")
}
return options, nil
}

View File

@ -1,9 +1,11 @@
package cmd
import (
"context"
"fmt"
"log"
"os"
"os/signal"
"github.com/OJ/gobuster/v3/libgobuster"
@ -14,8 +16,30 @@ var rootCmd = &cobra.Command{
Use: "gobuster",
}
var mainContext context.Context
// Execute is the main cobra method
func Execute() {
var cancel context.CancelFunc
mainContext, cancel = context.WithCancel(context.Background())
defer cancel()
signalChan := make(chan os.Signal, 1)
signal.Notify(signalChan, os.Interrupt)
defer func() {
signal.Stop(signalChan)
cancel()
}()
go func() {
select {
case <-signalChan:
// caught CTRL+C
fmt.Println("\n[!] Keyboard interrupt detected, terminating.")
cancel()
case <-mainContext.Done():
}
}()
if err := rootCmd.Execute(); err != nil {
// Leaving this in results in the same error appearing twice
// Once before and once after the help output. Not sure if

View File

@ -1,22 +1,13 @@
package cmd
import (
"context"
"fmt"
"log"
"os"
"os/signal"
"regexp"
"strconv"
"strings"
"syscall"
"time"
"github.com/OJ/gobuster/v3/cli"
"github.com/OJ/gobuster/v3/gobustervhost"
"github.com/OJ/gobuster/v3/libgobuster"
"github.com/spf13/cobra"
"golang.org/x/crypto/ssh/terminal"
)
var cmdVhost *cobra.Command
@ -26,32 +17,13 @@ func runVhost(cmd *cobra.Command, args []string) error {
if err != nil {
return fmt.Errorf("error on parsing arguments: %v", err)
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
plugin, err := gobustervhost.NewGobusterVhost(ctx, globalopts, pluginopts)
plugin, err := gobustervhost.NewGobusterVhost(mainContext, globalopts, pluginopts)
if err != nil {
return fmt.Errorf("error on creating gobustervhost: %v", err)
}
signalChan := make(chan os.Signal, 1)
signal.Notify(signalChan, os.Interrupt)
defer func() {
signal.Stop(signalChan)
cancel()
}()
go func() {
select {
case <-signalChan:
// caught CTRL+C
if !globalopts.Quiet {
fmt.Println("\n[!] Keyboard interrupt detected, terminating.")
}
cancel()
case <-ctx.Done():
}
}()
if err := cli.Gobuster(ctx, globalopts, plugin); err != nil {
if err := cli.Gobuster(mainContext, globalopts, plugin); err != nil {
return fmt.Errorf("error on running goubster: %v", err)
}
return nil
@ -64,87 +36,19 @@ func parseVhostOptions() (*libgobuster.Options, *gobustervhost.OptionsVhost, err
}
var plugin gobustervhost.OptionsVhost
url, err := cmdVhost.Flags().GetString("url")
httpOpts, err := parseCommonHTTPOptions(cmdVhost)
if err != nil {
return nil, nil, fmt.Errorf("invalid value for url: %v", err)
}
plugin.URL = url
if !strings.HasPrefix(plugin.URL, "http") {
// check to see if a port was specified
re := regexp.MustCompile(`^[^/]+:(\d+)`)
match := re.FindStringSubmatch(plugin.URL)
if len(match) < 2 {
// no port, default to http on 80
plugin.URL = fmt.Sprintf("http://%s", plugin.URL)
} else {
port, err2 := strconv.Atoi(match[1])
if err2 != nil || (port != 80 && port != 443) {
return nil, nil, fmt.Errorf("url scheme not specified")
} else if port == 80 {
plugin.URL = fmt.Sprintf("http://%s", plugin.URL)
} else {
plugin.URL = fmt.Sprintf("https://%s", plugin.URL)
}
}
}
plugin.Cookies, err = cmdVhost.Flags().GetString("cookies")
if err != nil {
return nil, nil, fmt.Errorf("invalid value for cookies: %v", err)
}
plugin.Username, err = cmdVhost.Flags().GetString("username")
if err != nil {
return nil, nil, fmt.Errorf("invalid value for username: %v", err)
}
plugin.Password, err = cmdVhost.Flags().GetString("password")
if err != nil {
return nil, nil, fmt.Errorf("invalid value for password: %v", err)
}
plugin.UserAgent, err = cmdVhost.Flags().GetString("useragent")
if err != nil {
return nil, nil, fmt.Errorf("invalid value for useragent: %v", err)
}
plugin.Proxy, err = cmdVhost.Flags().GetString("proxy")
if err != nil {
return nil, nil, fmt.Errorf("invalid value for proxy: %v", err)
}
plugin.Timeout, err = cmdVhost.Flags().GetDuration("timeout")
if err != nil {
return nil, nil, fmt.Errorf("invalid value for timeout: %v", err)
}
plugin.FollowRedirect, err = cmdDir.Flags().GetBool("followredirect")
if err != nil {
return nil, nil, fmt.Errorf("invalid value for followredirect: %v", err)
}
plugin.InsecureSSL, err = cmdVhost.Flags().GetBool("insecuressl")
if err != nil {
return nil, nil, fmt.Errorf("invalid value for insecuressl: %v", err)
}
// Prompt for PW if not provided
if plugin.Username != "" && plugin.Password == "" {
fmt.Printf("[?] Auth Password: ")
passBytes, err := terminal.ReadPassword(int(syscall.Stdin))
// print a newline to simulate the newline that was entered
// this means that formatting/printing after doesn't look bad.
fmt.Println("")
if err != nil {
return nil, nil, fmt.Errorf("username given but reading of password failed")
}
plugin.Password = string(passBytes)
}
// if it's still empty bail out
if plugin.Username != "" && plugin.Password == "" {
return nil, nil, fmt.Errorf("username was provided but password is missing")
return nil, nil, err
}
plugin.Password = httpOpts.Password
plugin.URL = httpOpts.URL
plugin.UserAgent = httpOpts.UserAgent
plugin.Username = httpOpts.Username
plugin.Proxy = httpOpts.Proxy
plugin.Cookies = httpOpts.Cookies
plugin.Timeout = httpOpts.Timeout
plugin.FollowRedirect = httpOpts.FollowRedirect
plugin.InsecureSSL = httpOpts.InsecureSSL
return globalopts, &plugin, nil
}
@ -155,17 +59,8 @@ func init() {
Short: "Uses VHOST bruteforcing mode",
RunE: runVhost,
}
cmdVhost.Flags().StringP("url", "u", "", "The target URL")
cmdVhost.Flags().StringP("cookies", "c", "", "Cookies to use for the requests")
cmdVhost.Flags().StringP("username", "U", "", "Username for Basic Auth")
cmdVhost.Flags().StringP("password", "P", "", "Password for Basic Auth")
cmdVhost.Flags().StringP("useragent", "a", libgobuster.DefaultUserAgent(), "Set the User-Agent string")
cmdVhost.Flags().StringP("proxy", "p", "", "Proxy to use for requests [http(s)://host:port]")
cmdVhost.Flags().DurationP("timeout", "", 10*time.Second, "HTTP Timeout")
cmdVhost.Flags().BoolP("followredirect", "r", true, "Follow redirects")
cmdVhost.Flags().BoolP("insecuressl", "k", false, "Skip SSL certificate verification")
if err := cmdVhost.MarkFlagRequired("url"); err != nil {
log.Fatalf("error on marking flag as required: %v", err)
if err := addCommonHTTPOptions(cmdVhost); err != nil {
log.Fatalf("%v", err)
}
cmdVhost.PersistentPreRun = func(cmd *cobra.Command, args []string) {

68
cli/cmd/vhost_test.go Normal file
View File

@ -0,0 +1,68 @@
package cmd
import (
"context"
"fmt"
"io/ioutil"
"log"
"os"
"testing"
"time"
"github.com/OJ/gobuster/v3/cli"
"github.com/OJ/gobuster/v3/gobustervhost"
"github.com/OJ/gobuster/v3/libgobuster"
)
func BenchmarkVhostMode(b *testing.B) {
h := httpServer(b, "test")
defer h.Close()
pluginopts := gobustervhost.OptionsVhost{}
pluginopts.URL = h.URL
pluginopts.Timeout = 10 * time.Second
wordlist, err := ioutil.TempFile("", "")
if err != nil {
b.Fatalf("could not create tempfile: %v", err)
}
defer os.Remove(wordlist.Name())
for w := 0; w < 1000; w++ {
wordlist.WriteString(fmt.Sprintf("%d\n", w))
}
wordlist.Close()
globalopts := libgobuster.Options{
Threads: 10,
Wordlist: wordlist.Name(),
NoProgress: true,
}
ctx := context.Background()
oldStdout := os.Stdout
oldStderr := os.Stderr
defer func(out, err *os.File) { os.Stdout = out; os.Stderr = err }(oldStdout, oldStderr)
devnull, err := os.Open(os.DevNull)
if err != nil {
b.Fatalf("could not get devnull %v", err)
}
defer devnull.Close()
log.SetFlags(0)
log.SetOutput(ioutil.Discard)
// Run the real benchmark
for x := 0; x < b.N; x++ {
os.Stdout = devnull
os.Stderr = devnull
plugin, err := gobustervhost.NewGobusterVhost(ctx, &globalopts, &pluginopts)
if err != nil {
b.Fatalf("error on creating gobusterdir: %v", err)
}
if err := cli.Gobuster(ctx, &globalopts, plugin); err != nil {
b.Fatalf("error on running goubster: %v", err)
}
os.Stdout = oldStdout
os.Stderr = oldStderr
}
}

6
go.mod
View File

@ -1,10 +1,10 @@
module github.com/OJ/gobuster/v3
require (
github.com/google/uuid v1.1.0
github.com/google/uuid v1.1.1
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/spf13/cobra v0.0.3
github.com/spf13/pflag v1.0.3 // indirect
golang.org/x/crypto v0.0.0-20190103213133-ff983b9c42bc
golang.org/x/sys v0.0.0-20190109145017-48ac38b7c8cb // indirect
golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734
golang.org/x/sys v0.0.0-20190428183149-804c0c7841b5 // indirect
)

17
go.sum
View File

@ -1,12 +1,17 @@
github.com/google/uuid v1.1.0 h1:Jf4mxPC/ziBnoPIdpQdPJ9OeiomAUHLvxmPRSPH9m4s=
github.com/google/uuid v1.1.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
golang.org/x/crypto v0.0.0-20190103213133-ff983b9c42bc h1:F5tKCVGp+MUAHhKp5MZtGqAlGX3+oCsiL1Q629FL90M=
golang.org/x/crypto v0.0.0-20190103213133-ff983b9c42bc/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/sys v0.0.0-20190109145017-48ac38b7c8cb h1:1w588/yEchbPNpa9sEvOcMZYbWHedwJjg4VOAdDHWHk=
golang.org/x/sys v0.0.0-20190109145017-48ac38b7c8cb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734 h1:p/H982KKEjUnLJkM3tt/LemDnOc1GiZL5FCVlORJ5zo=
golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190428183149-804c0c7841b5 h1:m0i9YywO9THhxmJvLEwKJDD/pD8ljCB+EaT/wYS41Is=
golang.org/x/sys v0.0.0-20190428183149-804c0c7841b5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

View File

@ -1,39 +0,0 @@
package gobusterdir
import (
"fmt"
"strconv"
"strings"
)
// ParseExtensions parses the extensions provided as a comma seperated list
func (opt *OptionsDir) ParseExtensions() error {
if opt.Extensions == "" {
return fmt.Errorf("invalid extension string provided")
}
exts := strings.Split(opt.Extensions, ",")
for _, e := range exts {
e = strings.TrimSpace(e)
// remove leading . from extensions
opt.ExtensionsParsed.Add(strings.TrimPrefix(e, "."))
}
return nil
}
// ParseStatusCodes parses the status codes provided as a comma seperated list
func (opt *OptionsDir) ParseStatusCodes() error {
if opt.StatusCodes == "" {
return fmt.Errorf("invalid status code string provided")
}
for _, c := range strings.Split(opt.StatusCodes, ",") {
c = strings.TrimSpace(c)
i, err := strconv.Atoi(c)
if err != nil {
return fmt.Errorf("invalid status code given: %s", c)
}
opt.StatusCodesParsed.Add(i)
}
return nil
}

View File

@ -1,73 +0,0 @@
package gobusterdir
import (
"reflect"
"testing"
"github.com/OJ/gobuster/v3/libgobuster"
)
func TestParseExtensions(t *testing.T) {
t.Parallel()
var tt = []struct {
testName string
Extensions string
expectedExtensions libgobuster.StringSet
expectedError string
}{
{"Valid extensions", "php,asp,txt", libgobuster.StringSet{Set: map[string]bool{"php": true, "asp": true, "txt": true}}, ""},
{"Spaces", "php, asp , txt", libgobuster.StringSet{Set: map[string]bool{"php": true, "asp": true, "txt": true}}, ""},
{"Double extensions", "php,asp,txt,php,asp,txt", libgobuster.StringSet{Set: map[string]bool{"php": true, "asp": true, "txt": true}}, ""},
{"Leading dot", ".php,asp,.txt", libgobuster.StringSet{Set: map[string]bool{"php": true, "asp": true, "txt": true}}, ""},
{"Empty string", "", libgobuster.NewStringSet(), "invalid extension string provided"},
}
for _, x := range tt {
t.Run(x.testName, func(t *testing.T) {
o := NewOptionsDir()
o.Extensions = x.Extensions
err := o.ParseExtensions()
if x.expectedError != "" {
if err.Error() != x.expectedError {
t.Fatalf("Expected error %q but got %q", x.expectedError, err.Error())
}
} else if !reflect.DeepEqual(x.expectedExtensions, o.ExtensionsParsed) {
t.Fatalf("Expected %v but got %v", x.expectedExtensions, o.ExtensionsParsed)
}
})
}
}
func TestParseStatusCodes(t *testing.T) {
t.Parallel()
var tt = []struct {
testName string
stringCodes string
expectedCodes libgobuster.IntSet
expectedError string
}{
{"Valid codes", "200,100,202", libgobuster.IntSet{Set: map[int]bool{100: true, 200: true, 202: true}}, ""},
{"Spaces", "200, 100 , 202", libgobuster.IntSet{Set: map[int]bool{100: true, 200: true, 202: true}}, ""},
{"Double codes", "200, 100, 202, 100", libgobuster.IntSet{Set: map[int]bool{100: true, 200: true, 202: true}}, ""},
{"Invalid code", "200,AAA", libgobuster.NewIntSet(), "invalid status code given: AAA"},
{"Invalid integer", "2000000000000000000000000000000", libgobuster.NewIntSet(), "invalid status code given: 2000000000000000000000000000000"},
{"Empty string", "", libgobuster.NewIntSet(), "invalid status code string provided"},
}
for _, x := range tt {
t.Run(x.testName, func(t *testing.T) {
o := NewOptionsDir()
o.StatusCodes = x.stringCodes
err := o.ParseStatusCodes()
if x.expectedError != "" {
if err.Error() != x.expectedError {
t.Fatalf("Expected error %q but got %q", x.expectedError, err.Error())
}
} else if !reflect.DeepEqual(x.expectedCodes, o.StatusCodesParsed) {
t.Fatalf("Expected %v but got %v", x.expectedCodes, o.StatusCodesParsed)
}
})
}
}

View File

@ -1,31 +1,21 @@
package gobusterdir
import (
"time"
"github.com/OJ/gobuster/v3/libgobuster"
)
// OptionsDir is the struct to hold all options for this plugin
type OptionsDir struct {
libgobuster.OptionsHTTP
Extensions string
ExtensionsParsed libgobuster.StringSet
Password string
StatusCodes string
StatusCodesParsed libgobuster.IntSet
URL string
UserAgent string
Username string
Proxy string
Cookies string
Timeout time.Duration
FollowRedirect bool
UseSlash bool
WildcardForced bool
IncludeLength bool
Expanded bool
NoStatus bool
InsecureSSL bool
UseSlash bool
WildcardForced bool
}
// NewOptionsDir returns a new initialized OptionsDir

View File

@ -19,8 +19,8 @@ type GobusterVhost struct {
globalopts *libgobuster.Options
http *libgobuster.HTTPClient
domain string
baseline1 string
baseline2 string
baseline1 []byte
baseline2 []byte
}
// NewGobusterVhost creates a new initialized GobusterDir
@ -71,7 +71,7 @@ func (v *GobusterVhost) PreRun() error {
v.domain = url.Host
// request default vhost for baseline1
_, tmp, err := v.http.GetBody(v.options.URL, "", v.options.Cookies)
_, tmp, err := v.http.GetWithBody(v.options.URL, "", v.options.Cookies)
if err != nil {
return fmt.Errorf("unable to connect to %s: %v", v.options.URL, err)
}
@ -79,7 +79,7 @@ func (v *GobusterVhost) PreRun() error {
// request non existent vhost for baseline2
subdomain := fmt.Sprintf("%s.%s", uuid.New(), v.domain)
_, tmp, err = v.http.GetBody(v.options.URL, subdomain, v.options.Cookies)
_, tmp, err = v.http.GetWithBody(v.options.URL, subdomain, v.options.Cookies)
if err != nil {
return fmt.Errorf("unable to connect to %s: %v", v.options.URL, err)
}
@ -90,7 +90,7 @@ func (v *GobusterVhost) PreRun() error {
// Run is the process implementation of gobusterdir
func (v *GobusterVhost) Run(word string) ([]libgobuster.Result, error) {
subdomain := fmt.Sprintf("%s.%s", word, v.domain)
status, body, err := v.http.GetBody(v.options.URL, subdomain, v.options.Cookies)
status, body, err := v.http.GetWithBody(v.options.URL, subdomain, v.options.Cookies)
var ret []libgobuster.Result
if err != nil {
return ret, err
@ -98,7 +98,7 @@ func (v *GobusterVhost) Run(word string) ([]libgobuster.Result, error) {
// subdomain must not match default vhost and non existent vhost
// or verbose mode is enabled
found := *body != v.baseline1 && *body != v.baseline2
found := !bytes.Equal(*body, v.baseline1) && !bytes.Equal(*body, v.baseline2)
if found || v.globalopts.Verbose {
size := int64(len(*body))
resultStatus := libgobuster.StatusMissed

View File

@ -1,18 +1,10 @@
package gobustervhost
import (
"time"
"github.com/OJ/gobuster/v3/libgobuster"
)
// OptionsVhost is the struct to hold all options for this plugin
type OptionsVhost struct {
Password string
URL string
UserAgent string
Username string
Proxy string
Cookies string
Timeout time.Duration
InsecureSSL bool
FollowRedirect bool
libgobuster.OptionsHTTP
}

43
helper/helper.go Normal file
View File

@ -0,0 +1,43 @@
package helper
import (
"fmt"
"strconv"
"strings"
"github.com/OJ/gobuster/v3/libgobuster"
)
// ParseExtensions parses the extensions provided as a comma seperated list
func ParseExtensions(extensions string) (libgobuster.StringSet, error) {
if extensions == "" {
return libgobuster.StringSet{}, fmt.Errorf("invalid extension string provided")
}
ret := libgobuster.NewStringSet()
exts := strings.Split(extensions, ",")
for _, e := range exts {
e = strings.TrimSpace(e)
// remove leading . from extensions
ret.Add(strings.TrimPrefix(e, "."))
}
return ret, nil
}
// ParseStatusCodes parses the status codes provided as a comma seperated list
func ParseStatusCodes(statuscodes string) (libgobuster.IntSet, error) {
if statuscodes == "" {
return libgobuster.IntSet{}, fmt.Errorf("invalid status code string provided")
}
ret := libgobuster.NewIntSet()
for _, c := range strings.Split(statuscodes, ",") {
c = strings.TrimSpace(c)
i, err := strconv.Atoi(c)
if err != nil {
return libgobuster.IntSet{}, fmt.Errorf("invalid status code given: %s", c)
}
ret.Add(i)
}
return ret, nil
}

116
helper/helper_test.go Normal file
View File

@ -0,0 +1,116 @@
package helper
import (
"reflect"
"testing"
"github.com/OJ/gobuster/v3/libgobuster"
)
func TestParseExtensions(t *testing.T) {
t.Parallel()
var tt = []struct {
testName string
extensions string
expectedExtensions libgobuster.StringSet
expectedError string
}{
{"Valid extensions", "php,asp,txt", libgobuster.StringSet{Set: map[string]bool{"php": true, "asp": true, "txt": true}}, ""},
{"Spaces", "php, asp , txt", libgobuster.StringSet{Set: map[string]bool{"php": true, "asp": true, "txt": true}}, ""},
{"Double extensions", "php,asp,txt,php,asp,txt", libgobuster.StringSet{Set: map[string]bool{"php": true, "asp": true, "txt": true}}, ""},
{"Leading dot", ".php,asp,.txt", libgobuster.StringSet{Set: map[string]bool{"php": true, "asp": true, "txt": true}}, ""},
{"Empty string", "", libgobuster.NewStringSet(), "invalid extension string provided"},
}
for _, x := range tt {
t.Run(x.testName, func(t *testing.T) {
ret, err := ParseExtensions(x.extensions)
if x.expectedError != "" {
if err.Error() != x.expectedError {
t.Fatalf("Expected error %q but got %q", x.expectedError, err.Error())
}
} else if !reflect.DeepEqual(x.expectedExtensions, ret) {
t.Fatalf("Expected %v but got %v", x.expectedExtensions, ret)
}
})
}
}
func TestParseStatusCodes(t *testing.T) {
t.Parallel()
var tt = []struct {
testName string
stringCodes string
expectedCodes libgobuster.IntSet
expectedError string
}{
{"Valid codes", "200,100,202", libgobuster.IntSet{Set: map[int]bool{100: true, 200: true, 202: true}}, ""},
{"Spaces", "200, 100 , 202", libgobuster.IntSet{Set: map[int]bool{100: true, 200: true, 202: true}}, ""},
{"Double codes", "200, 100, 202, 100", libgobuster.IntSet{Set: map[int]bool{100: true, 200: true, 202: true}}, ""},
{"Invalid code", "200,AAA", libgobuster.NewIntSet(), "invalid status code given: AAA"},
{"Invalid integer", "2000000000000000000000000000000", libgobuster.NewIntSet(), "invalid status code given: 2000000000000000000000000000000"},
{"Empty string", "", libgobuster.NewIntSet(), "invalid status code string provided"},
}
for _, x := range tt {
t.Run(x.testName, func(t *testing.T) {
ret, err := ParseStatusCodes(x.stringCodes)
if x.expectedError != "" {
if err.Error() != x.expectedError {
t.Fatalf("Expected error %q but got %q", x.expectedError, err.Error())
}
} else if !reflect.DeepEqual(x.expectedCodes, ret) {
t.Fatalf("Expected %v but got %v", x.expectedCodes, ret)
}
})
}
}
func BenchmarkParseExtensions(b *testing.B) {
var tt = []struct {
testName string
extensions string
expectedExtensions libgobuster.StringSet
expectedError string
}{
{"Valid extensions", "php,asp,txt", libgobuster.StringSet{Set: map[string]bool{"php": true, "asp": true, "txt": true}}, ""},
{"Spaces", "php, asp , txt", libgobuster.StringSet{Set: map[string]bool{"php": true, "asp": true, "txt": true}}, ""},
{"Double extensions", "php,asp,txt,php,asp,txt", libgobuster.StringSet{Set: map[string]bool{"php": true, "asp": true, "txt": true}}, ""},
{"Leading dot", ".php,asp,.txt", libgobuster.StringSet{Set: map[string]bool{"php": true, "asp": true, "txt": true}}, ""},
{"Empty string", "", libgobuster.NewStringSet(), "invalid extension string provided"},
}
for _, x := range tt {
b.Run(x.testName, func(b2 *testing.B) {
for y := 0; y < b2.N; y++ {
_, _ = ParseExtensions(x.extensions)
}
})
}
}
func BenchmarkParseStatusCodes(b *testing.B) {
var tt = []struct {
testName string
stringCodes string
expectedCodes libgobuster.IntSet
expectedError string
}{
{"Valid codes", "200,100,202", libgobuster.IntSet{Set: map[int]bool{100: true, 200: true, 202: true}}, ""},
{"Spaces", "200, 100 , 202", libgobuster.IntSet{Set: map[int]bool{100: true, 200: true, 202: true}}, ""},
{"Double codes", "200, 100, 202, 100", libgobuster.IntSet{Set: map[int]bool{100: true, 200: true, 202: true}}, ""},
{"Invalid code", "200,AAA", libgobuster.NewIntSet(), "invalid status code given: AAA"},
{"Invalid integer", "2000000000000000000000000000000", libgobuster.NewIntSet(), "invalid status code given: 2000000000000000000000000000000"},
{"Empty string", "", libgobuster.NewIntSet(), "invalid status code string provided"},
}
for _, x := range tt {
b.Run(x.testName, func(b2 *testing.B) {
for y := 0; y < b2.N; y++ {
_, _ = ParseStatusCodes(x.stringCodes)
}
})
}
}

View File

@ -13,6 +13,8 @@ import (
"unicode/utf8"
)
var defaultUserAgent = DefaultUserAgent()
// HTTPClient represents a http object
type HTTPClient struct {
client *http.Client
@ -79,9 +81,29 @@ func NewHTTPClient(c context.Context, opt *HTTPOptions) (*HTTPClient, error) {
return &client, nil
}
// Get makes an http request and returns the status, the length and an error
// Get gets an URL and returns the status, the length and an error
func (client *HTTPClient) Get(fullURL, host, cookie string) (*int, *int64, error) {
resp, err := client.makeRequest(fullURL, host, cookie)
return client.requestWithoutBody(http.MethodGet, fullURL, host, cookie, nil)
}
// Post posts to an URL and returns the status, the length and an error
func (client *HTTPClient) Post(fullURL, host, cookie string, data io.Reader) (*int, *int64, error) {
return client.requestWithoutBody(http.MethodPost, fullURL, host, cookie, data)
}
// GetWithBody gets an URL and returns the status and the body
func (client *HTTPClient) GetWithBody(fullURL, host, cookie string) (*int, *[]byte, error) {
return client.requestWithBody(http.MethodGet, fullURL, host, cookie, nil)
}
// PostWithBody gets an URL and returns the status and the body
func (client *HTTPClient) PostWithBody(fullURL, host, cookie string, data io.Reader) (*int, *[]byte, error) {
return client.requestWithBody(http.MethodPost, fullURL, host, cookie, data)
}
// requestWithoutBody makes an http request and returns the status, the length and an error
func (client *HTTPClient) requestWithoutBody(method, fullURL, host, cookie string, data io.Reader) (*int, *int64, error) {
resp, err := client.makeRequest(method, fullURL, host, cookie, data)
if err != nil {
// ignore context canceled errors
if client.context.Err() == context.Canceled {
@ -115,9 +137,9 @@ func (client *HTTPClient) Get(fullURL, host, cookie string) (*int, *int64, error
return &resp.StatusCode, length, nil
}
// GetBody makes an http request and returns the status and the body
func (client *HTTPClient) GetBody(fullURL, host, cookie string) (*int, *string, error) {
resp, err := client.makeRequest(fullURL, host, cookie)
// requestWithBody makes an http request and returns the status and the body
func (client *HTTPClient) requestWithBody(method, fullURL, host, cookie string, data io.Reader) (*int, *[]byte, error) {
resp, err := client.makeRequest(method, fullURL, host, cookie, data)
if err != nil {
// ignore context canceled errors
if client.context.Err() == context.Canceled {
@ -131,15 +153,27 @@ func (client *HTTPClient) GetBody(fullURL, host, cookie string) (*int, *string,
if err != nil {
return nil, nil, fmt.Errorf("could not read body: %v", err)
}
bodyString := string(body)
return &resp.StatusCode, &bodyString, nil
return &resp.StatusCode, &body, nil
}
func (client *HTTPClient) makeRequest(fullURL, host, cookie string) (*http.Response, error) {
req, err := http.NewRequest(http.MethodGet, fullURL, nil)
if err != nil {
return nil, err
func (client *HTTPClient) makeRequest(method, fullURL, host, cookie string, data io.Reader) (*http.Response, error) {
var req *http.Request
var err error
switch method {
case http.MethodGet:
req, err = http.NewRequest(http.MethodGet, fullURL, nil)
if err != nil {
return nil, err
}
case http.MethodPost:
req, err = http.NewRequest(http.MethodPost, fullURL, data)
if err != nil {
return nil, err
}
default:
return nil, fmt.Errorf("invalid method %s", method)
}
// add the context so we can easily cancel out
@ -153,11 +187,11 @@ func (client *HTTPClient) makeRequest(fullURL, host, cookie string) (*http.Respo
req.Host = host
}
ua := DefaultUserAgent()
if client.userAgent != "" {
ua = client.userAgent
req.Header.Set("User-Agent", client.userAgent)
} else {
req.Header.Set("User-Agent", defaultUserAgent)
}
req.Header.Set("User-Agent", ua)
if client.username != "" {
req.SetBasicAuth(client.username, client.password)
@ -172,5 +206,6 @@ func (client *HTTPClient) makeRequest(fullURL, host, cookie string) (*http.Respo
}
return nil, err
}
return resp, nil
}

View File

@ -8,7 +8,15 @@ import (
"testing"
)
func httpServer(t *testing.T, content string) *httptest.Server {
func httpServerB(b *testing.B, content string) *httptest.Server {
b.Helper()
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, content)
}))
return ts
}
func httpServerT(t *testing.T, content string) *httptest.Server {
t.Helper()
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, content)
@ -16,8 +24,8 @@ func httpServer(t *testing.T, content string) *httptest.Server {
return ts
}
func TestMakeRequest(t *testing.T) {
h := httpServer(t, "test")
func TestGet(t *testing.T) {
h := httpServerT(t, "test")
defer h.Close()
var o HTTPOptions
c, err := NewHTTPClient(context.Background(), &o)
@ -35,3 +43,31 @@ func TestMakeRequest(t *testing.T) {
t.Fatalf("Invalid length returned: %d", b)
}
}
func BenchmarkGet(b *testing.B) {
h := httpServerB(b, "test")
defer h.Close()
var o HTTPOptions
c, err := NewHTTPClient(context.Background(), &o)
if err != nil {
b.Fatalf("Got Error: %v", err)
}
for x := 0; x < b.N; x++ {
_, _, err := c.Get(h.URL, "", "")
if err != nil {
b.Fatalf("Got Error: %v", err)
}
}
}
func BenchmarkNewHTTPClient(b *testing.B) {
h := httpServerB(b, "test")
defer h.Close()
var o HTTPOptions
for x := 0; x < b.N; x++ {
_, err := NewHTTPClient(context.Background(), &o)
if err != nil {
b.Fatalf("Got Error: %v", err)
}
}
}

View File

@ -142,6 +142,9 @@ func (g *Gobuster) getWordlist() (*bufio.Scanner, error) {
// Start the busting of the website with the given
// set of settings from the command line.
func (g *Gobuster) Start() error {
defer close(g.resultChan)
defer close(g.errorChan)
if err := g.plugin.PreRun(); err != nil {
return err
}
@ -172,8 +175,6 @@ Scan:
}
close(wordChan)
workerGroup.Wait()
close(g.resultChan)
close(g.errorChan)
return nil
}

View File

@ -0,0 +1,18 @@
package libgobuster
import (
"time"
)
// OptionsHTTP is the struct to hold all options for common HTTP options
type OptionsHTTP struct {
Password string
URL string
UserAgent string
Username string
Proxy string
Cookies string
Timeout time.Duration
FollowRedirect bool
InsecureSSL bool
}