Codebase list gobuster / 5573a7c
New upstream version 2.0.0 Sophie Brun 5 years ago
20 changed file(s) with 1673 addition(s) and 835 deletion(s). Raw diff Collapse all Expand all
2121 *.exe
2222 *.test
2323 *.prof
24 gobuster
2524 *.txt
2625 *.swp
26
27 gobuster
0 language: go
1
2 go:
3 - "1.x"
4 - "1.8"
5 - "1.10.x"
6 - master
0 Gobuster v1.2 (OJ Reeves @TheColonial)
1 ======================================
0 Gobuster v2.0.0 (OJ Reeves @TheColonial)
1 ========================================
22
33 Gobuster is a tool used to brute-force:
44
55 * URIs (directories and files) in web sites.
66 * DNS subdomains (with wildcard support).
7
8 ### Travis CI Status
9
10 Because all the cool kids are doing it: [![Build Status](https://travis-ci.com/OJ/gobuster.svg?branch=master)](https://travis-ci.com/OJ/gobuster)
711
812 ### Oh dear God.. WHY!?
913
2832
2933 ### Common Command line options
3034
31 * `-m <mode>` - which mode to use, either `dir` or `dns` (default: `dir`)
35 * `-fw` - force processing of a domain with wildcard results.
36 * `-np` - hide the progress output.
37 * `-m <mode>` - which mode to use, either `dir` or `dns` (default: `dir`).
3238 * `-q` - disables banner/underline output.
3339 * `-t <threads>` - number of threads to run (default: `10`).
3440 * `-u <url/domain>` - full URL (including scheme), or base domain name.
3541 * `-v` - verbose output (show all results).
36 * `-w <wordlist>` - path to the wordlist used for brute forcing.
42 * `-w <wordlist>` - path to the wordlist used for brute forcing (use `-` for stdin).
3743
3844 ### Command line options for `dns` mode
3945
40 * `-fw` - Force processing of a domain with wildcard DNS.
46 * `-cn` - show CNAME records (cannot be used with '-i' option).
4147 * `-i` - show all IP addresses for the result.
4248
4349 ### Command line options for `dir` mode
4450
45 * `-a <user agent string>` - specify a user agent string to send in the request header
51 * `-a <user agent string>` - specify a user agent string to send in the request header.
4652 * `-c <http cookies>` - use this to specify any cookies that you might need (simulating auth).
4753 * `-e` - specify extended mode that renders the full URL.
4854 * `-f` - append `/` for directory brute forces.
55 * `-k` - Skip verification of SSL certificates.
4956 * `-l` - show the length of the response.
5057 * `-n` - "no status" mode, disables the output of the result's status code.
51 * `-p <proxy url>` - specify a proxy to use for all requests (scheme much match the URL scheme)
58 * `-o <file>` - specify a file name to write the output to.
59 * `-p <proxy url>` - specify a proxy to use for all requests (scheme much match the URL scheme).
5260 * `-r` - follow redirects.
5361 * `-s <status codes>` - comma-separated set of the list of status codes to be deemed a "positive" (default: `200,204,301,302,307`).
5462 * `-x <extensions>` - list of extensions to check for, if any.
5563 * `-P <password>` - HTTP Authorization password (Basic Auth only, prompted if missing).
5664 * `-U <username>` - HTTP Authorization username (Basic Auth only).
65 * `-to <timeout>` - HTTP timeout. Examples: 10s, 100ms, 1m (default: 10s).
5766
5867 ### Building
5968
7180
7281 #### Running as a script
7382 ```
74 gobuster$ go run main.go <parameters>
83 gobuster $ go run main.go <parameters>
7584 ```
7685
7786 ### Wordlists via STDIN
78 Wordlists can be piped into `gobuster` via stdin:
79 ```
80 hashcat -a 3 --stdout ?l | gobuster -u https://mysite.com
87 Wordlists can be piped into `gobuster` via stdin by providing a `-` to the `-w` option:
88 ```
89 hashcat -a 3 --stdout ?l | gobuster -u https://mysite.com -w -
8190 ```
8291 Note: If the `-w` option is specified at the same time as piping from STDIN, an error will be shown and the program will terminate.
8392
91100 ```
92101 Default options looks like this:
93102 ```
94 $ gobuster -u http://buffered.io/ -w words.txt
95
96 Gobuster v1.2 OJ Reeves (@TheColonial)
103 $ gobuster -u https://buffered.io -w ~/wordlists/shortlist.txt
104
105 =====================================================
106 Gobuster v2.0.0 OJ Reeves (@TheColonial)
97107 =====================================================
98108 [+] Mode : dir
99 [+] Url/Domain : http://buffered.io/
100 [+] Threads : 10
101 [+] Wordlist : words.txt
102 [+] Status codes : 200,204,301,302,307
103 =====================================================
109 [+] Url/Domain : https://buffered.io/
110 [+] Threads : 10
111 [+] Wordlist : /home/oj/wordlists/shortlist.txt
112 [+] Status codes : 200,204,301,302,307,403
113 [+] Timeout : 10s
114 =====================================================
115 2018/08/27 11:49:43 Starting gobuster
116 =====================================================
117 /categories (Status: 301)
118 /contact (Status: 301)
119 /posts (Status: 301)
104120 /index (Status: 200)
105 /posts (Status: 301)
106 /contact (Status: 301)
121 =====================================================
122 2018/08/27 11:49:44 Finished
107123 =====================================================
108124 ```
109125 Default options with status codes disabled looks like this:
110126 ```
111 $ gobuster -u http://buffered.io/ -w words.txt -n
112
113 Gobuster v1.2 OJ Reeves (@TheColonial)
127 $ gobuster -u https://buffered.io -w ~/wordlists/shortlist.txt -n
128
129 =====================================================
130 Gobuster v2.0.0 OJ Reeves (@TheColonial)
114131 =====================================================
115132 [+] Mode : dir
116 [+] Url/Domain : http://buffered.io/
117 [+] Threads : 10
118 [+] Wordlist : words.txt
119 [+] Status codes : 200,204,301,302,307
133 [+] Url/Domain : https://buffered.io/
134 [+] Threads : 10
135 [+] Wordlist : /home/oj/wordlists/shortlist.txt
136 [+] Status codes : 200,204,301,302,307,403
120137 [+] No status : true
121 =====================================================
138 [+] Timeout : 10s
139 =====================================================
140 2018/08/27 11:50:18 Starting gobuster
141 =====================================================
142 /categories
143 /contact
122144 /index
123145 /posts
124 /contact
146 =====================================================
147 2018/08/27 11:50:18 Finished
125148 =====================================================
126149 ```
127150 Verbose output looks like this:
128151 ```
129 $ gobuster -u http://buffered.io/ -w words.txt -v
130
131 Gobuster v1.2 OJ Reeves (@TheColonial)
152 $ gobuster -u https://buffered.io -w ~/wordlists/shortlist.txt -v
153
154 =====================================================
155 Gobuster v2.0.0 OJ Reeves (@TheColonial)
132156 =====================================================
133157 [+] Mode : dir
134 [+] Url/Domain : http://buffered.io/
135 [+] Threads : 10
136 [+] Wordlist : words.txt
137 [+] Status codes : 200,204,301,302,307
158 [+] Url/Domain : https://buffered.io/
159 [+] Threads : 10
160 [+] Wordlist : /home/oj/wordlists/shortlist.txt
161 [+] Status codes : 200,204,301,302,307,403
138162 [+] Verbose : true
139 =====================================================
140 Found : /index (Status: 200)
141 Missed: /derp (Status: 404)
142 Found : /posts (Status: 301)
143 Found : /contact (Status: 301)
163 [+] Timeout : 10s
164 =====================================================
165 2018/08/27 11:50:51 Starting gobuster
166 =====================================================
167 Missed: /alsodoesnotexist (Status: 404)
168 Found: /index (Status: 200)
169 Missed: /doesnotexist (Status: 404)
170 Found: /categories (Status: 301)
171 Found: /posts (Status: 301)
172 Found: /contact (Status: 301)
173 =====================================================
174 2018/08/27 11:50:51 Finished
144175 =====================================================
145176 ```
146177 Example showing content length:
147178 ```
148 $ gobuster -u http://buffered.io/ -w words.txt -l
149
150 Gobuster v1.2 OJ Reeves (@TheColonial)
179 $ gobuster -u https://buffered.io -w ~/wordlists/shortlist.txt -l
180
181 =====================================================
182 Gobuster v2.0.0 OJ Reeves (@TheColonial)
151183 =====================================================
152184 [+] Mode : dir
153 [+] Url/Domain : http://buffered.io/
154 [+] Threads : 10
155 [+] Wordlist : /tmp/words
156 [+] Status codes : 301,302,307,200,204
185 [+] Url/Domain : https://buffered.io/
186 [+] Threads : 10
187 [+] Wordlist : /home/oj/wordlists/shortlist.txt
188 [+] Status codes : 200,204,301,302,307,403
157189 [+] Show length : true
158 =====================================================
159 /contact (Status: 301)
160 /posts (Status: 301)
161 /index (Status: 200) [Size: 61481]
190 [+] Timeout : 10s
191 =====================================================
192 2018/08/27 11:51:16 Starting gobuster
193 =====================================================
194 /categories (Status: 301) [Size: 178]
195 /posts (Status: 301) [Size: 178]
196 /contact (Status: 301) [Size: 178]
197 /index (Status: 200) [Size: 51759]
198 =====================================================
199 2018/08/27 11:51:17 Finished
162200 =====================================================
163201 ```
164202 Quiet output, with status disabled and expanded mode looks like this ("grep mode"):
165203 ```
166 $ gobuster -u http://buffered.io/ -w words.txt -q -n -e
167 http://buffered.io/posts
168 http://buffered.io/contact
169 http://buffered.io/index
204 $ gobuster -u https://buffered.io -w ~/wordlists/shortlist.txt -q -n -e
205 https://buffered.io/index
206 https://buffered.io/contact
207 https://buffered.io/posts
208 https://buffered.io/categories
170209 ```
171210
172211 #### `dns` mode
177216 ```
178217 Normal sample run goes like this:
179218 ```
180 $ gobuster -m dns -w subdomains.txt -u google.com
181
182 Gobuster v1.2 OJ Reeves (@TheColonial)
219 $ gobuster -m dns -w ~/wordlists/subdomains.txt -u google.com
220
221 =====================================================
222 Gobuster v2.0.0 OJ Reeves (@TheColonial)
183223 =====================================================
184224 [+] Mode : dns
185225 [+] Url/Domain : google.com
186226 [+] Threads : 10
187 [+] Wordlist : subdomains.txt
188 =====================================================
189 Found: m.google.com
190 Found: admin.google.com
191 Found: mobile.google.com
192 Found: www.google.com
193 Found: search.google.com
227 [+] Wordlist : /home/oj/wordlists/subdomains.txt
228 =====================================================
229 2018/08/27 11:54:20 Starting gobuster
230 =====================================================
194231 Found: chrome.google.com
195232 Found: ns1.google.com
196 Found: store.google.com
197 Found: wap.google.com
233 Found: admin.google.com
234 Found: www.google.com
235 Found: m.google.com
198236 Found: support.google.com
199 Found: directory.google.com
200237 Found: translate.google.com
238 Found: cse.google.com
201239 Found: news.google.com
202240 Found: music.google.com
203241 Found: mail.google.com
242 Found: store.google.com
243 Found: mobile.google.com
244 Found: search.google.com
245 Found: wap.google.com
246 Found: directory.google.com
247 Found: local.google.com
204248 Found: blog.google.com
205 Found: cse.google.com
206 Found: local.google.com
249 =====================================================
250 2018/08/27 11:54:20 Finished
207251 =====================================================
208252 ```
209253 Show IP sample run goes like this:
210254 ```
211 $ gobuster -m dns -w subdomains.txt -u google.com -i
212
213 Gobuster v1.2 OJ Reeves (@TheColonial)
255 $ gobuster -m dns -w ~/wordlists/subdomains.txt -u google.com -i
256
257 =====================================================
258 Gobuster v2.0.0 OJ Reeves (@TheColonial)
214259 =====================================================
215260 [+] Mode : dns
216261 [+] Url/Domain : google.com
217262 [+] Threads : 10
218 [+] Wordlist : subdomains.txt
219 [+] Verbose : true
220 =====================================================
221 Found: chrome.google.com [2404:6800:4006:801::200e, 216.58.220.110]
222 Found: m.google.com [216.58.220.107, 2404:6800:4006:801::200b]
223 Found: www.google.com [74.125.237.179, 74.125.237.177, 74.125.237.178, 74.125.237.180, 74.125.237.176, 2404:6800:4006:801::2004]
224 Found: search.google.com [2404:6800:4006:801::200e, 216.58.220.110]
225 Found: admin.google.com [216.58.220.110, 2404:6800:4006:801::200e]
226 Found: store.google.com [216.58.220.110, 2404:6800:4006:801::200e]
227 Found: mobile.google.com [216.58.220.107, 2404:6800:4006:801::200b]
228 Found: ns1.google.com [216.239.32.10]
229 Found: directory.google.com [216.58.220.110, 2404:6800:4006:801::200e]
230 Found: translate.google.com [216.58.220.110, 2404:6800:4006:801::200e]
231 Found: cse.google.com [216.58.220.110, 2404:6800:4006:801::200e]
232 Found: local.google.com [2404:6800:4006:801::200e, 216.58.220.110]
233 Found: music.google.com [2404:6800:4006:801::200e, 216.58.220.110]
234 Found: wap.google.com [216.58.220.110, 2404:6800:4006:801::200e]
235 Found: blog.google.com [216.58.220.105, 2404:6800:4006:801::2009]
236 Found: support.google.com [216.58.220.110, 2404:6800:4006:801::200e]
237 Found: news.google.com [216.58.220.110, 2404:6800:4006:801::200e]
238 Found: mail.google.com [216.58.220.101, 2404:6800:4006:801::2005]
263 [+] Wordlist : /home/oj/wordlists/subdomains.txt
264 =====================================================
265 2018/08/27 11:54:54 Starting gobuster
266 =====================================================
267 Found: www.google.com [172.217.25.36, 2404:6800:4006:802::2004]
268 Found: admin.google.com [172.217.25.46, 2404:6800:4006:806::200e]
269 Found: store.google.com [172.217.167.78, 2404:6800:4006:802::200e]
270 Found: mobile.google.com [172.217.25.43, 2404:6800:4006:802::200b]
271 Found: ns1.google.com [216.239.32.10, 2001:4860:4802:32::a]
272 Found: m.google.com [172.217.25.43, 2404:6800:4006:802::200b]
273 Found: cse.google.com [172.217.25.46, 2404:6800:4006:80a::200e]
274 Found: chrome.google.com [172.217.25.46, 2404:6800:4006:802::200e]
275 Found: search.google.com [172.217.25.46, 2404:6800:4006:802::200e]
276 Found: local.google.com [172.217.25.46, 2404:6800:4006:80a::200e]
277 Found: news.google.com [172.217.25.46, 2404:6800:4006:802::200e]
278 Found: blog.google.com [216.58.199.73, 2404:6800:4006:806::2009]
279 Found: support.google.com [172.217.25.46, 2404:6800:4006:802::200e]
280 Found: wap.google.com [172.217.25.46, 2404:6800:4006:802::200e]
281 Found: directory.google.com [172.217.25.46, 2404:6800:4006:802::200e]
282 Found: translate.google.com [172.217.25.46, 2404:6800:4006:802::200e]
283 Found: music.google.com [172.217.25.46, 2404:6800:4006:802::200e]
284 Found: mail.google.com [172.217.25.37, 2404:6800:4006:802::2005]
285 =====================================================
286 2018/08/27 11:54:55 Finished
239287 =====================================================
240288 ```
241289 Base domain validation warning when the base domain fails to resolve. This is a warning rather than a failure in case the user fat-fingers while typing the domain.
242290 ```
243 $ gobuster -m dns -w subdomains.txt -u yp.to -i
244
245 Gobuster v1.2 OJ Reeves (@TheColonial)
291 $ gobuster -m dns -w ~/wordlists/subdomains.txt -u yp.to -i
292
293 =====================================================
294 Gobuster v2.0.0 OJ Reeves (@TheColonial)
246295 =====================================================
247296 [+] Mode : dns
248297 [+] Url/Domain : yp.to
249298 [+] Threads : 10
250 [+] Wordlist : /tmp/test.txt
251 =====================================================
252 [-] Unable to validate base domain: yp.to
253 Found: cr.yp.to [131.155.70.11, 131.155.70.13]
299 [+] Wordlist : /home/oj/wordlists/subdomains.txt
300 =====================================================
301 2018/08/27 11:56:43 Starting gobuster
302 =====================================================
303 2018/08/27 11:56:53 [-] Unable to validate base domain: yp.to
304 Found: cr.yp.to [131.193.32.108, 131.193.32.109]
305 =====================================================
306 2018/08/27 11:56:53 Finished
254307 =====================================================
255308 ```
256309 Wildcard DNS is also detected properly:
257310 ```
258 $ gobuster -w subdomainsbig.txt -u doesntexist.com -m dns
259
260 Gobuster v1.2 OJ Reeves (@TheColonial)
261 =====================================================
262 [+] Mode : dns
263 [+] Url/Domain : doesntexist.com
264 [+] Threads : 10
265 [+] Wordlist : subdomainsbig.txt
266 =====================================================
267 [-] Wildcard DNS found. IP address(es): 123.123.123.123
268 [-] To force processing of Wildcard DNS, specify the '-fw' switch.
311 $ gobuster -m dns -w ~/wordlists/subdomains.txt -u 0.0.1.xip.io
312
313 =====================================================
314 Gobuster v2.0.0 OJ Reeves (@TheColonial)
315 =====================================================
316 [+] Mode : dns
317 [+] Url/Domain : 0.0.1.xip.io
318 [+] Threads : 10
319 [+] Wordlist : /home/oj/wordlists/subdomains.txt
320 =====================================================
321 2018/08/27 12:13:48 Starting gobuster
322 =====================================================
323 2018/08/27 12:13:48 [-] Wildcard DNS found. IP address(es): 1.0.0.0
324 2018/08/27 12:13:48 [!] To force processing of Wildcard DNS, specify the '-fw' switch.
325 =====================================================
326 2018/08/27 12:13:48 Finished
269327 =====================================================
270328 ```
271329 If the user wants to force processing of a domain that has wildcard entries, use `-fw`:
272330 ```
273 $ gobuster -w subdomainsbig.txt -u doesntexist.com -m dns -fw
274
275 Gobuster v1.2 OJ Reeves (@TheColonial)
276 =====================================================
277 [+] Mode : dns
278 [+] Url/Domain : doesntexist.com
279 [+] Threads : 10
280 [+] Wordlist : subdomainsbig.txt
281 =====================================================
282 [-] Wildcard DNS found. IP address(es): 123.123.123.123
283 Found: email.doesntexist.com
284 ^C[!] Keyboard interrupt detected, terminating.
285 =====================================================
331 $ gobuster -m dns -w ~/wordlists/subdomains.txt -u 0.0.1.xip.io -fw
332
333 =====================================================
334 Gobuster v2.0.0 OJ Reeves (@TheColonial)
335 =====================================================
336 [+] Mode : dns
337 [+] Url/Domain : 0.0.1.xip.io
338 [+] Threads : 10
339 [+] Wordlist : /home/oj/wordlists/subdomains.txt
340 =====================================================
341 2018/08/27 12:13:51 Starting gobuster
342 =====================================================
343 2018/08/27 12:13:51 [-] Wildcard DNS found. IP address(es): 1.0.0.0
344 Found: 127.0.0.1.xip.io
345 Found: test.127.0.0.1.xip.io
346 =====================================================
347 2018/08/27 12:13:53 Finished
348 =====================================================
349 ```
286350
287351 ### License
288352
00 @0x42424242 - initial DNS support
1 @0xdevalias - Refactoring of code, and lots of other stuff
2 @FireFart - Supporting connection reuse, resulting in been speed ups, refactoring, progress output and more
3 @Ne0nd0g - STDIN support for wordlists
4 @UID1K - initial DNS wildcard check support
15 @averagesecurityguy - quiet mode support
6 @eur0pa - Compiler error fixes for updated dependencies
27 @g0tmi1k - content length, wordlist and command line parsing fixes
8 @gehaxelt - DIR mode UUID wildcard detection
9 @ilyaglow - Refactoring and tidying of code
310 @justinsteven - HTTP basic auth support
411 @kevinnz - custom user agent support
12 @knapsy - saving output to file, and CNAME resolution for DNS mode
13 @rverton - CLI flag to skip SSL verification
514 @viaMorgoth - base domain validation for DNS mode
6 @UID1K - initial DNS wildcard check support
7 @Ne0nd0g - STDIN support for wordlists
0 * return specific errors and do not mention command line switches in libgobuster
1 * no log.Printf and fmt.Printf inside libgobuster
2 * use smth like `tabwriter.NewWriter(bw, 0, 5, 3, ' ', 0)` for outputting options (`GetConfigString`)
0 package gobusterdir
1
2 import (
3 "bytes"
4 "fmt"
5 "log"
6
7 "github.com/OJ/gobuster/libgobuster"
8 "github.com/google/uuid"
9 )
10
11 // GobusterDir is the main type to implement the interface
12 type GobusterDir struct{}
13
14 // Setup is the setup implementation of gobusterdir
15 func (d GobusterDir) Setup(g *libgobuster.Gobuster) error {
16 _, _, err := g.GetRequest(g.Opts.URL)
17 if err != nil {
18 return fmt.Errorf("unable to connect to %s: %v", g.Opts.URL, err)
19 }
20
21 guid := uuid.New()
22 url := fmt.Sprintf("%s%s", g.Opts.URL, guid)
23 wildcardResp, _, err := g.GetRequest(url)
24
25 if err != nil {
26 return err
27 }
28
29 if g.Opts.StatusCodesParsed.Contains(*wildcardResp) {
30 g.IsWildcard = true
31 log.Printf("[-] Wildcard response found: %s => %d", url, *wildcardResp)
32 if !g.Opts.WildcardForced {
33 return fmt.Errorf("To force processing of Wildcard responses, specify the '-fw' switch.")
34 }
35 }
36
37 return nil
38 }
39
40 // Process is the process implementation of gobusterdir
41 func (d GobusterDir) Process(g *libgobuster.Gobuster, word string) ([]libgobuster.Result, error) {
42 suffix := ""
43 if g.Opts.UseSlash {
44 suffix = "/"
45 }
46
47 // Try the DIR first
48 url := fmt.Sprintf("%s%s%s", g.Opts.URL, word, suffix)
49 dirResp, dirSize, err := g.GetRequest(url)
50 if err != nil {
51 return nil, err
52 }
53 var ret []libgobuster.Result
54 if dirResp != nil {
55 ret = append(ret, libgobuster.Result{
56 Entity: fmt.Sprintf("%s%s", word, suffix),
57 Status: *dirResp,
58 Size: dirSize,
59 })
60 }
61
62 // Follow up with files using each ext.
63 for ext := range g.Opts.ExtensionsParsed.Set {
64 file := fmt.Sprintf("%s.%s", word, ext)
65 url = fmt.Sprintf("%s%s", g.Opts.URL, file)
66 fileResp, fileSize, err := g.GetRequest(url)
67 if err != nil {
68 return nil, err
69 }
70
71 if fileResp != nil {
72 ret = append(ret, libgobuster.Result{
73 Entity: file,
74 Status: *fileResp,
75 Size: fileSize,
76 })
77 }
78 }
79 return ret, nil
80 }
81
82 // ResultToString is the to string implementation of gobusterdir
83 func (d GobusterDir) ResultToString(g *libgobuster.Gobuster, r *libgobuster.Result) (*string, error) {
84 buf := &bytes.Buffer{}
85
86 // Prefix if we're in verbose mode
87 if g.Opts.Verbose {
88 if g.Opts.StatusCodesParsed.Contains(r.Status) {
89 if _, err := fmt.Fprintf(buf, "Found: "); err != nil {
90 return nil, err
91 }
92 } else {
93 if _, err := fmt.Fprintf(buf, "Missed: "); err != nil {
94 return nil, err
95 }
96 }
97 }
98
99 if g.Opts.StatusCodesParsed.Contains(r.Status) || g.Opts.Verbose {
100 if g.Opts.Expanded {
101 if _, err := fmt.Fprintf(buf, g.Opts.URL); err != nil {
102 return nil, err
103 }
104 } else {
105 if _, err := fmt.Fprintf(buf, "/"); err != nil {
106 return nil, err
107 }
108 }
109 if _, err := fmt.Fprintf(buf, r.Entity); err != nil {
110 return nil, err
111 }
112
113 if !g.Opts.NoStatus {
114 if _, err := fmt.Fprintf(buf, " (Status: %d)", r.Status); err != nil {
115 return nil, err
116 }
117 }
118
119 if r.Size != nil {
120 if _, err := fmt.Fprintf(buf, " [Size: %d]", *r.Size); err != nil {
121 return nil, err
122 }
123 }
124 if _, err := fmt.Fprintf(buf, "\n"); err != nil {
125 return nil, err
126 }
127 }
128 s := buf.String()
129 return &s, nil
130 }
0 package gobusterdns
1
2 import (
3 "bytes"
4 "fmt"
5 "log"
6 "net"
7 "strings"
8
9 "github.com/OJ/gobuster/libgobuster"
10 "github.com/google/uuid"
11 )
12
13 // GobusterDNS is the main type to implement the interface
14 type GobusterDNS struct{}
15
16 // Setup is the setup implementation of gobusterdns
17 func (d GobusterDNS) Setup(g *libgobuster.Gobuster) error {
18 // Resolve a subdomain sthat probably shouldn't exist
19 guid := uuid.New()
20 wildcardIps, err := net.LookupHost(fmt.Sprintf("%s.%s", guid, g.Opts.URL))
21 if err == nil {
22 g.IsWildcard = true
23 g.WildcardIps.AddRange(wildcardIps)
24 log.Printf("[-] Wildcard DNS found. IP address(es): %s", g.WildcardIps.Stringify())
25 if !g.Opts.WildcardForced {
26 return fmt.Errorf("To force processing of Wildcard DNS, specify the '-fw' switch.")
27 }
28 }
29
30 if !g.Opts.Quiet {
31 // Provide a warning if the base domain doesn't resolve (in case of typo)
32 _, err = net.LookupHost(g.Opts.URL)
33 if err != nil {
34 // Not an error, just a warning. Eg. `yp.to` doesn't resolve, but `cr.py.to` does!
35 log.Printf("[-] Unable to validate base domain: %s", g.Opts.URL)
36 }
37 }
38
39 return nil
40 }
41
42 // Process is the process implementation of gobusterdns
43 func (d GobusterDNS) Process(g *libgobuster.Gobuster, word string) ([]libgobuster.Result, error) {
44 subdomain := fmt.Sprintf("%s.%s", word, g.Opts.URL)
45 ips, err := net.LookupHost(subdomain)
46 var ret []libgobuster.Result
47 if err == nil {
48 if !g.IsWildcard || !g.WildcardIps.ContainsAny(ips) {
49 result := libgobuster.Result{
50 Entity: subdomain,
51 }
52 if g.Opts.ShowIPs {
53 result.Extra = strings.Join(ips, ", ")
54 } else if g.Opts.ShowCNAME {
55 cname, err := net.LookupCNAME(subdomain)
56 if err == nil {
57 result.Extra = cname
58 }
59 }
60 ret = append(ret, result)
61 }
62 } else if g.Opts.Verbose {
63 ret = append(ret, libgobuster.Result{
64 Entity: subdomain,
65 Status: 404,
66 })
67 }
68 return ret, nil
69 }
70
71 // ResultToString is the to string implementation of gobusterdns
72 func (d GobusterDNS) ResultToString(g *libgobuster.Gobuster, r *libgobuster.Result) (*string, error) {
73 buf := &bytes.Buffer{}
74
75 if r.Status == 404 {
76 if _, err := fmt.Fprintf(buf, "Missing: %s\n", r.Entity); err != nil {
77 return nil, err
78 }
79 } else if g.Opts.ShowIPs {
80 if _, err := fmt.Fprintf(buf, "Found: %s [%s]\n", r.Entity, r.Extra); err != nil {
81 return nil, err
82 }
83 } else if g.Opts.ShowCNAME {
84 if _, err := fmt.Fprintf(buf, "Found: %s [%s]\n", r.Entity, r.Extra); err != nil {
85 return nil, err
86 }
87 } else {
88 if _, err := fmt.Fprintf(buf, "Found: %s\n", r.Entity); err != nil {
89 return nil, err
90 }
91 }
92
93 s := buf.String()
94 return &s, nil
95 }
0 package libgobuster
1
2 import (
3 "bytes"
4 "fmt"
5 "io"
6 "sort"
7 "strings"
8 )
9
10 type intSet struct {
11 Set map[int]bool
12 }
13
14 type stringSet struct {
15 Set map[string]bool
16 }
17
18 func newStringSet() stringSet {
19 return stringSet{Set: map[string]bool{}}
20 }
21
22 // Add an element to a set
23 func (set *stringSet) Add(s string) bool {
24 _, found := set.Set[s]
25 set.Set[s] = true
26 return !found
27 }
28
29 // Add a list of elements to a set
30 func (set *stringSet) AddRange(ss []string) {
31 for _, s := range ss {
32 set.Set[s] = true
33 }
34 }
35
36 // Test if an element is in a set
37 func (set *stringSet) Contains(s string) bool {
38 _, found := set.Set[s]
39 return found
40 }
41
42 // Check if any of the elements exist
43 func (set *stringSet) ContainsAny(ss []string) bool {
44 for _, s := range ss {
45 if set.Set[s] {
46 return true
47 }
48 }
49 return false
50 }
51
52 // Stringify the set
53 func (set *stringSet) Stringify() string {
54 values := []string{}
55 for s := range set.Set {
56 values = append(values, s)
57 }
58 return strings.Join(values, ",")
59 }
60
61 func newIntSet() intSet {
62 return intSet{Set: map[int]bool{}}
63 }
64
65 // Add an element to a set
66 func (set *intSet) Add(i int) bool {
67 _, found := set.Set[i]
68 set.Set[i] = true
69 return !found
70 }
71
72 // Test if an element is in a set
73 func (set *intSet) Contains(i int) bool {
74 _, found := set.Set[i]
75 return found
76 }
77
78 // Stringify the set
79 func (set *intSet) Stringify() string {
80 values := []int{}
81 for s := range set.Set {
82 values = append(values, s)
83 }
84 sort.Ints(values)
85
86 delim := ","
87 return strings.Trim(strings.Join(strings.Fields(fmt.Sprint(values)), delim), "[]")
88 }
89
90 func lineCounter(r io.Reader) (int, error) {
91 buf := make([]byte, 32*1024)
92 count := 1
93 lineSep := []byte{'\n'}
94
95 for {
96 c, err := r.Read(buf)
97 count += bytes.Count(buf[:c], lineSep)
98
99 switch {
100 case err == io.EOF:
101 return count, nil
102
103 case err != nil:
104 return count, err
105 }
106 }
107 }
0 package libgobuster
1
2 import (
3 "strings"
4 "testing"
5 "testing/iotest"
6 )
7
8 func TestNewStringSet(t *testing.T) {
9 if newStringSet().Set == nil {
10 t.Fatal("newStringSet returned nil Set")
11 }
12 }
13
14 func TestNewIntSet(t *testing.T) {
15 if newIntSet().Set == nil {
16 t.Fatal("newIntSet returned nil Set")
17 }
18 }
19
20 func TestStringSetAdd(t *testing.T) {
21 x := newStringSet()
22 x.Add("test")
23 if len(x.Set) != 1 {
24 t.Fatalf("Unexptected size. Should have 1 Got %v", len(x.Set))
25 }
26 }
27
28 func TestStringSetAddDouble(t *testing.T) {
29 x := newStringSet()
30 x.Add("test")
31 x.Add("test")
32 if len(x.Set) != 1 {
33 t.Fatalf("Unexptected size. Should have 1 Got %d", len(x.Set))
34 }
35 }
36
37 func TestStringSetAddRange(t *testing.T) {
38 x := newStringSet()
39 x.AddRange([]string{"asdf", "ghjk"})
40 if len(x.Set) != 2 {
41 t.Fatalf("Unexptected size. Should have 2 Got %d", len(x.Set))
42 }
43 }
44
45 func TestStringSetAddRangeDouble(t *testing.T) {
46 x := newStringSet()
47 x.AddRange([]string{"asdf", "ghjk", "asdf", "ghjk"})
48 if len(x.Set) != 2 {
49 t.Fatalf("Unexptected size. Should have 2 Got %d", len(x.Set))
50 }
51 }
52
53 func TestStringSetContains(t *testing.T) {
54 x := newStringSet()
55 v := []string{"asdf", "ghjk", "1234", "5678"}
56 x.AddRange(v)
57 for _, y := range v {
58 if !x.Contains(y) {
59 t.Fatalf("Did not find value %s in array. %v", y, x.Set)
60 }
61 }
62 }
63
64 func TestStringSetContainsAny(t *testing.T) {
65 x := newStringSet()
66 v := []string{"asdf", "ghjk", "1234", "5678"}
67 x.AddRange(v)
68 if !x.ContainsAny(v) {
69 t.Fatalf("Did not find any")
70 }
71
72 // test not found
73 if x.ContainsAny([]string{"mmmm", "nnnnn"}) {
74 t.Fatal("Found unexpected values")
75 }
76 }
77
78 func TestStringSetStringify(t *testing.T) {
79 x := newStringSet()
80 v := []string{"asdf", "ghjk", "1234", "5678"}
81 x.AddRange(v)
82 z := x.Stringify()
83 // order is random
84 for _, y := range v {
85 if !strings.Contains(z, y) {
86 t.Fatalf("Did not find value %q in %q", y, z)
87 }
88 }
89 }
90
91 func TestIntSetAdd(t *testing.T) {
92 x := newIntSet()
93 x.Add(1)
94 if len(x.Set) != 1 {
95 t.Fatalf("Unexptected size. Should have 1 Got %d", len(x.Set))
96 }
97 }
98
99 func TestIntSetAddDouble(t *testing.T) {
100 x := newIntSet()
101 x.Add(1)
102 x.Add(1)
103 if len(x.Set) != 1 {
104 t.Fatalf("Unexptected size. Should have 1 Got %d", len(x.Set))
105 }
106 }
107
108 func TestIntSetContains(t *testing.T) {
109 x := newIntSet()
110 v := []int{1, 2, 3, 4}
111 for _, y := range v {
112 x.Add(y)
113 }
114 for _, y := range v {
115 if !x.Contains(y) {
116 t.Fatalf("Did not find value %d in array. %v", y, x.Set)
117 }
118 }
119 }
120
121 func TestIntSetStringify(t *testing.T) {
122 x := newIntSet()
123 v := []int{1, 3, 2, 4}
124 expected := "1,2,3,4"
125 for _, y := range v {
126 x.Add(y)
127 }
128 z := x.Stringify()
129 // should be sorted
130 if expected != z {
131 t.Fatalf("Expected %q got %q", expected, z)
132 }
133 }
134
135 func TestLineCounter(t *testing.T) {
136 var tt = []struct {
137 testName string
138 s string
139 expected int
140 }{
141 {"One Line", "test", 1},
142 {"3 Lines", "TestString\nTest\n1234", 3},
143 {"Trailing newline", "TestString\nTest\n1234\n", 4},
144 {"3 Lines cr lf", "TestString\r\nTest\r\n1234", 3},
145 {"Empty", "", 1},
146 }
147 for _, x := range tt {
148 t.Run(x.testName, func(t *testing.T) {
149 r := strings.NewReader(x.s)
150 l, err := lineCounter(r)
151 if err != nil {
152 t.Fatalf("Got error: %v", err)
153 }
154 if l != x.expected {
155 t.Fatalf("wrong line count! Got %d expected %d", l, x.expected)
156 }
157 })
158 }
159 }
160
161 func TestLineCounterError(t *testing.T) {
162 r := iotest.TimeoutReader(strings.NewReader("test"))
163 _, err := lineCounter(r)
164 if err != iotest.ErrTimeout {
165 t.Fatalf("Got wrong error! %v", err)
166 }
167 }
0 package libgobuster
1
2 import (
3 "context"
4 "crypto/tls"
5 "fmt"
6 "io"
7 "io/ioutil"
8 "net/http"
9 "net/url"
10 "strings"
11 "unicode/utf8"
12 )
13
14 type httpClient struct {
15 client *http.Client
16 context context.Context
17 userAgent string
18 username string
19 password string
20 includeLength bool
21 }
22
23 // NewHTTPClient returns a new HTTPClient
24 func newHTTPClient(c context.Context, opt *Options) (*httpClient, error) {
25 var proxyURLFunc func(*http.Request) (*url.URL, error)
26 var client httpClient
27 proxyURLFunc = http.ProxyFromEnvironment
28
29 if opt == nil {
30 return nil, fmt.Errorf("options is nil")
31 }
32
33 if opt.Proxy != "" {
34 proxyURL, err := url.Parse(opt.Proxy)
35 if err != nil {
36 return nil, fmt.Errorf("proxy URL is invalid (%v)", err)
37 }
38 proxyURLFunc = http.ProxyURL(proxyURL)
39 }
40
41 var redirectFunc func(req *http.Request, via []*http.Request) error
42 if !opt.FollowRedirect {
43 redirectFunc = func(req *http.Request, via []*http.Request) error {
44 return http.ErrUseLastResponse
45 }
46 } else {
47 redirectFunc = nil
48 }
49
50 client.client = &http.Client{
51 Timeout: opt.Timeout,
52 CheckRedirect: redirectFunc,
53 Transport: &http.Transport{
54 Proxy: proxyURLFunc,
55 TLSClientConfig: &tls.Config{
56 InsecureSkipVerify: opt.InsecureSSL,
57 },
58 }}
59 client.context = c
60 client.username = opt.Username
61 client.password = opt.Password
62 client.includeLength = opt.IncludeLength
63 client.userAgent = opt.UserAgent
64 return &client, nil
65 }
66
67 // MakeRequest makes a request to the specified url
68 func (client *httpClient) makeRequest(fullURL, cookie string) (*int, *int64, error) {
69 req, err := http.NewRequest(http.MethodGet, fullURL, nil)
70
71 if err != nil {
72 return nil, nil, err
73 }
74
75 // add the context so we can easily cancel out
76 req = req.WithContext(client.context)
77
78 if cookie != "" {
79 req.Header.Set("Cookie", cookie)
80 }
81
82 ua := fmt.Sprintf("gobuster %s", VERSION)
83 if client.userAgent != "" {
84 ua = client.userAgent
85 }
86 req.Header.Set("User-Agent", ua)
87
88 if client.username != "" {
89 req.SetBasicAuth(client.username, client.password)
90 }
91
92 resp, err := client.client.Do(req)
93 if err != nil {
94 if ue, ok := err.(*url.Error); ok {
95
96 if strings.HasPrefix(ue.Err.Error(), "x509") {
97 return nil, nil, fmt.Errorf("Invalid certificate: %v", ue.Err)
98 }
99 }
100 return nil, nil, err
101 }
102
103 defer resp.Body.Close()
104
105 var length *int64
106
107 if client.includeLength {
108 length = new(int64)
109 if resp.ContentLength <= 0 {
110 body, err2 := ioutil.ReadAll(resp.Body)
111 if err2 == nil {
112 *length = int64(utf8.RuneCountInString(string(body)))
113 }
114 } else {
115 *length = resp.ContentLength
116 }
117 } else {
118 // DO NOT REMOVE!
119 // absolutely needed so golang will reuse connections!
120 _, err = io.Copy(ioutil.Discard, resp.Body)
121 if err != nil {
122 return nil, nil, err
123 }
124 }
125
126 return &resp.StatusCode, length, nil
127 }
0 package libgobuster
1
2 import (
3 "context"
4 "testing"
5
6 "github.com/h2non/gock"
7 )
8
9 func TestMakeRequest(t *testing.T) {
10 defer gock.Off()
11 gock.New("http://server.com").
12 Get("/bar").
13 Reply(200).
14 BodyString("test")
15
16 o := NewOptions()
17 c, err := newHTTPClient(context.Background(), o)
18 if err != nil {
19 t.Fatalf("Got Error: %v", err)
20 }
21 gock.InterceptClient(c.client)
22 defer gock.RestoreClient(c.client)
23 a, b, err := c.makeRequest("http://server.com/bar", "")
24 if err != nil {
25 t.Fatalf("Got Error: %v", err)
26 }
27 if *a != 200 {
28 t.Fatalf("Invalid status returned: %d", a)
29 }
30 if b != nil && *b != int64(len("test")) {
31 t.Fatalf("Invalid length returned: %d", b)
32 }
33 }
0 package libgobuster
1
2 import (
3 "bufio"
4 "bytes"
5 "context"
6 "fmt"
7 "os"
8 "strings"
9 "sync"
10 )
11
12 const (
13 // VERSION contains the current gobuster version
14 VERSION = "2.0.0"
15 )
16
17 // SetupFunc is the "setup" function prototype for implementations
18 type SetupFunc func(*Gobuster) error
19
20 // ProcessFunc is the "process" function prototype for implementations
21 type ProcessFunc func(*Gobuster, string) ([]Result, error)
22
23 // ResultToStringFunc is the "to string" function prototype for implementations
24 type ResultToStringFunc func(*Gobuster, *Result) (*string, error)
25
26 // Gobuster is the main object when creating a new run
27 type Gobuster struct {
28 Opts *Options
29 http *httpClient
30 WildcardIps stringSet
31 context context.Context
32 requestsExpected int
33 requestsIssued int
34 mu *sync.RWMutex
35 plugin GobusterPlugin
36 IsWildcard bool
37 resultChan chan Result
38 errorChan chan error
39 }
40
41 // GobusterPlugin is an interface which plugins must implement
42 type GobusterPlugin interface {
43 Setup(*Gobuster) error
44 Process(*Gobuster, string) ([]Result, error)
45 ResultToString(*Gobuster, *Result) (*string, error)
46 }
47
48 // NewGobuster returns a new Gobuster object
49 func NewGobuster(c context.Context, opts *Options, plugin GobusterPlugin) (*Gobuster, error) {
50 // validate given options
51 multiErr := opts.validate()
52 if multiErr != nil {
53 return nil, multiErr
54 }
55
56 var g Gobuster
57 g.WildcardIps = newStringSet()
58 g.context = c
59 g.Opts = opts
60 h, err := newHTTPClient(c, opts)
61 if err != nil {
62 return nil, err
63 }
64 g.http = h
65
66 g.plugin = plugin
67 g.mu = new(sync.RWMutex)
68
69 g.resultChan = make(chan Result)
70 g.errorChan = make(chan error)
71
72 return &g, nil
73 }
74
75 // Results returns a channel of Results
76 func (g *Gobuster) Results() <-chan Result {
77 return g.resultChan
78 }
79
80 // Errors returns a channel of errors
81 func (g *Gobuster) Errors() <-chan error {
82 return g.errorChan
83 }
84
85 func (g *Gobuster) incrementRequests() {
86 g.mu.Lock()
87 g.requestsIssued++
88 g.mu.Unlock()
89 }
90
91 // PrintProgress outputs the current wordlist progress to stderr
92 func (g *Gobuster) PrintProgress() {
93 if !g.Opts.Quiet && !g.Opts.NoProgress {
94 g.mu.RLock()
95 if g.Opts.Wordlist == "-" {
96 fmt.Fprintf(os.Stderr, "\rProgress: %d", g.requestsIssued)
97 // only print status if we already read in the wordlist
98 } else if g.requestsExpected > 0 {
99 fmt.Fprintf(os.Stderr, "\rProgress: %d / %d (%3.2f%%)", g.requestsIssued, g.requestsExpected, float32(g.requestsIssued)*100.0/float32(g.requestsExpected))
100 }
101 g.mu.RUnlock()
102 }
103 }
104
105 // ClearProgress removes the last status line from stderr
106 func (g *Gobuster) ClearProgress() {
107 fmt.Fprint(os.Stderr, resetTerminal())
108 }
109
110 // GetRequest issues a GET request to the target and returns
111 // the status code, length and an error
112 func (g *Gobuster) GetRequest(url string) (*int, *int64, error) {
113 g.incrementRequests()
114 return g.http.makeRequest(url, g.Opts.Cookies)
115 }
116
117 func (g *Gobuster) worker(wordChan <-chan string, wg *sync.WaitGroup) {
118 defer wg.Done()
119 for {
120 select {
121 case <-g.context.Done():
122 return
123 case word, ok := <-wordChan:
124 // worker finished
125 if !ok {
126 return
127 }
128 // Mode-specific processing
129 res, err := g.plugin.Process(g, word)
130 if err != nil {
131 // do not exit and continue
132 g.errorChan <- err
133 continue
134 } else {
135 for _, r := range res {
136 g.resultChan <- r
137 }
138 }
139 }
140 }
141 }
142
143 func (g *Gobuster) getWordlist() (*bufio.Scanner, error) {
144 if g.Opts.Wordlist == "-" {
145 // Read directly from stdin
146 return bufio.NewScanner(os.Stdin), nil
147 }
148 // Pull content from the wordlist
149 wordlist, err := os.Open(g.Opts.Wordlist)
150 if err != nil {
151 return nil, fmt.Errorf("failed to open wordlist: %v", err)
152 }
153
154 lines, err := lineCounter(wordlist)
155 if err != nil {
156 return nil, fmt.Errorf("failed to get number of lines: %v", err)
157 }
158
159 // mutiply by extensions to get the total number of requests
160 if len(g.Opts.ExtensionsParsed.Set) > 0 {
161 lines = lines + (lines * len(g.Opts.ExtensionsParsed.Set))
162 }
163 g.requestsExpected = lines
164 g.requestsIssued = 0
165
166 // rewind wordlist
167 _, err = wordlist.Seek(0, 0)
168 if err != nil {
169 return nil, fmt.Errorf("failed to rewind wordlist: %v", err)
170 }
171 return bufio.NewScanner(wordlist), nil
172 }
173
174 // Start the busting of the website with the given
175 // set of settings from the command line.
176 func (g *Gobuster) Start() error {
177 if err := g.plugin.Setup(g); err != nil {
178 return err
179 }
180
181 var workerGroup sync.WaitGroup
182 workerGroup.Add(g.Opts.Threads)
183
184 wordChan := make(chan string, g.Opts.Threads)
185
186 // Create goroutines for each of the number of threads
187 // specified.
188 for i := 0; i < g.Opts.Threads; i++ {
189 go g.worker(wordChan, &workerGroup)
190 }
191
192 scanner, err := g.getWordlist()
193 if err != nil {
194 return err
195 }
196
197 Scan:
198 for scanner.Scan() {
199 select {
200 case <-g.context.Done():
201 break Scan
202 default:
203 word := strings.TrimSpace(scanner.Text())
204 // Skip "comment" (starts with #), as well as empty lines
205 if !strings.HasPrefix(word, "#") && len(word) > 0 {
206 wordChan <- word
207 }
208 }
209 }
210 close(wordChan)
211 workerGroup.Wait()
212 close(g.resultChan)
213 close(g.errorChan)
214 return nil
215 }
216
217 // GetConfigString returns the current config as a printable string
218 func (g *Gobuster) GetConfigString() (string, error) {
219 buf := &bytes.Buffer{}
220 o := g.Opts
221 if _, err := fmt.Fprintf(buf, "[+] Mode : %s\n", o.Mode); err != nil {
222 return "", err
223 }
224 if _, err := fmt.Fprintf(buf, "[+] Url/Domain : %s\n", o.URL); err != nil {
225 return "", err
226 }
227 if _, err := fmt.Fprintf(buf, "[+] Threads : %d\n", o.Threads); err != nil {
228 return "", err
229 }
230
231 wordlist := "stdin (pipe)"
232 if o.Wordlist != "-" {
233 wordlist = o.Wordlist
234 }
235 if _, err := fmt.Fprintf(buf, "[+] Wordlist : %s\n", wordlist); err != nil {
236 return "", err
237 }
238
239 if o.Mode == ModeDir {
240 if _, err := fmt.Fprintf(buf, "[+] Status codes : %s\n", o.StatusCodesParsed.Stringify()); err != nil {
241 return "", err
242 }
243
244 if o.Proxy != "" {
245 if _, err := fmt.Fprintf(buf, "[+] Proxy : %s\n", o.Proxy); err != nil {
246 return "", err
247 }
248 }
249
250 if o.Cookies != "" {
251 if _, err := fmt.Fprintf(buf, "[+] Cookies : %s\n", o.Cookies); err != nil {
252 return "", err
253 }
254 }
255
256 if o.UserAgent != "" {
257 if _, err := fmt.Fprintf(buf, "[+] User Agent : %s\n", o.UserAgent); err != nil {
258 return "", err
259 }
260 }
261
262 if o.IncludeLength {
263 if _, err := fmt.Fprintf(buf, "[+] Show length : true\n"); err != nil {
264 return "", err
265 }
266 }
267
268 if o.Username != "" {
269 if _, err := fmt.Fprintf(buf, "[+] Auth User : %s\n", o.Username); err != nil {
270 return "", err
271 }
272 }
273
274 if len(o.Extensions) > 0 {
275 if _, err := fmt.Fprintf(buf, "[+] Extensions : %s\n", o.ExtensionsParsed.Stringify()); err != nil {
276 return "", err
277 }
278 }
279
280 if o.UseSlash {
281 if _, err := fmt.Fprintf(buf, "[+] Add Slash : true\n"); err != nil {
282 return "", err
283 }
284 }
285
286 if o.FollowRedirect {
287 if _, err := fmt.Fprintf(buf, "[+] Follow Redir : true\n"); err != nil {
288 return "", err
289 }
290 }
291
292 if o.Expanded {
293 if _, err := fmt.Fprintf(buf, "[+] Expanded : true\n"); err != nil {
294 return "", err
295 }
296 }
297
298 if o.NoStatus {
299 if _, err := fmt.Fprintf(buf, "[+] No status : true\n"); err != nil {
300 return "", err
301 }
302 }
303
304 if o.Verbose {
305 if _, err := fmt.Fprintf(buf, "[+] Verbose : true\n"); err != nil {
306 return "", err
307 }
308 }
309
310 if _, err := fmt.Fprintf(buf, "[+] Timeout : %s\n", o.Timeout.String()); err != nil {
311 return "", err
312 }
313 }
314
315 return strings.TrimSpace(buf.String()), nil
316 }
0 package libgobuster
1
2 import (
3 "fmt"
4 "os"
5 "regexp"
6 "strconv"
7 "strings"
8 "time"
9
10 multierror "github.com/hashicorp/go-multierror"
11 )
12
13 const (
14 // ModeDir represents -m dir
15 ModeDir = "dir"
16 // ModeDNS represents -m dns
17 ModeDNS = "dns"
18 )
19
20 // Options helds all options that can be passed to libgobuster
21 type Options struct {
22 Extensions string
23 ExtensionsParsed stringSet
24 Mode string
25 Password string
26 StatusCodes string
27 StatusCodesParsed intSet
28 Threads int
29 URL string
30 UserAgent string
31 Username string
32 Wordlist string
33 Proxy string
34 Cookies string
35 Timeout time.Duration
36 FollowRedirect bool
37 IncludeLength bool
38 NoStatus bool
39 NoProgress bool
40 Expanded bool
41 Quiet bool
42 ShowIPs bool
43 ShowCNAME bool
44 InsecureSSL bool
45 WildcardForced bool
46 Verbose bool
47 UseSlash bool
48 }
49
50 // NewOptions returns a new initialized Options object
51 func NewOptions() *Options {
52 return &Options{
53 StatusCodesParsed: newIntSet(),
54 ExtensionsParsed: newStringSet(),
55 }
56 }
57
58 // Validate validates the given options
59 func (opt *Options) validate() *multierror.Error {
60 var errorList *multierror.Error
61
62 if strings.ToLower(opt.Mode) != ModeDir && strings.ToLower(opt.Mode) != ModeDNS {
63 errorList = multierror.Append(errorList, fmt.Errorf("Mode (-m): Invalid value: %s", opt.Mode))
64 }
65
66 if opt.Threads < 0 {
67 errorList = multierror.Append(errorList, fmt.Errorf("Threads (-t): Invalid value: %d", opt.Threads))
68 }
69
70 if opt.Wordlist == "" {
71 errorList = multierror.Append(errorList, fmt.Errorf("WordList (-w): Must be specified (use `-w -` for stdin)"))
72 } else if opt.Wordlist == "-" {
73 // STDIN
74 } else if _, err := os.Stat(opt.Wordlist); os.IsNotExist(err) {
75 errorList = multierror.Append(errorList, fmt.Errorf("Wordlist (-w): File does not exist: %s", opt.Wordlist))
76 }
77
78 if opt.URL == "" {
79 errorList = multierror.Append(errorList, fmt.Errorf("Url/Domain (-u): Must be specified"))
80 }
81
82 if opt.StatusCodes != "" {
83 if err := opt.parseStatusCodes(); err != nil {
84 errorList = multierror.Append(errorList, err)
85 }
86 }
87
88 if opt.Extensions != "" {
89 if err := opt.parseExtensions(); err != nil {
90 errorList = multierror.Append(errorList, err)
91 }
92 }
93
94 if opt.Mode == ModeDir {
95 if !strings.HasSuffix(opt.URL, "/") {
96 opt.URL = fmt.Sprintf("%s/", opt.URL)
97 }
98
99 if err := opt.validateDirMode(); err != nil {
100 errorList = multierror.Append(errorList, err)
101 }
102 }
103
104 return errorList
105 }
106
107 // ParseExtensions parses the extensions provided as a comma seperated list
108 func (opt *Options) parseExtensions() error {
109 if opt.Extensions == "" {
110 return fmt.Errorf("invalid extension string provided")
111 }
112
113 exts := strings.Split(opt.Extensions, ",")
114 for _, e := range exts {
115 e = strings.TrimSpace(e)
116 // remove leading . from extensions
117 opt.ExtensionsParsed.Add(strings.TrimPrefix(e, "."))
118 }
119 return nil
120 }
121
122 // ParseStatusCodes parses the status codes provided as a comma seperated list
123 func (opt *Options) parseStatusCodes() error {
124 if opt.StatusCodes == "" {
125 return fmt.Errorf("invalid status code string provided")
126 }
127
128 for _, c := range strings.Split(opt.StatusCodes, ",") {
129 c = strings.TrimSpace(c)
130 i, err := strconv.Atoi(c)
131 if err != nil {
132 return fmt.Errorf("invalid status code given: %s", c)
133 }
134 opt.StatusCodesParsed.Add(i)
135 }
136 return nil
137 }
138
139 func (opt *Options) validateDirMode() error {
140 // bail out if we are not in dir mode
141 if opt.Mode != ModeDir {
142 return nil
143 }
144 if !strings.HasPrefix(opt.URL, "http") {
145 // check to see if a port was specified
146 re := regexp.MustCompile(`^[^/]+:(\d+)`)
147 match := re.FindStringSubmatch(opt.URL)
148
149 if len(match) < 2 {
150 // no port, default to http on 80
151 opt.URL = fmt.Sprintf("http://%s", opt.URL)
152 } else {
153 port, err := strconv.Atoi(match[1])
154 if err != nil || (port != 80 && port != 443) {
155 return fmt.Errorf("url scheme not specified")
156 } else if port == 80 {
157 opt.URL = fmt.Sprintf("http://%s", opt.URL)
158 } else {
159 opt.URL = fmt.Sprintf("https://%s", opt.URL)
160 }
161 }
162 }
163
164 if opt.Username != "" && opt.Password == "" {
165 return fmt.Errorf("username was provided but password is missing")
166 }
167
168 return nil
169 }
0 package libgobuster
1
2 import (
3 "reflect"
4 "testing"
5 )
6
7 func TestNewOptions(t *testing.T) {
8 t.Parallel()
9
10 o := NewOptions()
11 if o.StatusCodesParsed.Set == nil {
12 t.Fatal("StatusCodesParsed not initialized")
13 }
14
15 if o.ExtensionsParsed.Set == nil {
16 t.Fatal("ExtensionsParsed not initialized")
17 }
18 }
19
20 func TestParseExtensions(t *testing.T) {
21 t.Parallel()
22
23 var tt = []struct {
24 testName string
25 Extensions string
26 expectedExtensions stringSet
27 expectedError string
28 }{
29 {"Valid extensions", "php,asp,txt", stringSet{Set: map[string]bool{"php": true, "asp": true, "txt": true}}, ""},
30 {"Spaces", "php, asp , txt", stringSet{Set: map[string]bool{"php": true, "asp": true, "txt": true}}, ""},
31 {"Double extensions", "php,asp,txt,php,asp,txt", stringSet{Set: map[string]bool{"php": true, "asp": true, "txt": true}}, ""},
32 {"Leading dot", ".php,asp,.txt", stringSet{Set: map[string]bool{"php": true, "asp": true, "txt": true}}, ""},
33 {"Empty string", "", newStringSet(), "invalid extension string provided"},
34 }
35
36 for _, x := range tt {
37 t.Run(x.testName, func(t *testing.T) {
38 o := NewOptions()
39 o.Extensions = x.Extensions
40 err := o.parseExtensions()
41 if x.expectedError != "" {
42 if err.Error() != x.expectedError {
43 t.Fatalf("Expected error %q but got %q", x.expectedError, err.Error())
44 }
45 } else if !reflect.DeepEqual(x.expectedExtensions, o.ExtensionsParsed) {
46 t.Fatalf("Expected %v but got %v", x.expectedExtensions, o.ExtensionsParsed)
47 }
48 })
49 }
50 }
51
52 func TestParseStatusCodes(t *testing.T) {
53 t.Parallel()
54
55 var tt = []struct {
56 testName string
57 stringCodes string
58 expectedCodes intSet
59 expectedError string
60 }{
61 {"Valid codes", "200,100,202", intSet{Set: map[int]bool{100: true, 200: true, 202: true}}, ""},
62 {"Spaces", "200, 100 , 202", intSet{Set: map[int]bool{100: true, 200: true, 202: true}}, ""},
63 {"Double codes", "200, 100, 202, 100", intSet{Set: map[int]bool{100: true, 200: true, 202: true}}, ""},
64 {"Invalid code", "200,AAA", newIntSet(), "invalid status code given: AAA"},
65 {"Invalid integer", "2000000000000000000000000000000", newIntSet(), "invalid status code given: 2000000000000000000000000000000"},
66 {"Empty string", "", newIntSet(), "invalid status code string provided"},
67 }
68
69 for _, x := range tt {
70 t.Run(x.testName, func(t *testing.T) {
71 o := NewOptions()
72 o.StatusCodes = x.stringCodes
73 err := o.parseStatusCodes()
74 if x.expectedError != "" {
75 if err.Error() != x.expectedError {
76 t.Fatalf("Expected error %q but got %q", x.expectedError, err.Error())
77 }
78 } else if !reflect.DeepEqual(x.expectedCodes, o.StatusCodesParsed) {
79 t.Fatalf("Expected %v but got %v", x.expectedCodes, o.StatusCodesParsed)
80 }
81 })
82 }
83 }
0 // +build !windows
1
2 package libgobuster
3
4 func resetTerminal() string {
5 return "\r\x1b[2K"
6 }
0 // +build windows
1
2 package libgobuster
3
4 func resetTerminal() string {
5 return "\r\r"
6 }
0 package libgobuster
1
2 // Result represents a single gobuster result
3 type Result struct {
4 Entity string
5 Status int
6 Extra string
7 Size *int64
8 }
9
10 // ToString converts the Result to it's textual representation
11 func (r *Result) ToString(g *Gobuster) (string, error) {
12 s, err := g.plugin.ResultToString(g, r)
13 if err != nil {
14 return "", err
15 }
16 return *s, nil
17 }
+181
-705
main.go less more
1616 //----------------------------------------------------
1717
1818 import (
19 "bufio"
20 "crypto/tls"
19 "context"
2120 "flag"
2221 "fmt"
23 "github.com/satori/go.uuid"
24 "golang.org/x/crypto/ssh/terminal"
25 "io/ioutil"
26 "net"
27 "net/http"
28 "net/url"
22 "log"
2923 "os"
3024 "os/signal"
31 "strconv"
3225 "strings"
3326 "sync"
3427 "syscall"
35 "unicode/utf8"
28 "time"
29
30 "github.com/OJ/gobuster/gobusterdir"
31 "github.com/OJ/gobuster/gobusterdns"
32 "github.com/OJ/gobuster/libgobuster"
33 "golang.org/x/crypto/ssh/terminal"
3634 )
3735
38 // A single result which comes from an individual web
39 // request.
40 type Result struct {
41 Entity string
42 Status int
43 Extra string
44 Size *int64
45 }
46
47 type PrintResultFunc func(s *State, r *Result)
48 type ProcessorFunc func(s *State, entity string, resultChan chan<- Result)
49 type SetupFunc func(s *State) bool
50
51 // Shim type for "set" containing ints
52 type IntSet struct {
53 set map[int]bool
54 }
55
56 // Shim type for "set" containing strings
57 type StringSet struct {
58 set map[string]bool
59 }
60
61 // Contains State that are read in from the command
62 // line when the program is invoked.
63 type State struct {
64 Client *http.Client
65 Cookies string
66 Expanded bool
67 Extensions []string
68 FollowRedirect bool
69 IncludeLength bool
70 Mode string
71 NoStatus bool
72 Password string
73 Printer PrintResultFunc
74 Processor ProcessorFunc
75 ProxyUrl *url.URL
76 Quiet bool
77 Setup SetupFunc
78 ShowIPs bool
79 StatusCodes IntSet
80 Threads int
81 Url string
82 UseSlash bool
83 UserAgent string
84 Username string
85 Verbose bool
86 Wordlist string
87 IsWildcard bool
88 WildcardForced bool
89 WildcardIps StringSet
90 SignalChan chan os.Signal
91 Terminate bool
92 StdIn bool
93 }
94
95 type RedirectHandler struct {
96 Transport http.RoundTripper
97 State *State
98 }
99
100 type RedirectError struct {
101 StatusCode int
102 }
103
104 // Add an element to a set
105 func (set *StringSet) Add(s string) bool {
106 _, found := set.set[s]
107 set.set[s] = true
108 return !found
109 }
110
111 // Add a list of elements to a set
112 func (set *StringSet) AddRange(ss []string) {
113 for _, s := range ss {
114 set.set[s] = true
115 }
116 }
117
118 // Test if an element is in a set
119 func (set *StringSet) Contains(s string) bool {
120 _, found := set.set[s]
121 return found
122 }
123
124 // Check if any of the elements exist
125 func (set *StringSet) ContainsAny(ss []string) bool {
126 for _, s := range ss {
127 if set.set[s] {
128 return true
129 }
130 }
131 return false
132 }
133
134 // Stringify the set
135 func (set *StringSet) Stringify() string {
136 values := []string{}
137 for s, _ := range set.set {
138 values = append(values, s)
139 }
140 return strings.Join(values, ",")
141 }
142
143 // Add an element to a set
144 func (set *IntSet) Add(i int) bool {
145 _, found := set.set[i]
146 set.set[i] = true
147 return !found
148 }
149
150 // Test if an element is in a set
151 func (set *IntSet) Contains(i int) bool {
152 _, found := set.set[i]
153 return found
154 }
155
156 // Stringify the set
157 func (set *IntSet) Stringify() string {
158 values := []string{}
159 for s, _ := range set.set {
160 values = append(values, strconv.Itoa(s))
161 }
162 return strings.Join(values, ",")
163 }
164
165 // Make a request to the given URL.
166 func MakeRequest(s *State, fullUrl, cookie string) (*int, *int64) {
167 req, err := http.NewRequest("GET", fullUrl, nil)
168
169 if err != nil {
170 return nil, nil
171 }
172
173 if cookie != "" {
174 req.Header.Set("Cookie", cookie)
175 }
176
177 if s.UserAgent != "" {
178 req.Header.Set("User-Agent", s.UserAgent)
179 }
180
181 if s.Username != "" {
182 req.SetBasicAuth(s.Username, s.Password)
183 }
184
185 resp, err := s.Client.Do(req)
186
187 if err != nil {
188 if ue, ok := err.(*url.Error); ok {
189 if re, ok := ue.Err.(*RedirectError); ok {
190 return &re.StatusCode, nil
191 }
192 }
193 return nil, nil
194 }
195
196 defer resp.Body.Close()
197
198 var length *int64 = nil
199
200 if s.IncludeLength {
201 length = new(int64)
202 if resp.ContentLength <= 0 {
203 body, err := ioutil.ReadAll(resp.Body)
204 if err == nil {
205 *length = int64(utf8.RuneCountInString(string(body)))
206 }
207 } else {
208 *length = resp.ContentLength
209 }
210 }
211
212 return &resp.StatusCode, length
213 }
214
215 // Small helper to combine URL with URI then make a
216 // request to the generated location.
217 func GoGet(s *State, url, uri, cookie string) (*int, *int64) {
218 return MakeRequest(s, url+uri, cookie)
219 }
220
221 // Parse all the command line options into a settings
222 // instance for future use.
223 func ParseCmdLine() *State {
224 var extensions string
225 var codes string
226 var proxy string
227 valid := true
228
229 s := State{
230 StatusCodes: IntSet{set: map[int]bool{}},
231 WildcardIps: StringSet{set: map[string]bool{}},
232 IsWildcard: false,
233 StdIn: false,
234 }
235
236 // Set up the variables we're interested in parsing.
237 flag.IntVar(&s.Threads, "t", 10, "Number of concurrent threads")
238 flag.StringVar(&s.Mode, "m", "dir", "Directory/File mode (dir) or DNS mode (dns)")
239 flag.StringVar(&s.Wordlist, "w", "", "Path to the wordlist")
240 flag.StringVar(&codes, "s", "200,204,301,302,307", "Positive status codes (dir mode only)")
241 flag.StringVar(&s.Url, "u", "", "The target URL or Domain")
242 flag.StringVar(&s.Cookies, "c", "", "Cookies to use for the requests (dir mode only)")
243 flag.StringVar(&s.Username, "U", "", "Username for Basic Auth (dir mode only)")
244 flag.StringVar(&s.Password, "P", "", "Password for Basic Auth (dir mode only)")
245 flag.StringVar(&extensions, "x", "", "File extension(s) to search for (dir mode only)")
246 flag.StringVar(&s.UserAgent, "a", "", "Set the User-Agent string (dir mode only)")
247 flag.StringVar(&proxy, "p", "", "Proxy to use for requests [http(s)://host:port] (dir mode only)")
248 flag.BoolVar(&s.Verbose, "v", false, "Verbose output (errors)")
249 flag.BoolVar(&s.ShowIPs, "i", false, "Show IP addresses (dns mode only)")
250 flag.BoolVar(&s.FollowRedirect, "r", false, "Follow redirects")
251 flag.BoolVar(&s.Quiet, "q", false, "Don't print the banner and other noise")
252 flag.BoolVar(&s.Expanded, "e", false, "Expanded mode, print full URLs")
253 flag.BoolVar(&s.NoStatus, "n", false, "Don't print status codes")
254 flag.BoolVar(&s.IncludeLength, "l", false, "Include the length of the body in the output (dir mode only)")
255 flag.BoolVar(&s.UseSlash, "f", false, "Append a forward-slash to each directory request (dir mode only)")
256 flag.BoolVar(&s.WildcardForced, "fw", false, "Force continued operation when wildcard found (dns mode only)")
257
258 flag.Parse()
259
260 Banner(&s)
261
262 switch strings.ToLower(s.Mode) {
263 case "dir":
264 s.Printer = PrintDirResult
265 s.Processor = ProcessDirEntry
266 s.Setup = SetupDir
267 case "dns":
268 s.Printer = PrintDnsResult
269 s.Processor = ProcessDnsEntry
270 s.Setup = SetupDns
271 default:
272 fmt.Println("[!] Mode (-m): Invalid value:", s.Mode)
273 valid = false
274 }
275
276 if s.Threads < 0 {
277 fmt.Println("[!] Threads (-t): Invalid value:", s.Threads)
278 valid = false
279 }
280
281 stdin, err := os.Stdin.Stat()
282 if err != nil {
283 fmt.Println("[!] Unable to stat stdin, falling back to wordlist file.")
284 } else if (stdin.Mode()&os.ModeCharDevice) == 0 && stdin.Size() > 0 {
285 s.StdIn = true
286 }
287
288 if !s.StdIn {
289 if s.Wordlist == "" {
290 fmt.Println("[!] WordList (-w): Must be specified")
291 valid = false
292 } else if _, err := os.Stat(s.Wordlist); os.IsNotExist(err) {
293 fmt.Println("[!] Wordlist (-w): File does not exist:", s.Wordlist)
294 valid = false
295 }
296 } else if s.Wordlist != "" {
297 fmt.Println("[!] Wordlist (-w) specified with pipe from stdin. Can't have both!")
298 valid = false
299 }
300
301 if s.Url == "" {
302 fmt.Println("[!] Url/Domain (-u): Must be specified")
303 valid = false
304 }
305
306 if s.Mode == "dir" {
307 if strings.HasSuffix(s.Url, "/") == false {
308 s.Url = s.Url + "/"
309 }
310
311 if strings.HasPrefix(s.Url, "http") == false {
312 s.Url = "http://" + s.Url
313 }
314
315 // extensions are comma separated
316 if extensions != "" {
317 s.Extensions = strings.Split(extensions, ",")
318 for i := range s.Extensions {
319 if s.Extensions[i][0] != '.' {
320 s.Extensions[i] = "." + s.Extensions[i]
36 func ruler() {
37 fmt.Println("=====================================================")
38 }
39
40 func banner() {
41 fmt.Printf("Gobuster v%s OJ Reeves (@TheColonial)\n", libgobuster.VERSION)
42 }
43
44 func resultWorker(g *libgobuster.Gobuster, filename string, wg *sync.WaitGroup) {
45 defer wg.Done()
46 var f *os.File
47 var err error
48 if filename != "" {
49 f, err = os.Create(filename)
50 if err != nil {
51 log.Fatalf("error on creating output file: %v", err)
52 }
53 }
54 for r := range g.Results() {
55 s, err := r.ToString(g)
56 if err != nil {
57 log.Fatal(err)
58 }
59 if s != "" {
60 g.ClearProgress()
61 s = strings.TrimSpace(s)
62 fmt.Println(s)
63 if f != nil {
64 err = writeToFile(f, s)
65 if err != nil {
66 log.Fatalf("error on writing output file: %v", err)
32167 }
32268 }
32369 }
324
325 // status codes are comma separated
326 if codes != "" {
327 for _, c := range strings.Split(codes, ",") {
328 i, err := strconv.Atoi(c)
329 if err != nil {
330 fmt.Println("[!] Invalid status code given: ", c)
331 valid = false
332 } else {
333 s.StatusCodes.Add(i)
334 }
70 }
71 }
72
73 func errorWorker(g *libgobuster.Gobuster, wg *sync.WaitGroup) {
74 defer wg.Done()
75 for e := range g.Errors() {
76 if !g.Opts.Quiet {
77 g.ClearProgress()
78 log.Printf("[!] %v", e)
79 }
80 }
81 }
82
83 func progressWorker(c context.Context, g *libgobuster.Gobuster) {
84 tick := time.NewTicker(1 * time.Second)
85
86 for {
87 select {
88 case <-tick.C:
89 g.PrintProgress()
90 case <-c.Done():
91 return
92 }
93 }
94 }
95
96 func writeToFile(f *os.File, output string) error {
97 _, err := f.WriteString(fmt.Sprintf("%s\n", output))
98 if err != nil {
99 return fmt.Errorf("[!] Unable to write to file %v", err)
100 }
101 return nil
102 }
103
104 func main() {
105 var outputFilename string
106 o := libgobuster.NewOptions()
107 flag.IntVar(&o.Threads, "t", 10, "Number of concurrent threads")
108 flag.StringVar(&o.Mode, "m", "dir", "Directory/File mode (dir) or DNS mode (dns)")
109 flag.StringVar(&o.Wordlist, "w", "", "Path to the wordlist")
110 flag.StringVar(&o.StatusCodes, "s", "200,204,301,302,307,403", "Positive status codes (dir mode only)")
111 flag.StringVar(&outputFilename, "o", "", "Output file to write results to (defaults to stdout)")
112 flag.StringVar(&o.URL, "u", "", "The target URL or Domain")
113 flag.StringVar(&o.Cookies, "c", "", "Cookies to use for the requests (dir mode only)")
114 flag.StringVar(&o.Username, "U", "", "Username for Basic Auth (dir mode only)")
115 flag.StringVar(&o.Password, "P", "", "Password for Basic Auth (dir mode only)")
116 flag.StringVar(&o.Extensions, "x", "", "File extension(s) to search for (dir mode only)")
117 flag.StringVar(&o.UserAgent, "a", "", "Set the User-Agent string (dir mode only)")
118 flag.StringVar(&o.Proxy, "p", "", "Proxy to use for requests [http(s)://host:port] (dir mode only)")
119 flag.DurationVar(&o.Timeout, "to", 10*time.Second, "HTTP Timeout in seconds (dir mode only)")
120 flag.BoolVar(&o.Verbose, "v", false, "Verbose output (errors)")
121 flag.BoolVar(&o.ShowIPs, "i", false, "Show IP addresses (dns mode only)")
122 flag.BoolVar(&o.ShowCNAME, "cn", false, "Show CNAME records (dns mode only, cannot be used with '-i' option)")
123 flag.BoolVar(&o.FollowRedirect, "r", false, "Follow redirects")
124 flag.BoolVar(&o.Quiet, "q", false, "Don't print the banner and other noise")
125 flag.BoolVar(&o.Expanded, "e", false, "Expanded mode, print full URLs")
126 flag.BoolVar(&o.NoStatus, "n", false, "Don't print status codes")
127 flag.BoolVar(&o.IncludeLength, "l", false, "Include the length of the body in the output (dir mode only)")
128 flag.BoolVar(&o.UseSlash, "f", false, "Append a forward-slash to each directory request (dir mode only)")
129 flag.BoolVar(&o.WildcardForced, "fw", false, "Force continued operation when wildcard found")
130 flag.BoolVar(&o.InsecureSSL, "k", false, "Skip SSL certificate verification")
131 flag.BoolVar(&o.NoProgress, "np", false, "Don't display progress")
132
133 flag.Parse()
134
135 // Prompt for PW if not provided
136 if o.Username != "" && o.Password == "" {
137 fmt.Printf("[?] Auth Password: ")
138 passBytes, err := terminal.ReadPassword(int(syscall.Stdin))
139 // print a newline to simulate the newline that was entered
140 // this means that formatting/printing after doesn't look bad.
141 fmt.Println("")
142 if err != nil {
143 log.Fatal("[!] Auth username given but reading of password failed")
144 }
145 o.Password = string(passBytes)
146 }
147
148 ctx, cancel := context.WithCancel(context.Background())
149 defer cancel()
150
151 var plugin libgobuster.GobusterPlugin
152 switch o.Mode {
153 case libgobuster.ModeDir:
154 plugin = gobusterdir.GobusterDir{}
155 case libgobuster.ModeDNS:
156 plugin = gobusterdns.GobusterDNS{}
157 }
158
159 gobuster, err := libgobuster.NewGobuster(ctx, o, plugin)
160 if err != nil {
161 log.Fatalf("[!] %v", err)
162 }
163
164 if !o.Quiet {
165 fmt.Println("")
166 ruler()
167 banner()
168 ruler()
169 c, err := gobuster.GetConfigString()
170 if err != nil {
171 log.Fatalf("error on creating config string: %v", err)
172 }
173 fmt.Println(c)
174 ruler()
175 log.Println("Starting gobuster")
176 ruler()
177 }
178
179 signalChan := make(chan os.Signal, 1)
180 signal.Notify(signalChan, os.Interrupt)
181 go func() {
182 for range signalChan {
183 // caught CTRL+C
184 if !gobuster.Opts.Quiet {
185 fmt.Println("\n[!] Keyboard interrupt detected, terminating.")
335186 }
336 }
337
338 // prompt for password if needed
339 if valid && s.Username != "" && s.Password == "" {
340 fmt.Printf("[?] Auth Password: ")
341 passBytes, err := terminal.ReadPassword(int(syscall.Stdin))
342
343 // print a newline to simulate the newline that was entered
344 // this means that formatting/printing after doesn't look bad.
345 fmt.Println("")
346
347 if err == nil {
348 s.Password = string(passBytes)
349 } else {
350 fmt.Println("[!] Auth username given but reading of password failed")
351 valid = false
352 }
353 }
354
355 if valid {
356 var proxyUrlFunc func(*http.Request) (*url.URL, error)
357 proxyUrlFunc = http.ProxyFromEnvironment
358
359 if proxy != "" {
360 proxyUrl, err := url.Parse(proxy)
361 if err != nil {
362 panic("[!] Proxy URL is invalid")
363 }
364 s.ProxyUrl = proxyUrl
365 proxyUrlFunc = http.ProxyURL(s.ProxyUrl)
366 }
367
368 s.Client = &http.Client{
369 Transport: &RedirectHandler{
370 State: &s,
371 Transport: &http.Transport{
372 Proxy: proxyUrlFunc,
373 TLSClientConfig: &tls.Config{
374 InsecureSkipVerify: true,
375 },
376 },
377 }}
378
379 code, _ := GoGet(&s, s.Url, "", s.Cookies)
380 if code == nil {
381 fmt.Println("[-] Unable to connect:", s.Url)
382 valid = false
383 }
384 } else {
385 Ruler(&s)
386 }
387 }
388
389 if valid {
390 return &s
391 }
392
393 return nil
394 }
395
396 // Process the busting of the website with the given
397 // set of settings from the command line.
398 func Process(s *State) {
399
400 ShowConfig(s)
401
402 if s.Setup(s) == false {
403 Ruler(s)
404 return
405 }
406
407 PrepareSignalHandler(s)
408
409 // channels used for comms
410 wordChan := make(chan string, s.Threads)
411 resultChan := make(chan Result)
412
413 // Use a wait group for waiting for all threads
414 // to finish
415 processorGroup := new(sync.WaitGroup)
416 processorGroup.Add(s.Threads)
417 printerGroup := new(sync.WaitGroup)
418 printerGroup.Add(1)
419
420 // Create goroutines for each of the number of threads
421 // specified.
422 for i := 0; i < s.Threads; i++ {
423 go func() {
424 for {
425 word := <-wordChan
426
427 // Did we reach the end? If so break.
428 if word == "" {
429 break
430 }
431
432 // Mode-specific processing
433 s.Processor(s, word, resultChan)
434 }
435
436 // Indicate to the wait group that the thread
437 // has finished.
438 processorGroup.Done()
439 }()
440 }
441
442 // Single goroutine which handles the results as they
443 // appear from the worker threads.
444 go func() {
445 for r := range resultChan {
446 s.Printer(s, &r)
447 }
448 printerGroup.Done()
187 cancel()
188 }
449189 }()
450190
451 var scanner *bufio.Scanner
452
453 if s.StdIn {
454 // Read directly from stdin
455 scanner = bufio.NewScanner(os.Stdin)
191 var wg sync.WaitGroup
192 wg.Add(2)
193 go errorWorker(gobuster, &wg)
194 go resultWorker(gobuster, outputFilename, &wg)
195
196 if !o.Quiet && !o.NoProgress {
197 go progressWorker(ctx, gobuster)
198 }
199
200 if err := gobuster.Start(); err != nil {
201 log.Printf("[!] %v", err)
456202 } else {
457 // Pull content from the wordlist
458 wordlist, err := os.Open(s.Wordlist)
459 if err != nil {
460 panic("Failed to open wordlist")
461 }
462 defer wordlist.Close()
463
464 // Lazy reading of the wordlist line by line
465 scanner = bufio.NewScanner(wordlist)
466 }
467
468 for scanner.Scan() {
469 if s.Terminate {
470 break
471 }
472 word := strings.TrimSpace(scanner.Text())
473
474 // Skip "comment" (starts with #), as well as empty lines
475 if !strings.HasPrefix(word, "#") && len(word) > 0 {
476 wordChan <- word
477 }
478 }
479
480 close(wordChan)
481 processorGroup.Wait()
482 close(resultChan)
483 printerGroup.Wait()
484 Ruler(s)
485 }
486
487 func SetupDns(s *State) bool {
488 // Resolve a subdomain that probably shouldn't exist
489 guid := uuid.NewV4()
490 wildcardIps, err := net.LookupHost(fmt.Sprintf("%s.%s", guid, s.Url))
491 if err == nil {
492 s.IsWildcard = true
493 s.WildcardIps.AddRange(wildcardIps)
494 fmt.Println("[-] Wildcard DNS found. IP address(es): ", s.WildcardIps.Stringify())
495 if !s.WildcardForced {
496 fmt.Println("[-] To force processing of Wildcard DNS, specify the '-fw' switch.")
497 }
498 return s.WildcardForced
499 }
500
501 if !s.Quiet {
502 // Provide a warning if the base domain doesn't resolve (in case of typo)
503 _, err = net.LookupHost(s.Url)
504 if err != nil {
505 // Not an error, just a warning. Eg. `yp.to` doesn't resolve, but `cr.py.to` does!
506 fmt.Println("[-] Unable to validate base domain:", s.Url)
507 }
508 }
509
510 return true
511 }
512
513 func SetupDir(s *State) bool {
514 return true
515 }
516
517 func ProcessDnsEntry(s *State, word string, resultChan chan<- Result) {
518 subdomain := word + "." + s.Url
519 ips, err := net.LookupHost(subdomain)
520
521 if err == nil {
522 if !s.IsWildcard || !s.WildcardIps.ContainsAny(ips) {
523 result := Result{
524 Entity: subdomain,
525 }
526 if s.ShowIPs {
527 result.Extra = strings.Join(ips, ", ")
528 }
529 resultChan <- result
530 }
531 } else if s.Verbose {
532 result := Result{
533 Entity: subdomain,
534 Status: 404,
535 }
536 resultChan <- result
537 }
538 }
539
540 func ProcessDirEntry(s *State, word string, resultChan chan<- Result) {
541 suffix := ""
542 if s.UseSlash {
543 suffix = "/"
544 }
545
546 // Try the DIR first
547 dirResp, dirSize := GoGet(s, s.Url, word+suffix, s.Cookies)
548 if dirResp != nil {
549 resultChan <- Result{
550 Entity: word + suffix,
551 Status: *dirResp,
552 Size: dirSize,
553 }
554 }
555
556 // Follow up with files using each ext.
557 for ext := range s.Extensions {
558 file := word + s.Extensions[ext]
559 fileResp, fileSize := GoGet(s, s.Url, file, s.Cookies)
560
561 if fileResp != nil {
562 resultChan <- Result{
563 Entity: file,
564 Status: *fileResp,
565 Size: fileSize,
566 }
567 }
568 }
569 }
570
571 func PrintDnsResult(s *State, r *Result) {
572 if r.Status == 404 {
573 fmt.Printf("Missing: %s\n", r.Entity)
574 } else if s.ShowIPs {
575 fmt.Printf("Found: %s [%s]\n", r.Entity, r.Extra)
576 } else {
577 fmt.Printf("Found: %s\n", r.Entity)
578 }
579 }
580
581 func PrintDirResult(s *State, r *Result) {
582 output := ""
583
584 // Prefix if we're in verbose mode
585 if s.Verbose {
586 if s.StatusCodes.Contains(r.Status) {
587 output += "Found : "
588 } else {
589 output += "Missed: "
590 }
591 }
592
593 if s.StatusCodes.Contains(r.Status) || s.Verbose {
594 if s.Expanded {
595 output += s.Url
596 } else {
597 output += "/"
598 }
599 output += r.Entity
600
601 if !s.NoStatus {
602 output += fmt.Sprintf(" (Status: %d)", r.Status)
603 }
604
605 if r.Size != nil {
606 output += fmt.Sprintf(" [Size: %d]", *r.Size)
607 }
608
609 fmt.Println(output)
610 }
611 }
612
613 func PrepareSignalHandler(s *State) {
614 s.SignalChan = make(chan os.Signal, 1)
615 signal.Notify(s.SignalChan, os.Interrupt)
616 go func() {
617 for _ = range s.SignalChan {
618 // caught CTRL+C
619 if !s.Quiet {
620 fmt.Println("[!] Keyboard interrupt detected, terminating.")
621 s.Terminate = true
622 }
623 }
624 }()
625 }
626
627 func (e *RedirectError) Error() string {
628 return fmt.Sprintf("Redirect code: %d", e.StatusCode)
629 }
630
631 func (rh *RedirectHandler) RoundTrip(req *http.Request) (resp *http.Response, err error) {
632 if rh.State.FollowRedirect {
633 return rh.Transport.RoundTrip(req)
634 }
635
636 resp, err = rh.Transport.RoundTrip(req)
637 if err != nil {
638 return resp, err
639 }
640
641 switch resp.StatusCode {
642 case http.StatusMovedPermanently, http.StatusFound, http.StatusSeeOther,
643 http.StatusNotModified, http.StatusUseProxy, http.StatusTemporaryRedirect:
644 return nil, &RedirectError{StatusCode: resp.StatusCode}
645 }
646
647 return resp, err
648 }
649
650 func Ruler(s *State) {
651 if !s.Quiet {
652 fmt.Println("=====================================================")
653 }
654 }
655
656 func Banner(state *State) {
657 if state.Quiet {
658 return
659 }
660
661 fmt.Println("")
662 fmt.Println("Gobuster v1.2 OJ Reeves (@TheColonial)")
663 Ruler(state)
664 }
665
666 func ShowConfig(state *State) {
667 if state.Quiet {
668 return
669 }
670
671 if state != nil {
672 fmt.Printf("[+] Mode : %s\n", state.Mode)
673 fmt.Printf("[+] Url/Domain : %s\n", state.Url)
674 fmt.Printf("[+] Threads : %d\n", state.Threads)
675
676 wordlist := "stdin (pipe)"
677 if !state.StdIn {
678 wordlist = state.Wordlist
679 }
680 fmt.Printf("[+] Wordlist : %s\n", wordlist)
681
682 if state.Mode == "dir" {
683 fmt.Printf("[+] Status codes : %s\n", state.StatusCodes.Stringify())
684
685 if state.ProxyUrl != nil {
686 fmt.Printf("[+] Proxy : %s\n", state.ProxyUrl)
687 }
688
689 if state.Cookies != "" {
690 fmt.Printf("[+] Cookies : %s\n", state.Cookies)
691 }
692
693 if state.UserAgent != "" {
694 fmt.Printf("[+] User Agent : %s\n", state.UserAgent)
695 }
696
697 if state.IncludeLength {
698 fmt.Printf("[+] Show length : true\n")
699 }
700
701 if state.Username != "" {
702 fmt.Printf("[+] Auth User : %s\n", state.Username)
703 }
704
705 if len(state.Extensions) > 0 {
706 fmt.Printf("[+] Extensions : %s\n", strings.Join(state.Extensions, ","))
707 }
708
709 if state.UseSlash {
710 fmt.Printf("[+] Add Slash : true\n")
711 }
712
713 if state.FollowRedirect {
714 fmt.Printf("[+] Follow Redir : true\n")
715 }
716
717 if state.Expanded {
718 fmt.Printf("[+] Expanded : true\n")
719 }
720
721 if state.NoStatus {
722 fmt.Printf("[+] No status : true\n")
723 }
724
725 if state.Verbose {
726 fmt.Printf("[+] Verbose : true\n")
727 }
728 }
729
730 Ruler(state)
731 }
732 }
733
734 func main() {
735 state := ParseCmdLine()
736 if state != nil {
737 Process(state)
738 }
739 }
203 // call cancel func to free ressources and stop progressFunc
204 cancel()
205 // wait for all output funcs to finish
206 wg.Wait()
207 }
208
209 if !o.Quiet {
210 gobuster.ClearProgress()
211 ruler()
212 log.Println("Finished")
213 ruler()
214 }
215 }
0 @echo off
1 set GOOS=linux
2 set GOARCH=amd64
3
4 go test -v -race ./...
5 go build -o gobuster
0 @echo off
1 set GOOS=windows
2 set GOARCH=amd64
3
4 go test -v -race ./...
5 go build -o gobuster.exe