mirror of
https://github.com/OJ/gobuster.git
synced 2026-10-11 21:59:56 +00:00
goodbye cobra
This commit is contained in:
@@ -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.
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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")
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
@@ -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,4 +1,4 @@
|
||||
package cmd
|
||||
package dir
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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()
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -4672,5 +4672,5 @@ func GetRandomUserAgent() (string, error) {
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return userAgents[n.Int64()], err
|
||||
return userAgents[n.Int64()], nil
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user