revert stupid go modules
This commit is contained in:
casual 2024-09-04 20:26:39 +00:00
parent 1b6f1ddc06
commit 81b714f780
17 changed files with 1468 additions and 1 deletions

188
cli/cmd/dir.go Normal file
View File

@ -0,0 +1,188 @@
package cmd
import (
"errors"
"fmt"
"log"
"git.sual.in/casual/gobuster-lib/cli"
"git.sual.in/casual/gobuster-lib/gobusterdir"
"git.sual.in/casual/gobuster-lib/libgobuster"
"github.com/spf13/cobra"
)
// nolint:gochecknoglobals
var cmdDir *cobra.Command
func runDir(cmd *cobra.Command, args []string) error {
globalopts, pluginopts, err := parseDirOptions()
if err != nil {
return fmt.Errorf("error on parsing arguments: %w", err)
}
plugin, err := gobusterdir.NewGobusterDir(globalopts, pluginopts)
if err != nil {
return fmt.Errorf("error on creating gobusterdir: %w", err)
}
log := libgobuster.NewLogger(globalopts.Debug)
if _,err := cli.Gobuster(mainContext, globalopts, plugin, log); err != nil {
var wErr *gobusterdir.ErrWildcard
if errors.As(err, &wErr) {
return fmt.Errorf("%w. To continue please exclude the status code or the length", wErr)
}
log.Debugf("%#v", err)
return fmt.Errorf("error on running gobuster: %w", err)
}
return nil
}
func parseDirOptions() (*libgobuster.Options, *gobusterdir.OptionsDir, error) {
globalopts, err := parseGlobalOptions()
if err != nil {
return nil, nil, err
}
pluginOpts := gobusterdir.NewOptionsDir()
httpOpts, err := parseCommonHTTPOptions(cmdDir)
if err != nil {
return nil, nil, err
}
pluginOpts.Password = httpOpts.Password
pluginOpts.URL = httpOpts.URL
pluginOpts.UserAgent = httpOpts.UserAgent
pluginOpts.Username = httpOpts.Username
pluginOpts.Proxy = httpOpts.Proxy
pluginOpts.Cookies = httpOpts.Cookies
pluginOpts.Timeout = httpOpts.Timeout
pluginOpts.FollowRedirect = httpOpts.FollowRedirect
pluginOpts.NoTLSValidation = httpOpts.NoTLSValidation
pluginOpts.Headers = httpOpts.Headers
pluginOpts.Method = httpOpts.Method
pluginOpts.RetryOnTimeout = httpOpts.RetryOnTimeout
pluginOpts.RetryAttempts = httpOpts.RetryAttempts
pluginOpts.TLSCertificate = httpOpts.TLSCertificate
pluginOpts.NoCanonicalizeHeaders = httpOpts.NoCanonicalizeHeaders
pluginOpts.Extensions, err = cmdDir.Flags().GetString("extensions")
if err != nil {
return nil, nil, fmt.Errorf("invalid value for extensions: %w", err)
}
ret, err := libgobuster.ParseExtensions(pluginOpts.Extensions)
if err != nil {
return nil, nil, fmt.Errorf("invalid value for extensions: %w", err)
}
pluginOpts.ExtensionsParsed = ret
pluginOpts.ExtensionsFile, err = cmdDir.Flags().GetString("extensions-file")
if err != nil {
return nil, nil, fmt.Errorf("invalid value for extensions file: %w", err)
}
if pluginOpts.ExtensionsFile != "" {
extensions, err := libgobuster.ParseExtensionsFile(pluginOpts.ExtensionsFile)
if err != nil {
return nil, nil, fmt.Errorf("invalid value for extensions file: %w", err)
}
pluginOpts.ExtensionsParsed.AddRange(extensions)
}
// parse normal status codes
pluginOpts.StatusCodes, err = cmdDir.Flags().GetString("status-codes")
if err != nil {
return nil, nil, fmt.Errorf("invalid value for status-codes: %w", err)
}
ret2, err := libgobuster.ParseCommaSeparatedInt(pluginOpts.StatusCodes)
if err != nil {
return nil, nil, fmt.Errorf("invalid value for status-codes: %w", err)
}
pluginOpts.StatusCodesParsed = ret2
// blacklist will override the normal status codes
pluginOpts.StatusCodesBlacklist, err = cmdDir.Flags().GetString("status-codes-blacklist")
if err != nil {
return nil, nil, fmt.Errorf("invalid value for status-codes-blacklist: %w", err)
}
ret3, err := libgobuster.ParseCommaSeparatedInt(pluginOpts.StatusCodesBlacklist)
if err != nil {
return nil, nil, fmt.Errorf("invalid value for status-codes-blacklist: %w", err)
}
pluginOpts.StatusCodesBlacklistParsed = ret3
if pluginOpts.StatusCodes != "" && pluginOpts.StatusCodesBlacklist != "" {
return nil, nil, fmt.Errorf("status-codes (%q) and status-codes-blacklist (%q) are both set - please set only one. status-codes-blacklist is set by default so you might want to disable it by supplying an empty string.",
pluginOpts.StatusCodes, pluginOpts.StatusCodesBlacklist)
}
if pluginOpts.StatusCodes == "" && pluginOpts.StatusCodesBlacklist == "" {
return nil, nil, fmt.Errorf("status-codes and status-codes-blacklist are both not set, please set one")
}
pluginOpts.UseSlash, err = cmdDir.Flags().GetBool("add-slash")
if err != nil {
return nil, nil, fmt.Errorf("invalid value for add-slash: %w", err)
}
pluginOpts.Expanded, err = cmdDir.Flags().GetBool("expanded")
if err != nil {
return nil, nil, fmt.Errorf("invalid value for expanded: %w", err)
}
pluginOpts.NoStatus, err = cmdDir.Flags().GetBool("no-status")
if err != nil {
return nil, nil, fmt.Errorf("invalid value for no-status: %w", err)
}
pluginOpts.HideLength, err = cmdDir.Flags().GetBool("hide-length")
if err != nil {
return nil, nil, fmt.Errorf("invalid value for hide-length: %w", err)
}
pluginOpts.DiscoverBackup, err = cmdDir.Flags().GetBool("discover-backup")
if err != nil {
return nil, nil, fmt.Errorf("invalid value for discover-backup: %w", err)
}
pluginOpts.ExcludeLength, err = cmdDir.Flags().GetString("exclude-length")
if err != nil {
return nil, nil, fmt.Errorf("invalid value for exclude-length: %w", err)
}
ret4, err := libgobuster.ParseCommaSeparatedInt(pluginOpts.ExcludeLength)
if err != nil {
return nil, nil, fmt.Errorf("invalid value for exclude-length: %w", err)
}
pluginOpts.ExcludeLengthParsed = ret4
return globalopts, pluginOpts, nil
}
// nolint:gochecknoinits
func init() {
cmdDir = &cobra.Command{
Use: "dir",
Short: "Uses directory/file enumeration mode",
RunE: runDir,
}
if err := addCommonHTTPOptions(cmdDir); err != nil {
log.Fatalf("%v", err)
}
cmdDir.Flags().StringP("status-codes", "s", "", "Positive status codes (will be overwritten with status-codes-blacklist if set). Can also handle ranges like 200,300-400,404.")
cmdDir.Flags().StringP("status-codes-blacklist", "b", "404", "Negative status codes (will override status-codes if set). Can also handle ranges like 200,300-400,404.")
cmdDir.Flags().StringP("extensions", "x", "", "File extension(s) to search for")
cmdDir.Flags().StringP("extensions-file", "X", "", "Read file extension(s) to search from the file")
cmdDir.Flags().BoolP("expanded", "e", false, "Expanded mode, print full URLs")
cmdDir.Flags().BoolP("no-status", "n", false, "Don't print status codes")
cmdDir.Flags().Bool("hide-length", false, "Hide the length of the body in the output")
cmdDir.Flags().BoolP("add-slash", "f", false, "Append / to each request")
cmdDir.Flags().BoolP("discover-backup", "d", false, "Also search for backup files by appending multiple backup extensions")
cmdDir.Flags().String("exclude-length", "", "exclude the following content lengths (completely ignores the status). You can separate multiple lengths by comma and it also supports ranges like 203-206")
cmdDir.PersistentPreRun = func(cmd *cobra.Command, args []string) {
configureGlobalOptions()
}
rootCmd.AddCommand(cmdDir)
}

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

@ -0,0 +1,88 @@
package cmd
import (
"context"
"fmt"
"net/http"
"net/http/httptest"
"os"
"testing"
"time"
"git.sual.in/casual/gobuster-lib/cli"
"git.sual.in/casual/gobuster-lib/gobusterdir"
"git.sual.in/casual/gobuster-lib/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.Extensions = ".php,.csv"
tmpExt, err := libgobuster.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 := libgobuster.ParseCommaSeparatedInt(pluginopts.StatusCodes)
if err != nil {
b.Fatalf("could not parse status codes: %v", err)
}
pluginopts.StatusCodesParsed = tmpStat
wordlist, err := os.CreateTemp("", "")
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 := libgobuster.NewLogger(false)
// Run the real benchmark
for x := 0; x < b.N; x++ {
os.Stdout = devnull
os.Stderr = devnull
plugin, err := gobusterdir.NewGobusterDir(&globalopts, pluginopts)
if err != nil {
b.Fatalf("error on creating gobusterdir: %v", err)
}
if err := cli.Gobuster(ctx, &globalopts, plugin, log); err != nil {
b.Fatalf("error on running gobuster: %v", err)
}
os.Stdout = oldStdout
os.Stderr = oldStderr
}
}

115
cli/cmd/dns.go Normal file
View File

@ -0,0 +1,115 @@
package cmd
import (
"errors"
"fmt"
"log"
"runtime"
"time"
"git.sual.in/casual/gobuster-lib/cli"
"git.sual.in/casual/gobuster-lib/gobusterdns"
"git.sual.in/casual/gobuster-lib/libgobuster"
"github.com/spf13/cobra"
)
// nolint:gochecknoglobals
var cmdDNS *cobra.Command
func runDNS(cmd *cobra.Command, args []string) error {
globalopts, pluginopts, err := parseDNSOptions()
if err != nil {
return fmt.Errorf("error on parsing arguments: %w", err)
}
plugin, err := gobusterdns.NewGobusterDNS(globalopts, pluginopts)
if err != nil {
return fmt.Errorf("error on creating gobusterdns: %w", err)
}
log := libgobuster.NewLogger(globalopts.Debug)
if _,err := cli.Gobuster(mainContext, globalopts, plugin, log); err != nil {
var wErr *gobusterdns.ErrWildcard
if errors.As(err, &wErr) {
return fmt.Errorf("%w. To force processing of Wildcard DNS, specify the '--wildcard' switch", wErr)
}
log.Debugf("%#v", err)
return fmt.Errorf("error on running gobuster: %w", err)
}
return nil
}
func parseDNSOptions() (*libgobuster.Options, *gobusterdns.OptionsDNS, error) {
globalopts, err := parseGlobalOptions()
if err != nil {
return nil, nil, err
}
pluginOpts := gobusterdns.NewOptionsDNS()
pluginOpts.Domain, err = cmdDNS.Flags().GetString("domain")
if err != nil {
return nil, nil, fmt.Errorf("invalid value for domain: %w", err)
}
pluginOpts.ShowIPs, err = cmdDNS.Flags().GetBool("show-ips")
if err != nil {
return nil, nil, fmt.Errorf("invalid value for show-ips: %w", err)
}
pluginOpts.ShowCNAME, err = cmdDNS.Flags().GetBool("show-cname")
if err != nil {
return nil, nil, fmt.Errorf("invalid value for show-cname: %w", err)
}
pluginOpts.WildcardForced, err = cmdDNS.Flags().GetBool("wildcard")
if err != nil {
return nil, nil, fmt.Errorf("invalid value for wildcard: %w", err)
}
pluginOpts.Timeout, err = cmdDNS.Flags().GetDuration("timeout")
if err != nil {
return nil, nil, fmt.Errorf("invalid value for timeout: %w", err)
}
pluginOpts.Resolver, err = cmdDNS.Flags().GetString("resolver")
if err != nil {
return nil, nil, fmt.Errorf("invalid value for resolver: %w", err)
}
pluginOpts.NoFQDN, err = cmdDNS.Flags().GetBool("no-fqdn")
if err != nil {
return nil, nil, fmt.Errorf("invalid value for no-fqdn: %w", err)
}
if pluginOpts.Resolver != "" && runtime.GOOS == "windows" {
return nil, nil, fmt.Errorf("currently can not set custom dns resolver on windows. See https://golang.org/pkg/net/#hdr-Name_Resolution")
}
return globalopts, pluginOpts, nil
}
// nolint:gochecknoinits
func init() {
cmdDNS = &cobra.Command{
Use: "dns",
Short: "Uses DNS subdomain enumeration mode",
RunE: runDNS,
}
cmdDNS.Flags().StringP("domain", "d", "", "The target domain")
cmdDNS.Flags().BoolP("show-ips", "i", false, "Show IP addresses")
cmdDNS.Flags().BoolP("show-cname", "c", false, "Show CNAME records (cannot be used with '-i' option)")
cmdDNS.Flags().DurationP("timeout", "", time.Second, "DNS resolver timeout")
cmdDNS.Flags().BoolP("wildcard", "", false, "Force continued operation when wildcard found")
cmdDNS.Flags().BoolP("no-fqdn", "", false, "Do not automatically add a trailing dot to the domain, so the resolver uses the DNS search domain")
cmdDNS.Flags().StringP("resolver", "r", "", "Use custom DNS server (format server.com or server.com:port)")
if err := cmdDNS.MarkFlagRequired("domain"); err != nil {
log.Fatalf("error on marking flag as required: %v", err)
}
cmdDNS.PersistentPreRun = func(cmd *cobra.Command, args []string) {
configureGlobalOptions()
}
rootCmd.AddCommand(cmdDNS)
}

148
cli/cmd/fuzz.go Normal file
View File

@ -0,0 +1,148 @@
package cmd
import (
"errors"
"fmt"
"log"
"strings"
"git.sual.in/casual/gobuster-lib/cli"
"git.sual.in/casual/gobuster-lib/gobusterfuzz"
"git.sual.in/casual/gobuster-lib/libgobuster"
"github.com/spf13/cobra"
)
// nolint:gochecknoglobals
var cmdFuzz *cobra.Command
func runFuzz(cmd *cobra.Command, args []string) error {
globalopts, pluginopts, err := parseFuzzOptions()
if err != nil {
return fmt.Errorf("error on parsing arguments: %w", err)
}
if !containsFuzzKeyword(*pluginopts) {
return fmt.Errorf("please provide the %s keyword", gobusterfuzz.FuzzKeyword)
}
plugin, err := gobusterfuzz.NewGobusterFuzz(globalopts, pluginopts)
if err != nil {
return fmt.Errorf("error on creating gobusterfuzz: %w", err)
}
log := libgobuster.NewLogger(globalopts.Debug)
if _,err := cli.Gobuster(mainContext, globalopts, plugin, log); err != nil {
var wErr *gobusterfuzz.ErrWildcard
if errors.As(err, &wErr) {
return fmt.Errorf("%w. To continue please exclude the status code or the length", wErr)
}
log.Debugf("%#v", err)
return fmt.Errorf("error on running gobuster: %w", err)
}
return nil
}
func parseFuzzOptions() (*libgobuster.Options, *gobusterfuzz.OptionsFuzz, error) {
globalopts, err := parseGlobalOptions()
if err != nil {
return nil, nil, err
}
pluginOpts := gobusterfuzz.NewOptionsFuzz()
httpOpts, err := parseCommonHTTPOptions(cmdFuzz)
if err != nil {
return nil, nil, err
}
pluginOpts.Password = httpOpts.Password
pluginOpts.URL = httpOpts.URL
pluginOpts.UserAgent = httpOpts.UserAgent
pluginOpts.Username = httpOpts.Username
pluginOpts.Proxy = httpOpts.Proxy
pluginOpts.Cookies = httpOpts.Cookies
pluginOpts.Timeout = httpOpts.Timeout
pluginOpts.FollowRedirect = httpOpts.FollowRedirect
pluginOpts.NoTLSValidation = httpOpts.NoTLSValidation
pluginOpts.Headers = httpOpts.Headers
pluginOpts.Method = httpOpts.Method
pluginOpts.RetryOnTimeout = httpOpts.RetryOnTimeout
pluginOpts.RetryAttempts = httpOpts.RetryAttempts
pluginOpts.TLSCertificate = httpOpts.TLSCertificate
pluginOpts.NoCanonicalizeHeaders = httpOpts.NoCanonicalizeHeaders
// blacklist will override the normal status codes
pluginOpts.ExcludedStatusCodes, err = cmdFuzz.Flags().GetString("excludestatuscodes")
if err != nil {
return nil, nil, fmt.Errorf("invalid value for excludestatuscodes: %w", err)
}
ret, err := libgobuster.ParseCommaSeparatedInt(pluginOpts.ExcludedStatusCodes)
if err != nil {
return nil, nil, fmt.Errorf("invalid value for excludestatuscodes: %w", err)
}
pluginOpts.ExcludedStatusCodesParsed = ret
pluginOpts.ExcludeLength, err = cmdFuzz.Flags().GetString("exclude-length")
if err != nil {
return nil, nil, fmt.Errorf("invalid value for exclude-length: %w", err)
}
ret2, err := libgobuster.ParseCommaSeparatedInt(pluginOpts.ExcludeLength)
if err != nil {
return nil, nil, fmt.Errorf("invalid value for exclude-length: %w", err)
}
pluginOpts.ExcludeLengthParsed = ret2
pluginOpts.RequestBody, err = cmdFuzz.Flags().GetString("body")
if err != nil {
return nil, nil, fmt.Errorf("invalid value for body: %w", err)
}
return globalopts, pluginOpts, nil
}
// nolint:gochecknoinits
func init() {
cmdFuzz = &cobra.Command{
Use: "fuzz",
Short: fmt.Sprintf("Uses fuzzing mode. Replaces the keyword %s in the URL, Headers and the request body", gobusterfuzz.FuzzKeyword),
RunE: runFuzz,
}
if err := addCommonHTTPOptions(cmdFuzz); err != nil {
log.Fatalf("%v", err)
}
cmdFuzz.Flags().StringP("excludestatuscodes", "b", "", "Excluded status codes. Can also handle ranges like 200,300-400,404.")
cmdFuzz.Flags().String("exclude-length", "", "exclude the following content lengths (completely ignores the status). You can separate multiple lengths by comma and it also supports ranges like 203-206")
cmdFuzz.Flags().StringP("body", "B", "", "Request body")
cmdFuzz.PersistentPreRun = func(cmd *cobra.Command, args []string) {
configureGlobalOptions()
}
rootCmd.AddCommand(cmdFuzz)
}
func containsFuzzKeyword(pluginopts gobusterfuzz.OptionsFuzz) bool {
if strings.Contains(pluginopts.URL, gobusterfuzz.FuzzKeyword) {
return true
}
if strings.Contains(pluginopts.RequestBody, gobusterfuzz.FuzzKeyword) {
return true
}
for _, h := range pluginopts.Headers {
if strings.Contains(h.Name, gobusterfuzz.FuzzKeyword) || strings.Contains(h.Value, gobusterfuzz.FuzzKeyword) {
return true
}
}
if strings.Contains(pluginopts.Username, gobusterfuzz.FuzzKeyword) {
return true
}
if strings.Contains(pluginopts.Password, gobusterfuzz.FuzzKeyword) {
return true
}
return false
}

79
cli/cmd/gcs.go Normal file
View File

@ -0,0 +1,79 @@
package cmd
import (
"fmt"
"git.sual.in/casual/gobuster-lib/cli"
"git.sual.in/casual/gobuster-lib/gobustergcs"
"git.sual.in/casual/gobuster-lib/libgobuster"
"github.com/spf13/cobra"
)
// nolint:gochecknoglobals
var cmdGCS *cobra.Command
func runGCS(cmd *cobra.Command, args []string) error {
globalopts, pluginopts, err := parseGCSOptions()
if err != nil {
return fmt.Errorf("error on parsing arguments: %w", err)
}
plugin, err := gobustergcs.NewGobusterGCS(globalopts, pluginopts)
if err != nil {
return fmt.Errorf("error on creating gobustergcs: %w", err)
}
log := libgobuster.NewLogger(globalopts.Debug)
if _,err := cli.Gobuster(mainContext, globalopts, plugin, log); err != nil {
log.Debugf("%#v", err)
return fmt.Errorf("error on running gobuster: %w", err)
}
return nil
}
func parseGCSOptions() (*libgobuster.Options, *gobustergcs.OptionsGCS, error) {
globalopts, err := parseGlobalOptions()
if err != nil {
return nil, nil, err
}
pluginopts := gobustergcs.NewOptionsGCS()
httpOpts, err := parseBasicHTTPOptions(cmdGCS)
if err != nil {
return nil, nil, err
}
pluginopts.UserAgent = httpOpts.UserAgent
pluginopts.Proxy = httpOpts.Proxy
pluginopts.Timeout = httpOpts.Timeout
pluginopts.NoTLSValidation = httpOpts.NoTLSValidation
pluginopts.RetryOnTimeout = httpOpts.RetryOnTimeout
pluginopts.RetryAttempts = httpOpts.RetryAttempts
pluginopts.TLSCertificate = httpOpts.TLSCertificate
pluginopts.MaxFilesToList, err = cmdGCS.Flags().GetInt("maxfiles")
if err != nil {
return nil, nil, fmt.Errorf("invalid value for maxfiles: %w", err)
}
return globalopts, pluginopts, nil
}
// nolint:gochecknoinits
func init() {
cmdGCS = &cobra.Command{
Use: "gcs",
Short: "Uses gcs bucket enumeration mode",
RunE: runGCS,
}
addBasicHTTPOptions(cmdGCS)
cmdGCS.Flags().IntP("maxfiles", "m", 5, "max files to list when listing buckets (only shown in verbose mode)")
cmdGCS.PersistentPreRun = func(cmd *cobra.Command, args []string) {
configureGlobalOptions()
}
rootCmd.AddCommand(cmdGCS)
}

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

@ -0,0 +1,258 @@
package cmd
import (
"crypto/tls"
"encoding/pem"
"fmt"
"os"
"regexp"
"strconv"
"strings"
"syscall"
"time"
"git.sual.in/casual/gobuster-lib/libgobuster"
"github.com/spf13/cobra"
"golang.org/x/crypto/pkcs12"
"golang.org/x/term"
)
func addBasicHTTPOptions(cmd *cobra.Command) {
cmd.Flags().StringP("useragent", "a", libgobuster.DefaultUserAgent(), "Set the User-Agent string")
cmd.Flags().BoolP("random-agent", "", false, "Use a random User-Agent string")
cmd.Flags().StringP("proxy", "", "", "Proxy to use for requests [http(s)://host:port] or [socks5://host:port]")
cmd.Flags().DurationP("timeout", "", 10*time.Second, "HTTP Timeout")
cmd.Flags().BoolP("no-tls-validation", "k", false, "Skip TLS certificate verification")
cmd.Flags().BoolP("retry", "", false, "Should retry on request timeout")
cmd.Flags().IntP("retry-attempts", "", 3, "Times to retry on request timeout")
// client certificates, either pem or p12
cmd.Flags().StringP("client-cert-pem", "", "", "public key in PEM format for optional TLS client certificates")
cmd.Flags().StringP("client-cert-pem-key", "", "", "private key in PEM format for optional TLS client certificates (this key needs to have no password)")
cmd.Flags().StringP("client-cert-p12", "", "", "a p12 file to use for options TLS client certificates")
cmd.Flags().StringP("client-cert-p12-password", "", "", "the password to the p12 file")
}
func addCommonHTTPOptions(cmd *cobra.Command) error {
addBasicHTTPOptions(cmd)
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().BoolP("follow-redirect", "r", false, "Follow redirects")
cmd.Flags().StringArrayP("headers", "H", []string{""}, "Specify HTTP headers, -H 'Header1: val1' -H 'Header2: val2'")
cmd.Flags().BoolP("no-canonicalize-headers", "", false, "Do not canonicalize HTTP header names. If set header names are sent as is.")
cmd.Flags().StringP("method", "m", "GET", "Use the following HTTP method")
if err := cmd.MarkFlagRequired("url"); err != nil {
return fmt.Errorf("error on marking flag as required: %w", err)
}
return nil
}
func parseBasicHTTPOptions(cmd *cobra.Command) (libgobuster.BasicHTTPOptions, error) {
options := libgobuster.BasicHTTPOptions{}
var err error
options.UserAgent, err = cmd.Flags().GetString("useragent")
if err != nil {
return options, fmt.Errorf("invalid value for useragent: %w", err)
}
randomUA, err := cmd.Flags().GetBool("random-agent")
if err != nil {
return options, fmt.Errorf("invalid value for random-agent: %w", err)
}
if randomUA {
ua, err := libgobuster.GetRandomUserAgent()
if err != nil {
return options, err
}
options.UserAgent = ua
}
options.Proxy, err = cmd.Flags().GetString("proxy")
if err != nil {
return options, fmt.Errorf("invalid value for proxy: %w", err)
}
options.Timeout, err = cmd.Flags().GetDuration("timeout")
if err != nil {
return options, fmt.Errorf("invalid value for timeout: %w", err)
}
options.RetryOnTimeout, err = cmd.Flags().GetBool("retry")
if err != nil {
return options, fmt.Errorf("invalid value for retry: %w", err)
}
options.RetryAttempts, err = cmd.Flags().GetInt("retry-attempts")
if err != nil {
return options, fmt.Errorf("invalid value for retry-attempts: %w", err)
}
options.NoTLSValidation, err = cmd.Flags().GetBool("no-tls-validation")
if err != nil {
return options, fmt.Errorf("invalid value for no-tls-validation: %w", err)
}
pemFile, err := cmd.Flags().GetString("client-cert-pem")
if err != nil {
return options, fmt.Errorf("invalid value for client-cert-pem: %w", err)
}
pemKeyFile, err := cmd.Flags().GetString("client-cert-pem-key")
if err != nil {
return options, fmt.Errorf("invalid value for client-cert-pem-key: %w", err)
}
p12File, err := cmd.Flags().GetString("client-cert-p12")
if err != nil {
return options, fmt.Errorf("invalid value for client-cert-p12: %w", err)
}
p12Pass, err := cmd.Flags().GetString("client-cert-p12-password")
if err != nil {
return options, fmt.Errorf("invalid value for client-cert-p12-password: %w", err)
}
if pemFile != "" && p12File != "" {
return options, fmt.Errorf("please supply either a pem or a p12, not both")
}
if pemFile != "" {
cert, err := tls.LoadX509KeyPair(pemFile, pemKeyFile)
if err != nil {
return options, fmt.Errorf("could not load supplied pem key: %w", err)
}
options.TLSCertificate = &cert
} else if p12File != "" {
p12Content, err := os.ReadFile(p12File)
if err != nil {
return options, fmt.Errorf("could not read p12 %s: %w", p12File, err)
}
blocks, err := pkcs12.ToPEM(p12Content, p12Pass)
if err != nil {
return options, fmt.Errorf("could not load P12: %w", err)
}
var pemData []byte
for _, b := range blocks {
pemData = append(pemData, pem.EncodeToMemory(b)...)
}
cert, err := tls.X509KeyPair(pemData, pemData)
if err != nil {
return options, fmt.Errorf("could not load certificate from P12: %w", err)
}
options.TLSCertificate = &cert
}
return options, nil
}
func parseCommonHTTPOptions(cmd *cobra.Command) (libgobuster.HTTPOptions, error) {
options := libgobuster.HTTPOptions{}
var err error
basic, err := parseBasicHTTPOptions(cmd)
if err != nil {
return options, err
}
options.Proxy = basic.Proxy
options.Timeout = basic.Timeout
options.UserAgent = basic.UserAgent
options.NoTLSValidation = basic.NoTLSValidation
options.RetryOnTimeout = basic.RetryOnTimeout
options.RetryAttempts = basic.RetryAttempts
options.TLSCertificate = basic.TLSCertificate
options.URL, err = cmd.Flags().GetString("url")
if err != nil {
return options, fmt.Errorf("invalid value for url: %w", 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: %w", err)
}
options.Username, err = cmd.Flags().GetString("username")
if err != nil {
return options, fmt.Errorf("invalid value for username: %w", err)
}
options.Password, err = cmd.Flags().GetString("password")
if err != nil {
return options, fmt.Errorf("invalid value for password: %w", err)
}
options.FollowRedirect, err = cmd.Flags().GetBool("follow-redirect")
if err != nil {
return options, fmt.Errorf("invalid value for follow-redirect: %w", err)
}
options.Method, err = cmd.Flags().GetString("method")
if err != nil {
return options, fmt.Errorf("invalid value for method: %w", err)
}
headers, err := cmd.Flags().GetStringArray("headers")
if err != nil {
return options, fmt.Errorf("invalid value for headers: %w", err)
}
for _, h := range headers {
keyAndValue := strings.SplitN(h, ":", 2)
if len(keyAndValue) != 2 {
return options, fmt.Errorf("invalid header format for header %q", h)
}
key := strings.TrimSpace(keyAndValue[0])
value := strings.TrimSpace(keyAndValue[1])
if len(key) == 0 {
return options, fmt.Errorf("invalid header format for header %q - name is empty", h)
}
header := libgobuster.HTTPHeader{Name: key, Value: value}
options.Headers = append(options.Headers, header)
}
noCanonHeaders, err := cmd.Flags().GetBool("no-canonicalize-headers")
if err != nil {
return options, fmt.Errorf("invalid value for no-canonicalize-headers: %w", err)
}
options.NoCanonicalizeHeaders = noCanonHeaders
// Prompt for PW if not provided
if options.Username != "" && options.Password == "" {
fmt.Printf("[?] Auth Password: ")
// please don't remove the int cast here as it is sadly needed on windows :/
passBytes, err := term.ReadPassword(int(syscall.Stdin)) //nolint:unconvert
// 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
}

194
cli/cmd/root.go Normal file
View File

@ -0,0 +1,194 @@
package cmd
import (
"bufio"
"context"
"fmt"
"log"
"os"
"os/signal"
"git.sual.in/casual/gobuster-lib/libgobuster"
"github.com/fatih/color"
"github.com/spf13/cobra"
)
// nolint:gochecknoglobals
var rootCmd = &cobra.Command{
Use: "gobuster",
SilenceUsage: true,
}
// nolint:gochecknoglobals
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
// this is going to be needed to output other errors that
// aren't automatically outputted.
// fmt.Println(err)
os.Exit(1)
}
}
func parseGlobalOptions() (*libgobuster.Options, error) {
globalopts := libgobuster.NewOptions()
threads, err := rootCmd.Flags().GetInt("threads")
if err != nil {
return nil, fmt.Errorf("invalid value for threads: %w", err)
}
if threads <= 0 {
return nil, fmt.Errorf("threads must be bigger than 0")
}
globalopts.Threads = threads
delay, err := rootCmd.Flags().GetDuration("delay")
if err != nil {
return nil, fmt.Errorf("invalid value for delay: %w", err)
}
if delay < 0 {
return nil, fmt.Errorf("delay must be positive")
}
globalopts.Delay = delay
globalopts.Wordlist, err = rootCmd.Flags().GetString("wordlist")
if err != nil {
return nil, fmt.Errorf("invalid value for wordlist: %w", err)
}
if globalopts.Wordlist == "-" {
// STDIN
} else if _, err2 := os.Stat(globalopts.Wordlist); os.IsNotExist(err2) {
return nil, fmt.Errorf("wordlist file %q does not exist: %w", globalopts.Wordlist, err2)
}
offset, err := rootCmd.Flags().GetInt("wordlist-offset")
if err != nil {
return nil, fmt.Errorf("invalid value for wordlist-offset: %w", err)
}
if offset < 0 {
return nil, fmt.Errorf("wordlist-offset must be bigger or equal to 0")
}
globalopts.WordlistOffset = offset
if globalopts.Wordlist == "-" && globalopts.WordlistOffset > 0 {
return nil, fmt.Errorf("wordlist-offset is not supported when reading from STDIN")
}
globalopts.PatternFile, err = rootCmd.Flags().GetString("pattern")
if err != nil {
return nil, fmt.Errorf("invalid value for pattern: %w", err)
}
if globalopts.PatternFile != "" {
if _, err = os.Stat(globalopts.PatternFile); os.IsNotExist(err) {
return nil, fmt.Errorf("pattern file %q does not exist: %w", globalopts.PatternFile, err)
}
patternFile, err := os.Open(globalopts.PatternFile)
if err != nil {
return nil, fmt.Errorf("could not open pattern file %q: %w", globalopts.PatternFile, err)
}
defer patternFile.Close()
scanner := bufio.NewScanner(patternFile)
for scanner.Scan() {
globalopts.Patterns = append(globalopts.Patterns, scanner.Text())
}
if err := scanner.Err(); err != nil {
return nil, fmt.Errorf("could not read pattern file %q: %w", globalopts.PatternFile, err)
}
}
globalopts.OutputFilename, err = rootCmd.Flags().GetString("output")
if err != nil {
return nil, fmt.Errorf("invalid value for output filename: %w", err)
}
globalopts.Verbose, err = rootCmd.Flags().GetBool("verbose")
if err != nil {
return nil, fmt.Errorf("invalid value for verbose: %w", err)
}
globalopts.Quiet, err = rootCmd.Flags().GetBool("quiet")
if err != nil {
return nil, fmt.Errorf("invalid value for quiet: %w", err)
}
globalopts.NoProgress, err = rootCmd.Flags().GetBool("no-progress")
if err != nil {
return nil, fmt.Errorf("invalid value for no-progress: %w", err)
}
globalopts.NoError, err = rootCmd.Flags().GetBool("no-error")
if err != nil {
return nil, fmt.Errorf("invalid value for no-error: %w", err)
}
noColor, err := rootCmd.Flags().GetBool("no-color")
if err != nil {
return nil, fmt.Errorf("invalid value for no-color: %w", err)
}
if noColor {
color.NoColor = true
}
globalopts.Debug, err = rootCmd.Flags().GetBool("debug")
if err != nil {
return nil, fmt.Errorf("invalid value for debug: %w", err)
}
return globalopts, nil
}
// This has to be called as part of the pre-run for sub commands. Including
// this in the init() function results in the built-in `help` command not
// working as intended. The required flags should only be marked as required
// on the global flags when one of the non-help commands is used.
func configureGlobalOptions() {
if err := rootCmd.MarkPersistentFlagRequired("wordlist"); err != nil {
log.Fatalf("error on marking flag as required: %v", err)
}
}
// nolint:gochecknoinits
func init() {
rootCmd.PersistentFlags().DurationP("delay", "", 0, "Time each thread waits between requests (e.g. 1500ms)")
rootCmd.PersistentFlags().IntP("threads", "t", 10, "Number of concurrent threads")
rootCmd.PersistentFlags().StringP("wordlist", "w", "", "Path to the wordlist. Set to - to use STDIN.")
rootCmd.PersistentFlags().IntP("wordlist-offset", "", 0, "Resume from a given position in the wordlist (defaults to 0)")
rootCmd.PersistentFlags().StringP("output", "o", "", "Output file to write results to (defaults to stdout)")
rootCmd.PersistentFlags().BoolP("verbose", "v", false, "Verbose output (errors)")
rootCmd.PersistentFlags().BoolP("quiet", "q", false, "Don't print the banner and other noise")
rootCmd.PersistentFlags().BoolP("no-progress", "z", false, "Don't display progress")
rootCmd.PersistentFlags().Bool("no-error", false, "Don't display errors")
rootCmd.PersistentFlags().StringP("pattern", "p", "", "File containing replacement patterns")
rootCmd.PersistentFlags().Bool("no-color", false, "Disable color output")
rootCmd.PersistentFlags().Bool("debug", false, "Enable debug output")
}

79
cli/cmd/s3.go Normal file
View File

@ -0,0 +1,79 @@
package cmd
import (
"fmt"
"git.sual.in/casual/gobuster-lib/cli"
"git.sual.in/casual/gobuster-lib/gobusters3"
"git.sual.in/casual/gobuster-lib/libgobuster"
"github.com/spf13/cobra"
)
// nolint:gochecknoglobals
var cmdS3 *cobra.Command
func runS3(cmd *cobra.Command, args []string) error {
globalopts, pluginopts, err := parseS3Options()
if err != nil {
return fmt.Errorf("error on parsing arguments: %w", err)
}
plugin, err := gobusters3.NewGobusterS3(globalopts, pluginopts)
if err != nil {
return fmt.Errorf("error on creating gobusters3: %w", err)
}
log := libgobuster.NewLogger(globalopts.Debug)
if _,err := cli.Gobuster(mainContext, globalopts, plugin, log); err != nil {
log.Debugf("%#v", err)
return fmt.Errorf("error on running gobuster: %w", err)
}
return nil
}
func parseS3Options() (*libgobuster.Options, *gobusters3.OptionsS3, error) {
globalopts, err := parseGlobalOptions()
if err != nil {
return nil, nil, err
}
pluginOpts := gobusters3.NewOptionsS3()
httpOpts, err := parseBasicHTTPOptions(cmdS3)
if err != nil {
return nil, nil, err
}
pluginOpts.UserAgent = httpOpts.UserAgent
pluginOpts.Proxy = httpOpts.Proxy
pluginOpts.Timeout = httpOpts.Timeout
pluginOpts.NoTLSValidation = httpOpts.NoTLSValidation
pluginOpts.RetryOnTimeout = httpOpts.RetryOnTimeout
pluginOpts.RetryAttempts = httpOpts.RetryAttempts
pluginOpts.TLSCertificate = httpOpts.TLSCertificate
pluginOpts.MaxFilesToList, err = cmdS3.Flags().GetInt("maxfiles")
if err != nil {
return nil, nil, fmt.Errorf("invalid value for maxfiles: %w", err)
}
return globalopts, pluginOpts, nil
}
// nolint:gochecknoinits
func init() {
cmdS3 = &cobra.Command{
Use: "s3",
Short: "Uses aws bucket enumeration mode",
RunE: runS3,
}
addBasicHTTPOptions(cmdS3)
cmdS3.Flags().IntP("maxfiles", "m", 5, "max files to list when listing buckets (only shown in verbose mode)")
cmdS3.PersistentPreRun = func(cmd *cobra.Command, args []string) {
configureGlobalOptions()
}
rootCmd.AddCommand(cmdS3)
}

80
cli/cmd/tftp.go Normal file
View File

@ -0,0 +1,80 @@
package cmd
import (
"fmt"
"log"
"strings"
"time"
"git.sual.in/casual/gobuster-lib/cli"
"git.sual.in/casual/gobuster-lib/gobustertftp"
"git.sual.in/casual/gobuster-lib/libgobuster"
"github.com/spf13/cobra"
)
// nolint:gochecknoglobals
var cmdTFTP *cobra.Command
func runTFTP(cmd *cobra.Command, args []string) error {
globalopts, pluginopts, err := parseTFTPOptions()
if err != nil {
return fmt.Errorf("error on parsing arguments: %w", err)
}
plugin, err := gobustertftp.NewGobusterTFTP(globalopts, pluginopts)
if err != nil {
return fmt.Errorf("error on creating gobustertftp: %w", err)
}
log := libgobuster.NewLogger(globalopts.Debug)
if _,err := cli.Gobuster(mainContext, globalopts, plugin, log); err != nil {
log.Debugf("%#v", err)
return fmt.Errorf("error on running gobuster: %w", err)
}
return nil
}
func parseTFTPOptions() (*libgobuster.Options, *gobustertftp.OptionsTFTP, error) {
globalopts, err := parseGlobalOptions()
if err != nil {
return nil, nil, err
}
pluginOpts := gobustertftp.NewOptionsTFTP()
pluginOpts.Server, err = cmdTFTP.Flags().GetString("server")
if err != nil {
return nil, nil, fmt.Errorf("invalid value for domain: %w", err)
}
if !strings.Contains(pluginOpts.Server, ":") {
pluginOpts.Server = fmt.Sprintf("%s:69", pluginOpts.Server)
}
pluginOpts.Timeout, err = cmdTFTP.Flags().GetDuration("timeout")
if err != nil {
return nil, nil, fmt.Errorf("invalid value for timeout: %w", err)
}
return globalopts, pluginOpts, nil
}
// nolint:gochecknoinits
func init() {
cmdTFTP = &cobra.Command{
Use: "tftp",
Short: "Uses TFTP enumeration mode",
RunE: runTFTP,
}
cmdTFTP.Flags().StringP("server", "s", "", "The target TFTP server")
cmdTFTP.Flags().DurationP("timeout", "", time.Second, "TFTP timeout")
if err := cmdTFTP.MarkFlagRequired("server"); err != nil {
log.Fatalf("error on marking flag as required: %v", err)
}
cmdTFTP.PersistentPreRun = func(cmd *cobra.Command, args []string) {
configureGlobalOptions()
}
rootCmd.AddCommand(cmdTFTP)
}

27
cli/cmd/version.go Normal file
View File

@ -0,0 +1,27 @@
package cmd
import (
"fmt"
"git.sual.in/casual/gobuster-lib/libgobuster"
"github.com/spf13/cobra"
)
// nolint:gochecknoglobals
var cmdVersion *cobra.Command
func runVersion(cmd *cobra.Command, args []string) error {
fmt.Println(libgobuster.VERSION)
return nil
}
// nolint:gochecknoinits
func init() {
cmdVersion = &cobra.Command{
Use: "version",
Short: "shows the current version",
RunE: runVersion,
}
rootCmd.AddCommand(cmdVersion)
}

105
cli/cmd/vhost.go Normal file
View File

@ -0,0 +1,105 @@
package cmd
import (
"fmt"
"log"
"git.sual.in/casual/gobuster-lib/cli"
"git.sual.in/casual/gobuster-lib/gobustervhost"
"git.sual.in/casual/gobuster-lib/libgobuster"
"github.com/spf13/cobra"
)
// nolint:gochecknoglobals
var cmdVhost *cobra.Command
func runVhost(cmd *cobra.Command, args []string) error {
globalopts, pluginopts, err := parseVhostOptions()
if err != nil {
return fmt.Errorf("error on parsing arguments: %w", err)
}
plugin, err := gobustervhost.NewGobusterVhost(globalopts, pluginopts)
if err != nil {
return fmt.Errorf("error on creating gobustervhost: %w", err)
}
log := libgobuster.NewLogger(globalopts.Debug)
if _,err := cli.Gobuster(mainContext, globalopts, plugin, log); err != nil {
log.Debugf("%#v", err)
return fmt.Errorf("error on running gobuster: %w", err)
}
return nil
}
func parseVhostOptions() (*libgobuster.Options, *gobustervhost.OptionsVhost, error) {
globalopts, err := parseGlobalOptions()
if err != nil {
return nil, nil, err
}
pluginOpts := gobustervhost.NewOptionsVhost()
httpOpts, err := parseCommonHTTPOptions(cmdVhost)
if err != nil {
return nil, nil, err
}
pluginOpts.Password = httpOpts.Password
pluginOpts.URL = httpOpts.URL
pluginOpts.UserAgent = httpOpts.UserAgent
pluginOpts.Username = httpOpts.Username
pluginOpts.Proxy = httpOpts.Proxy
pluginOpts.Cookies = httpOpts.Cookies
pluginOpts.Timeout = httpOpts.Timeout
pluginOpts.FollowRedirect = httpOpts.FollowRedirect
pluginOpts.NoTLSValidation = httpOpts.NoTLSValidation
pluginOpts.Headers = httpOpts.Headers
pluginOpts.Method = httpOpts.Method
pluginOpts.RetryOnTimeout = httpOpts.RetryOnTimeout
pluginOpts.RetryAttempts = httpOpts.RetryAttempts
pluginOpts.TLSCertificate = httpOpts.TLSCertificate
pluginOpts.NoCanonicalizeHeaders = httpOpts.NoCanonicalizeHeaders
pluginOpts.AppendDomain, err = cmdVhost.Flags().GetBool("append-domain")
if err != nil {
return nil, nil, fmt.Errorf("invalid value for append-domain: %w", err)
}
pluginOpts.ExcludeLength, err = cmdVhost.Flags().GetString("exclude-length")
if err != nil {
return nil, nil, fmt.Errorf("invalid value for exclude-length: %w", err)
}
ret, err := libgobuster.ParseCommaSeparatedInt(pluginOpts.ExcludeLength)
if err != nil {
return nil, nil, fmt.Errorf("invalid value for exclude-length: %w", err)
}
pluginOpts.ExcludeLengthParsed = ret
pluginOpts.Domain, err = cmdVhost.Flags().GetString("domain")
if err != nil {
return nil, nil, fmt.Errorf("invalid value for domain: %w", err)
}
return globalopts, pluginOpts, nil
}
// nolint:gochecknoinits
func init() {
cmdVhost = &cobra.Command{
Use: "vhost",
Short: "Uses VHOST enumeration mode (you most probably want to use the IP address as the URL parameter)",
RunE: runVhost,
}
if err := addCommonHTTPOptions(cmdVhost); err != nil {
log.Fatalf("%v", err)
}
cmdVhost.Flags().BoolP("append-domain", "", false, "Append main domain from URL to words from wordlist. Otherwise the fully qualified domains need to be specified in the wordlist.")
cmdVhost.Flags().String("exclude-length", "", "exclude the following content lengths (completely ignores the status). You can separate multiple lengths by comma and it also supports ranges like 203-206")
cmdVhost.Flags().String("domain", "", "the domain to append when using an IP address as URL. If left empty and you specify a domain based URL the hostname from the URL is extracted")
cmdVhost.PersistentPreRun = func(cmd *cobra.Command, args []string) {
configureGlobalOptions()
}
rootCmd.AddCommand(cmdVhost)
}

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

@ -0,0 +1,65 @@
package cmd
import (
"context"
"fmt"
"os"
"testing"
"time"
"git.sual.in/casual/gobuster-lib/cli"
"git.sual.in/casual/gobuster-lib/gobustervhost"
"git.sual.in/casual/gobuster-lib/libgobuster"
)
func BenchmarkVhostMode(b *testing.B) {
h := httpServer(b, "test")
defer h.Close()
pluginopts := gobustervhost.NewOptionsVhost()
pluginopts.URL = h.URL
pluginopts.Timeout = 10 * time.Second
wordlist, err := os.CreateTemp("", "")
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 := libgobuster.NewLogger(false)
// Run the real benchmark
for x := 0; x < b.N; x++ {
os.Stdout = devnull
os.Stderr = devnull
plugin, err := gobustervhost.NewGobusterVhost(&globalopts, pluginopts)
if err != nil {
b.Fatalf("error on creating gobusterdir: %v", err)
}
if err := cli.Gobuster(ctx, &globalopts, plugin, log); err != nil {
b.Fatalf("error on running gobuster: %v", err)
}
os.Stdout = oldStdout
os.Stderr = oldStderr
}
}

7
go.mod
View File

@ -1,4 +1,4 @@
module git.sual.in/casual/gobusterlib module git.sual.in/casual/gobuster-lib
go 1.22.5 go 1.22.5
@ -7,11 +7,16 @@ require (
github.com/fatih/color v1.17.0 github.com/fatih/color v1.17.0
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/pin/tftp/v3 v3.1.0 github.com/pin/tftp/v3 v3.1.0
github.com/spf13/cobra v1.8.1
golang.org/x/crypto v0.26.0
golang.org/x/term v0.23.0
) )
require ( require (
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/spf13/pflag v1.0.5 // indirect
golang.org/x/net v0.28.0 // indirect golang.org/x/net v0.28.0 // indirect
golang.org/x/sys v0.25.0 // indirect golang.org/x/sys v0.25.0 // indirect
) )

14
go.sum
View File

@ -1,9 +1,12 @@
git.sual.in/casual/gobuster-lib/libgobuster v0.0.0-20240904201007-8210f5ee7e12 h1:+CnxZE3aMK45ZGtoSNv/5J2VSL2igm0/Iytvbxcatog= git.sual.in/casual/gobuster-lib/libgobuster v0.0.0-20240904201007-8210f5ee7e12 h1:+CnxZE3aMK45ZGtoSNv/5J2VSL2igm0/Iytvbxcatog=
git.sual.in/casual/gobuster-lib/libgobuster v0.0.0-20240904201007-8210f5ee7e12/go.mod h1:bkuQXxQgSQ+tO2Qs6PiRKRLXd8g5izddsxraLjHzrD8= git.sual.in/casual/gobuster-lib/libgobuster v0.0.0-20240904201007-8210f5ee7e12/go.mod h1:bkuQXxQgSQ+tO2Qs6PiRKRLXd8g5izddsxraLjHzrD8=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
@ -11,7 +14,14 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/pin/tftp/v3 v3.1.0 h1:rQaxd4pGwcAJnpId8zC+O2NX3B2/NscjDZQaqEjuE7c= github.com/pin/tftp/v3 v3.1.0 h1:rQaxd4pGwcAJnpId8zC+O2NX3B2/NscjDZQaqEjuE7c=
github.com/pin/tftp/v3 v3.1.0/go.mod h1:xwQaN4viYL019tM4i8iecm++5cGxSqen6AJEOEyEI0w= github.com/pin/tftp/v3 v3.1.0/go.mod h1:xwQaN4viYL019tM4i8iecm++5cGxSqen6AJEOEyEI0w=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
@ -20,4 +30,8 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU=
golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

11
libgobuster/go.mod Normal file
View File

@ -0,0 +1,11 @@
module git.sual.in/casual/gobuster-lib/libgobuster
go 1.22.5
require github.com/fatih/color v1.17.0
require (
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
golang.org/x/sys v0.18.0 // indirect
)

11
libgobuster/go.sum Normal file
View File

@ -0,0 +1,11 @@
github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=