mirror of
https://github.com/OJ/gobuster.git
synced 2025-04-16 01:27:54 +02:00
Dev (#362)
This commit is contained in:
parent
64a5d221e4
commit
b90dd32291
@ -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]
|
||||
|
@ -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")
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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: ")
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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
7
go.mod
@ -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
14
go.sum
@ -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=
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -10,6 +10,7 @@ type OptionsFuzz struct {
|
||||
ExcludedStatusCodes string
|
||||
ExcludedStatusCodesParsed libgobuster.Set[int]
|
||||
ExcludeLength []int
|
||||
RequestBody string
|
||||
}
|
||||
|
||||
// NewOptionsFuzz returns a new initialized OptionsFuzz
|
||||
|
@ -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{
|
||||
|
@ -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{
|
||||
|
@ -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)
|
||||
|
@ -11,3 +11,8 @@ type OptionsVhost struct {
|
||||
ExcludeLength []int
|
||||
Domain string
|
||||
}
|
||||
|
||||
// NewOptionsVhost returns a new initialized OptionsVhost
|
||||
func NewOptionsVhost() *OptionsVhost {
|
||||
return &OptionsVhost{}
|
||||
}
|
||||
|
@ -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]()
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -175,6 +175,11 @@ Scan:
|
||||
}
|
||||
close(wordChan)
|
||||
workerGroup.Wait()
|
||||
|
||||
if err := scanner.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -2,5 +2,5 @@ package libgobuster
|
||||
|
||||
const (
|
||||
// VERSION contains the current gobuster version
|
||||
VERSION = "3.2.1"
|
||||
VERSION = "3.3"
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user