1
1
Fork 0
mirror of https://github.com/OJ/gobuster.git synced 2024-04-25 07:05:01 +02:00
This commit is contained in:
Christian Mehlmauer 2022-10-29 16:34:51 +02:00 committed by GitHub
parent 64a5d221e4
commit b90dd32291
Signed by: GitHub
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 404 additions and 157 deletions

View File

@ -22,6 +22,13 @@ All funds that are donated to this project will be donated to charity. A full lo
# Changes
## 3.3
- Support TLS client certificates / mtl
- support loading extensions from file
- support fuzzing POST body, HTTP headers and basic auth
- new option to not canonicalize header names
## 3.2
- Use go 1.19
@ -479,7 +486,7 @@ https://buffered.io/categories
### Options
```text
Uses VHOST enumeration mode (you most probably want to use the IP adress as the URL parameter
Uses VHOST enumeration mode (you most probably want to use the IP address as the URL parameter)
Usage:
gobuster vhost [flags]

View File

@ -42,98 +42,114 @@ func parseDirOptions() (*libgobuster.Options, *gobusterdir.OptionsDir, error) {
return nil, nil, err
}
plugin := gobusterdir.NewOptionsDir()
pluginOpts := gobusterdir.NewOptionsDir()
httpOpts, err := parseCommonHTTPOptions(cmdDir)
if err != nil {
return nil, nil, err
}
plugin.Password = httpOpts.Password
plugin.URL = httpOpts.URL
plugin.UserAgent = httpOpts.UserAgent
plugin.Username = httpOpts.Username
plugin.Proxy = httpOpts.Proxy
plugin.Cookies = httpOpts.Cookies
plugin.Timeout = httpOpts.Timeout
plugin.FollowRedirect = httpOpts.FollowRedirect
plugin.NoTLSValidation = httpOpts.NoTLSValidation
plugin.Headers = httpOpts.Headers
plugin.Method = httpOpts.Method
plugin.RetryOnTimeout = httpOpts.RetryOnTimeout
plugin.RetryAttempts = httpOpts.RetryAttempts
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
plugin.Extensions, err = cmdDir.Flags().GetString("extensions")
pluginOpts.Extensions, err = cmdDir.Flags().GetString("extensions")
if err != nil {
return nil, nil, fmt.Errorf("invalid value for extensions: %w", err)
}
ret, err := helper.ParseExtensions(plugin.Extensions)
ret, err := helper.ParseExtensions(pluginOpts.Extensions)
if err != nil {
return nil, nil, fmt.Errorf("invalid value for extensions: %w", err)
}
plugin.ExtensionsParsed = ret
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 := helper.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
plugin.StatusCodes, err = cmdDir.Flags().GetString("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 := helper.ParseCommaSeparatedInt(plugin.StatusCodes)
ret2, err := helper.ParseCommaSeparatedInt(pluginOpts.StatusCodes)
if err != nil {
return nil, nil, fmt.Errorf("invalid value for status-codes: %w", err)
}
plugin.StatusCodesParsed = ret2
pluginOpts.StatusCodesParsed = ret2
// blacklist will override the normal status codes
plugin.StatusCodesBlacklist, err = cmdDir.Flags().GetString("status-codes-blacklist")
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 := helper.ParseCommaSeparatedInt(plugin.StatusCodesBlacklist)
ret3, err := helper.ParseCommaSeparatedInt(pluginOpts.StatusCodesBlacklist)
if err != nil {
return nil, nil, fmt.Errorf("invalid value for status-codes-blacklist: %w", err)
}
plugin.StatusCodesBlacklistParsed = ret3
pluginOpts.StatusCodesBlacklistParsed = ret3
if plugin.StatusCodes != "" && plugin.StatusCodesBlacklist != "" {
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.",
plugin.StatusCodes, plugin.StatusCodesBlacklist)
pluginOpts.StatusCodes, pluginOpts.StatusCodesBlacklist)
}
if plugin.StatusCodes == "" && plugin.StatusCodesBlacklist == "" {
if pluginOpts.StatusCodes == "" && pluginOpts.StatusCodesBlacklist == "" {
return nil, nil, fmt.Errorf("status-codes and status-codes-blacklist are both not set, please set one")
}
plugin.UseSlash, err = cmdDir.Flags().GetBool("add-slash")
pluginOpts.UseSlash, err = cmdDir.Flags().GetBool("add-slash")
if err != nil {
return nil, nil, fmt.Errorf("invalid value for add-slash: %w", err)
}
plugin.Expanded, err = cmdDir.Flags().GetBool("expanded")
pluginOpts.Expanded, err = cmdDir.Flags().GetBool("expanded")
if err != nil {
return nil, nil, fmt.Errorf("invalid value for expanded: %w", err)
}
plugin.NoStatus, err = cmdDir.Flags().GetBool("no-status")
pluginOpts.NoStatus, err = cmdDir.Flags().GetBool("no-status")
if err != nil {
return nil, nil, fmt.Errorf("invalid value for no-status: %w", err)
}
plugin.HideLength, err = cmdDir.Flags().GetBool("hide-length")
pluginOpts.HideLength, err = cmdDir.Flags().GetBool("hide-length")
if err != nil {
return nil, nil, fmt.Errorf("invalid value for hide-length: %w", err)
}
plugin.DiscoverBackup, err = cmdDir.Flags().GetBool("discover-backup")
pluginOpts.DiscoverBackup, err = cmdDir.Flags().GetBool("discover-backup")
if err != nil {
return nil, nil, fmt.Errorf("invalid value for discover-backup: %w", err)
}
plugin.ExcludeLength, err = cmdDir.Flags().GetIntSlice("exclude-length")
pluginOpts.ExcludeLength, err = cmdDir.Flags().GetIntSlice("exclude-length")
if err != nil {
return nil, nil, fmt.Errorf("invalid value for excludelength: %w", err)
}
return globalopts, plugin, nil
return globalopts, pluginOpts, nil
}
// nolint:gochecknoinits
@ -150,6 +166,7 @@ func init() {
cmdDir.Flags().StringP("status-codes", "s", "", "Positive status codes (will be overwritten with status-codes-blacklist if set)")
cmdDir.Flags().StringP("status-codes-blacklist", "b", "404", "Negative status codes (will override status-codes if set)")
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")

View File

@ -42,43 +42,43 @@ func parseDNSOptions() (*libgobuster.Options, *gobusterdns.OptionsDNS, error) {
if err != nil {
return nil, nil, err
}
plugin := gobusterdns.NewOptionsDNS()
pluginOpts := gobusterdns.NewOptionsDNS()
plugin.Domain, err = cmdDNS.Flags().GetString("domain")
pluginOpts.Domain, err = cmdDNS.Flags().GetString("domain")
if err != nil {
return nil, nil, fmt.Errorf("invalid value for domain: %w", err)
}
plugin.ShowIPs, err = cmdDNS.Flags().GetBool("show-ips")
pluginOpts.ShowIPs, err = cmdDNS.Flags().GetBool("show-ips")
if err != nil {
return nil, nil, fmt.Errorf("invalid value for show-ips: %w", err)
}
plugin.ShowCNAME, err = cmdDNS.Flags().GetBool("show-cname")
pluginOpts.ShowCNAME, err = cmdDNS.Flags().GetBool("show-cname")
if err != nil {
return nil, nil, fmt.Errorf("invalid value for show-cname: %w", err)
}
plugin.WildcardForced, err = cmdDNS.Flags().GetBool("wildcard")
pluginOpts.WildcardForced, err = cmdDNS.Flags().GetBool("wildcard")
if err != nil {
return nil, nil, fmt.Errorf("invalid value for wildcard: %w", err)
}
plugin.Timeout, err = cmdDNS.Flags().GetDuration("timeout")
pluginOpts.Timeout, err = cmdDNS.Flags().GetDuration("timeout")
if err != nil {
return nil, nil, fmt.Errorf("invalid value for timeout: %w", err)
}
plugin.Resolver, err = cmdDNS.Flags().GetString("resolver")
pluginOpts.Resolver, err = cmdDNS.Flags().GetString("resolver")
if err != nil {
return nil, nil, fmt.Errorf("invalid value for resolver: %w", err)
}
if plugin.Resolver != "" && runtime.GOOS == "windows" {
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, plugin, nil
return globalopts, pluginOpts, nil
}
// nolint:gochecknoinits

View File

@ -4,6 +4,7 @@ import (
"errors"
"fmt"
"log"
"strings"
"github.com/OJ/gobuster/v3/cli"
"github.com/OJ/gobuster/v3/gobusterfuzz"
@ -21,6 +22,10 @@ func runFuzz(cmd *cobra.Command, args []string) error {
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)
@ -42,50 +47,57 @@ func parseFuzzOptions() (*libgobuster.Options, *gobusterfuzz.OptionsFuzz, error)
return nil, nil, err
}
plugin := gobusterfuzz.NewOptionsFuzz()
pluginOpts := gobusterfuzz.NewOptionsFuzz()
httpOpts, err := parseCommonHTTPOptions(cmdFuzz)
if err != nil {
return nil, nil, err
}
plugin.Password = httpOpts.Password
plugin.URL = httpOpts.URL
plugin.UserAgent = httpOpts.UserAgent
plugin.Username = httpOpts.Username
plugin.Proxy = httpOpts.Proxy
plugin.Cookies = httpOpts.Cookies
plugin.Timeout = httpOpts.Timeout
plugin.FollowRedirect = httpOpts.FollowRedirect
plugin.NoTLSValidation = httpOpts.NoTLSValidation
plugin.Headers = httpOpts.Headers
plugin.Method = httpOpts.Method
plugin.RetryOnTimeout = httpOpts.RetryOnTimeout
plugin.RetryAttempts = httpOpts.RetryAttempts
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
plugin.ExcludedStatusCodes, err = cmdFuzz.Flags().GetString("excludestatuscodes")
pluginOpts.ExcludedStatusCodes, err = cmdFuzz.Flags().GetString("excludestatuscodes")
if err != nil {
return nil, nil, fmt.Errorf("invalid value for excludestatuscodes: %w", err)
}
ret, err := helper.ParseCommaSeparatedInt(plugin.ExcludedStatusCodes)
ret, err := helper.ParseCommaSeparatedInt(pluginOpts.ExcludedStatusCodes)
if err != nil {
return nil, nil, fmt.Errorf("invalid value for excludestatuscodes: %w", err)
}
plugin.ExcludedStatusCodesParsed = ret
pluginOpts.ExcludedStatusCodesParsed = ret
plugin.ExcludeLength, err = cmdFuzz.Flags().GetIntSlice("exclude-length")
pluginOpts.ExcludeLength, err = cmdFuzz.Flags().GetIntSlice("exclude-length")
if err != nil {
return nil, nil, fmt.Errorf("invalid value for excludelength: %w", err)
}
return globalopts, plugin, nil
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: "Uses fuzzing mode",
Short: fmt.Sprintf("Uses fuzzing mode. Replaces the keyword %s in the URL, Headers and the request body", gobusterfuzz.FuzzKeyword),
RunE: runFuzz,
}
@ -94,6 +106,7 @@ func init() {
}
cmdFuzz.Flags().StringP("excludestatuscodes", "b", "", "Negative status codes (will override statuscodes if set)")
cmdFuzz.Flags().IntSlice("exclude-length", []int{}, "exclude the following content length (completely ignores the status). Supply multiple times to exclude multiple sizes.")
cmdFuzz.Flags().StringP("body", "B", "", "Request body")
cmdFuzz.PersistentPreRun = func(cmd *cobra.Command, args []string) {
configureGlobalOptions()
@ -101,3 +114,29 @@ func init() {
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
}

View File

@ -48,6 +48,7 @@ func parseGCSOptions() (*libgobuster.Options, *gobustergcs.OptionsGCS, error) {
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 {

View File

@ -1,7 +1,10 @@
package cmd
import (
"crypto/tls"
"encoding/pem"
"fmt"
"os"
"regexp"
"strconv"
"strings"
@ -11,6 +14,7 @@ import (
"github.com/OJ/gobuster/v3/helper"
"github.com/OJ/gobuster/v3/libgobuster"
"github.com/spf13/cobra"
"golang.org/x/crypto/pkcs12"
"golang.org/x/term"
)
@ -22,6 +26,11 @@ func addBasicHTTPOptions(cmd *cobra.Command) {
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 {
@ -32,6 +41,7 @@ func addCommonHTTPOptions(cmd *cobra.Command) error {
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 {
@ -85,6 +95,54 @@ func parseBasicHTTPOptions(cmd *cobra.Command) (libgobuster.BasicHTTPOptions, er
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
}
@ -102,6 +160,7 @@ func parseCommonHTTPOptions(cmd *cobra.Command) (libgobuster.HTTPOptions, error)
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 {
@ -172,6 +231,12 @@ func parseCommonHTTPOptions(cmd *cobra.Command) (libgobuster.HTTPOptions, error)
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: ")

View File

@ -35,26 +35,27 @@ func parseS3Options() (*libgobuster.Options, *gobusters3.OptionsS3, error) {
return nil, nil, err
}
plugin := gobusters3.NewOptionsS3()
pluginOpts := gobusters3.NewOptionsS3()
httpOpts, err := parseBasicHTTPOptions(cmdS3)
if err != nil {
return nil, nil, err
}
plugin.UserAgent = httpOpts.UserAgent
plugin.Proxy = httpOpts.Proxy
plugin.Timeout = httpOpts.Timeout
plugin.NoTLSValidation = httpOpts.NoTLSValidation
plugin.RetryOnTimeout = httpOpts.RetryOnTimeout
plugin.RetryAttempts = httpOpts.RetryAttempts
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
plugin.MaxFilesToList, err = cmdS3.Flags().GetInt("maxfiles")
pluginOpts.MaxFilesToList, err = cmdS3.Flags().GetInt("maxfiles")
if err != nil {
return nil, nil, fmt.Errorf("invalid value for maxfiles: %w", err)
}
return globalopts, plugin, nil
return globalopts, pluginOpts, nil
}
// nolint:gochecknoinits

View File

@ -35,49 +35,52 @@ func parseVhostOptions() (*libgobuster.Options, *gobustervhost.OptionsVhost, err
if err != nil {
return nil, nil, err
}
var plugin gobustervhost.OptionsVhost
pluginOpts := gobustervhost.NewOptionsVhost()
httpOpts, err := parseCommonHTTPOptions(cmdVhost)
if err != nil {
return nil, nil, err
}
plugin.Password = httpOpts.Password
plugin.URL = httpOpts.URL
plugin.UserAgent = httpOpts.UserAgent
plugin.Username = httpOpts.Username
plugin.Proxy = httpOpts.Proxy
plugin.Cookies = httpOpts.Cookies
plugin.Timeout = httpOpts.Timeout
plugin.FollowRedirect = httpOpts.FollowRedirect
plugin.NoTLSValidation = httpOpts.NoTLSValidation
plugin.Headers = httpOpts.Headers
plugin.Method = httpOpts.Method
plugin.RetryOnTimeout = httpOpts.RetryOnTimeout
plugin.RetryAttempts = httpOpts.RetryAttempts
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
plugin.AppendDomain, err = cmdVhost.Flags().GetBool("append-domain")
pluginOpts.AppendDomain, err = cmdVhost.Flags().GetBool("append-domain")
if err != nil {
return nil, nil, fmt.Errorf("invalid value for append-domain: %w", err)
}
plugin.ExcludeLength, err = cmdVhost.Flags().GetIntSlice("exclude-length")
pluginOpts.ExcludeLength, err = cmdVhost.Flags().GetIntSlice("exclude-length")
if err != nil {
return nil, nil, fmt.Errorf("invalid value for excludelength: %w", err)
}
plugin.Domain, err = cmdVhost.Flags().GetString("domain")
pluginOpts.Domain, err = cmdVhost.Flags().GetString("domain")
if err != nil {
return nil, nil, fmt.Errorf("invalid value for domain: %w", err)
}
return globalopts, &plugin, nil
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 adress as the URL parameter",
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 {

View File

@ -18,7 +18,7 @@ func BenchmarkVhostMode(b *testing.B) {
h := httpServer(b, "test")
defer h.Close()
pluginopts := gobustervhost.OptionsVhost{}
pluginopts := gobustervhost.NewOptionsVhost()
pluginopts.URL = h.URL
pluginopts.Timeout = 10 * time.Second
@ -54,7 +54,7 @@ func BenchmarkVhostMode(b *testing.B) {
for x := 0; x < b.N; x++ {
os.Stdout = devnull
os.Stderr = devnull
plugin, err := gobustervhost.NewGobusterVhost(&globalopts, &pluginopts)
plugin, err := gobustervhost.NewGobusterVhost(&globalopts, pluginopts)
if err != nil {
b.Fatalf("error on creating gobusterdir: %v", err)
}

7
go.mod
View File

@ -5,8 +5,9 @@ go 1.19
require (
github.com/fatih/color v1.13.0
github.com/google/uuid v1.3.0
github.com/spf13/cobra v1.6.0
golang.org/x/term v0.0.0-20220919170432-7a66f970e087
github.com/spf13/cobra v1.6.1
golang.org/x/crypto v0.1.0
golang.org/x/term v0.1.0
)
require (
@ -14,5 +15,5 @@ require (
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.16 // indirect
github.com/spf13/pflag v1.0.5 // indirect
golang.org/x/sys v0.0.0-20221006211917-84dc82d7e875 // indirect
golang.org/x/sys v0.1.0 // indirect
)

14
go.sum
View File

@ -13,17 +13,19 @@ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.6.0 h1:42a0n6jwCot1pUmomAp4T7DeMD+20LFv4Q54pxLf2LI=
github.com/spf13/cobra v1.6.0/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY=
github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA=
github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU=
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20221006211917-84dc82d7e875 h1:AzgQNqF+FKwyQ5LbVrVqOcuuFB67N47F9+htZYH0wFM=
golang.org/x/sys v0.0.0-20221006211917-84dc82d7e875/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20220919170432-7a66f970e087 h1:tPwmk4vmvVCMdr98VgL4JH+qZxPL8fqlUOHnyOM8N3w=
golang.org/x/term v0.0.0-20220919170432-7a66f970e087/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.1.0 h1:g6Z6vPFA9dYBAF7DWcH6sCcOntplXsDKcliusYijMlw=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
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=

View File

@ -62,16 +62,18 @@ func NewGobusterDir(globalopts *libgobuster.Options, opts *OptionsDir) (*Gobuste
NoTLSValidation: opts.NoTLSValidation,
RetryOnTimeout: opts.RetryOnTimeout,
RetryAttempts: opts.RetryAttempts,
TLSCertificate: opts.TLSCertificate,
}
httpOpts := libgobuster.HTTPOptions{
BasicHTTPOptions: basicOptions,
FollowRedirect: opts.FollowRedirect,
Username: opts.Username,
Password: opts.Password,
Headers: opts.Headers,
Cookies: opts.Cookies,
Method: opts.Method,
BasicHTTPOptions: basicOptions,
FollowRedirect: opts.FollowRedirect,
Username: opts.Username,
Password: opts.Password,
Headers: opts.Headers,
NoCanonicalizeHeaders: opts.NoCanonicalizeHeaders,
Cookies: opts.Cookies,
Method: opts.Method,
}
h, err := libgobuster.NewHTTPClient(&httpOpts)
@ -321,12 +323,18 @@ func (d *GobusterDir) GetConfigString() (string, error) {
}
}
if o.Extensions != "" {
if o.Extensions != "" || o.ExtensionsFile != "" {
if _, err := fmt.Fprintf(tw, "[+] Extensions:\t%s\n", o.ExtensionsParsed.Stringify()); err != nil {
return "", err
}
}
if o.ExtensionsFile != "" {
if _, err := fmt.Fprintf(tw, "[+] Extensions file:\t%s\n", o.ExtensionsFile); err != nil {
return "", err
}
}
if o.UseSlash {
if _, err := fmt.Fprintf(tw, "[+] Add Slash:\ttrue\n"); err != nil {
return "", err

View File

@ -9,6 +9,7 @@ type OptionsDir struct {
libgobuster.HTTPOptions
Extensions string
ExtensionsParsed libgobuster.Set[string]
ExtensionsFile string
StatusCodes string
StatusCodesParsed libgobuster.Set[int]
StatusCodesBlacklist string

View File

@ -13,6 +13,8 @@ import (
"github.com/OJ/gobuster/v3/libgobuster"
)
const FuzzKeyword = "FUZZ"
// ErrWildcard is returned if a wildcard response is found
type ErrWildcard struct {
url string
@ -53,16 +55,18 @@ func NewGobusterFuzz(globalopts *libgobuster.Options, opts *OptionsFuzz) (*Gobus
NoTLSValidation: opts.NoTLSValidation,
RetryOnTimeout: opts.RetryOnTimeout,
RetryAttempts: opts.RetryAttempts,
TLSCertificate: opts.TLSCertificate,
}
httpOpts := libgobuster.HTTPOptions{
BasicHTTPOptions: basicOptions,
FollowRedirect: opts.FollowRedirect,
Username: opts.Username,
Password: opts.Password,
Headers: opts.Headers,
Cookies: opts.Cookies,
Method: opts.Method,
BasicHTTPOptions: basicOptions,
FollowRedirect: opts.FollowRedirect,
Username: opts.Username,
Password: opts.Password,
Headers: opts.Headers,
NoCanonicalizeHeaders: opts.NoCanonicalizeHeaders,
Cookies: opts.Cookies,
Method: opts.Method,
}
h, err := libgobuster.NewHTTPClient(&httpOpts)
@ -85,7 +89,31 @@ func (d *GobusterFuzz) PreRun(ctx context.Context) error {
// ProcessWord is the process implementation of gobusterfuzz
func (d *GobusterFuzz) ProcessWord(ctx context.Context, word string, progress *libgobuster.Progress) error {
url := strings.ReplaceAll(d.options.URL, "FUZZ", word)
url := strings.ReplaceAll(d.options.URL, FuzzKeyword, word)
requestOptions := libgobuster.RequestOptions{}
if len(d.options.Headers) > 0 {
requestOptions.ModifiedHeaders = make([]libgobuster.HTTPHeader, len(d.options.Headers))
for i := range d.options.Headers {
requestOptions.ModifiedHeaders[i] = libgobuster.HTTPHeader{
Name: strings.ReplaceAll(d.options.Headers[i].Name, FuzzKeyword, word),
Value: strings.ReplaceAll(d.options.Headers[i].Value, FuzzKeyword, word),
}
}
}
if d.options.RequestBody != "" {
data := strings.ReplaceAll(d.options.RequestBody, FuzzKeyword, word)
buffer := strings.NewReader(data)
requestOptions.Body = buffer
}
// fuzzing of basic auth
if strings.Contains(d.options.Username, FuzzKeyword) || strings.Contains(d.options.Password, FuzzKeyword) {
requestOptions.UpdatedBasicAuthUsername = strings.ReplaceAll(d.options.Username, FuzzKeyword, word)
requestOptions.UpdatedBasicAuthPassword = strings.ReplaceAll(d.options.Password, FuzzKeyword, word)
}
tries := 1
if d.options.RetryOnTimeout && d.options.RetryAttempts > 0 {
@ -97,7 +125,7 @@ func (d *GobusterFuzz) ProcessWord(ctx context.Context, word string, progress *l
var size int64
for i := 1; i <= tries; i++ {
var err error
statusCode, size, _, _, err = d.http.Request(ctx, url, libgobuster.RequestOptions{})
statusCode, size, _, _, err = d.http.Request(ctx, url, requestOptions)
if err != nil {
// check if it's a timeout and if we should try again and try again
// otherwise the timeout error is raised

View File

@ -10,6 +10,7 @@ type OptionsFuzz struct {
ExcludedStatusCodes string
ExcludedStatusCodesParsed libgobuster.Set[int]
ExcludeLength []int
RequestBody string
}
// NewOptionsFuzz returns a new initialized OptionsFuzz

View File

@ -45,6 +45,7 @@ func NewGobusterGCS(globalopts *libgobuster.Options, opts *OptionsGCS) (*Gobuste
NoTLSValidation: opts.NoTLSValidation,
RetryOnTimeout: opts.RetryOnTimeout,
RetryAttempts: opts.RetryAttempts,
TLSCertificate: opts.TLSCertificate,
}
httpOpts := libgobuster.HTTPOptions{

View File

@ -45,6 +45,7 @@ func NewGobusterS3(globalopts *libgobuster.Options, opts *OptionsS3) (*GobusterS
NoTLSValidation: opts.NoTLSValidation,
RetryOnTimeout: opts.RetryOnTimeout,
RetryAttempts: opts.RetryAttempts,
TLSCertificate: opts.TLSCertificate,
}
httpOpts := libgobuster.HTTPOptions{

View File

@ -48,16 +48,18 @@ func NewGobusterVhost(globalopts *libgobuster.Options, opts *OptionsVhost) (*Gob
NoTLSValidation: opts.NoTLSValidation,
RetryOnTimeout: opts.RetryOnTimeout,
RetryAttempts: opts.RetryAttempts,
TLSCertificate: opts.TLSCertificate,
}
httpOpts := libgobuster.HTTPOptions{
BasicHTTPOptions: basicOptions,
FollowRedirect: opts.FollowRedirect,
Username: opts.Username,
Password: opts.Password,
Headers: opts.Headers,
Cookies: opts.Cookies,
Method: opts.Method,
BasicHTTPOptions: basicOptions,
FollowRedirect: opts.FollowRedirect,
Username: opts.Username,
Password: opts.Password,
Headers: opts.Headers,
NoCanonicalizeHeaders: opts.NoCanonicalizeHeaders,
Cookies: opts.Cookies,
Method: opts.Method,
}
h, err := libgobuster.NewHTTPClient(&httpOpts)

View File

@ -11,3 +11,8 @@ type OptionsVhost struct {
ExcludeLength []int
Domain string
}
// NewOptionsVhost returns a new initialized OptionsVhost
func NewOptionsVhost() *OptionsVhost {
return &OptionsVhost{}
}

View File

@ -1,7 +1,9 @@
package helper
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
@ -24,6 +26,30 @@ func ParseExtensions(extensions string) (libgobuster.Set[string], error) {
return ret, nil
}
func ParseExtensionsFile(file string) ([]string, error) {
var ret []string
stream, err := os.Open(file)
if err != nil {
return ret, err
}
defer stream.Close()
scanner := bufio.NewScanner(stream)
for scanner.Scan() {
e := scanner.Text()
e = strings.TrimSpace(e)
// remove leading . from extensions
ret = append(ret, (strings.TrimPrefix(e, ".")))
}
if err := scanner.Err(); err != nil {
return nil, err
}
return ret, nil
}
// ParseCommaSeparatedInt parses the status codes provided as a comma separated list
func ParseCommaSeparatedInt(inputString string) (libgobuster.Set[int], error) {
ret := libgobuster.NewSet[int]()

View File

@ -19,22 +19,26 @@ type HTTPHeader struct {
// HTTPClient represents a http object
type HTTPClient struct {
client *http.Client
userAgent string
defaultUserAgent string
username string
password string
headers []HTTPHeader
cookies string
method string
host string
client *http.Client
userAgent string
defaultUserAgent string
username string
password string
headers []HTTPHeader
noCanonicalizeHeaders bool
cookies string
method string
host string
}
// RequestOptions is used to pass options to a single individual request
type RequestOptions struct {
Host string
Body io.Reader
ReturnBody bool
Host string
Body io.Reader
ReturnBody bool
ModifiedHeaders []HTTPHeader
UpdatedBasicAuthUsername string
UpdatedBasicAuthPassword string
}
// NewHTTPClient returns a new HTTPClient
@ -64,6 +68,13 @@ func NewHTTPClient(opt *HTTPOptions) (*HTTPClient, error) {
redirectFunc = nil
}
tlsConfig := tls.Config{
InsecureSkipVerify: opt.NoTLSValidation,
}
if opt.TLSCertificate != nil {
tlsConfig.Certificates = []tls.Certificate{*opt.TLSCertificate}
}
client.client = &http.Client{
Timeout: opt.Timeout,
CheckRedirect: redirectFunc,
@ -71,15 +82,14 @@ func NewHTTPClient(opt *HTTPOptions) (*HTTPClient, error) {
Proxy: proxyURLFunc,
MaxIdleConns: 100,
MaxIdleConnsPerHost: 100,
TLSClientConfig: &tls.Config{
InsecureSkipVerify: opt.NoTLSValidation,
},
TLSClientConfig: &tlsConfig,
}}
client.username = opt.Username
client.password = opt.Password
client.userAgent = opt.UserAgent
client.defaultUserAgent = DefaultUserAgent()
client.headers = opt.Headers
client.noCanonicalizeHeaders = opt.NoCanonicalizeHeaders
client.cookies = opt.Cookies
client.method = opt.Method
if client.method == "" {
@ -98,7 +108,7 @@ func NewHTTPClient(opt *HTTPOptions) (*HTTPClient, error) {
// Request makes an http request and returns the status, the content length, the headers, the body and an error
// if you want the body returned set the corresponding property inside RequestOptions
func (client *HTTPClient) Request(ctx context.Context, fullURL string, opts RequestOptions) (int, int64, http.Header, []byte, error) {
resp, err := client.makeRequest(ctx, fullURL, opts.Host, opts.Body)
resp, err := client.makeRequest(ctx, fullURL, opts)
if err != nil {
// ignore context canceled errors
if errors.Is(ctx.Err(), context.Canceled) {
@ -128,8 +138,8 @@ func (client *HTTPClient) Request(ctx context.Context, fullURL string, opts Requ
return resp.StatusCode, length, resp.Header, body, nil
}
func (client *HTTPClient) makeRequest(ctx context.Context, fullURL, host string, data io.Reader) (*http.Response, error) {
req, err := http.NewRequest(client.method, fullURL, data)
func (client *HTTPClient) makeRequest(ctx context.Context, fullURL string, opts RequestOptions) (*http.Response, error) {
req, err := http.NewRequest(client.method, fullURL, opts.Body)
if err != nil {
return nil, err
}
@ -142,8 +152,8 @@ func (client *HTTPClient) makeRequest(ctx context.Context, fullURL, host string,
}
// Use host for VHOST mode on a per request basis, otherwise the one provided from headers
if host != "" {
req.Host = host
if opts.Host != "" {
req.Host = opts.Host
} else if client.host != "" {
req.Host = client.host
}
@ -155,11 +165,31 @@ func (client *HTTPClient) makeRequest(ctx context.Context, fullURL, host string,
}
// add custom headers
for _, h := range client.headers {
req.Header.Set(h.Name, h.Value)
// if ModifiedHeaders are supplied use those, otherwise use the original ones
// currently only relevant on fuzzing
if len(opts.ModifiedHeaders) > 0 {
for _, h := range opts.ModifiedHeaders {
if client.noCanonicalizeHeaders {
// https://stackoverflow.com/questions/26351716/how-to-keep-key-case-sensitive-in-request-header-using-golang
req.Header[h.Name] = []string{h.Value}
} else {
req.Header.Set(h.Name, h.Value)
}
}
} else {
for _, h := range client.headers {
if client.noCanonicalizeHeaders {
// https://stackoverflow.com/questions/26351716/how-to-keep-key-case-sensitive-in-request-header-using-golang
req.Header[h.Name] = []string{h.Value}
} else {
req.Header.Set(h.Name, h.Value)
}
}
}
if client.username != "" {
if opts.UpdatedBasicAuthUsername != "" {
req.SetBasicAuth(opts.UpdatedBasicAuthUsername, opts.UpdatedBasicAuthPassword)
} else if client.username != "" {
req.SetBasicAuth(client.username, client.password)
}

View File

@ -175,6 +175,11 @@ Scan:
}
close(wordChan)
workerGroup.Wait()
if err := scanner.Err(); err != nil {
return err
}
return nil
}

View File

@ -1,6 +1,7 @@
package libgobuster
import (
"crypto/tls"
"time"
)
@ -12,16 +13,18 @@ type BasicHTTPOptions struct {
Timeout time.Duration
RetryOnTimeout bool
RetryAttempts int
TLSCertificate *tls.Certificate
}
// HTTPOptions is the struct to pass in all http options to Gobuster
type HTTPOptions struct {
BasicHTTPOptions
Password string
URL string
Username string
Cookies string
Headers []HTTPHeader
FollowRedirect bool
Method string
Password string
URL string
Username string
Cookies string
Headers []HTTPHeader
NoCanonicalizeHeaders bool
FollowRedirect bool
Method string
}

View File

@ -2,5 +2,5 @@ package libgobuster
const (
// VERSION contains the current gobuster version
VERSION = "3.2.1"
VERSION = "3.3"
)