New upstream version 3.3.0
Sophie Brun
1 year, 6 months ago
6 | 6 | updates: |
7 | 7 | - package-ecosystem: "gomod" |
8 | 8 | directory: "/" |
9 | target-branch: "dev" | |
9 | 10 | schedule: |
10 | 11 | interval: "weekly" |
11 | 12 | |
12 | 13 | - package-ecosystem: "github-actions" |
13 | 14 | directory: "/" |
15 | target-branch: "dev" | |
14 | 16 | schedule: |
15 | 17 | # Check for updates to GitHub Actions every weekday |
16 | 18 | interval: "daily" |
20 | 20 | All funds that are donated to this project will be donated to charity. A full log of charity donations will be available in this repository as they are processed. |
21 | 21 | |
22 | 22 | # Changes |
23 | ||
24 | ## 3.3 | |
25 | ||
26 | - Support TLS client certificates / mtl | |
27 | - support loading extensions from file | |
28 | - support fuzzing POST body, HTTP headers and basic auth | |
29 | - new option to not canonicalize header names | |
23 | 30 | |
24 | 31 | ## 3.2 |
25 | 32 | |
478 | 485 | ### Options |
479 | 486 | |
480 | 487 | ```text |
481 | Uses VHOST enumeration mode (you most probably want to use the IP adress as the URL parameter | |
488 | Uses VHOST enumeration mode (you most probably want to use the IP address as the URL parameter) | |
482 | 489 | |
483 | 490 | Usage: |
484 | 491 | gobuster vhost [flags] |
41 | 41 | return nil, nil, err |
42 | 42 | } |
43 | 43 | |
44 | plugin := gobusterdir.NewOptionsDir() | |
44 | pluginOpts := gobusterdir.NewOptionsDir() | |
45 | 45 | |
46 | 46 | httpOpts, err := parseCommonHTTPOptions(cmdDir) |
47 | 47 | if err != nil { |
48 | 48 | return nil, nil, err |
49 | 49 | } |
50 | plugin.Password = httpOpts.Password | |
51 | plugin.URL = httpOpts.URL | |
52 | plugin.UserAgent = httpOpts.UserAgent | |
53 | plugin.Username = httpOpts.Username | |
54 | plugin.Proxy = httpOpts.Proxy | |
55 | plugin.Cookies = httpOpts.Cookies | |
56 | plugin.Timeout = httpOpts.Timeout | |
57 | plugin.FollowRedirect = httpOpts.FollowRedirect | |
58 | plugin.NoTLSValidation = httpOpts.NoTLSValidation | |
59 | plugin.Headers = httpOpts.Headers | |
60 | plugin.Method = httpOpts.Method | |
61 | plugin.RetryOnTimeout = httpOpts.RetryOnTimeout | |
62 | plugin.RetryAttempts = httpOpts.RetryAttempts | |
50 | pluginOpts.Password = httpOpts.Password | |
51 | pluginOpts.URL = httpOpts.URL | |
52 | pluginOpts.UserAgent = httpOpts.UserAgent | |
53 | pluginOpts.Username = httpOpts.Username | |
54 | pluginOpts.Proxy = httpOpts.Proxy | |
55 | pluginOpts.Cookies = httpOpts.Cookies | |
56 | pluginOpts.Timeout = httpOpts.Timeout | |
57 | pluginOpts.FollowRedirect = httpOpts.FollowRedirect | |
58 | pluginOpts.NoTLSValidation = httpOpts.NoTLSValidation | |
59 | pluginOpts.Headers = httpOpts.Headers | |
60 | pluginOpts.Method = httpOpts.Method | |
61 | pluginOpts.RetryOnTimeout = httpOpts.RetryOnTimeout | |
62 | pluginOpts.RetryAttempts = httpOpts.RetryAttempts | |
63 | pluginOpts.TLSCertificate = httpOpts.TLSCertificate | |
64 | pluginOpts.NoCanonicalizeHeaders = httpOpts.NoCanonicalizeHeaders | |
63 | 65 | |
64 | plugin.Extensions, err = cmdDir.Flags().GetString("extensions") | |
66 | pluginOpts.Extensions, err = cmdDir.Flags().GetString("extensions") | |
65 | 67 | if err != nil { |
66 | 68 | return nil, nil, fmt.Errorf("invalid value for extensions: %w", err) |
67 | 69 | } |
68 | ret, err := helper.ParseExtensions(plugin.Extensions) | |
70 | ||
71 | ret, err := helper.ParseExtensions(pluginOpts.Extensions) | |
69 | 72 | if err != nil { |
70 | 73 | return nil, nil, fmt.Errorf("invalid value for extensions: %w", err) |
71 | 74 | } |
72 | plugin.ExtensionsParsed = ret | |
75 | pluginOpts.ExtensionsParsed = ret | |
76 | ||
77 | pluginOpts.ExtensionsFile, err = cmdDir.Flags().GetString("extensions-file") | |
78 | if err != nil { | |
79 | return nil, nil, fmt.Errorf("invalid value for extensions file: %w", err) | |
80 | } | |
81 | ||
82 | if pluginOpts.ExtensionsFile != "" { | |
83 | extensions, err := helper.ParseExtensionsFile(pluginOpts.ExtensionsFile) | |
84 | if err != nil { | |
85 | return nil, nil, fmt.Errorf("invalid value for extensions file: %w", err) | |
86 | } | |
87 | pluginOpts.ExtensionsParsed.AddRange(extensions) | |
88 | } | |
73 | 89 | |
74 | 90 | // parse normal status codes |
75 | plugin.StatusCodes, err = cmdDir.Flags().GetString("status-codes") | |
91 | pluginOpts.StatusCodes, err = cmdDir.Flags().GetString("status-codes") | |
76 | 92 | if err != nil { |
77 | 93 | return nil, nil, fmt.Errorf("invalid value for status-codes: %w", err) |
78 | 94 | } |
79 | ret2, err := helper.ParseCommaSeparatedInt(plugin.StatusCodes) | |
95 | ret2, err := helper.ParseCommaSeparatedInt(pluginOpts.StatusCodes) | |
80 | 96 | if err != nil { |
81 | 97 | return nil, nil, fmt.Errorf("invalid value for status-codes: %w", err) |
82 | 98 | } |
83 | plugin.StatusCodesParsed = ret2 | |
99 | pluginOpts.StatusCodesParsed = ret2 | |
84 | 100 | |
85 | 101 | // blacklist will override the normal status codes |
86 | plugin.StatusCodesBlacklist, err = cmdDir.Flags().GetString("status-codes-blacklist") | |
102 | pluginOpts.StatusCodesBlacklist, err = cmdDir.Flags().GetString("status-codes-blacklist") | |
87 | 103 | if err != nil { |
88 | 104 | return nil, nil, fmt.Errorf("invalid value for status-codes-blacklist: %w", err) |
89 | 105 | } |
90 | ret3, err := helper.ParseCommaSeparatedInt(plugin.StatusCodesBlacklist) | |
106 | ret3, err := helper.ParseCommaSeparatedInt(pluginOpts.StatusCodesBlacklist) | |
91 | 107 | if err != nil { |
92 | 108 | return nil, nil, fmt.Errorf("invalid value for status-codes-blacklist: %w", err) |
93 | 109 | } |
94 | plugin.StatusCodesBlacklistParsed = ret3 | |
110 | pluginOpts.StatusCodesBlacklistParsed = ret3 | |
95 | 111 | |
96 | if plugin.StatusCodes != "" && plugin.StatusCodesBlacklist != "" { | |
112 | if pluginOpts.StatusCodes != "" && pluginOpts.StatusCodesBlacklist != "" { | |
97 | 113 | 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.", |
98 | plugin.StatusCodes, plugin.StatusCodesBlacklist) | |
114 | pluginOpts.StatusCodes, pluginOpts.StatusCodesBlacklist) | |
99 | 115 | } |
100 | 116 | |
101 | if plugin.StatusCodes == "" && plugin.StatusCodesBlacklist == "" { | |
117 | if pluginOpts.StatusCodes == "" && pluginOpts.StatusCodesBlacklist == "" { | |
102 | 118 | return nil, nil, fmt.Errorf("status-codes and status-codes-blacklist are both not set, please set one") |
103 | 119 | } |
104 | 120 | |
105 | plugin.UseSlash, err = cmdDir.Flags().GetBool("add-slash") | |
121 | pluginOpts.UseSlash, err = cmdDir.Flags().GetBool("add-slash") | |
106 | 122 | if err != nil { |
107 | 123 | return nil, nil, fmt.Errorf("invalid value for add-slash: %w", err) |
108 | 124 | } |
109 | 125 | |
110 | plugin.Expanded, err = cmdDir.Flags().GetBool("expanded") | |
126 | pluginOpts.Expanded, err = cmdDir.Flags().GetBool("expanded") | |
111 | 127 | if err != nil { |
112 | 128 | return nil, nil, fmt.Errorf("invalid value for expanded: %w", err) |
113 | 129 | } |
114 | 130 | |
115 | plugin.NoStatus, err = cmdDir.Flags().GetBool("no-status") | |
131 | pluginOpts.NoStatus, err = cmdDir.Flags().GetBool("no-status") | |
116 | 132 | if err != nil { |
117 | 133 | return nil, nil, fmt.Errorf("invalid value for no-status: %w", err) |
118 | 134 | } |
119 | 135 | |
120 | plugin.HideLength, err = cmdDir.Flags().GetBool("hide-length") | |
136 | pluginOpts.HideLength, err = cmdDir.Flags().GetBool("hide-length") | |
121 | 137 | if err != nil { |
122 | 138 | return nil, nil, fmt.Errorf("invalid value for hide-length: %w", err) |
123 | 139 | } |
124 | 140 | |
125 | plugin.DiscoverBackup, err = cmdDir.Flags().GetBool("discover-backup") | |
141 | pluginOpts.DiscoverBackup, err = cmdDir.Flags().GetBool("discover-backup") | |
126 | 142 | if err != nil { |
127 | 143 | return nil, nil, fmt.Errorf("invalid value for discover-backup: %w", err) |
128 | 144 | } |
129 | 145 | |
130 | plugin.ExcludeLength, err = cmdDir.Flags().GetIntSlice("exclude-length") | |
146 | pluginOpts.ExcludeLength, err = cmdDir.Flags().GetIntSlice("exclude-length") | |
131 | 147 | if err != nil { |
132 | 148 | return nil, nil, fmt.Errorf("invalid value for excludelength: %w", err) |
133 | 149 | } |
134 | 150 | |
135 | return globalopts, plugin, nil | |
151 | return globalopts, pluginOpts, nil | |
136 | 152 | } |
137 | 153 | |
138 | 154 | // nolint:gochecknoinits |
149 | 165 | cmdDir.Flags().StringP("status-codes", "s", "", "Positive status codes (will be overwritten with status-codes-blacklist if set)") |
150 | 166 | cmdDir.Flags().StringP("status-codes-blacklist", "b", "404", "Negative status codes (will override status-codes if set)") |
151 | 167 | cmdDir.Flags().StringP("extensions", "x", "", "File extension(s) to search for") |
168 | cmdDir.Flags().StringP("extensions-file", "X", "", "Read file extension(s) to search from the file") | |
152 | 169 | cmdDir.Flags().BoolP("expanded", "e", false, "Expanded mode, print full URLs") |
153 | 170 | cmdDir.Flags().BoolP("no-status", "n", false, "Don't print status codes") |
154 | 171 | cmdDir.Flags().Bool("hide-length", false, "Hide the length of the body in the output") |
41 | 41 | if err != nil { |
42 | 42 | return nil, nil, err |
43 | 43 | } |
44 | plugin := gobusterdns.NewOptionsDNS() | |
44 | pluginOpts := gobusterdns.NewOptionsDNS() | |
45 | 45 | |
46 | plugin.Domain, err = cmdDNS.Flags().GetString("domain") | |
46 | pluginOpts.Domain, err = cmdDNS.Flags().GetString("domain") | |
47 | 47 | if err != nil { |
48 | 48 | return nil, nil, fmt.Errorf("invalid value for domain: %w", err) |
49 | 49 | } |
50 | 50 | |
51 | plugin.ShowIPs, err = cmdDNS.Flags().GetBool("show-ips") | |
51 | pluginOpts.ShowIPs, err = cmdDNS.Flags().GetBool("show-ips") | |
52 | 52 | if err != nil { |
53 | 53 | return nil, nil, fmt.Errorf("invalid value for show-ips: %w", err) |
54 | 54 | } |
55 | 55 | |
56 | plugin.ShowCNAME, err = cmdDNS.Flags().GetBool("show-cname") | |
56 | pluginOpts.ShowCNAME, err = cmdDNS.Flags().GetBool("show-cname") | |
57 | 57 | if err != nil { |
58 | 58 | return nil, nil, fmt.Errorf("invalid value for show-cname: %w", err) |
59 | 59 | } |
60 | 60 | |
61 | plugin.WildcardForced, err = cmdDNS.Flags().GetBool("wildcard") | |
61 | pluginOpts.WildcardForced, err = cmdDNS.Flags().GetBool("wildcard") | |
62 | 62 | if err != nil { |
63 | 63 | return nil, nil, fmt.Errorf("invalid value for wildcard: %w", err) |
64 | 64 | } |
65 | 65 | |
66 | plugin.Timeout, err = cmdDNS.Flags().GetDuration("timeout") | |
66 | pluginOpts.Timeout, err = cmdDNS.Flags().GetDuration("timeout") | |
67 | 67 | if err != nil { |
68 | 68 | return nil, nil, fmt.Errorf("invalid value for timeout: %w", err) |
69 | 69 | } |
70 | 70 | |
71 | plugin.Resolver, err = cmdDNS.Flags().GetString("resolver") | |
71 | pluginOpts.Resolver, err = cmdDNS.Flags().GetString("resolver") | |
72 | 72 | if err != nil { |
73 | 73 | return nil, nil, fmt.Errorf("invalid value for resolver: %w", err) |
74 | 74 | } |
75 | 75 | |
76 | if plugin.Resolver != "" && runtime.GOOS == "windows" { | |
76 | if pluginOpts.Resolver != "" && runtime.GOOS == "windows" { | |
77 | 77 | return nil, nil, fmt.Errorf("currently can not set custom dns resolver on windows. See https://golang.org/pkg/net/#hdr-Name_Resolution") |
78 | 78 | } |
79 | 79 | |
80 | return globalopts, plugin, nil | |
80 | return globalopts, pluginOpts, nil | |
81 | 81 | } |
82 | 82 | |
83 | 83 | // nolint:gochecknoinits |
3 | 3 | "errors" |
4 | 4 | "fmt" |
5 | 5 | "log" |
6 | "strings" | |
6 | 7 | |
7 | 8 | "github.com/OJ/gobuster/v3/cli" |
8 | 9 | "github.com/OJ/gobuster/v3/gobusterfuzz" |
18 | 19 | globalopts, pluginopts, err := parseFuzzOptions() |
19 | 20 | if err != nil { |
20 | 21 | return fmt.Errorf("error on parsing arguments: %w", err) |
22 | } | |
23 | ||
24 | if !containsFuzzKeyword(*pluginopts) { | |
25 | return fmt.Errorf("please provide the %s keyword", gobusterfuzz.FuzzKeyword) | |
21 | 26 | } |
22 | 27 | |
23 | 28 | plugin, err := gobusterfuzz.NewGobusterFuzz(globalopts, pluginopts) |
41 | 46 | return nil, nil, err |
42 | 47 | } |
43 | 48 | |
44 | plugin := gobusterfuzz.NewOptionsFuzz() | |
49 | pluginOpts := gobusterfuzz.NewOptionsFuzz() | |
45 | 50 | |
46 | 51 | httpOpts, err := parseCommonHTTPOptions(cmdFuzz) |
47 | 52 | if err != nil { |
48 | 53 | return nil, nil, err |
49 | 54 | } |
50 | plugin.Password = httpOpts.Password | |
51 | plugin.URL = httpOpts.URL | |
52 | plugin.UserAgent = httpOpts.UserAgent | |
53 | plugin.Username = httpOpts.Username | |
54 | plugin.Proxy = httpOpts.Proxy | |
55 | plugin.Cookies = httpOpts.Cookies | |
56 | plugin.Timeout = httpOpts.Timeout | |
57 | plugin.FollowRedirect = httpOpts.FollowRedirect | |
58 | plugin.NoTLSValidation = httpOpts.NoTLSValidation | |
59 | plugin.Headers = httpOpts.Headers | |
60 | plugin.Method = httpOpts.Method | |
61 | plugin.RetryOnTimeout = httpOpts.RetryOnTimeout | |
62 | plugin.RetryAttempts = httpOpts.RetryAttempts | |
55 | pluginOpts.Password = httpOpts.Password | |
56 | pluginOpts.URL = httpOpts.URL | |
57 | pluginOpts.UserAgent = httpOpts.UserAgent | |
58 | pluginOpts.Username = httpOpts.Username | |
59 | pluginOpts.Proxy = httpOpts.Proxy | |
60 | pluginOpts.Cookies = httpOpts.Cookies | |
61 | pluginOpts.Timeout = httpOpts.Timeout | |
62 | pluginOpts.FollowRedirect = httpOpts.FollowRedirect | |
63 | pluginOpts.NoTLSValidation = httpOpts.NoTLSValidation | |
64 | pluginOpts.Headers = httpOpts.Headers | |
65 | pluginOpts.Method = httpOpts.Method | |
66 | pluginOpts.RetryOnTimeout = httpOpts.RetryOnTimeout | |
67 | pluginOpts.RetryAttempts = httpOpts.RetryAttempts | |
68 | pluginOpts.TLSCertificate = httpOpts.TLSCertificate | |
69 | pluginOpts.NoCanonicalizeHeaders = httpOpts.NoCanonicalizeHeaders | |
63 | 70 | |
64 | 71 | // blacklist will override the normal status codes |
65 | plugin.ExcludedStatusCodes, err = cmdFuzz.Flags().GetString("excludestatuscodes") | |
72 | pluginOpts.ExcludedStatusCodes, err = cmdFuzz.Flags().GetString("excludestatuscodes") | |
66 | 73 | if err != nil { |
67 | 74 | return nil, nil, fmt.Errorf("invalid value for excludestatuscodes: %w", err) |
68 | 75 | } |
69 | ret, err := helper.ParseCommaSeparatedInt(plugin.ExcludedStatusCodes) | |
76 | ret, err := helper.ParseCommaSeparatedInt(pluginOpts.ExcludedStatusCodes) | |
70 | 77 | if err != nil { |
71 | 78 | return nil, nil, fmt.Errorf("invalid value for excludestatuscodes: %w", err) |
72 | 79 | } |
73 | plugin.ExcludedStatusCodesParsed = ret | |
80 | pluginOpts.ExcludedStatusCodesParsed = ret | |
74 | 81 | |
75 | plugin.ExcludeLength, err = cmdFuzz.Flags().GetIntSlice("exclude-length") | |
82 | pluginOpts.ExcludeLength, err = cmdFuzz.Flags().GetIntSlice("exclude-length") | |
76 | 83 | if err != nil { |
77 | 84 | return nil, nil, fmt.Errorf("invalid value for excludelength: %w", err) |
78 | 85 | } |
79 | 86 | |
80 | return globalopts, plugin, nil | |
87 | pluginOpts.RequestBody, err = cmdFuzz.Flags().GetString("body") | |
88 | if err != nil { | |
89 | return nil, nil, fmt.Errorf("invalid value for body: %w", err) | |
90 | } | |
91 | ||
92 | return globalopts, pluginOpts, nil | |
81 | 93 | } |
82 | 94 | |
83 | 95 | // nolint:gochecknoinits |
84 | 96 | func init() { |
85 | 97 | cmdFuzz = &cobra.Command{ |
86 | 98 | Use: "fuzz", |
87 | Short: "Uses fuzzing mode", | |
99 | Short: fmt.Sprintf("Uses fuzzing mode. Replaces the keyword %s in the URL, Headers and the request body", gobusterfuzz.FuzzKeyword), | |
88 | 100 | RunE: runFuzz, |
89 | 101 | } |
90 | 102 | |
93 | 105 | } |
94 | 106 | cmdFuzz.Flags().StringP("excludestatuscodes", "b", "", "Negative status codes (will override statuscodes if set)") |
95 | 107 | cmdFuzz.Flags().IntSlice("exclude-length", []int{}, "exclude the following content length (completely ignores the status). Supply multiple times to exclude multiple sizes.") |
108 | cmdFuzz.Flags().StringP("body", "B", "", "Request body") | |
96 | 109 | |
97 | 110 | cmdFuzz.PersistentPreRun = func(cmd *cobra.Command, args []string) { |
98 | 111 | configureGlobalOptions() |
100 | 113 | |
101 | 114 | rootCmd.AddCommand(cmdFuzz) |
102 | 115 | } |
116 | ||
117 | func containsFuzzKeyword(pluginopts gobusterfuzz.OptionsFuzz) bool { | |
118 | if strings.Contains(pluginopts.URL, gobusterfuzz.FuzzKeyword) { | |
119 | return true | |
120 | } | |
121 | ||
122 | if strings.Contains(pluginopts.RequestBody, gobusterfuzz.FuzzKeyword) { | |
123 | return true | |
124 | } | |
125 | ||
126 | for _, h := range pluginopts.Headers { | |
127 | if strings.Contains(h.Name, gobusterfuzz.FuzzKeyword) || strings.Contains(h.Value, gobusterfuzz.FuzzKeyword) { | |
128 | return true | |
129 | } | |
130 | } | |
131 | ||
132 | if strings.Contains(pluginopts.Username, gobusterfuzz.FuzzKeyword) { | |
133 | return true | |
134 | } | |
135 | ||
136 | if strings.Contains(pluginopts.Password, gobusterfuzz.FuzzKeyword) { | |
137 | return true | |
138 | } | |
139 | ||
140 | return false | |
141 | } |
47 | 47 | pluginopts.NoTLSValidation = httpOpts.NoTLSValidation |
48 | 48 | pluginopts.RetryOnTimeout = httpOpts.RetryOnTimeout |
49 | 49 | pluginopts.RetryAttempts = httpOpts.RetryAttempts |
50 | pluginopts.TLSCertificate = httpOpts.TLSCertificate | |
50 | 51 | |
51 | 52 | pluginopts.MaxFilesToList, err = cmdGCS.Flags().GetInt("maxfiles") |
52 | 53 | if err != nil { |
0 | 0 | package cmd |
1 | 1 | |
2 | 2 | import ( |
3 | "crypto/tls" | |
4 | "encoding/pem" | |
3 | 5 | "fmt" |
6 | "os" | |
4 | 7 | "regexp" |
5 | 8 | "strconv" |
6 | 9 | "strings" |
10 | 13 | "github.com/OJ/gobuster/v3/helper" |
11 | 14 | "github.com/OJ/gobuster/v3/libgobuster" |
12 | 15 | "github.com/spf13/cobra" |
16 | "golang.org/x/crypto/pkcs12" | |
13 | 17 | "golang.org/x/term" |
14 | 18 | ) |
15 | 19 | |
21 | 25 | cmd.Flags().BoolP("no-tls-validation", "k", false, "Skip TLS certificate verification") |
22 | 26 | cmd.Flags().BoolP("retry", "", false, "Should retry on request timeout") |
23 | 27 | cmd.Flags().IntP("retry-attempts", "", 3, "Times to retry on request timeout") |
28 | // client certificates, either pem or p12 | |
29 | cmd.Flags().StringP("client-cert-pem", "", "", "public key in PEM format for optional TLS client certificates") | |
30 | cmd.Flags().StringP("client-cert-pem-key", "", "", "private key in PEM format for optional TLS client certificates (this key needs to have no password)") | |
31 | cmd.Flags().StringP("client-cert-p12", "", "", "a p12 file to use for options TLS client certificates") | |
32 | cmd.Flags().StringP("client-cert-p12-password", "", "", "the password to the p12 file") | |
24 | 33 | } |
25 | 34 | |
26 | 35 | func addCommonHTTPOptions(cmd *cobra.Command) error { |
31 | 40 | cmd.Flags().StringP("password", "P", "", "Password for Basic Auth") |
32 | 41 | cmd.Flags().BoolP("follow-redirect", "r", false, "Follow redirects") |
33 | 42 | cmd.Flags().StringArrayP("headers", "H", []string{""}, "Specify HTTP headers, -H 'Header1: val1' -H 'Header2: val2'") |
43 | cmd.Flags().BoolP("no-canonicalize-headers", "", false, "Do not canonicalize HTTP header names. If set header names are sent as is.") | |
34 | 44 | cmd.Flags().StringP("method", "m", "GET", "Use the following HTTP method") |
35 | 45 | |
36 | 46 | if err := cmd.MarkFlagRequired("url"); err != nil { |
84 | 94 | if err != nil { |
85 | 95 | return options, fmt.Errorf("invalid value for no-tls-validation: %w", err) |
86 | 96 | } |
97 | ||
98 | pemFile, err := cmd.Flags().GetString("client-cert-pem") | |
99 | if err != nil { | |
100 | return options, fmt.Errorf("invalid value for client-cert-pem: %w", err) | |
101 | } | |
102 | pemKeyFile, err := cmd.Flags().GetString("client-cert-pem-key") | |
103 | if err != nil { | |
104 | return options, fmt.Errorf("invalid value for client-cert-pem-key: %w", err) | |
105 | } | |
106 | p12File, err := cmd.Flags().GetString("client-cert-p12") | |
107 | if err != nil { | |
108 | return options, fmt.Errorf("invalid value for client-cert-p12: %w", err) | |
109 | } | |
110 | p12Pass, err := cmd.Flags().GetString("client-cert-p12-password") | |
111 | if err != nil { | |
112 | return options, fmt.Errorf("invalid value for client-cert-p12-password: %w", err) | |
113 | } | |
114 | ||
115 | if pemFile != "" && p12File != "" { | |
116 | return options, fmt.Errorf("please supply either a pem or a p12, not both") | |
117 | } | |
118 | ||
119 | if pemFile != "" { | |
120 | cert, err := tls.LoadX509KeyPair(pemFile, pemKeyFile) | |
121 | if err != nil { | |
122 | return options, fmt.Errorf("could not load supplied pem key: %w", err) | |
123 | } | |
124 | options.TLSCertificate = &cert | |
125 | } else if p12File != "" { | |
126 | p12Content, err := os.ReadFile(p12File) | |
127 | if err != nil { | |
128 | return options, fmt.Errorf("could not read p12 %s: %w", p12File, err) | |
129 | } | |
130 | blocks, err := pkcs12.ToPEM(p12Content, p12Pass) | |
131 | if err != nil { | |
132 | return options, fmt.Errorf("could not load P12: %w", err) | |
133 | } | |
134 | var pemData []byte | |
135 | for _, b := range blocks { | |
136 | pemData = append(pemData, pem.EncodeToMemory(b)...) | |
137 | } | |
138 | cert, err := tls.X509KeyPair(pemData, pemData) | |
139 | if err != nil { | |
140 | return options, fmt.Errorf("could not load certificate from P12: %w", err) | |
141 | } | |
142 | options.TLSCertificate = &cert | |
143 | } | |
144 | ||
87 | 145 | return options, nil |
88 | 146 | } |
89 | 147 | |
101 | 159 | options.NoTLSValidation = basic.NoTLSValidation |
102 | 160 | options.RetryOnTimeout = basic.RetryOnTimeout |
103 | 161 | options.RetryAttempts = basic.RetryAttempts |
162 | options.TLSCertificate = basic.TLSCertificate | |
104 | 163 | |
105 | 164 | options.URL, err = cmd.Flags().GetString("url") |
106 | 165 | if err != nil { |
171 | 230 | options.Headers = append(options.Headers, header) |
172 | 231 | } |
173 | 232 | |
233 | noCanonHeaders, err := cmd.Flags().GetBool("no-canonicalize-headers") | |
234 | if err != nil { | |
235 | return options, fmt.Errorf("invalid value for no-canonicalize-headers: %w", err) | |
236 | } | |
237 | options.NoCanonicalizeHeaders = noCanonHeaders | |
238 | ||
174 | 239 | // Prompt for PW if not provided |
175 | 240 | if options.Username != "" && options.Password == "" { |
176 | 241 | fmt.Printf("[?] Auth Password: ") |
34 | 34 | return nil, nil, err |
35 | 35 | } |
36 | 36 | |
37 | plugin := gobusters3.NewOptionsS3() | |
37 | pluginOpts := gobusters3.NewOptionsS3() | |
38 | 38 | |
39 | 39 | httpOpts, err := parseBasicHTTPOptions(cmdS3) |
40 | 40 | if err != nil { |
41 | 41 | return nil, nil, err |
42 | 42 | } |
43 | 43 | |
44 | plugin.UserAgent = httpOpts.UserAgent | |
45 | plugin.Proxy = httpOpts.Proxy | |
46 | plugin.Timeout = httpOpts.Timeout | |
47 | plugin.NoTLSValidation = httpOpts.NoTLSValidation | |
48 | plugin.RetryOnTimeout = httpOpts.RetryOnTimeout | |
49 | plugin.RetryAttempts = httpOpts.RetryAttempts | |
44 | pluginOpts.UserAgent = httpOpts.UserAgent | |
45 | pluginOpts.Proxy = httpOpts.Proxy | |
46 | pluginOpts.Timeout = httpOpts.Timeout | |
47 | pluginOpts.NoTLSValidation = httpOpts.NoTLSValidation | |
48 | pluginOpts.RetryOnTimeout = httpOpts.RetryOnTimeout | |
49 | pluginOpts.RetryAttempts = httpOpts.RetryAttempts | |
50 | pluginOpts.TLSCertificate = httpOpts.TLSCertificate | |
50 | 51 | |
51 | plugin.MaxFilesToList, err = cmdS3.Flags().GetInt("maxfiles") | |
52 | pluginOpts.MaxFilesToList, err = cmdS3.Flags().GetInt("maxfiles") | |
52 | 53 | if err != nil { |
53 | 54 | return nil, nil, fmt.Errorf("invalid value for maxfiles: %w", err) |
54 | 55 | } |
55 | 56 | |
56 | return globalopts, plugin, nil | |
57 | return globalopts, pluginOpts, nil | |
57 | 58 | } |
58 | 59 | |
59 | 60 | // nolint:gochecknoinits |
34 | 34 | if err != nil { |
35 | 35 | return nil, nil, err |
36 | 36 | } |
37 | var plugin gobustervhost.OptionsVhost | |
37 | ||
38 | pluginOpts := gobustervhost.NewOptionsVhost() | |
38 | 39 | |
39 | 40 | httpOpts, err := parseCommonHTTPOptions(cmdVhost) |
40 | 41 | if err != nil { |
41 | 42 | return nil, nil, err |
42 | 43 | } |
43 | plugin.Password = httpOpts.Password | |
44 | plugin.URL = httpOpts.URL | |
45 | plugin.UserAgent = httpOpts.UserAgent | |
46 | plugin.Username = httpOpts.Username | |
47 | plugin.Proxy = httpOpts.Proxy | |
48 | plugin.Cookies = httpOpts.Cookies | |
49 | plugin.Timeout = httpOpts.Timeout | |
50 | plugin.FollowRedirect = httpOpts.FollowRedirect | |
51 | plugin.NoTLSValidation = httpOpts.NoTLSValidation | |
52 | plugin.Headers = httpOpts.Headers | |
53 | plugin.Method = httpOpts.Method | |
54 | plugin.RetryOnTimeout = httpOpts.RetryOnTimeout | |
55 | plugin.RetryAttempts = httpOpts.RetryAttempts | |
44 | pluginOpts.Password = httpOpts.Password | |
45 | pluginOpts.URL = httpOpts.URL | |
46 | pluginOpts.UserAgent = httpOpts.UserAgent | |
47 | pluginOpts.Username = httpOpts.Username | |
48 | pluginOpts.Proxy = httpOpts.Proxy | |
49 | pluginOpts.Cookies = httpOpts.Cookies | |
50 | pluginOpts.Timeout = httpOpts.Timeout | |
51 | pluginOpts.FollowRedirect = httpOpts.FollowRedirect | |
52 | pluginOpts.NoTLSValidation = httpOpts.NoTLSValidation | |
53 | pluginOpts.Headers = httpOpts.Headers | |
54 | pluginOpts.Method = httpOpts.Method | |
55 | pluginOpts.RetryOnTimeout = httpOpts.RetryOnTimeout | |
56 | pluginOpts.RetryAttempts = httpOpts.RetryAttempts | |
57 | pluginOpts.TLSCertificate = httpOpts.TLSCertificate | |
58 | pluginOpts.NoCanonicalizeHeaders = httpOpts.NoCanonicalizeHeaders | |
56 | 59 | |
57 | plugin.AppendDomain, err = cmdVhost.Flags().GetBool("append-domain") | |
60 | pluginOpts.AppendDomain, err = cmdVhost.Flags().GetBool("append-domain") | |
58 | 61 | if err != nil { |
59 | 62 | return nil, nil, fmt.Errorf("invalid value for append-domain: %w", err) |
60 | 63 | } |
61 | 64 | |
62 | plugin.ExcludeLength, err = cmdVhost.Flags().GetIntSlice("exclude-length") | |
65 | pluginOpts.ExcludeLength, err = cmdVhost.Flags().GetIntSlice("exclude-length") | |
63 | 66 | if err != nil { |
64 | 67 | return nil, nil, fmt.Errorf("invalid value for excludelength: %w", err) |
65 | 68 | } |
66 | 69 | |
67 | plugin.Domain, err = cmdVhost.Flags().GetString("domain") | |
70 | pluginOpts.Domain, err = cmdVhost.Flags().GetString("domain") | |
68 | 71 | if err != nil { |
69 | 72 | return nil, nil, fmt.Errorf("invalid value for domain: %w", err) |
70 | 73 | } |
71 | 74 | |
72 | return globalopts, &plugin, nil | |
75 | return globalopts, pluginOpts, nil | |
73 | 76 | } |
74 | 77 | |
75 | 78 | // nolint:gochecknoinits |
76 | 79 | func init() { |
77 | 80 | cmdVhost = &cobra.Command{ |
78 | 81 | Use: "vhost", |
79 | Short: "Uses VHOST enumeration mode (you most probably want to use the IP adress as the URL parameter", | |
82 | Short: "Uses VHOST enumeration mode (you most probably want to use the IP address as the URL parameter)", | |
80 | 83 | RunE: runVhost, |
81 | 84 | } |
82 | 85 | if err := addCommonHTTPOptions(cmdVhost); err != nil { |
17 | 17 | h := httpServer(b, "test") |
18 | 18 | defer h.Close() |
19 | 19 | |
20 | pluginopts := gobustervhost.OptionsVhost{} | |
20 | pluginopts := gobustervhost.NewOptionsVhost() | |
21 | 21 | pluginopts.URL = h.URL |
22 | 22 | pluginopts.Timeout = 10 * time.Second |
23 | 23 | |
53 | 53 | for x := 0; x < b.N; x++ { |
54 | 54 | os.Stdout = devnull |
55 | 55 | os.Stderr = devnull |
56 | plugin, err := gobustervhost.NewGobusterVhost(&globalopts, &pluginopts) | |
56 | plugin, err := gobustervhost.NewGobusterVhost(&globalopts, pluginopts) | |
57 | 57 | if err != nil { |
58 | 58 | b.Fatalf("error on creating gobusterdir: %v", err) |
59 | 59 | } |
4 | 4 | require ( |
5 | 5 | github.com/fatih/color v1.13.0 |
6 | 6 | github.com/google/uuid v1.3.0 |
7 | github.com/spf13/cobra v1.5.0 | |
8 | golang.org/x/term v0.0.0-20220919170432-7a66f970e087 | |
7 | github.com/spf13/cobra v1.6.1 | |
8 | golang.org/x/crypto v0.1.0 | |
9 | golang.org/x/term v0.1.0 | |
9 | 10 | ) |
10 | 11 | |
11 | 12 | require ( |
13 | 14 | github.com/mattn/go-colorable v0.1.13 // indirect |
14 | 15 | github.com/mattn/go-isatty v0.0.16 // indirect |
15 | 16 | github.com/spf13/pflag v1.0.5 // indirect |
16 | golang.org/x/sys v0.0.0-20221006211917-84dc82d7e875 // indirect | |
17 | golang.org/x/sys v0.1.0 // indirect | |
17 | 18 | ) |
2 | 2 | github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= |
3 | 3 | github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= |
4 | 4 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= |
5 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= | |
6 | 5 | github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= |
7 | 6 | github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= |
8 | 7 | github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= |
13 | 12 | github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= |
14 | 13 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= |
15 | 14 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= |
16 | github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU= | |
17 | github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM= | |
15 | github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= | |
16 | github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= | |
18 | 17 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= |
19 | 18 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= |
19 | golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= | |
20 | golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= | |
20 | 21 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
21 | 22 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
22 | 23 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= |
23 | 24 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= |
24 | golang.org/x/sys v0.0.0-20221006211917-84dc82d7e875 h1:AzgQNqF+FKwyQ5LbVrVqOcuuFB67N47F9+htZYH0wFM= | |
25 | golang.org/x/sys v0.0.0-20221006211917-84dc82d7e875/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | |
26 | golang.org/x/term v0.0.0-20220919170432-7a66f970e087 h1:tPwmk4vmvVCMdr98VgL4JH+qZxPL8fqlUOHnyOM8N3w= | |
27 | golang.org/x/term v0.0.0-20220919170432-7a66f970e087/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= | |
25 | golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= | |
26 | golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | |
27 | golang.org/x/term v0.1.0 h1:g6Z6vPFA9dYBAF7DWcH6sCcOntplXsDKcliusYijMlw= | |
28 | golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= | |
28 | 29 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= |
29 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= | |
30 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= |
61 | 61 | NoTLSValidation: opts.NoTLSValidation, |
62 | 62 | RetryOnTimeout: opts.RetryOnTimeout, |
63 | 63 | RetryAttempts: opts.RetryAttempts, |
64 | TLSCertificate: opts.TLSCertificate, | |
64 | 65 | } |
65 | 66 | |
66 | 67 | httpOpts := libgobuster.HTTPOptions{ |
67 | BasicHTTPOptions: basicOptions, | |
68 | FollowRedirect: opts.FollowRedirect, | |
69 | Username: opts.Username, | |
70 | Password: opts.Password, | |
71 | Headers: opts.Headers, | |
72 | Cookies: opts.Cookies, | |
73 | Method: opts.Method, | |
68 | BasicHTTPOptions: basicOptions, | |
69 | FollowRedirect: opts.FollowRedirect, | |
70 | Username: opts.Username, | |
71 | Password: opts.Password, | |
72 | Headers: opts.Headers, | |
73 | NoCanonicalizeHeaders: opts.NoCanonicalizeHeaders, | |
74 | Cookies: opts.Cookies, | |
75 | Method: opts.Method, | |
74 | 76 | } |
75 | 77 | |
76 | 78 | h, err := libgobuster.NewHTTPClient(&httpOpts) |
320 | 322 | } |
321 | 323 | } |
322 | 324 | |
323 | if o.Extensions != "" { | |
325 | if o.Extensions != "" || o.ExtensionsFile != "" { | |
324 | 326 | if _, err := fmt.Fprintf(tw, "[+] Extensions:\t%s\n", o.ExtensionsParsed.Stringify()); err != nil { |
327 | return "", err | |
328 | } | |
329 | } | |
330 | ||
331 | if o.ExtensionsFile != "" { | |
332 | if _, err := fmt.Fprintf(tw, "[+] Extensions file:\t%s\n", o.ExtensionsFile); err != nil { | |
325 | 333 | return "", err |
326 | 334 | } |
327 | 335 | } |
8 | 8 | libgobuster.HTTPOptions |
9 | 9 | Extensions string |
10 | 10 | ExtensionsParsed libgobuster.Set[string] |
11 | ExtensionsFile string | |
11 | 12 | StatusCodes string |
12 | 13 | StatusCodesParsed libgobuster.Set[int] |
13 | 14 | StatusCodesBlacklist string |
12 | 12 | "github.com/OJ/gobuster/v3/libgobuster" |
13 | 13 | ) |
14 | 14 | |
15 | const FuzzKeyword = "FUZZ" | |
16 | ||
15 | 17 | // ErrWildcard is returned if a wildcard response is found |
16 | 18 | type ErrWildcard struct { |
17 | 19 | url string |
52 | 54 | NoTLSValidation: opts.NoTLSValidation, |
53 | 55 | RetryOnTimeout: opts.RetryOnTimeout, |
54 | 56 | RetryAttempts: opts.RetryAttempts, |
57 | TLSCertificate: opts.TLSCertificate, | |
55 | 58 | } |
56 | 59 | |
57 | 60 | httpOpts := libgobuster.HTTPOptions{ |
58 | BasicHTTPOptions: basicOptions, | |
59 | FollowRedirect: opts.FollowRedirect, | |
60 | Username: opts.Username, | |
61 | Password: opts.Password, | |
62 | Headers: opts.Headers, | |
63 | Cookies: opts.Cookies, | |
64 | Method: opts.Method, | |
61 | BasicHTTPOptions: basicOptions, | |
62 | FollowRedirect: opts.FollowRedirect, | |
63 | Username: opts.Username, | |
64 | Password: opts.Password, | |
65 | Headers: opts.Headers, | |
66 | NoCanonicalizeHeaders: opts.NoCanonicalizeHeaders, | |
67 | Cookies: opts.Cookies, | |
68 | Method: opts.Method, | |
65 | 69 | } |
66 | 70 | |
67 | 71 | h, err := libgobuster.NewHTTPClient(&httpOpts) |
84 | 88 | |
85 | 89 | // ProcessWord is the process implementation of gobusterfuzz |
86 | 90 | func (d *GobusterFuzz) ProcessWord(ctx context.Context, word string, progress *libgobuster.Progress) error { |
87 | url := strings.ReplaceAll(d.options.URL, "FUZZ", word) | |
91 | url := strings.ReplaceAll(d.options.URL, FuzzKeyword, word) | |
92 | ||
93 | requestOptions := libgobuster.RequestOptions{} | |
94 | ||
95 | if len(d.options.Headers) > 0 { | |
96 | requestOptions.ModifiedHeaders = make([]libgobuster.HTTPHeader, len(d.options.Headers)) | |
97 | for i := range d.options.Headers { | |
98 | requestOptions.ModifiedHeaders[i] = libgobuster.HTTPHeader{ | |
99 | Name: strings.ReplaceAll(d.options.Headers[i].Name, FuzzKeyword, word), | |
100 | Value: strings.ReplaceAll(d.options.Headers[i].Value, FuzzKeyword, word), | |
101 | } | |
102 | } | |
103 | } | |
104 | ||
105 | if d.options.RequestBody != "" { | |
106 | data := strings.ReplaceAll(d.options.RequestBody, FuzzKeyword, word) | |
107 | buffer := strings.NewReader(data) | |
108 | requestOptions.Body = buffer | |
109 | } | |
110 | ||
111 | // fuzzing of basic auth | |
112 | if strings.Contains(d.options.Username, FuzzKeyword) || strings.Contains(d.options.Password, FuzzKeyword) { | |
113 | requestOptions.UpdatedBasicAuthUsername = strings.ReplaceAll(d.options.Username, FuzzKeyword, word) | |
114 | requestOptions.UpdatedBasicAuthPassword = strings.ReplaceAll(d.options.Password, FuzzKeyword, word) | |
115 | } | |
88 | 116 | |
89 | 117 | tries := 1 |
90 | 118 | if d.options.RetryOnTimeout && d.options.RetryAttempts > 0 { |
96 | 124 | var size int64 |
97 | 125 | for i := 1; i <= tries; i++ { |
98 | 126 | var err error |
99 | statusCode, size, _, _, err = d.http.Request(ctx, url, libgobuster.RequestOptions{}) | |
127 | statusCode, size, _, _, err = d.http.Request(ctx, url, requestOptions) | |
100 | 128 | if err != nil { |
101 | 129 | // check if it's a timeout and if we should try again and try again |
102 | 130 | // otherwise the timeout error is raised |
9 | 9 | ExcludedStatusCodes string |
10 | 10 | ExcludedStatusCodesParsed libgobuster.Set[int] |
11 | 11 | ExcludeLength []int |
12 | RequestBody string | |
12 | 13 | } |
13 | 14 | |
14 | 15 | // NewOptionsFuzz returns a new initialized OptionsFuzz |
44 | 44 | NoTLSValidation: opts.NoTLSValidation, |
45 | 45 | RetryOnTimeout: opts.RetryOnTimeout, |
46 | 46 | RetryAttempts: opts.RetryAttempts, |
47 | TLSCertificate: opts.TLSCertificate, | |
47 | 48 | } |
48 | 49 | |
49 | 50 | httpOpts := libgobuster.HTTPOptions{ |
44 | 44 | NoTLSValidation: opts.NoTLSValidation, |
45 | 45 | RetryOnTimeout: opts.RetryOnTimeout, |
46 | 46 | RetryAttempts: opts.RetryAttempts, |
47 | TLSCertificate: opts.TLSCertificate, | |
47 | 48 | } |
48 | 49 | |
49 | 50 | httpOpts := libgobuster.HTTPOptions{ |
47 | 47 | NoTLSValidation: opts.NoTLSValidation, |
48 | 48 | RetryOnTimeout: opts.RetryOnTimeout, |
49 | 49 | RetryAttempts: opts.RetryAttempts, |
50 | TLSCertificate: opts.TLSCertificate, | |
50 | 51 | } |
51 | 52 | |
52 | 53 | httpOpts := libgobuster.HTTPOptions{ |
53 | BasicHTTPOptions: basicOptions, | |
54 | FollowRedirect: opts.FollowRedirect, | |
55 | Username: opts.Username, | |
56 | Password: opts.Password, | |
57 | Headers: opts.Headers, | |
58 | Cookies: opts.Cookies, | |
59 | Method: opts.Method, | |
54 | BasicHTTPOptions: basicOptions, | |
55 | FollowRedirect: opts.FollowRedirect, | |
56 | Username: opts.Username, | |
57 | Password: opts.Password, | |
58 | Headers: opts.Headers, | |
59 | NoCanonicalizeHeaders: opts.NoCanonicalizeHeaders, | |
60 | Cookies: opts.Cookies, | |
61 | Method: opts.Method, | |
60 | 62 | } |
61 | 63 | |
62 | 64 | h, err := libgobuster.NewHTTPClient(&httpOpts) |
10 | 10 | ExcludeLength []int |
11 | 11 | Domain string |
12 | 12 | } |
13 | ||
14 | // NewOptionsVhost returns a new initialized OptionsVhost | |
15 | func NewOptionsVhost() *OptionsVhost { | |
16 | return &OptionsVhost{} | |
17 | } |
0 | 0 | package helper |
1 | 1 | |
2 | 2 | import ( |
3 | "bufio" | |
3 | 4 | "fmt" |
5 | "os" | |
4 | 6 | "strconv" |
5 | 7 | "strings" |
6 | 8 | |
20 | 22 | // remove leading . from extensions |
21 | 23 | ret.Add(strings.TrimPrefix(e, ".")) |
22 | 24 | } |
25 | return ret, nil | |
26 | } | |
27 | ||
28 | func ParseExtensionsFile(file string) ([]string, error) { | |
29 | var ret []string | |
30 | ||
31 | stream, err := os.Open(file) | |
32 | if err != nil { | |
33 | return ret, err | |
34 | } | |
35 | defer stream.Close() | |
36 | ||
37 | scanner := bufio.NewScanner(stream) | |
38 | for scanner.Scan() { | |
39 | e := scanner.Text() | |
40 | e = strings.TrimSpace(e) | |
41 | // remove leading . from extensions | |
42 | ret = append(ret, (strings.TrimPrefix(e, "."))) | |
43 | } | |
44 | ||
45 | if err := scanner.Err(); err != nil { | |
46 | return nil, err | |
47 | } | |
48 | ||
23 | 49 | return ret, nil |
24 | 50 | } |
25 | 51 |
18 | 18 | |
19 | 19 | // HTTPClient represents a http object |
20 | 20 | type HTTPClient struct { |
21 | client *http.Client | |
22 | userAgent string | |
23 | defaultUserAgent string | |
24 | username string | |
25 | password string | |
26 | headers []HTTPHeader | |
27 | cookies string | |
28 | method string | |
29 | host string | |
21 | client *http.Client | |
22 | userAgent string | |
23 | defaultUserAgent string | |
24 | username string | |
25 | password string | |
26 | headers []HTTPHeader | |
27 | noCanonicalizeHeaders bool | |
28 | cookies string | |
29 | method string | |
30 | host string | |
30 | 31 | } |
31 | 32 | |
32 | 33 | // RequestOptions is used to pass options to a single individual request |
33 | 34 | type RequestOptions struct { |
34 | Host string | |
35 | Body io.Reader | |
36 | ReturnBody bool | |
35 | Host string | |
36 | Body io.Reader | |
37 | ReturnBody bool | |
38 | ModifiedHeaders []HTTPHeader | |
39 | UpdatedBasicAuthUsername string | |
40 | UpdatedBasicAuthPassword string | |
37 | 41 | } |
38 | 42 | |
39 | 43 | // NewHTTPClient returns a new HTTPClient |
61 | 65 | } |
62 | 66 | } else { |
63 | 67 | redirectFunc = nil |
68 | } | |
69 | ||
70 | tlsConfig := tls.Config{ | |
71 | InsecureSkipVerify: opt.NoTLSValidation, | |
72 | } | |
73 | if opt.TLSCertificate != nil { | |
74 | tlsConfig.Certificates = []tls.Certificate{*opt.TLSCertificate} | |
64 | 75 | } |
65 | 76 | |
66 | 77 | client.client = &http.Client{ |
70 | 81 | Proxy: proxyURLFunc, |
71 | 82 | MaxIdleConns: 100, |
72 | 83 | MaxIdleConnsPerHost: 100, |
73 | TLSClientConfig: &tls.Config{ | |
74 | InsecureSkipVerify: opt.NoTLSValidation, | |
75 | }, | |
84 | TLSClientConfig: &tlsConfig, | |
76 | 85 | }} |
77 | 86 | client.username = opt.Username |
78 | 87 | client.password = opt.Password |
79 | 88 | client.userAgent = opt.UserAgent |
80 | 89 | client.defaultUserAgent = DefaultUserAgent() |
81 | 90 | client.headers = opt.Headers |
91 | client.noCanonicalizeHeaders = opt.NoCanonicalizeHeaders | |
82 | 92 | client.cookies = opt.Cookies |
83 | 93 | client.method = opt.Method |
84 | 94 | if client.method == "" { |
97 | 107 | // Request makes an http request and returns the status, the content length, the headers, the body and an error |
98 | 108 | // if you want the body returned set the corresponding property inside RequestOptions |
99 | 109 | func (client *HTTPClient) Request(ctx context.Context, fullURL string, opts RequestOptions) (int, int64, http.Header, []byte, error) { |
100 | resp, err := client.makeRequest(ctx, fullURL, opts.Host, opts.Body) | |
110 | resp, err := client.makeRequest(ctx, fullURL, opts) | |
101 | 111 | if err != nil { |
102 | 112 | // ignore context canceled errors |
103 | 113 | if errors.Is(ctx.Err(), context.Canceled) { |
127 | 137 | return resp.StatusCode, length, resp.Header, body, nil |
128 | 138 | } |
129 | 139 | |
130 | func (client *HTTPClient) makeRequest(ctx context.Context, fullURL, host string, data io.Reader) (*http.Response, error) { | |
131 | req, err := http.NewRequest(client.method, fullURL, data) | |
140 | func (client *HTTPClient) makeRequest(ctx context.Context, fullURL string, opts RequestOptions) (*http.Response, error) { | |
141 | req, err := http.NewRequest(client.method, fullURL, opts.Body) | |
132 | 142 | if err != nil { |
133 | 143 | return nil, err |
134 | 144 | } |
141 | 151 | } |
142 | 152 | |
143 | 153 | // Use host for VHOST mode on a per request basis, otherwise the one provided from headers |
144 | if host != "" { | |
145 | req.Host = host | |
154 | if opts.Host != "" { | |
155 | req.Host = opts.Host | |
146 | 156 | } else if client.host != "" { |
147 | 157 | req.Host = client.host |
148 | 158 | } |
154 | 164 | } |
155 | 165 | |
156 | 166 | // add custom headers |
157 | for _, h := range client.headers { | |
158 | req.Header.Set(h.Name, h.Value) | |
159 | } | |
160 | ||
161 | if client.username != "" { | |
167 | // if ModifiedHeaders are supplied use those, otherwise use the original ones | |
168 | // currently only relevant on fuzzing | |
169 | if len(opts.ModifiedHeaders) > 0 { | |
170 | for _, h := range opts.ModifiedHeaders { | |
171 | if client.noCanonicalizeHeaders { | |
172 | // https://stackoverflow.com/questions/26351716/how-to-keep-key-case-sensitive-in-request-header-using-golang | |
173 | req.Header[h.Name] = []string{h.Value} | |
174 | } else { | |
175 | req.Header.Set(h.Name, h.Value) | |
176 | } | |
177 | } | |
178 | } else { | |
179 | for _, h := range client.headers { | |
180 | if client.noCanonicalizeHeaders { | |
181 | // https://stackoverflow.com/questions/26351716/how-to-keep-key-case-sensitive-in-request-header-using-golang | |
182 | req.Header[h.Name] = []string{h.Value} | |
183 | } else { | |
184 | req.Header.Set(h.Name, h.Value) | |
185 | } | |
186 | } | |
187 | } | |
188 | ||
189 | if opts.UpdatedBasicAuthUsername != "" { | |
190 | req.SetBasicAuth(opts.UpdatedBasicAuthUsername, opts.UpdatedBasicAuthPassword) | |
191 | } else if client.username != "" { | |
162 | 192 | req.SetBasicAuth(client.username, client.password) |
163 | 193 | } |
164 | 194 |
174 | 174 | } |
175 | 175 | close(wordChan) |
176 | 176 | workerGroup.Wait() |
177 | ||
178 | if err := scanner.Err(); err != nil { | |
179 | return err | |
180 | } | |
181 | ||
177 | 182 | return nil |
178 | 183 | } |
179 | 184 |
0 | 0 | package libgobuster |
1 | 1 | |
2 | 2 | import ( |
3 | "crypto/tls" | |
3 | 4 | "time" |
4 | 5 | ) |
5 | 6 | |
11 | 12 | Timeout time.Duration |
12 | 13 | RetryOnTimeout bool |
13 | 14 | RetryAttempts int |
15 | TLSCertificate *tls.Certificate | |
14 | 16 | } |
15 | 17 | |
16 | 18 | // HTTPOptions is the struct to pass in all http options to Gobuster |
17 | 19 | type HTTPOptions struct { |
18 | 20 | BasicHTTPOptions |
19 | Password string | |
20 | URL string | |
21 | Username string | |
22 | Cookies string | |
23 | Headers []HTTPHeader | |
24 | FollowRedirect bool | |
25 | Method string | |
21 | Password string | |
22 | URL string | |
23 | Username string | |
24 | Cookies string | |
25 | Headers []HTTPHeader | |
26 | NoCanonicalizeHeaders bool | |
27 | FollowRedirect bool | |
28 | Method string | |
26 | 29 | } |