parent
1b6f1ddc06
commit
81b714f780
188
cli/cmd/dir.go
Normal file
188
cli/cmd/dir.go
Normal 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
88
cli/cmd/dir_test.go
Normal 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
115
cli/cmd/dns.go
Normal 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
148
cli/cmd/fuzz.go
Normal 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
79
cli/cmd/gcs.go
Normal 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
258
cli/cmd/http.go
Normal 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
194
cli/cmd/root.go
Normal 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
79
cli/cmd/s3.go
Normal 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
80
cli/cmd/tftp.go
Normal 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
27
cli/cmd/version.go
Normal 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
105
cli/cmd/vhost.go
Normal 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
65
cli/cmd/vhost_test.go
Normal 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
7
go.mod
@ -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
14
go.sum
@ -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
11
libgobuster/go.mod
Normal 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
11
libgobuster/go.sum
Normal 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=
|
Loading…
Reference in New Issue
Block a user