1
1
Fork 0
mirror of https://github.com/OJ/gobuster.git synced 2024-05-04 22:46:07 +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 # 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 ## 3.2
- Use go 1.19 - Use go 1.19
@ -479,7 +486,7 @@ https://buffered.io/categories
### Options ### Options
```text ```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: Usage:
gobuster vhost [flags] gobuster vhost [flags]

View File

@ -42,98 +42,114 @@ func parseDirOptions() (*libgobuster.Options, *gobusterdir.OptionsDir, error) {
return nil, nil, err return nil, nil, err
} }
plugin := gobusterdir.NewOptionsDir() pluginOpts := gobusterdir.NewOptionsDir()
httpOpts, err := parseCommonHTTPOptions(cmdDir) httpOpts, err := parseCommonHTTPOptions(cmdDir)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
plugin.Password = httpOpts.Password pluginOpts.Password = httpOpts.Password
plugin.URL = httpOpts.URL pluginOpts.URL = httpOpts.URL
plugin.UserAgent = httpOpts.UserAgent pluginOpts.UserAgent = httpOpts.UserAgent
plugin.Username = httpOpts.Username pluginOpts.Username = httpOpts.Username
plugin.Proxy = httpOpts.Proxy pluginOpts.Proxy = httpOpts.Proxy
plugin.Cookies = httpOpts.Cookies pluginOpts.Cookies = httpOpts.Cookies
plugin.Timeout = httpOpts.Timeout pluginOpts.Timeout = httpOpts.Timeout
plugin.FollowRedirect = httpOpts.FollowRedirect pluginOpts.FollowRedirect = httpOpts.FollowRedirect
plugin.NoTLSValidation = httpOpts.NoTLSValidation pluginOpts.NoTLSValidation = httpOpts.NoTLSValidation
plugin.Headers = httpOpts.Headers pluginOpts.Headers = httpOpts.Headers
plugin.Method = httpOpts.Method pluginOpts.Method = httpOpts.Method
plugin.RetryOnTimeout = httpOpts.RetryOnTimeout pluginOpts.RetryOnTimeout = httpOpts.RetryOnTimeout
plugin.RetryAttempts = httpOpts.RetryAttempts 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 { if err != nil {
return nil, nil, fmt.Errorf("invalid value for extensions: %w", err) 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 { if err != nil {
return nil, nil, fmt.Errorf("invalid value for extensions: %w", err) 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 // parse normal status codes
plugin.StatusCodes, err = cmdDir.Flags().GetString("status-codes") pluginOpts.StatusCodes, err = cmdDir.Flags().GetString("status-codes")
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("invalid value for status-codes: %w", err) 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 { if err != nil {
return nil, nil, fmt.Errorf("invalid value for status-codes: %w", err) 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 // 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 { if err != nil {
return nil, nil, fmt.Errorf("invalid value for status-codes-blacklist: %w", err) 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 { if err != nil {
return nil, nil, fmt.Errorf("invalid value for status-codes-blacklist: %w", err) 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.", 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") 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 { if err != nil {
return nil, nil, fmt.Errorf("invalid value for add-slash: %w", err) 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 { if err != nil {
return nil, nil, fmt.Errorf("invalid value for expanded: %w", err) 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 { if err != nil {
return nil, nil, fmt.Errorf("invalid value for no-status: %w", err) 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 { if err != nil {
return nil, nil, fmt.Errorf("invalid value for hide-length: %w", err) 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 { if err != nil {
return nil, nil, fmt.Errorf("invalid value for discover-backup: %w", err) 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 { if err != nil {
return nil, nil, fmt.Errorf("invalid value for excludelength: %w", err) return nil, nil, fmt.Errorf("invalid value for excludelength: %w", err)
} }
return globalopts, plugin, nil return globalopts, pluginOpts, nil
} }
// nolint:gochecknoinits // 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", "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("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", "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("expanded", "e", false, "Expanded mode, print full URLs")
cmdDir.Flags().BoolP("no-status", "n", false, "Don't print status codes") 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().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 { if err != nil {
return nil, nil, err 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 { if err != nil {
return nil, nil, fmt.Errorf("invalid value for domain: %w", err) 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 { if err != nil {
return nil, nil, fmt.Errorf("invalid value for show-ips: %w", err) 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 { if err != nil {
return nil, nil, fmt.Errorf("invalid value for show-cname: %w", err) 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 { if err != nil {
return nil, nil, fmt.Errorf("invalid value for wildcard: %w", err) 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 { if err != nil {
return nil, nil, fmt.Errorf("invalid value for timeout: %w", err) 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 { if err != nil {
return nil, nil, fmt.Errorf("invalid value for resolver: %w", err) 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 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 // nolint:gochecknoinits

View File

@ -4,6 +4,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"log" "log"
"strings"
"github.com/OJ/gobuster/v3/cli" "github.com/OJ/gobuster/v3/cli"
"github.com/OJ/gobuster/v3/gobusterfuzz" "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) 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) plugin, err := gobusterfuzz.NewGobusterFuzz(globalopts, pluginopts)
if err != nil { if err != nil {
return fmt.Errorf("error on creating gobusterfuzz: %w", err) return fmt.Errorf("error on creating gobusterfuzz: %w", err)
@ -42,50 +47,57 @@ func parseFuzzOptions() (*libgobuster.Options, *gobusterfuzz.OptionsFuzz, error)
return nil, nil, err return nil, nil, err
} }
plugin := gobusterfuzz.NewOptionsFuzz() pluginOpts := gobusterfuzz.NewOptionsFuzz()
httpOpts, err := parseCommonHTTPOptions(cmdFuzz) httpOpts, err := parseCommonHTTPOptions(cmdFuzz)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
plugin.Password = httpOpts.Password pluginOpts.Password = httpOpts.Password
plugin.URL = httpOpts.URL pluginOpts.URL = httpOpts.URL
plugin.UserAgent = httpOpts.UserAgent pluginOpts.UserAgent = httpOpts.UserAgent
plugin.Username = httpOpts.Username pluginOpts.Username = httpOpts.Username
plugin.Proxy = httpOpts.Proxy pluginOpts.Proxy = httpOpts.Proxy
plugin.Cookies = httpOpts.Cookies pluginOpts.Cookies = httpOpts.Cookies
plugin.Timeout = httpOpts.Timeout pluginOpts.Timeout = httpOpts.Timeout
plugin.FollowRedirect = httpOpts.FollowRedirect pluginOpts.FollowRedirect = httpOpts.FollowRedirect
plugin.NoTLSValidation = httpOpts.NoTLSValidation pluginOpts.NoTLSValidation = httpOpts.NoTLSValidation
plugin.Headers = httpOpts.Headers pluginOpts.Headers = httpOpts.Headers
plugin.Method = httpOpts.Method pluginOpts.Method = httpOpts.Method
plugin.RetryOnTimeout = httpOpts.RetryOnTimeout pluginOpts.RetryOnTimeout = httpOpts.RetryOnTimeout
plugin.RetryAttempts = httpOpts.RetryAttempts pluginOpts.RetryAttempts = httpOpts.RetryAttempts
pluginOpts.TLSCertificate = httpOpts.TLSCertificate
pluginOpts.NoCanonicalizeHeaders = httpOpts.NoCanonicalizeHeaders
// blacklist will override the normal status codes // blacklist will override the normal status codes
plugin.ExcludedStatusCodes, err = cmdFuzz.Flags().GetString("excludestatuscodes") pluginOpts.ExcludedStatusCodes, err = cmdFuzz.Flags().GetString("excludestatuscodes")
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("invalid value for excludestatuscodes: %w", err) 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 { if err != nil {
return nil, nil, fmt.Errorf("invalid value for excludestatuscodes: %w", err) 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 { if err != nil {
return nil, nil, fmt.Errorf("invalid value for excludelength: %w", err) 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 // nolint:gochecknoinits
func init() { func init() {
cmdFuzz = &cobra.Command{ cmdFuzz = &cobra.Command{
Use: "fuzz", 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, RunE: runFuzz,
} }
@ -94,6 +106,7 @@ func init() {
} }
cmdFuzz.Flags().StringP("excludestatuscodes", "b", "", "Negative status codes (will override statuscodes if set)") 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().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) { cmdFuzz.PersistentPreRun = func(cmd *cobra.Command, args []string) {
configureGlobalOptions() configureGlobalOptions()
@ -101,3 +114,29 @@ func init() {
rootCmd.AddCommand(cmdFuzz) 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.NoTLSValidation = httpOpts.NoTLSValidation
pluginopts.RetryOnTimeout = httpOpts.RetryOnTimeout pluginopts.RetryOnTimeout = httpOpts.RetryOnTimeout
pluginopts.RetryAttempts = httpOpts.RetryAttempts pluginopts.RetryAttempts = httpOpts.RetryAttempts
pluginopts.TLSCertificate = httpOpts.TLSCertificate
pluginopts.MaxFilesToList, err = cmdGCS.Flags().GetInt("maxfiles") pluginopts.MaxFilesToList, err = cmdGCS.Flags().GetInt("maxfiles")
if err != nil { if err != nil {

View File

@ -1,7 +1,10 @@
package cmd package cmd
import ( import (
"crypto/tls"
"encoding/pem"
"fmt" "fmt"
"os"
"regexp" "regexp"
"strconv" "strconv"
"strings" "strings"
@ -11,6 +14,7 @@ import (
"github.com/OJ/gobuster/v3/helper" "github.com/OJ/gobuster/v3/helper"
"github.com/OJ/gobuster/v3/libgobuster" "github.com/OJ/gobuster/v3/libgobuster"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"golang.org/x/crypto/pkcs12"
"golang.org/x/term" "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("no-tls-validation", "k", false, "Skip TLS certificate verification")
cmd.Flags().BoolP("retry", "", false, "Should retry on request timeout") cmd.Flags().BoolP("retry", "", false, "Should retry on request timeout")
cmd.Flags().IntP("retry-attempts", "", 3, "Times to 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 { 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().StringP("password", "P", "", "Password for Basic Auth")
cmd.Flags().BoolP("follow-redirect", "r", false, "Follow redirects") 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().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") cmd.Flags().StringP("method", "m", "GET", "Use the following HTTP method")
if err := cmd.MarkFlagRequired("url"); err != nil { if err := cmd.MarkFlagRequired("url"); err != nil {
@ -85,6 +95,54 @@ func parseBasicHTTPOptions(cmd *cobra.Command) (libgobuster.BasicHTTPOptions, er
if err != nil { if err != nil {
return options, fmt.Errorf("invalid value for no-tls-validation: %w", err) 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 return options, nil
} }
@ -102,6 +160,7 @@ func parseCommonHTTPOptions(cmd *cobra.Command) (libgobuster.HTTPOptions, error)
options.NoTLSValidation = basic.NoTLSValidation options.NoTLSValidation = basic.NoTLSValidation
options.RetryOnTimeout = basic.RetryOnTimeout options.RetryOnTimeout = basic.RetryOnTimeout
options.RetryAttempts = basic.RetryAttempts options.RetryAttempts = basic.RetryAttempts
options.TLSCertificate = basic.TLSCertificate
options.URL, err = cmd.Flags().GetString("url") options.URL, err = cmd.Flags().GetString("url")
if err != nil { if err != nil {
@ -172,6 +231,12 @@ func parseCommonHTTPOptions(cmd *cobra.Command) (libgobuster.HTTPOptions, error)
options.Headers = append(options.Headers, header) 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 // Prompt for PW if not provided
if options.Username != "" && options.Password == "" { if options.Username != "" && options.Password == "" {
fmt.Printf("[?] Auth Password: ") fmt.Printf("[?] Auth Password: ")

View File

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

View File

@ -35,49 +35,52 @@ func parseVhostOptions() (*libgobuster.Options, *gobustervhost.OptionsVhost, err
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
var plugin gobustervhost.OptionsVhost
pluginOpts := gobustervhost.NewOptionsVhost()
httpOpts, err := parseCommonHTTPOptions(cmdVhost) httpOpts, err := parseCommonHTTPOptions(cmdVhost)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
plugin.Password = httpOpts.Password pluginOpts.Password = httpOpts.Password
plugin.URL = httpOpts.URL pluginOpts.URL = httpOpts.URL
plugin.UserAgent = httpOpts.UserAgent pluginOpts.UserAgent = httpOpts.UserAgent
plugin.Username = httpOpts.Username pluginOpts.Username = httpOpts.Username
plugin.Proxy = httpOpts.Proxy pluginOpts.Proxy = httpOpts.Proxy
plugin.Cookies = httpOpts.Cookies pluginOpts.Cookies = httpOpts.Cookies
plugin.Timeout = httpOpts.Timeout pluginOpts.Timeout = httpOpts.Timeout
plugin.FollowRedirect = httpOpts.FollowRedirect pluginOpts.FollowRedirect = httpOpts.FollowRedirect
plugin.NoTLSValidation = httpOpts.NoTLSValidation pluginOpts.NoTLSValidation = httpOpts.NoTLSValidation
plugin.Headers = httpOpts.Headers pluginOpts.Headers = httpOpts.Headers
plugin.Method = httpOpts.Method pluginOpts.Method = httpOpts.Method
plugin.RetryOnTimeout = httpOpts.RetryOnTimeout pluginOpts.RetryOnTimeout = httpOpts.RetryOnTimeout
plugin.RetryAttempts = httpOpts.RetryAttempts 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 { if err != nil {
return nil, nil, fmt.Errorf("invalid value for append-domain: %w", err) 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 { if err != nil {
return nil, nil, fmt.Errorf("invalid value for excludelength: %w", err) 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 { if err != nil {
return nil, nil, fmt.Errorf("invalid value for domain: %w", err) return nil, nil, fmt.Errorf("invalid value for domain: %w", err)
} }
return globalopts, &plugin, nil return globalopts, pluginOpts, nil
} }
// nolint:gochecknoinits // nolint:gochecknoinits
func init() { func init() {
cmdVhost = &cobra.Command{ cmdVhost = &cobra.Command{
Use: "vhost", 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, RunE: runVhost,
} }
if err := addCommonHTTPOptions(cmdVhost); err != nil { if err := addCommonHTTPOptions(cmdVhost); err != nil {

View File

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

7
go.mod
View File

@ -5,8 +5,9 @@ go 1.19
require ( require (
github.com/fatih/color v1.13.0 github.com/fatih/color v1.13.0
github.com/google/uuid v1.3.0 github.com/google/uuid v1.3.0
github.com/spf13/cobra v1.6.0 github.com/spf13/cobra v1.6.1
golang.org/x/term v0.0.0-20220919170432-7a66f970e087 golang.org/x/crypto v0.1.0
golang.org/x/term v0.1.0
) )
require ( require (
@ -14,5 +15,5 @@ require (
github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.16 // indirect github.com/mattn/go-isatty v0.0.16 // indirect
github.com/spf13/pflag v1.0.5 // 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 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 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.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA=
github.com/spf13/cobra v1.6.0/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= 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 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 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-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-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-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-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.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
golang.org/x/sys v0.0.0-20221006211917-84dc82d7e875/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20220919170432-7a66f970e087 h1:tPwmk4vmvVCMdr98VgL4JH+qZxPL8fqlUOHnyOM8N3w= golang.org/x/term v0.1.0 h1:g6Z6vPFA9dYBAF7DWcH6sCcOntplXsDKcliusYijMlw=
golang.org/x/term v0.0.0-20220919170432-7a66f970e087/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 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/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= 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, NoTLSValidation: opts.NoTLSValidation,
RetryOnTimeout: opts.RetryOnTimeout, RetryOnTimeout: opts.RetryOnTimeout,
RetryAttempts: opts.RetryAttempts, RetryAttempts: opts.RetryAttempts,
TLSCertificate: opts.TLSCertificate,
} }
httpOpts := libgobuster.HTTPOptions{ httpOpts := libgobuster.HTTPOptions{
BasicHTTPOptions: basicOptions, BasicHTTPOptions: basicOptions,
FollowRedirect: opts.FollowRedirect, FollowRedirect: opts.FollowRedirect,
Username: opts.Username, Username: opts.Username,
Password: opts.Password, Password: opts.Password,
Headers: opts.Headers, Headers: opts.Headers,
Cookies: opts.Cookies, NoCanonicalizeHeaders: opts.NoCanonicalizeHeaders,
Method: opts.Method, Cookies: opts.Cookies,
Method: opts.Method,
} }
h, err := libgobuster.NewHTTPClient(&httpOpts) 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 { if _, err := fmt.Fprintf(tw, "[+] Extensions:\t%s\n", o.ExtensionsParsed.Stringify()); err != nil {
return "", err 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 o.UseSlash {
if _, err := fmt.Fprintf(tw, "[+] Add Slash:\ttrue\n"); err != nil { if _, err := fmt.Fprintf(tw, "[+] Add Slash:\ttrue\n"); err != nil {
return "", err return "", err

View File

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

View File

@ -13,6 +13,8 @@ import (
"github.com/OJ/gobuster/v3/libgobuster" "github.com/OJ/gobuster/v3/libgobuster"
) )
const FuzzKeyword = "FUZZ"
// ErrWildcard is returned if a wildcard response is found // ErrWildcard is returned if a wildcard response is found
type ErrWildcard struct { type ErrWildcard struct {
url string url string
@ -53,16 +55,18 @@ func NewGobusterFuzz(globalopts *libgobuster.Options, opts *OptionsFuzz) (*Gobus
NoTLSValidation: opts.NoTLSValidation, NoTLSValidation: opts.NoTLSValidation,
RetryOnTimeout: opts.RetryOnTimeout, RetryOnTimeout: opts.RetryOnTimeout,
RetryAttempts: opts.RetryAttempts, RetryAttempts: opts.RetryAttempts,
TLSCertificate: opts.TLSCertificate,
} }
httpOpts := libgobuster.HTTPOptions{ httpOpts := libgobuster.HTTPOptions{
BasicHTTPOptions: basicOptions, BasicHTTPOptions: basicOptions,
FollowRedirect: opts.FollowRedirect, FollowRedirect: opts.FollowRedirect,
Username: opts.Username, Username: opts.Username,
Password: opts.Password, Password: opts.Password,
Headers: opts.Headers, Headers: opts.Headers,
Cookies: opts.Cookies, NoCanonicalizeHeaders: opts.NoCanonicalizeHeaders,
Method: opts.Method, Cookies: opts.Cookies,
Method: opts.Method,
} }
h, err := libgobuster.NewHTTPClient(&httpOpts) h, err := libgobuster.NewHTTPClient(&httpOpts)
@ -85,7 +89,31 @@ func (d *GobusterFuzz) PreRun(ctx context.Context) error {
// ProcessWord is the process implementation of gobusterfuzz // ProcessWord is the process implementation of gobusterfuzz
func (d *GobusterFuzz) ProcessWord(ctx context.Context, word string, progress *libgobuster.Progress) error { 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 tries := 1
if d.options.RetryOnTimeout && d.options.RetryAttempts > 0 { 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 var size int64
for i := 1; i <= tries; i++ { for i := 1; i <= tries; i++ {
var err error 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 { if err != nil {
// check if it's a timeout and if we should try again and try again // check if it's a timeout and if we should try again and try again
// otherwise the timeout error is raised // otherwise the timeout error is raised

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,7 +1,9 @@
package helper package helper
import ( import (
"bufio"
"fmt" "fmt"
"os"
"strconv" "strconv"
"strings" "strings"
@ -24,6 +26,30 @@ func ParseExtensions(extensions string) (libgobuster.Set[string], error) {
return ret, nil 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 // ParseCommaSeparatedInt parses the status codes provided as a comma separated list
func ParseCommaSeparatedInt(inputString string) (libgobuster.Set[int], error) { func ParseCommaSeparatedInt(inputString string) (libgobuster.Set[int], error) {
ret := libgobuster.NewSet[int]() ret := libgobuster.NewSet[int]()

View File

@ -19,22 +19,26 @@ type HTTPHeader struct {
// HTTPClient represents a http object // HTTPClient represents a http object
type HTTPClient struct { type HTTPClient struct {
client *http.Client client *http.Client
userAgent string userAgent string
defaultUserAgent string defaultUserAgent string
username string username string
password string password string
headers []HTTPHeader headers []HTTPHeader
cookies string noCanonicalizeHeaders bool
method string cookies string
host string method string
host string
} }
// RequestOptions is used to pass options to a single individual request // RequestOptions is used to pass options to a single individual request
type RequestOptions struct { type RequestOptions struct {
Host string Host string
Body io.Reader Body io.Reader
ReturnBody bool ReturnBody bool
ModifiedHeaders []HTTPHeader
UpdatedBasicAuthUsername string
UpdatedBasicAuthPassword string
} }
// NewHTTPClient returns a new HTTPClient // NewHTTPClient returns a new HTTPClient
@ -64,6 +68,13 @@ func NewHTTPClient(opt *HTTPOptions) (*HTTPClient, error) {
redirectFunc = nil redirectFunc = nil
} }
tlsConfig := tls.Config{
InsecureSkipVerify: opt.NoTLSValidation,
}
if opt.TLSCertificate != nil {
tlsConfig.Certificates = []tls.Certificate{*opt.TLSCertificate}
}
client.client = &http.Client{ client.client = &http.Client{
Timeout: opt.Timeout, Timeout: opt.Timeout,
CheckRedirect: redirectFunc, CheckRedirect: redirectFunc,
@ -71,15 +82,14 @@ func NewHTTPClient(opt *HTTPOptions) (*HTTPClient, error) {
Proxy: proxyURLFunc, Proxy: proxyURLFunc,
MaxIdleConns: 100, MaxIdleConns: 100,
MaxIdleConnsPerHost: 100, MaxIdleConnsPerHost: 100,
TLSClientConfig: &tls.Config{ TLSClientConfig: &tlsConfig,
InsecureSkipVerify: opt.NoTLSValidation,
},
}} }}
client.username = opt.Username client.username = opt.Username
client.password = opt.Password client.password = opt.Password
client.userAgent = opt.UserAgent client.userAgent = opt.UserAgent
client.defaultUserAgent = DefaultUserAgent() client.defaultUserAgent = DefaultUserAgent()
client.headers = opt.Headers client.headers = opt.Headers
client.noCanonicalizeHeaders = opt.NoCanonicalizeHeaders
client.cookies = opt.Cookies client.cookies = opt.Cookies
client.method = opt.Method client.method = opt.Method
if client.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 // 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 // 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) { 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 { if err != nil {
// ignore context canceled errors // ignore context canceled errors
if errors.Is(ctx.Err(), context.Canceled) { 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 return resp.StatusCode, length, resp.Header, body, nil
} }
func (client *HTTPClient) makeRequest(ctx context.Context, fullURL, host string, data io.Reader) (*http.Response, error) { func (client *HTTPClient) makeRequest(ctx context.Context, fullURL string, opts RequestOptions) (*http.Response, error) {
req, err := http.NewRequest(client.method, fullURL, data) req, err := http.NewRequest(client.method, fullURL, opts.Body)
if err != nil { if err != nil {
return nil, err 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 // Use host for VHOST mode on a per request basis, otherwise the one provided from headers
if host != "" { if opts.Host != "" {
req.Host = host req.Host = opts.Host
} else if client.host != "" { } else if client.host != "" {
req.Host = client.host req.Host = client.host
} }
@ -155,11 +165,31 @@ func (client *HTTPClient) makeRequest(ctx context.Context, fullURL, host string,
} }
// add custom headers // add custom headers
for _, h := range client.headers { // if ModifiedHeaders are supplied use those, otherwise use the original ones
req.Header.Set(h.Name, h.Value) // 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) req.SetBasicAuth(client.username, client.password)
} }

View File

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

View File

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

View File

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