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:
parent
16409fad4a
commit
9b6813f4c4
|
@ -27,3 +27,4 @@ _testmain.go
|
|||
|
||||
gobuster
|
||||
build
|
||||
v3
|
||||
|
|
157
cli/cmd/dir.go
157
cli/cmd/dir.go
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
|
|
137
cli/cmd/vhost.go
137
cli/cmd/vhost.go
|
@ -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) {
|
||||
|
|
|
@ -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
6
go.mod
|
@ -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
17
go.sum
|
@ -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=
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
Loading…
Reference in New Issue