goodbye cobra

This commit is contained in:
firefart
2023-08-30 01:13:54 +02:00
parent e3ed5cb24f
commit 21b2304262
27 changed files with 907 additions and 1313 deletions
+5 -5
View File
@@ -1,4 +1,5 @@
Apache License
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
@@ -178,7 +179,7 @@ Apache License
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
@@ -186,7 +187,7 @@ Apache License
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -198,5 +199,4 @@ Apache License
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
limitations under the License.
+1
View File
@@ -26,6 +26,7 @@ All funds that are donated to this project will be donated to charity. A full lo
## 3.7
- use new cli library that does not rely on global variables
- More user friendly error messages
- Clean up DNS mode
- renamed `show-cname` to `check-cname` in dns mode
-188
View File
@@ -1,188 +0,0 @@
package cmd
import (
"errors"
"fmt"
"log"
"github.com/OJ/gobuster/v3/cli"
"github.com/OJ/gobuster/v3/gobusterdir"
"github.com/OJ/gobuster/v3/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 on %s: %w", pluginopts.URL, 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)
}
-115
View File
@@ -1,115 +0,0 @@
package cmd
import (
"errors"
"fmt"
"log"
"runtime"
"time"
"github.com/OJ/gobuster/v3/cli"
"github.com/OJ/gobuster/v3/gobusterdns"
"github.com/OJ/gobuster/v3/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 on %s: %w", pluginopts.Domain, 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.CheckCNAME, err = cmdDNS.Flags().GetBool("check-cname")
if err != nil {
return nil, nil, fmt.Errorf("invalid value for check-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 of found domains")
cmdDNS.Flags().BoolP("check-cname", "c", false, "Also check CNAME records")
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
View File
@@ -1,148 +0,0 @@
package cmd
import (
"errors"
"fmt"
"log"
"strings"
"github.com/OJ/gobuster/v3/cli"
"github.com/OJ/gobuster/v3/gobusterfuzz"
"github.com/OJ/gobuster/v3/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 on %s: %w", pluginopts.URL, 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
}
-84
View File
@@ -1,84 +0,0 @@
package cmd
import (
"fmt"
"github.com/OJ/gobuster/v3/cli"
"github.com/OJ/gobuster/v3/gobustergcs"
"github.com/OJ/gobuster/v3/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)
}
pluginopts.ShowFiles, err = cmdGCS.Flags().GetBool("show-files")
if err != nil {
return nil, nil, fmt.Errorf("invalid value for show-files: %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")
cmdGCS.Flags().BoolP("show-files", "s", true, "show files from found buckets")
cmdGCS.PersistentPreRun = func(cmd *cobra.Command, args []string) {
configureGlobalOptions()
}
rootCmd.AddCommand(cmdGCS)
}
-258
View File
@@ -1,258 +0,0 @@
package cmd
import (
"crypto/tls"
"encoding/pem"
"fmt"
"os"
"regexp"
"strconv"
"strings"
"syscall"
"time"
"github.com/OJ/gobuster/v3/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
}
-188
View File
@@ -1,188 +0,0 @@
package cmd
import (
"bufio"
"context"
"fmt"
"log"
"os"
"os/signal"
"github.com/OJ/gobuster/v3/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.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("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")
}
-84
View File
@@ -1,84 +0,0 @@
package cmd
import (
"fmt"
"github.com/OJ/gobuster/v3/cli"
"github.com/OJ/gobuster/v3/gobusters3"
"github.com/OJ/gobuster/v3/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)
}
pluginOpts.ShowFiles, err = cmdS3.Flags().GetBool("show-files")
if err != nil {
return nil, nil, fmt.Errorf("invalid value for show-files: %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.Flags().BoolP("show-files", "s", true, "show files from found buckets")
cmdS3.PersistentPreRun = func(cmd *cobra.Command, args []string) {
configureGlobalOptions()
}
rootCmd.AddCommand(cmdS3)
}
-80
View File
@@ -1,80 +0,0 @@
package cmd
import (
"fmt"
"log"
"strings"
"time"
"github.com/OJ/gobuster/v3/cli"
"github.com/OJ/gobuster/v3/gobustertftp"
"github.com/OJ/gobuster/v3/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 on %s: %w", pluginopts.Server, 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
View File
@@ -1,27 +0,0 @@
package cmd
import (
"fmt"
"github.com/OJ/gobuster/v3/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
View File
@@ -1,105 +0,0 @@
package cmd
import (
"fmt"
"log"
"github.com/OJ/gobuster/v3/cli"
"github.com/OJ/gobuster/v3/gobustervhost"
"github.com/OJ/gobuster/v3/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 on %s: %w", pluginopts.URL, 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)
}
+122
View File
@@ -0,0 +1,122 @@
package dir
import (
"errors"
"fmt"
internalcli "github.com/OJ/gobuster/v3/cli"
"github.com/OJ/gobuster/v3/gobusterdir"
"github.com/OJ/gobuster/v3/libgobuster"
"github.com/urfave/cli/v2"
)
func Command() *cli.Command {
cmd := cli.Command{
Name: "dir",
Usage: "Uses directory/file enumeration mode",
Action: run,
Flags: getFlags(),
}
return &cmd
}
func getFlags() []cli.Flag {
var flags []cli.Flag
flags = append(flags, internalcli.CommonHTTPOptions()...)
flags = append(flags, internalcli.GlobalOptions()...)
flags = append(flags, []cli.Flag{
&cli.StringFlag{Name: "status-codes", Aliases: []string{"s"}, Usage: "Positive status codes (will be overwritten with status-codes-blacklist if set). Can also handle ranges like 200,300-400,404"},
&cli.StringFlag{Name: "status-codes-blacklist", Aliases: []string{"b"}, Usage: "Negative status codes (will override status-codes if set). Can also handle ranges like 200,300-400,404.", Value: "404"},
&cli.StringFlag{Name: "extensions", Aliases: []string{"x"}, Usage: "File extension(s) to search for"},
&cli.StringFlag{Name: "extensions-file", Aliases: []string{"X"}, Usage: "Read file extension(s) to search from the file"},
&cli.BoolFlag{Name: "expanded", Aliases: []string{"e"}, Value: false, Usage: "Expanded mode, print full URLs"},
&cli.BoolFlag{Name: "no-status", Aliases: []string{"n"}, Value: false, Usage: "Don't print status codes"},
&cli.BoolFlag{Name: "hide-length", Value: false, Usage: "Hide the length of the body in the output"},
&cli.BoolFlag{Name: "add-slash", Aliases: []string{"f"}, Value: false, Usage: "Append / to each request"},
&cli.BoolFlag{Name: "discover-backup", Value: false, Usage: "Also search for backup files by appending multiple backup extensions"},
&cli.StringFlag{Name: "exclude-length", Aliases: []string{"l"}, Usage: "exclude the following content lengths (completely ignores the status). You can separate multiple lengths by comma and it also supports ranges like 203-206"},
}...)
return flags
}
func run(c *cli.Context) error {
pluginOpts := gobusterdir.NewOptionsDir()
httpOptions, err := internalcli.ParseCommonHTTPOptions(c)
if err != nil {
return err
}
pluginOpts.HTTPOptions = httpOptions
pluginOpts.Extensions = c.String("extensions")
ret, err := libgobuster.ParseExtensions(pluginOpts.Extensions)
if err != nil {
return fmt.Errorf("invalid value for extensions: %w", err)
}
pluginOpts.ExtensionsParsed = ret
pluginOpts.ExtensionsFile = c.String("extensions-file")
if pluginOpts.ExtensionsFile != "" {
extensions, err := libgobuster.ParseExtensionsFile(pluginOpts.ExtensionsFile)
if err != nil {
return fmt.Errorf("invalid value for extensions file: %w", err)
}
pluginOpts.ExtensionsParsed.AddRange(extensions)
}
pluginOpts.StatusCodes = c.String("status-codes")
ret2, err := libgobuster.ParseCommaSeparatedInt(pluginOpts.StatusCodes)
if err != nil {
return fmt.Errorf("invalid value for status-codes: %w", err)
}
pluginOpts.StatusCodesParsed = ret2
pluginOpts.StatusCodesBlacklist = c.String("status-codes-blacklist")
ret3, err := libgobuster.ParseCommaSeparatedInt(pluginOpts.StatusCodesBlacklist)
if err != nil {
return fmt.Errorf("invalid value for status-codes-blacklist: %w", err)
}
pluginOpts.StatusCodesBlacklistParsed = ret3
if pluginOpts.StatusCodes != "" && pluginOpts.StatusCodesBlacklist != "" {
return 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 fmt.Errorf("status-codes and status-codes-blacklist are both not set, please set one")
}
pluginOpts.UseSlash = c.Bool("add-slash")
pluginOpts.Expanded = c.Bool("expanded")
pluginOpts.NoStatus = c.Bool("no-status")
pluginOpts.HideLength = c.Bool("hide-length")
pluginOpts.DiscoverBackup = c.Bool("discover-backup")
pluginOpts.ExcludeLength = c.String("exclude-length")
ret4, err := libgobuster.ParseCommaSeparatedInt(pluginOpts.ExcludeLength)
if err != nil {
return fmt.Errorf("invalid value for exclude-length: %w", err)
}
pluginOpts.ExcludeLengthParsed = ret4
globalOpts, err := internalcli.ParseGlobalOptions(c)
if err != nil {
return 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 := internalcli.Gobuster(c.Context, &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 on %s: %w", pluginOpts.URL, err)
}
return nil
}
+1 -1
View File
@@ -1,4 +1,4 @@
package cmd
package dir
import (
"context"
+75
View File
@@ -0,0 +1,75 @@
package dns
import (
"errors"
"fmt"
"runtime"
"time"
internalcli "github.com/OJ/gobuster/v3/cli"
"github.com/OJ/gobuster/v3/gobusterdns"
"github.com/OJ/gobuster/v3/libgobuster"
"github.com/urfave/cli/v2"
)
func Command() *cli.Command {
cmd := cli.Command{
Name: "dns",
Usage: "Uses DNS subdomain enumeration mode",
Action: run,
Flags: getFlags(),
}
return &cmd
}
func getFlags() []cli.Flag {
var flags []cli.Flag
flags = append(flags, []cli.Flag{
&cli.StringFlag{Name: "domain", Aliases: []string{"do"}, Usage: "The target domain", Required: true},
&cli.BoolFlag{Name: "show-ips", Aliases: []string{"i"}, Value: false, Usage: "Show IP addresses of found domains"},
&cli.BoolFlag{Name: "check-cname", Aliases: []string{"c"}, Value: false, Usage: "Also check CNAME records"},
&cli.DurationFlag{Name: "timeout", Value: 1 * time.Second, Usage: "DNS resolver timeout"},
&cli.BoolFlag{Name: "wildcard", Value: false, Usage: "Force continued operation when wildcard found"},
&cli.BoolFlag{Name: "no-fqdn", Value: false, Usage: "Do not automatically add a trailing dot to the domain, so the resolver uses the DNS search domain"},
&cli.StringFlag{Name: "resolver", Usage: "Use custom DNS server (format server.com or server.com:port)"},
}...)
flags = append(flags, internalcli.GlobalOptions()...)
return flags
}
func run(c *cli.Context) error {
pluginOpts := gobusterdns.NewOptionsDNS()
pluginOpts.Domain = c.String("domain")
pluginOpts.ShowIPs = c.Bool("show-ips")
pluginOpts.CheckCNAME = c.Bool("check-cname")
pluginOpts.Timeout = c.Duration("timeout")
pluginOpts.WildcardForced = c.Bool("wildcard")
pluginOpts.NoFQDN = c.Bool("no-fqdn")
pluginOpts.Resolver = c.String("resolver")
if pluginOpts.Resolver != "" && runtime.GOOS == "windows" {
return fmt.Errorf("currently can not set custom dns resolver on windows. See https://golang.org/pkg/net/#hdr-Name_Resolution")
}
globalOpts, err := internalcli.ParseGlobalOptions(c)
if err != nil {
return 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 := internalcli.Gobuster(c.Context, &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 on %s: %w", pluginOpts.Domain, err)
}
return nil
}
+107
View File
@@ -0,0 +1,107 @@
package fuzz
import (
"fmt"
"strings"
internalcli "github.com/OJ/gobuster/v3/cli"
"github.com/OJ/gobuster/v3/gobusterfuzz"
"github.com/OJ/gobuster/v3/libgobuster"
"github.com/urfave/cli/v2"
)
func Command() *cli.Command {
cmd := cli.Command{
Name: "fuzz",
Usage: fmt.Sprintf("Uses fuzzing mode. Replaces the keyword %s in the URL, Headers and the request body", gobusterfuzz.FuzzKeyword),
Action: run,
Flags: getFlags(),
}
return &cmd
}
func getFlags() []cli.Flag {
var flags []cli.Flag
flags = append(flags, internalcli.CommonHTTPOptions()...)
flags = append(flags, internalcli.GlobalOptions()...)
flags = append(flags, []cli.Flag{
&cli.StringFlag{Name: "excludestatuscodes", Aliases: []string{"b"}, Usage: "Excluded status codes. Can also handle ranges like 200,300-400,404."},
&cli.StringFlag{Name: "exclude-length", Usage: "exclude the following content lengths (completely ignores the status). You can separate multiple lengths by comma and it also supports ranges like 203-206"},
&cli.StringFlag{Name: "body", Aliases: []string{"B"}, Usage: "Request body"},
}...)
return flags
}
func run(c *cli.Context) error {
pluginOpts := gobusterfuzz.NewOptionsFuzz()
httpOptions, err := internalcli.ParseCommonHTTPOptions(c)
if err != nil {
return err
}
pluginOpts.HTTPOptions = httpOptions
pluginOpts.ExcludedStatusCodes = c.String("excludestatuscodes")
ret, err := libgobuster.ParseCommaSeparatedInt(pluginOpts.ExcludedStatusCodes)
if err != nil {
return fmt.Errorf("invalid value for excludestatuscodes: %w", err)
}
pluginOpts.ExcludedStatusCodesParsed = ret
pluginOpts.ExcludeLength = c.String("exclude-length")
ret2, err := libgobuster.ParseCommaSeparatedInt(pluginOpts.ExcludeLength)
if err != nil {
return fmt.Errorf("invalid value for exclude-length: %w", err)
}
pluginOpts.ExcludeLengthParsed = ret2
pluginOpts.RequestBody = c.String("body")
globalOpts, err := internalcli.ParseGlobalOptions(c)
if err != nil {
return 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 := internalcli.Gobuster(c.Context, &globalOpts, plugin, log); err != nil {
log.Debugf("%#v", err)
return fmt.Errorf("error on running gobuster on %s: %w", pluginOpts.URL, err)
}
return nil
}
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
}
+61
View File
@@ -0,0 +1,61 @@
package gcs
import (
"fmt"
internalcli "github.com/OJ/gobuster/v3/cli"
"github.com/OJ/gobuster/v3/gobustergcs"
"github.com/OJ/gobuster/v3/libgobuster"
"github.com/urfave/cli/v2"
)
func Command() *cli.Command {
cmd := cli.Command{
Name: "gcs",
Usage: "Uses gcs bucket enumeration mode",
Action: run,
Flags: getFlags(),
}
return &cmd
}
func getFlags() []cli.Flag {
var flags []cli.Flag
flags = append(flags, []cli.Flag{
&cli.IntFlag{Name: "maxfiles", Aliases: []string{"m"}, Value: 5, Usage: "max files to list when listing buckets"},
&cli.BoolFlag{Name: "show-files", Aliases: []string{"s"}, Value: true, Usage: "show files from found buckets"},
}...)
flags = append(flags, internalcli.GlobalOptions()...)
flags = append(flags, internalcli.BasicHTTPOptions()...)
return flags
}
func run(c *cli.Context) error {
pluginOpts := gobustergcs.NewOptionsGCS()
httpOptions, err := internalcli.ParseBasicHTTPOptions(c)
if err != nil {
return err
}
pluginOpts.BasicHTTPOptions = httpOptions
pluginOpts.MaxFilesToList = c.Int("maxfiles")
pluginOpts.ShowFiles = c.Bool("show-files")
globalOpts, err := internalcli.ParseGlobalOptions(c)
if err != nil {
return 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 := internalcli.Gobuster(c.Context, &globalOpts, plugin, log); err != nil {
log.Debugf("%#v", err)
return fmt.Errorf("error on running gobuster: %w", err)
}
return nil
}
+246
View File
@@ -0,0 +1,246 @@
package cli
import (
"bufio"
"crypto/tls"
"encoding/pem"
"fmt"
"os"
"regexp"
"strconv"
"strings"
"syscall"
"time"
"github.com/OJ/gobuster/v3/libgobuster"
"github.com/fatih/color"
"github.com/urfave/cli/v2"
"golang.org/x/crypto/pkcs12"
"golang.org/x/term"
)
func BasicHTTPOptions() []cli.Flag {
return []cli.Flag{
&cli.StringFlag{Name: "useragent", Aliases: []string{"a"}, Value: libgobuster.DefaultUserAgent(), Usage: "Set the User-Agent string"},
&cli.BoolFlag{Name: "random-agent", Value: false, Usage: "Use a random User-Agent string"},
&cli.StringFlag{Name: "proxy", Usage: "Proxy to use for requests [http(s)://host:port] or [socks5://host:port]"},
&cli.DurationFlag{Name: "timeout", Value: 10 * time.Second, Usage: "HTTP Timeout"},
&cli.BoolFlag{Name: "no-tls-validation", Aliases: []string{"k"}, Value: false, Usage: "Skip TLS certificate verification"},
&cli.BoolFlag{Name: "retry", Value: false, Usage: "Should retry on request timeout"},
&cli.IntFlag{Name: "retry-attempts", Value: 3, Usage: "Times to retry on request timeout"},
&cli.StringFlag{Name: "client-cert-pem", Usage: "public key in PEM format for optional TLS client certificates]"},
&cli.StringFlag{Name: "client-cert-pem-key", Usage: "private key in PEM format for optional TLS client certificates (this key needs to have no password)"},
&cli.StringFlag{Name: "client-cert-p12", Usage: "a p12 file to use for options TLS client certificates"},
&cli.StringFlag{Name: "client-cert-p12-password", Usage: "the password to the p12 file"},
}
}
func ParseBasicHTTPOptions(c *cli.Context) (libgobuster.BasicHTTPOptions, error) {
var opts libgobuster.BasicHTTPOptions
opts.UserAgent = c.String("useragent")
randomUA := c.Bool("random-agent")
if randomUA {
ua, err := libgobuster.GetRandomUserAgent()
if err != nil {
return opts, err
}
opts.UserAgent = ua
}
opts.Proxy = c.String("proxy")
opts.Timeout = c.Duration("timeout")
opts.NoTLSValidation = c.Bool("no-tls-validation")
opts.RetryOnTimeout = c.Bool("retry")
opts.RetryAttempts = c.Int("retry-attempts")
pemFile := c.String("client-cert-pem")
pemKeyFile := c.String("client-cert-pem-key")
p12File := c.String("client-cert-p12")
p12Pass := c.String("client-cert-p12-password")
if pemFile != "" && p12File != "" {
return opts, fmt.Errorf("please supply either a pem or a p12, not both")
}
if pemFile != "" {
cert, err := tls.LoadX509KeyPair(pemFile, pemKeyFile)
if err != nil {
return opts, fmt.Errorf("could not load supplied pem key: %w", err)
}
opts.TLSCertificate = &cert
} else if p12File != "" {
p12Content, err := os.ReadFile(p12File)
if err != nil {
return opts, fmt.Errorf("could not read p12 %s: %w", p12File, err)
}
blocks, err := pkcs12.ToPEM(p12Content, p12Pass)
if err != nil {
return opts, 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 opts, fmt.Errorf("could not load certificate from P12: %w", err)
}
opts.TLSCertificate = &cert
}
return opts, nil
}
func CommonHTTPOptions() []cli.Flag {
var flags []cli.Flag
flags = append(flags, []cli.Flag{
&cli.StringFlag{Name: "url", Aliases: []string{"u"}, Usage: "The target URL", Required: true},
&cli.StringFlag{Name: "cookies", Aliases: []string{"c"}, Usage: "Cookies to use for the requests"},
&cli.StringFlag{Name: "username", Aliases: []string{"U"}, Usage: "Username for Basic Auth"},
&cli.StringFlag{Name: "password", Aliases: []string{"P"}, Usage: "Password for Basic Auth"},
&cli.BoolFlag{Name: "follow-redirect", Aliases: []string{"r"}, Value: false, Usage: "Follow redirects"},
&cli.StringSliceFlag{Name: "headers", Aliases: []string{"H"}, Usage: "Specify HTTP headers, -H 'Header1: val1' -H 'Header2: val2'"},
&cli.BoolFlag{Name: "no-canonicalize-headers", Value: false, Usage: "Do not canonicalize HTTP header names. If set header names are sent as is"},
&cli.StringFlag{Name: "method", Aliases: []string{"m"}, Value: "GET", Usage: "the password to the p12 file"},
}...)
flags = append(flags, BasicHTTPOptions()...)
return flags
}
func ParseCommonHTTPOptions(c *cli.Context) (libgobuster.HTTPOptions, error) {
var opts libgobuster.HTTPOptions
basic, err := ParseBasicHTTPOptions(c)
if err != nil {
return opts, err
}
opts.BasicHTTPOptions = basic
opts.URL = c.String("url")
if !strings.HasPrefix(opts.URL, "http") {
// check to see if a port was specified
re := regexp.MustCompile(`^[^/]+:(\d+)`)
match := re.FindStringSubmatch(opts.URL)
if len(match) < 2 {
// no port, default to http on 80
opts.URL = fmt.Sprintf("http://%s", opts.URL)
} else {
port, err2 := strconv.Atoi(match[1])
if err2 != nil || (port != 80 && port != 443) {
return opts, fmt.Errorf("url scheme not specified")
} else if port == 80 {
opts.URL = fmt.Sprintf("http://%s", opts.URL)
} else {
opts.URL = fmt.Sprintf("https://%s", opts.URL)
}
}
}
opts.Cookies = c.String("cookies")
opts.Username = c.String("username")
opts.Password = c.String("password")
// Prompt for PW if not provided
if opts.Username != "" && opts.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 opts, fmt.Errorf("username given but reading of password failed")
}
opts.Password = string(passBytes)
}
// if it's still empty bail out
if opts.Username != "" && opts.Password == "" {
return opts, fmt.Errorf("username was provided but password is missing")
}
opts.FollowRedirect = c.Bool("follow-redirect")
opts.NoCanonicalizeHeaders = c.Bool("no-canonicalize-headers")
opts.Method = c.String("method")
for _, h := range c.StringSlice("headers") {
keyAndValue := strings.SplitN(h, ":", 2)
if len(keyAndValue) != 2 {
return opts, fmt.Errorf("invalid header format for header %q", h)
}
key := strings.TrimSpace(keyAndValue[0])
value := strings.TrimSpace(keyAndValue[1])
if len(key) == 0 {
return opts, fmt.Errorf("invalid header format for header %q - name is empty", h)
}
header := libgobuster.HTTPHeader{Name: key, Value: value}
opts.Headers = append(opts.Headers, header)
}
return opts, nil
}
func GlobalOptions() []cli.Flag {
return []cli.Flag{
&cli.StringFlag{Name: "wordlist", Aliases: []string{"w"}, Usage: "Path to the wordlist. Set to - to use STDIN.", Required: true},
&cli.DurationFlag{Name: "delay", Usage: "Time each thread waits between requests (e.g. 1500ms)"},
&cli.IntFlag{Name: "threads", Aliases: []string{"t"}, Value: 10, Usage: "Number of concurrent threads"},
&cli.IntFlag{Name: "wordlist-offset", Value: 0, Usage: "Resume from a given position in the wordlist"},
&cli.StringFlag{Name: "output", Aliases: []string{"o"}, Usage: "Output file to write results to (defaults to stdout)"},
&cli.BoolFlag{Name: "quiet", Aliases: []string{"q"}, Value: false, Usage: "Don't print the banner and other noise"},
&cli.BoolFlag{Name: "no-progress", Aliases: []string{"z"}, Value: false, Usage: "Don't display progress"},
&cli.BoolFlag{Name: "no-error", Value: false, Usage: "Don't display errors"},
&cli.StringFlag{Name: "pattern", Aliases: []string{"p"}, Usage: "File containing replacement patterns"},
&cli.BoolFlag{Name: "no-color", Value: false, Usage: "Disable color output"},
&cli.BoolFlag{Name: "debug", Aliases: []string{"d"}, Value: false, Usage: "enable debug output"},
}
}
func ParseGlobalOptions(c *cli.Context) (libgobuster.Options, error) {
var opts libgobuster.Options
opts.Wordlist = c.String("wordlist")
if opts.Wordlist == "-" {
// STDIN
} else if _, err := os.Stat(opts.Wordlist); os.IsNotExist(err) {
return opts, fmt.Errorf("wordlist file %q does not exist: %w", opts.Wordlist, err)
}
opts.Delay = c.Duration("delay")
opts.Threads = c.Int("threads")
opts.WordlistOffset = c.Int("wordlist-offset")
if opts.Wordlist == "-" && opts.WordlistOffset > 0 {
return opts, fmt.Errorf("wordlist-offset is not supported when reading from STDIN")
} else if opts.WordlistOffset < 0 {
return opts, fmt.Errorf("wordlist-offset must be bigger or equal to 0")
}
opts.OutputFilename = c.String("output")
opts.Quiet = c.Bool("quiet")
opts.NoProgress = c.Bool("no-progress")
opts.NoError = c.Bool("no-error")
opts.PatternFile = c.String("pattern")
if opts.PatternFile != "" {
if _, err := os.Stat(opts.PatternFile); os.IsNotExist(err) {
return opts, fmt.Errorf("pattern file %q does not exist: %w", opts.PatternFile, err)
}
patternFile, err := os.Open(opts.PatternFile)
if err != nil {
return opts, fmt.Errorf("could not open pattern file %q: %w", opts.PatternFile, err)
}
defer patternFile.Close()
scanner := bufio.NewScanner(patternFile)
for scanner.Scan() {
opts.Patterns = append(opts.Patterns, scanner.Text())
}
if err := scanner.Err(); err != nil {
return opts, fmt.Errorf("could not read pattern file %q: %w", opts.PatternFile, err)
}
}
if c.Bool("no-color") {
color.NoColor = true
}
opts.Debug = c.Bool("debug")
return opts, nil
}
+61
View File
@@ -0,0 +1,61 @@
package s3
import (
"fmt"
internalcli "github.com/OJ/gobuster/v3/cli"
"github.com/OJ/gobuster/v3/gobusters3"
"github.com/OJ/gobuster/v3/libgobuster"
"github.com/urfave/cli/v2"
)
func Command() *cli.Command {
cmd := cli.Command{
Name: "s3",
Usage: "Uses aws bucket enumeration mode",
Action: run,
Flags: getFlags(),
}
return &cmd
}
func getFlags() []cli.Flag {
var flags []cli.Flag
flags = append(flags, []cli.Flag{
&cli.IntFlag{Name: "maxfiles", Aliases: []string{"m"}, Value: 5, Usage: "max files to list when listing buckets"},
&cli.BoolFlag{Name: "show-files", Aliases: []string{"s"}, Value: true, Usage: "show files from found buckets"},
}...)
flags = append(flags, internalcli.GlobalOptions()...)
flags = append(flags, internalcli.BasicHTTPOptions()...)
return flags
}
func run(c *cli.Context) error {
pluginOpts := gobusters3.NewOptionsS3()
httpOptions, err := internalcli.ParseBasicHTTPOptions(c)
if err != nil {
return err
}
pluginOpts.BasicHTTPOptions = httpOptions
pluginOpts.MaxFilesToList = c.Int("maxfiles")
pluginOpts.ShowFiles = c.Bool("show-files")
globalOpts, err := internalcli.ParseGlobalOptions(c)
if err != nil {
return 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 := internalcli.Gobuster(c.Context, &globalOpts, plugin, log); err != nil {
log.Debugf("%#v", err)
return fmt.Errorf("error on running gobuster: %w", err)
}
return nil
}
+60
View File
@@ -0,0 +1,60 @@
package tftp
import (
"fmt"
"strings"
"time"
internalcli "github.com/OJ/gobuster/v3/cli"
"github.com/OJ/gobuster/v3/gobustertftp"
"github.com/OJ/gobuster/v3/libgobuster"
"github.com/urfave/cli/v2"
)
func Command() *cli.Command {
cmd := cli.Command{
Name: "tftp",
Usage: "Uses TFTP enumeration mode",
Action: run,
Flags: getFlags(),
}
return &cmd
}
func getFlags() []cli.Flag {
var flags []cli.Flag
flags = append(flags, []cli.Flag{
&cli.StringFlag{Name: "server", Aliases: []string{"s"}, Usage: "The target TFTP server", Required: true},
&cli.DurationFlag{Name: "timeout", Value: 1 * time.Second, Usage: "TFTP timeout"},
}...)
flags = append(flags, internalcli.GlobalOptions()...)
return flags
}
func run(c *cli.Context) error {
pluginOpts := gobustertftp.NewOptionsTFTP()
pluginOpts.Server = c.String("server")
if !strings.Contains(pluginOpts.Server, ":") {
pluginOpts.Server = fmt.Sprintf("%s:69", pluginOpts.Server)
}
pluginOpts.Timeout = c.Duration("timeout")
globalOpts, err := internalcli.ParseGlobalOptions(c)
if err != nil {
return 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 := internalcli.Gobuster(c.Context, &globalOpts, plugin, log); err != nil {
log.Debugf("%#v", err)
return fmt.Errorf("error on running gobuster on %s: %w", pluginOpts.Server, err)
}
return nil
}
+70
View File
@@ -0,0 +1,70 @@
package vhost
import (
"fmt"
internalcli "github.com/OJ/gobuster/v3/cli"
"github.com/OJ/gobuster/v3/gobustervhost"
"github.com/OJ/gobuster/v3/libgobuster"
"github.com/urfave/cli/v2"
)
func Command() *cli.Command {
cmd := cli.Command{
Name: "vhost",
Usage: "Uses VHOST enumeration mode (you most probably want to use the IP address as the URL parameter)",
Action: run,
Flags: getFlags(),
}
return &cmd
}
func getFlags() []cli.Flag {
var flags []cli.Flag
flags = append(flags, internalcli.CommonHTTPOptions()...)
flags = append(flags, internalcli.GlobalOptions()...)
flags = append(flags, []cli.Flag{
&cli.BoolFlag{Name: "append-domain", Value: false, Usage: "Append main domain from URL to words from wordlist. Otherwise the fully qualified domains need to be specified in the wordlist."},
&cli.StringFlag{Name: "exclude-length", Usage: "exclude the following content lengths (completely ignores the status). You can separate multiple lengths by comma and it also supports ranges like 203-206"},
&cli.StringFlag{Name: "domain", Usage: "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"},
}...)
return flags
}
func run(c *cli.Context) error {
pluginOpts := gobustervhost.NewOptionsVhost()
httpOptions, err := internalcli.ParseCommonHTTPOptions(c)
if err != nil {
return err
}
pluginOpts.HTTPOptions = httpOptions
pluginOpts.AppendDomain = c.Bool("append-domain")
pluginOpts.ExcludeLength = c.String("exclude-length")
ret, err := libgobuster.ParseCommaSeparatedInt(pluginOpts.ExcludeLength)
if err != nil {
return fmt.Errorf("invalid value for exclude-length: %w", err)
}
pluginOpts.ExcludeLengthParsed = ret
pluginOpts.Domain = c.String("domain")
globalOpts, err := internalcli.ParseGlobalOptions(c)
if err != nil {
return 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 := internalcli.Gobuster(c.Context, &globalOpts, plugin, log); err != nil {
log.Debugf("%#v", err)
return fmt.Errorf("error on running gobuster on %s: %w", pluginOpts.URL, err)
}
return nil
}
@@ -1,8 +1,10 @@
package cmd
package vhost
import (
"context"
"fmt"
"net/http"
"net/http/httptest"
"os"
"testing"
"time"
@@ -12,6 +14,14 @@ import (
"github.com/OJ/gobuster/v3/libgobuster"
)
func httpServer(b *testing.B, content string) *httptest.Server {
b.Helper()
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, content)
}))
return ts
}
func BenchmarkVhostMode(b *testing.B) {
h := httpServer(b, "test")
defer h.Close()
+4 -3
View File
@@ -6,16 +6,17 @@ require (
github.com/fatih/color v1.15.0
github.com/google/uuid v1.3.1
github.com/pin/tftp/v3 v3.0.0
github.com/spf13/cobra v1.7.0
github.com/urfave/cli/v2 v2.25.7
golang.org/x/crypto v0.12.0
golang.org/x/term v0.11.0
)
require (
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
golang.org/x/net v0.14.0 // indirect
golang.org/x/sys v0.11.0 // indirect
)
+6 -8
View File
@@ -1,10 +1,9 @@
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
github.com/google/uuid v1.3.1/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/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
@@ -12,11 +11,12 @@ github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APP
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/pin/tftp/v3 v3.0.0 h1:o9cQpmWBSbgiaYXuN+qJAB12XBIv4dT7OuOONucn2l0=
github.com/pin/tftp/v3 v3.0.0/go.mod h1:xwQaN4viYL019tM4i8iecm++5cGxSqen6AJEOEyEI0w=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs=
github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
@@ -31,5 +31,3 @@ golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0=
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
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=
+1 -1
View File
@@ -4672,5 +4672,5 @@ func GetRandomUserAgent() (string, error) {
if err != nil {
return "", err
}
return userAgents[n.Int64()], err
return userAgents[n.Int64()], nil
}
+20
View File
@@ -1,6 +1,26 @@
package libgobuster
import (
"fmt"
"runtime/debug"
)
const (
// VERSION contains the current gobuster version
VERSION = "3.7"
)
func GetVersion() string {
version := VERSION
if info, ok := debug.ReadBuildInfo(); ok {
for _, setting := range info.Settings {
if setting.Key == "vcs.revision" {
version = fmt.Sprintf("%s Revision %s", version, setting.Value)
}
if setting.Key == "vcs.time" {
version = fmt.Sprintf("%s from %s", version, setting.Value)
}
}
}
return version
}
+56 -17
View File
@@ -1,23 +1,62 @@
package main
import "github.com/OJ/gobuster/v3/cli/cmd"
import (
"fmt"
"log"
"os"
"runtime/debug"
//----------------------------------------------------
// Gobuster -- by OJ Reeves
//
// A crap attempt at building something that resembles
// dirbuster or dirb using Go. The goal was to build
// a tool that would help learn Go and to actually do
// something useful. The idea of having this compile
// to native code is also appealing.
//
// Run: gobuster -h
//
// Please see THANKS file for contributors.
// Please see LICENSE file for license details.
//
//----------------------------------------------------
"github.com/OJ/gobuster/v3/cli/dir"
"github.com/OJ/gobuster/v3/cli/dns"
"github.com/OJ/gobuster/v3/cli/fuzz"
"github.com/OJ/gobuster/v3/cli/gcs"
"github.com/OJ/gobuster/v3/cli/s3"
"github.com/OJ/gobuster/v3/cli/tftp"
"github.com/OJ/gobuster/v3/cli/vhost"
"github.com/OJ/gobuster/v3/libgobuster"
"github.com/urfave/cli/v2"
)
func main() {
cmd.Execute()
// log := logrus.New()
// log.SetOutput(os.Stdout)
// log.SetLevel(logrus.InfoLevel)
cli.VersionPrinter = func(cCtx *cli.Context) {
fmt.Printf("gobuster version %s\n", libgobuster.VERSION)
if info, ok := debug.ReadBuildInfo(); ok {
fmt.Printf("Build info:\n")
fmt.Printf("%s", info)
}
}
app := &cli.App{
Name: "gobuster",
UsageText: "gobuster command [command options]",
Authors: []*cli.Author{
{
Name: "Christian Mehlmauer",
Email: "[email protected]",
},
{
Name: "OJ Reeves",
},
},
Version: libgobuster.GetVersion(),
}
app.Commands = []*cli.Command{
dir.Command(),
vhost.Command(),
dns.Command(),
fuzz.Command(),
tftp.Command(),
s3.Command(),
gcs.Command(),
}
err := app.Run(os.Args)
if err != nil {
log.Fatal(err)
}
}