mirror of
https://github.com/OJ/gobuster.git
synced 2025-01-05 16:24:10 +01:00
259 lines
8.6 KiB
Go
259 lines
8.6 KiB
Go
package cmd
|
|
|
|
import (
|
|
"crypto/tls"
|
|
"encoding/pem"
|
|
"fmt"
|
|
"os"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/OJ/gobuster/v3/libgobuster"
|
|
"github.com/spf13/cobra"
|
|
"golang.org/x/crypto/pkcs12"
|
|
"golang.org/x/term"
|
|
)
|
|
|
|
func addBasicHTTPOptions(cmd *cobra.Command) {
|
|
cmd.Flags().StringP("useragent", "a", libgobuster.DefaultUserAgent(), "Set the User-Agent string")
|
|
cmd.Flags().BoolP("random-agent", "", false, "Use a random User-Agent string")
|
|
cmd.Flags().StringP("proxy", "", "", "Proxy to use for requests [http(s)://host:port] or [socks5://host:port]")
|
|
cmd.Flags().DurationP("timeout", "", 10*time.Second, "HTTP Timeout")
|
|
cmd.Flags().BoolP("no-tls-validation", "k", false, "Skip TLS certificate verification")
|
|
cmd.Flags().BoolP("retry", "", false, "Should retry on request timeout")
|
|
cmd.Flags().IntP("retry-attempts", "", 3, "Times to retry on request timeout")
|
|
// client certificates, either pem or p12
|
|
cmd.Flags().StringP("client-cert-pem", "", "", "public key in PEM format for optional TLS client certificates")
|
|
cmd.Flags().StringP("client-cert-pem-key", "", "", "private key in PEM format for optional TLS client certificates (this key needs to have no password)")
|
|
cmd.Flags().StringP("client-cert-p12", "", "", "a p12 file to use for options TLS client certificates")
|
|
cmd.Flags().StringP("client-cert-p12-password", "", "", "the password to the p12 file")
|
|
}
|
|
|
|
func addCommonHTTPOptions(cmd *cobra.Command) error {
|
|
addBasicHTTPOptions(cmd)
|
|
cmd.Flags().StringP("url", "u", "", "The target URL")
|
|
cmd.Flags().StringP("cookies", "c", "", "Cookies to use for the requests")
|
|
cmd.Flags().StringP("username", "U", "", "Username for Basic Auth")
|
|
cmd.Flags().StringP("password", "P", "", "Password for Basic Auth")
|
|
cmd.Flags().BoolP("follow-redirect", "r", false, "Follow redirects")
|
|
cmd.Flags().StringArrayP("headers", "H", []string{""}, "Specify HTTP headers, -H 'Header1: val1' -H 'Header2: val2'")
|
|
cmd.Flags().BoolP("no-canonicalize-headers", "", false, "Do not canonicalize HTTP header names. If set header names are sent as is.")
|
|
cmd.Flags().StringP("method", "m", "GET", "Use the following HTTP method")
|
|
|
|
if err := cmd.MarkFlagRequired("url"); err != nil {
|
|
return fmt.Errorf("error on marking flag as required: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func parseBasicHTTPOptions(cmd *cobra.Command) (libgobuster.BasicHTTPOptions, error) {
|
|
options := libgobuster.BasicHTTPOptions{}
|
|
var err error
|
|
|
|
options.UserAgent, err = cmd.Flags().GetString("useragent")
|
|
if err != nil {
|
|
return options, fmt.Errorf("invalid value for useragent: %w", err)
|
|
}
|
|
randomUA, err := cmd.Flags().GetBool("random-agent")
|
|
if err != nil {
|
|
return options, fmt.Errorf("invalid value for random-agent: %w", err)
|
|
}
|
|
if randomUA {
|
|
ua, err := libgobuster.GetRandomUserAgent()
|
|
if err != nil {
|
|
return options, err
|
|
}
|
|
options.UserAgent = ua
|
|
}
|
|
|
|
options.Proxy, err = cmd.Flags().GetString("proxy")
|
|
if err != nil {
|
|
return options, fmt.Errorf("invalid value for proxy: %w", err)
|
|
}
|
|
|
|
options.Timeout, err = cmd.Flags().GetDuration("timeout")
|
|
if err != nil {
|
|
return options, fmt.Errorf("invalid value for timeout: %w", err)
|
|
}
|
|
|
|
options.RetryOnTimeout, err = cmd.Flags().GetBool("retry")
|
|
if err != nil {
|
|
return options, fmt.Errorf("invalid value for retry: %w", err)
|
|
}
|
|
|
|
options.RetryAttempts, err = cmd.Flags().GetInt("retry-attempts")
|
|
if err != nil {
|
|
return options, fmt.Errorf("invalid value for retry-attempts: %w", err)
|
|
}
|
|
|
|
options.NoTLSValidation, err = cmd.Flags().GetBool("no-tls-validation")
|
|
if err != nil {
|
|
return options, fmt.Errorf("invalid value for no-tls-validation: %w", err)
|
|
}
|
|
|
|
pemFile, err := cmd.Flags().GetString("client-cert-pem")
|
|
if err != nil {
|
|
return options, fmt.Errorf("invalid value for client-cert-pem: %w", err)
|
|
}
|
|
pemKeyFile, err := cmd.Flags().GetString("client-cert-pem-key")
|
|
if err != nil {
|
|
return options, fmt.Errorf("invalid value for client-cert-pem-key: %w", err)
|
|
}
|
|
p12File, err := cmd.Flags().GetString("client-cert-p12")
|
|
if err != nil {
|
|
return options, fmt.Errorf("invalid value for client-cert-p12: %w", err)
|
|
}
|
|
p12Pass, err := cmd.Flags().GetString("client-cert-p12-password")
|
|
if err != nil {
|
|
return options, fmt.Errorf("invalid value for client-cert-p12-password: %w", err)
|
|
}
|
|
|
|
if pemFile != "" && p12File != "" {
|
|
return options, fmt.Errorf("please supply either a pem or a p12, not both")
|
|
}
|
|
|
|
if pemFile != "" {
|
|
cert, err := tls.LoadX509KeyPair(pemFile, pemKeyFile)
|
|
if err != nil {
|
|
return options, fmt.Errorf("could not load supplied pem key: %w", err)
|
|
}
|
|
options.TLSCertificate = &cert
|
|
} else if p12File != "" {
|
|
p12Content, err := os.ReadFile(p12File)
|
|
if err != nil {
|
|
return options, fmt.Errorf("could not read p12 %s: %w", p12File, err)
|
|
}
|
|
blocks, err := pkcs12.ToPEM(p12Content, p12Pass)
|
|
if err != nil {
|
|
return options, fmt.Errorf("could not load P12: %w", err)
|
|
}
|
|
var pemData []byte
|
|
for _, b := range blocks {
|
|
pemData = append(pemData, pem.EncodeToMemory(b)...)
|
|
}
|
|
cert, err := tls.X509KeyPair(pemData, pemData)
|
|
if err != nil {
|
|
return options, fmt.Errorf("could not load certificate from P12: %w", err)
|
|
}
|
|
options.TLSCertificate = &cert
|
|
}
|
|
|
|
return options, nil
|
|
}
|
|
|
|
func parseCommonHTTPOptions(cmd *cobra.Command) (libgobuster.HTTPOptions, error) {
|
|
options := libgobuster.HTTPOptions{}
|
|
var err error
|
|
|
|
basic, err := parseBasicHTTPOptions(cmd)
|
|
if err != nil {
|
|
return options, err
|
|
}
|
|
options.Proxy = basic.Proxy
|
|
options.Timeout = basic.Timeout
|
|
options.UserAgent = basic.UserAgent
|
|
options.NoTLSValidation = basic.NoTLSValidation
|
|
options.RetryOnTimeout = basic.RetryOnTimeout
|
|
options.RetryAttempts = basic.RetryAttempts
|
|
options.TLSCertificate = basic.TLSCertificate
|
|
|
|
options.URL, err = cmd.Flags().GetString("url")
|
|
if err != nil {
|
|
return options, fmt.Errorf("invalid value for url: %w", err)
|
|
}
|
|
|
|
if !strings.HasPrefix(options.URL, "http") {
|
|
// check to see if a port was specified
|
|
re := regexp.MustCompile(`^[^/]+:(\d+)`)
|
|
match := re.FindStringSubmatch(options.URL)
|
|
|
|
if len(match) < 2 {
|
|
// no port, default to http on 80
|
|
options.URL = fmt.Sprintf("http://%s", options.URL)
|
|
} else {
|
|
port, err2 := strconv.Atoi(match[1])
|
|
if err2 != nil || (port != 80 && port != 443) {
|
|
return options, fmt.Errorf("url scheme not specified")
|
|
} else if port == 80 {
|
|
options.URL = fmt.Sprintf("http://%s", options.URL)
|
|
} else {
|
|
options.URL = fmt.Sprintf("https://%s", options.URL)
|
|
}
|
|
}
|
|
}
|
|
|
|
options.Cookies, err = cmd.Flags().GetString("cookies")
|
|
if err != nil {
|
|
return options, fmt.Errorf("invalid value for cookies: %w", err)
|
|
}
|
|
|
|
options.Username, err = cmd.Flags().GetString("username")
|
|
if err != nil {
|
|
return options, fmt.Errorf("invalid value for username: %w", err)
|
|
}
|
|
|
|
options.Password, err = cmd.Flags().GetString("password")
|
|
if err != nil {
|
|
return options, fmt.Errorf("invalid value for password: %w", err)
|
|
}
|
|
|
|
options.FollowRedirect, err = cmd.Flags().GetBool("follow-redirect")
|
|
if err != nil {
|
|
return options, fmt.Errorf("invalid value for follow-redirect: %w", err)
|
|
}
|
|
|
|
options.Method, err = cmd.Flags().GetString("method")
|
|
if err != nil {
|
|
return options, fmt.Errorf("invalid value for method: %w", err)
|
|
}
|
|
|
|
headers, err := cmd.Flags().GetStringArray("headers")
|
|
if err != nil {
|
|
return options, fmt.Errorf("invalid value for headers: %w", err)
|
|
}
|
|
|
|
for _, h := range headers {
|
|
keyAndValue := strings.SplitN(h, ":", 2)
|
|
if len(keyAndValue) != 2 {
|
|
return options, fmt.Errorf("invalid header format for header %q", h)
|
|
}
|
|
key := strings.TrimSpace(keyAndValue[0])
|
|
value := strings.TrimSpace(keyAndValue[1])
|
|
if len(key) == 0 {
|
|
return options, fmt.Errorf("invalid header format for header %q - name is empty", h)
|
|
}
|
|
header := libgobuster.HTTPHeader{Name: key, Value: value}
|
|
options.Headers = append(options.Headers, header)
|
|
}
|
|
|
|
noCanonHeaders, err := cmd.Flags().GetBool("no-canonicalize-headers")
|
|
if err != nil {
|
|
return options, fmt.Errorf("invalid value for no-canonicalize-headers: %w", err)
|
|
}
|
|
options.NoCanonicalizeHeaders = noCanonHeaders
|
|
|
|
// Prompt for PW if not provided
|
|
if options.Username != "" && options.Password == "" {
|
|
fmt.Printf("[?] Auth Password: ")
|
|
// please don't remove the int cast here as it is sadly needed on windows :/
|
|
passBytes, err := term.ReadPassword(int(syscall.Stdin)) //nolint:unconvert
|
|
// print a newline to simulate the newline that was entered
|
|
// this means that formatting/printing after doesn't look bad.
|
|
fmt.Println("")
|
|
if err != nil {
|
|
return options, fmt.Errorf("username given but reading of password failed")
|
|
}
|
|
options.Password = string(passBytes)
|
|
}
|
|
// if it's still empty bail out
|
|
if options.Username != "" && options.Password == "" {
|
|
return options, fmt.Errorf("username was provided but password is missing")
|
|
}
|
|
|
|
return options, nil
|
|
}
|