Codebase list gobuster / 07c1196
New upstream version 2.0.1 Alexander Fischer 5 years ago
25 changed file(s) with 1729 addition(s) and 946 deletion(s). Raw diff Collapse all Expand all
2323 *.prof
2424 *.txt
2525 *.swp
26
27 gobuster
28 build
0 language: go
1
2 go:
3 - "1.x"
4 - "1.8"
5 - "1.10.x"
6 - master
0 TARGET=./build
1 OSES=darwin linux windows
2 ARCHS=amd64 386
3
4 current: outputdir
5 @go build -o ./gobuster; \
6 echo "Done."
7
8 outputdir:
9 @mkdir -p ${TARGET}
10
11 windows: outputdir
12 @for GOARCH in ${ARCHS}; do \
13 echo "Building for windows $${GOARCH} ..." ; \
14 GOOS=windows GARCH=$${GOARCH} go build -o ${TARGET}/gobuster-$${GOARCH}.exe ; \
15 done; \
16 echo "Done."
17
18 linux: outputdir
19 @for GOARCH in ${ARCHS}; do \
20 echo "Building for linux $${GOARCH} ..." ; \
21 GOOS=linux GARCH=$${GOARCH} go build -o ${TARGET}/gobuster-linux-$${GOARCH} ; \
22 done; \
23 echo "Done."
24
25 darwin: outputdir
26 @for GOARCH in ${ARCHS}; do \
27 echo "Building for darwin $${GOARCH} ..." ; \
28 GOOS=darwin GARCH=$${GOARCH} go build -o ${TARGET}/gobuster-darwin-$${GOARCH} ; \
29 done; \
30 echo "Done."
31
32
33 all: darwin linux windows
34
35 test:
36 @go test -v -race ./... ; \
37 echo "Done."
38
39 clean:
40 @rm -rf ${TARGET}/* ; \
41 echo "Done."
0 Gobuster v1.4.1 (OJ Reeves @TheColonial)
0 Gobuster v2.0.1 (OJ Reeves @TheColonial)
11 ========================================
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 * `-fw` - Force processing of a domain with wildcard results.
32 * `-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`).
3338 * `-q` - disables banner/underline output.
3439 * `-t <threads>` - number of threads to run (default: `10`).
3540 * `-u <url/domain>` - full URL (including scheme), or base domain name.
3641 * `-v` - verbose output (show all results).
37 * `-w <wordlist>` - path to the wordlist used for brute forcing.
42 * `-w <wordlist>` - path to the wordlist used for brute forcing (use `-` for stdin).
3843
3944 ### Command line options for `dns` mode
4045
5762 * `-x <extensions>` - list of extensions to check for, if any.
5863 * `-P <password>` - HTTP Authorization password (Basic Auth only, prompted if missing).
5964 * `-U <username>` - HTTP Authorization username (Basic Auth only).
65 * `-to <timeout>` - HTTP timeout. Examples: 10s, 100ms, 1m (default: 10s).
6066
6167 ### Building
6268
7177 ```
7278 gobuster $ go install
7379 ```
80 If you have all the dependencies already, you can make use of the build scripts:
81 * `make` - builds for the current Go configuration (ie. runs `go build`).
82 * `make windows` - builds 32 and 64 bit binaries for windows, and writes them to the `build` subfolder.
83 * `make linux` - builds 32 and 64 bit binaries for linux, and writes them to the `build` subfolder.
84 * `make darwin` - builds 32 and 64 bit binaries for darwin, and writes them to the `build` subfolder.
85 * `make all` - builds for all platforms and architectures, and writes the resulting binaries to the `build` subfolder.
86 * `make clean` - clears out the `build` subfolder.
87 * `make test` - runs the tests (requires you to `go get githubcom/h2non/gock` first).
7488
7589 #### Running as a script
7690 ```
77 gobuster$ go run main.go <parameters>
91 gobuster $ go run main.go <parameters>
7892 ```
7993
8094 ### Wordlists via STDIN
81 Wordlists can be piped into `gobuster` via stdin:
82 ```
83 hashcat -a 3 --stdout ?l | gobuster -u https://mysite.com
95 Wordlists can be piped into `gobuster` via stdin by providing a `-` to the `-w` option:
96 ```
97 hashcat -a 3 --stdout ?l | gobuster -u https://mysite.com -w -
8498 ```
8599 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.
86100
94108 ```
95109 Default options looks like this:
96110 ```
97 $ gobuster -u http://buffered.io/ -w words.txt
98
99 Gobuster v1.4.1 OJ Reeves (@TheColonial)
111 $ gobuster -u https://buffered.io -w ~/wordlists/shortlist.txt
112
113 =====================================================
114 Gobuster v2.0.1 OJ Reeves (@TheColonial)
100115 =====================================================
101116 [+] Mode : dir
102 [+] Url/Domain : http://buffered.io/
103 [+] Threads : 10
104 [+] Wordlist : words.txt
105 [+] Status codes : 200,204,301,302,307
106 =====================================================
117 [+] Url/Domain : https://buffered.io/
118 [+] Threads : 10
119 [+] Wordlist : /home/oj/wordlists/shortlist.txt
120 [+] Status codes : 200,204,301,302,307,403
121 [+] Timeout : 10s
122 =====================================================
123 2018/08/27 11:49:43 Starting gobuster
124 =====================================================
125 /categories (Status: 301)
126 /contact (Status: 301)
127 /posts (Status: 301)
107128 /index (Status: 200)
108 /posts (Status: 301)
109 /contact (Status: 301)
129 =====================================================
130 2018/08/27 11:49:44 Finished
110131 =====================================================
111132 ```
112133 Default options with status codes disabled looks like this:
113134 ```
114 $ gobuster -u http://buffered.io/ -w words.txt -n
115
116 Gobuster v1.4.1 OJ Reeves (@TheColonial)
135 $ gobuster -u https://buffered.io -w ~/wordlists/shortlist.txt -n
136
137 =====================================================
138 Gobuster v2.0.1 OJ Reeves (@TheColonial)
117139 =====================================================
118140 [+] Mode : dir
119 [+] Url/Domain : http://buffered.io/
120 [+] Threads : 10
121 [+] Wordlist : words.txt
122 [+] Status codes : 200,204,301,302,307
141 [+] Url/Domain : https://buffered.io/
142 [+] Threads : 10
143 [+] Wordlist : /home/oj/wordlists/shortlist.txt
144 [+] Status codes : 200,204,301,302,307,403
123145 [+] No status : true
124 =====================================================
146 [+] Timeout : 10s
147 =====================================================
148 2018/08/27 11:50:18 Starting gobuster
149 =====================================================
150 /categories
151 /contact
125152 /index
126153 /posts
127 /contact
154 =====================================================
155 2018/08/27 11:50:18 Finished
128156 =====================================================
129157 ```
130158 Verbose output looks like this:
131159 ```
132 $ gobuster -u http://buffered.io/ -w words.txt -v
133
134 Gobuster v1.4.1 OJ Reeves (@TheColonial)
160 $ gobuster -u https://buffered.io -w ~/wordlists/shortlist.txt -v
161
162 =====================================================
163 Gobuster v2.0.1 OJ Reeves (@TheColonial)
135164 =====================================================
136165 [+] Mode : dir
137 [+] Url/Domain : http://buffered.io/
138 [+] Threads : 10
139 [+] Wordlist : words.txt
140 [+] Status codes : 200,204,301,302,307
166 [+] Url/Domain : https://buffered.io/
167 [+] Threads : 10
168 [+] Wordlist : /home/oj/wordlists/shortlist.txt
169 [+] Status codes : 200,204,301,302,307,403
141170 [+] Verbose : true
142 =====================================================
143 Found : /index (Status: 200)
144 Missed: /derp (Status: 404)
145 Found : /posts (Status: 301)
146 Found : /contact (Status: 301)
171 [+] Timeout : 10s
172 =====================================================
173 2018/08/27 11:50:51 Starting gobuster
174 =====================================================
175 Missed: /alsodoesnotexist (Status: 404)
176 Found: /index (Status: 200)
177 Missed: /doesnotexist (Status: 404)
178 Found: /categories (Status: 301)
179 Found: /posts (Status: 301)
180 Found: /contact (Status: 301)
181 =====================================================
182 2018/08/27 11:50:51 Finished
147183 =====================================================
148184 ```
149185 Example showing content length:
150186 ```
151 $ gobuster -u http://buffered.io/ -w words.txt -l
152
153 Gobuster v1.4.1 OJ Reeves (@TheColonial)
187 $ gobuster -u https://buffered.io -w ~/wordlists/shortlist.txt -l
188
189 =====================================================
190 Gobuster v2.0.1 OJ Reeves (@TheColonial)
154191 =====================================================
155192 [+] Mode : dir
156 [+] Url/Domain : http://buffered.io/
157 [+] Threads : 10
158 [+] Wordlist : /tmp/words
159 [+] Status codes : 301,302,307,200,204
193 [+] Url/Domain : https://buffered.io/
194 [+] Threads : 10
195 [+] Wordlist : /home/oj/wordlists/shortlist.txt
196 [+] Status codes : 200,204,301,302,307,403
160197 [+] Show length : true
161 =====================================================
162 /contact (Status: 301)
163 /posts (Status: 301)
164 /index (Status: 200) [Size: 61481]
198 [+] Timeout : 10s
199 =====================================================
200 2018/08/27 11:51:16 Starting gobuster
201 =====================================================
202 /categories (Status: 301) [Size: 178]
203 /posts (Status: 301) [Size: 178]
204 /contact (Status: 301) [Size: 178]
205 /index (Status: 200) [Size: 51759]
206 =====================================================
207 2018/08/27 11:51:17 Finished
165208 =====================================================
166209 ```
167210 Quiet output, with status disabled and expanded mode looks like this ("grep mode"):
168211 ```
169 $ gobuster -u http://buffered.io/ -w words.txt -q -n -e
170 http://buffered.io/posts
171 http://buffered.io/contact
172 http://buffered.io/index
212 $ gobuster -u https://buffered.io -w ~/wordlists/shortlist.txt -q -n -e
213 https://buffered.io/index
214 https://buffered.io/contact
215 https://buffered.io/posts
216 https://buffered.io/categories
173217 ```
174218
175219 #### `dns` mode
180224 ```
181225 Normal sample run goes like this:
182226 ```
183 $ gobuster -m dns -w subdomains.txt -u google.com
184
185 Gobuster v1.4.1 OJ Reeves (@TheColonial)
227 $ gobuster -m dns -w ~/wordlists/subdomains.txt -u google.com
228
229 =====================================================
230 Gobuster v2.0.1 OJ Reeves (@TheColonial)
186231 =====================================================
187232 [+] Mode : dns
188233 [+] Url/Domain : google.com
189234 [+] Threads : 10
190 [+] Wordlist : subdomains.txt
191 =====================================================
192 Found: m.google.com
193 Found: admin.google.com
194 Found: mobile.google.com
195 Found: www.google.com
196 Found: search.google.com
235 [+] Wordlist : /home/oj/wordlists/subdomains.txt
236 =====================================================
237 2018/08/27 11:54:20 Starting gobuster
238 =====================================================
197239 Found: chrome.google.com
198240 Found: ns1.google.com
199 Found: store.google.com
200 Found: wap.google.com
241 Found: admin.google.com
242 Found: www.google.com
243 Found: m.google.com
201244 Found: support.google.com
202 Found: directory.google.com
203245 Found: translate.google.com
246 Found: cse.google.com
204247 Found: news.google.com
205248 Found: music.google.com
206249 Found: mail.google.com
250 Found: store.google.com
251 Found: mobile.google.com
252 Found: search.google.com
253 Found: wap.google.com
254 Found: directory.google.com
255 Found: local.google.com
207256 Found: blog.google.com
208 Found: cse.google.com
209 Found: local.google.com
257 =====================================================
258 2018/08/27 11:54:20 Finished
210259 =====================================================
211260 ```
212261 Show IP sample run goes like this:
213262 ```
214 $ gobuster -m dns -w subdomains.txt -u google.com -i
215
216 Gobuster v1.4.1 OJ Reeves (@TheColonial)
263 $ gobuster -m dns -w ~/wordlists/subdomains.txt -u google.com -i
264
265 =====================================================
266 Gobuster v2.0.1 OJ Reeves (@TheColonial)
217267 =====================================================
218268 [+] Mode : dns
219269 [+] Url/Domain : google.com
220270 [+] Threads : 10
221 [+] Wordlist : subdomains.txt
222 [+] Verbose : true
223 =====================================================
224 Found: chrome.google.com [2404:6800:4006:801::200e, 216.58.220.110]
225 Found: m.google.com [216.58.220.107, 2404:6800:4006:801::200b]
226 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]
227 Found: search.google.com [2404:6800:4006:801::200e, 216.58.220.110]
228 Found: admin.google.com [216.58.220.110, 2404:6800:4006:801::200e]
229 Found: store.google.com [216.58.220.110, 2404:6800:4006:801::200e]
230 Found: mobile.google.com [216.58.220.107, 2404:6800:4006:801::200b]
231 Found: ns1.google.com [216.239.32.10]
232 Found: directory.google.com [216.58.220.110, 2404:6800:4006:801::200e]
233 Found: translate.google.com [216.58.220.110, 2404:6800:4006:801::200e]
234 Found: cse.google.com [216.58.220.110, 2404:6800:4006:801::200e]
235 Found: local.google.com [2404:6800:4006:801::200e, 216.58.220.110]
236 Found: music.google.com [2404:6800:4006:801::200e, 216.58.220.110]
237 Found: wap.google.com [216.58.220.110, 2404:6800:4006:801::200e]
238 Found: blog.google.com [216.58.220.105, 2404:6800:4006:801::2009]
239 Found: support.google.com [216.58.220.110, 2404:6800:4006:801::200e]
240 Found: news.google.com [216.58.220.110, 2404:6800:4006:801::200e]
241 Found: mail.google.com [216.58.220.101, 2404:6800:4006:801::2005]
271 [+] Wordlist : /home/oj/wordlists/subdomains.txt
272 =====================================================
273 2018/08/27 11:54:54 Starting gobuster
274 =====================================================
275 Found: www.google.com [172.217.25.36, 2404:6800:4006:802::2004]
276 Found: admin.google.com [172.217.25.46, 2404:6800:4006:806::200e]
277 Found: store.google.com [172.217.167.78, 2404:6800:4006:802::200e]
278 Found: mobile.google.com [172.217.25.43, 2404:6800:4006:802::200b]
279 Found: ns1.google.com [216.239.32.10, 2001:4860:4802:32::a]
280 Found: m.google.com [172.217.25.43, 2404:6800:4006:802::200b]
281 Found: cse.google.com [172.217.25.46, 2404:6800:4006:80a::200e]
282 Found: chrome.google.com [172.217.25.46, 2404:6800:4006:802::200e]
283 Found: search.google.com [172.217.25.46, 2404:6800:4006:802::200e]
284 Found: local.google.com [172.217.25.46, 2404:6800:4006:80a::200e]
285 Found: news.google.com [172.217.25.46, 2404:6800:4006:802::200e]
286 Found: blog.google.com [216.58.199.73, 2404:6800:4006:806::2009]
287 Found: support.google.com [172.217.25.46, 2404:6800:4006:802::200e]
288 Found: wap.google.com [172.217.25.46, 2404:6800:4006:802::200e]
289 Found: directory.google.com [172.217.25.46, 2404:6800:4006:802::200e]
290 Found: translate.google.com [172.217.25.46, 2404:6800:4006:802::200e]
291 Found: music.google.com [172.217.25.46, 2404:6800:4006:802::200e]
292 Found: mail.google.com [172.217.25.37, 2404:6800:4006:802::2005]
293 =====================================================
294 2018/08/27 11:54:55 Finished
242295 =====================================================
243296 ```
244297 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.
245298 ```
246 $ gobuster -m dns -w subdomains.txt -u yp.to -i
247
248 Gobuster v1.4.1 OJ Reeves (@TheColonial)
299 $ gobuster -m dns -w ~/wordlists/subdomains.txt -u yp.to -i
300
301 =====================================================
302 Gobuster v2.0.1 OJ Reeves (@TheColonial)
249303 =====================================================
250304 [+] Mode : dns
251305 [+] Url/Domain : yp.to
252306 [+] Threads : 10
253 [+] Wordlist : /tmp/test.txt
254 =====================================================
255 [-] Unable to validate base domain: yp.to
256 Found: cr.yp.to [131.155.70.11, 131.155.70.13]
307 [+] Wordlist : /home/oj/wordlists/subdomains.txt
308 =====================================================
309 2018/08/27 11:56:43 Starting gobuster
310 =====================================================
311 2018/08/27 11:56:53 [-] Unable to validate base domain: yp.to
312 Found: cr.yp.to [131.193.32.108, 131.193.32.109]
313 =====================================================
314 2018/08/27 11:56:53 Finished
257315 =====================================================
258316 ```
259317 Wildcard DNS is also detected properly:
260318 ```
261 $ gobuster -w subdomainsbig.txt -u doesntexist.com -m dns
262
263 Gobuster v1.4.1 OJ Reeves (@TheColonial)
264 =====================================================
265 [+] Mode : dns
266 [+] Url/Domain : doesntexist.com
267 [+] Threads : 10
268 [+] Wordlist : subdomainsbig.txt
269 =====================================================
270 [-] Wildcard DNS found. IP address(es): 123.123.123.123
271 [-] To force processing of Wildcard DNS, specify the '-fw' switch.
319 $ gobuster -m dns -w ~/wordlists/subdomains.txt -u 0.0.1.xip.io
320
321 =====================================================
322 Gobuster v2.0.1 OJ Reeves (@TheColonial)
323 =====================================================
324 [+] Mode : dns
325 [+] Url/Domain : 0.0.1.xip.io
326 [+] Threads : 10
327 [+] Wordlist : /home/oj/wordlists/subdomains.txt
328 =====================================================
329 2018/08/27 12:13:48 Starting gobuster
330 =====================================================
331 2018/08/27 12:13:48 [-] Wildcard DNS found. IP address(es): 1.0.0.0
332 2018/08/27 12:13:48 [!] To force processing of Wildcard DNS, specify the '-fw' switch.
333 =====================================================
334 2018/08/27 12:13:48 Finished
272335 =====================================================
273336 ```
274337 If the user wants to force processing of a domain that has wildcard entries, use `-fw`:
275338 ```
276 $ gobuster -w subdomainsbig.txt -u doesntexist.com -m dns -fw
277
278 Gobuster v1.4.1 OJ Reeves (@TheColonial)
279 =====================================================
280 [+] Mode : dns
281 [+] Url/Domain : doesntexist.com
282 [+] Threads : 10
283 [+] Wordlist : subdomainsbig.txt
284 =====================================================
285 [-] Wildcard DNS found. IP address(es): 123.123.123.123
286 Found: email.doesntexist.com
287 ^C[!] Keyboard interrupt detected, terminating.
339 $ gobuster -m dns -w ~/wordlists/subdomains.txt -u 0.0.1.xip.io -fw
340
341 =====================================================
342 Gobuster v2.0.1 OJ Reeves (@TheColonial)
343 =====================================================
344 [+] Mode : dns
345 [+] Url/Domain : 0.0.1.xip.io
346 [+] Threads : 10
347 [+] Wordlist : /home/oj/wordlists/subdomains.txt
348 =====================================================
349 2018/08/27 12:13:51 Starting gobuster
350 =====================================================
351 2018/08/27 12:13:51 [-] Wildcard DNS found. IP address(es): 1.0.0.0
352 Found: 127.0.0.1.xip.io
353 Found: test.127.0.0.1.xip.io
354 =====================================================
355 2018/08/27 12:13:53 Finished
288356 =====================================================
289357 ```
290358
00 @0x42424242 - initial DNS support
11 @0xdevalias - Refactoring of code, and lots of other stuff
2 @FireFart - Supporting connection reuse, resulting in been speed ups, refactoring, progress output and more
23 @Ne0nd0g - STDIN support for wordlists
34 @UID1K - initial DNS wildcard check support
45 @averagesecurityguy - quiet mode support
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 "strings"
7
8 "github.com/OJ/gobuster/libgobuster"
9 "github.com/google/uuid"
10 )
11
12 // GobusterDNS is the main type to implement the interface
13 type GobusterDNS struct{}
14
15 // Setup is the setup implementation of gobusterdns
16 func (d GobusterDNS) Setup(g *libgobuster.Gobuster) error {
17 // Resolve a subdomain sthat probably shouldn't exist
18 guid := uuid.New()
19 wildcardIps, err := g.DNSLookup(fmt.Sprintf("%s.%s", guid, g.Opts.URL))
20 if err == nil {
21 g.IsWildcard = true
22 g.WildcardIps.AddRange(wildcardIps)
23 log.Printf("[-] Wildcard DNS found. IP address(es): %s", g.WildcardIps.Stringify())
24 if !g.Opts.WildcardForced {
25 return fmt.Errorf("To force processing of Wildcard DNS, specify the '-fw' switch.")
26 }
27 }
28
29 if !g.Opts.Quiet {
30 // Provide a warning if the base domain doesn't resolve (in case of typo)
31 _, err = g.DNSLookup(g.Opts.URL)
32 if err != nil {
33 // Not an error, just a warning. Eg. `yp.to` doesn't resolve, but `cr.py.to` does!
34 log.Printf("[-] Unable to validate base domain: %s", g.Opts.URL)
35 }
36 }
37
38 return nil
39 }
40
41 // Process is the process implementation of gobusterdns
42 func (d GobusterDNS) Process(g *libgobuster.Gobuster, word string) ([]libgobuster.Result, error) {
43 subdomain := fmt.Sprintf("%s.%s", word, g.Opts.URL)
44 ips, err := g.DNSLookup(subdomain)
45 var ret []libgobuster.Result
46 if err == nil {
47 if !g.IsWildcard || !g.WildcardIps.ContainsAny(ips) {
48 result := libgobuster.Result{
49 Entity: subdomain,
50 }
51 if g.Opts.ShowIPs {
52 result.Extra = strings.Join(ips, ", ")
53 } else if g.Opts.ShowCNAME {
54 cname, err := g.DNSLookupCname(subdomain)
55 if err == nil {
56 result.Extra = cname
57 }
58 }
59 ret = append(ret, result)
60 }
61 } else if g.Opts.Verbose {
62 ret = append(ret, libgobuster.Result{
63 Entity: subdomain,
64 Status: 404,
65 })
66 }
67 return ret, nil
68 }
69
70 // ResultToString is the to string implementation of gobusterdns
71 func (d GobusterDNS) ResultToString(g *libgobuster.Gobuster, r *libgobuster.Result) (*string, error) {
72 buf := &bytes.Buffer{}
73
74 if r.Status == 404 {
75 if _, err := fmt.Fprintf(buf, "Missing: %s\n", r.Entity); err != nil {
76 return nil, err
77 }
78 } else if g.Opts.ShowIPs {
79 if _, err := fmt.Fprintf(buf, "Found: %s [%s]\n", r.Entity, r.Extra); err != nil {
80 return nil, err
81 }
82 } else if g.Opts.ShowCNAME {
83 if _, err := fmt.Fprintf(buf, "Found: %s [%s]\n", r.Entity, r.Extra); err != nil {
84 return nil, err
85 }
86 } else {
87 if _, err := fmt.Fprintf(buf, "Found: %s\n", r.Entity); err != nil {
88 return nil, err
89 }
90 }
91
92 s := buf.String()
93 return &s, nil
94 }
+0
-189
libgobuster/dir.go less more
0 package libgobuster
1
2 import (
3 "fmt"
4 "io/ioutil"
5 "net/http"
6 "net/url"
7 "strings"
8 "unicode/utf8"
9
10 uuid "github.com/satori/go.uuid"
11 )
12
13 type RedirectHandler struct {
14 Transport http.RoundTripper
15 State *State
16 }
17
18 type RedirectError struct {
19 StatusCode int
20 }
21
22 func (e *RedirectError) Error() string {
23 return fmt.Sprintf("Redirect code: %d", e.StatusCode)
24 }
25
26 func (rh *RedirectHandler) RoundTrip(req *http.Request) (resp *http.Response, err error) {
27 if rh.State.FollowRedirect {
28 return rh.Transport.RoundTrip(req)
29 }
30
31 resp, err = rh.Transport.RoundTrip(req)
32 if err != nil {
33 return resp, err
34 }
35
36 switch resp.StatusCode {
37 case http.StatusMovedPermanently, http.StatusFound, http.StatusSeeOther,
38 http.StatusNotModified, http.StatusUseProxy, http.StatusTemporaryRedirect:
39 return nil, &RedirectError{StatusCode: resp.StatusCode}
40 }
41
42 return resp, err
43 }
44
45 // Make a request to the given URL.
46 func MakeRequest(s *State, fullUrl, cookie string) (*int, *int64) {
47 req, err := http.NewRequest("GET", fullUrl, nil)
48
49 if err != nil {
50 return nil, nil
51 }
52
53 if cookie != "" {
54 req.Header.Set("Cookie", cookie)
55 }
56
57 if s.UserAgent != "" {
58 req.Header.Set("User-Agent", s.UserAgent)
59 }
60
61 if s.Username != "" {
62 req.SetBasicAuth(s.Username, s.Password)
63 }
64
65 resp, err := s.Client.Do(req)
66
67 if err != nil {
68 if ue, ok := err.(*url.Error); ok {
69
70 if strings.HasPrefix(ue.Err.Error(), "x509") {
71 fmt.Println("[-] Invalid certificate")
72 }
73
74 if re, ok := ue.Err.(*RedirectError); ok {
75 return &re.StatusCode, nil
76 }
77 }
78 return nil, nil
79 }
80
81 defer resp.Body.Close()
82
83 var length *int64 = nil
84
85 if s.IncludeLength {
86 length = new(int64)
87 if resp.ContentLength <= 0 {
88 body, err := ioutil.ReadAll(resp.Body)
89 if err == nil {
90 *length = int64(utf8.RuneCountInString(string(body)))
91 }
92 } else {
93 *length = resp.ContentLength
94 }
95 }
96
97 return &resp.StatusCode, length
98 }
99
100 // Small helper to combine URL with URI then make a
101 // request to the generated location.
102 func GoGet(s *State, url, uri, cookie string) (*int, *int64) {
103 return MakeRequest(s, url+uri, cookie)
104 }
105
106 func SetupDir(s *State) bool {
107 guid := uuid.Must(uuid.NewV4())
108 wildcardResp, _ := GoGet(s, s.Url, fmt.Sprintf("%s", guid), s.Cookies)
109
110 if s.StatusCodes.Contains(*wildcardResp) {
111 s.IsWildcard = true
112 fmt.Println("[-] Wildcard response found:", fmt.Sprintf("%s%s", s.Url, guid), "=>", *wildcardResp)
113 if !s.WildcardForced {
114 fmt.Println("[-] To force processing of Wildcard responses, specify the '-fw' switch.")
115 }
116 return s.WildcardForced
117 }
118
119 return true
120 }
121
122 func ProcessDirEntry(s *State, word string, resultChan chan<- Result) {
123 suffix := ""
124 if s.UseSlash {
125 suffix = "/"
126 }
127
128 // Try the DIR first
129 dirResp, dirSize := GoGet(s, s.Url, word+suffix, s.Cookies)
130 if dirResp != nil {
131 resultChan <- Result{
132 Entity: word + suffix,
133 Status: *dirResp,
134 Size: dirSize,
135 }
136 }
137
138 // Follow up with files using each ext.
139 for ext := range s.Extensions {
140 file := word + s.Extensions[ext]
141 fileResp, fileSize := GoGet(s, s.Url, file, s.Cookies)
142
143 if fileResp != nil {
144 resultChan <- Result{
145 Entity: file,
146 Status: *fileResp,
147 Size: fileSize,
148 }
149 }
150 }
151 }
152
153 func PrintDirResult(s *State, r *Result) {
154 output := ""
155
156 // Prefix if we're in verbose mode
157 if s.Verbose {
158 if s.StatusCodes.Contains(r.Status) {
159 output = "Found : "
160 } else {
161 output = "Missed: "
162 }
163 }
164
165 if s.StatusCodes.Contains(r.Status) || s.Verbose {
166 if s.Expanded {
167 output += s.Url
168 } else {
169 output += "/"
170 }
171 output += r.Entity
172
173 if !s.NoStatus {
174 output += fmt.Sprintf(" (Status: %d)", r.Status)
175 }
176
177 if r.Size != nil {
178 output += fmt.Sprintf(" [Size: %d]", *r.Size)
179 }
180 output += "\n"
181
182 fmt.Printf(output)
183
184 if s.OutputFile != nil {
185 WriteToFile(output, s)
186 }
187 }
188 }
+0
-81
libgobuster/dns.go less more
0 package libgobuster
1
2 import (
3 "fmt"
4 "net"
5 "strings"
6
7 uuid "github.com/satori/go.uuid"
8 )
9
10 func SetupDns(s *State) bool {
11 // Resolve a subdomain that probably shouldn't exist
12 guid := uuid.Must(uuid.NewV4())
13 wildcardIps, err := net.LookupHost(fmt.Sprintf("%s.%s", guid, s.Url))
14 if err == nil {
15 s.IsWildcard = true
16 s.WildcardIps.AddRange(wildcardIps)
17 fmt.Println("[-] Wildcard DNS found. IP address(es): ", s.WildcardIps.Stringify())
18 if !s.WildcardForced {
19 fmt.Println("[-] To force processing of Wildcard DNS, specify the '-fw' switch.")
20 }
21 return s.WildcardForced
22 }
23
24 if !s.Quiet {
25 // Provide a warning if the base domain doesn't resolve (in case of typo)
26 _, err = net.LookupHost(s.Url)
27 if err != nil {
28 // Not an error, just a warning. Eg. `yp.to` doesn't resolve, but `cr.py.to` does!
29 fmt.Println("[-] Unable to validate base domain:", s.Url)
30 }
31 }
32
33 return true
34 }
35
36 func ProcessDnsEntry(s *State, word string, resultChan chan<- Result) {
37 subdomain := word + "." + s.Url
38 ips, err := net.LookupHost(subdomain)
39
40 if err == nil {
41 if !s.IsWildcard || !s.WildcardIps.ContainsAny(ips) {
42 result := Result{
43 Entity: subdomain,
44 }
45 if s.ShowIPs {
46 result.Extra = strings.Join(ips, ", ")
47 } else if s.ShowCNAME {
48 cname, err := net.LookupCNAME(subdomain)
49 if err == nil {
50 result.Extra = cname
51 }
52 }
53 resultChan <- result
54 }
55 } else if s.Verbose {
56 result := Result{
57 Entity: subdomain,
58 Status: 404,
59 }
60 resultChan <- result
61 }
62 }
63
64 func PrintDnsResult(s *State, r *Result) {
65 output := ""
66 if r.Status == 404 {
67 output = fmt.Sprintf("Missing: %s\n", r.Entity)
68 } else if s.ShowIPs {
69 output = fmt.Sprintf("Found: %s [%s]\n", r.Entity, r.Extra)
70 } else if s.ShowCNAME {
71 output = fmt.Sprintf("Found: %s [%s]\n", r.Entity, r.Extra)
72 } else {
73 output = fmt.Sprintf("Found: %s\n", r.Entity)
74 }
75 fmt.Printf("%s", output)
76
77 if s.OutputFile != nil {
78 WriteToFile(output, s)
79 }
80 }
00 package libgobuster
11
22 import (
3 "bytes"
34 "fmt"
4 "os"
5 "os/signal"
6 "strconv"
5 "io"
6 "sort"
77 "strings"
88 )
99
10 func PrepareSignalHandler(s *State) {
11 s.SignalChan = make(chan os.Signal, 1)
12 signal.Notify(s.SignalChan, os.Interrupt)
13 go func() {
14 for _ = range s.SignalChan {
15 // caught CTRL+C
16 if !s.Quiet {
17 fmt.Println("[!] Keyboard interrupt detected, terminating.")
18 s.Terminate = true
19 }
20 }
21 }()
10 type intSet struct {
11 Set map[int]bool
2212 }
2313
24 func Ruler(s *State) {
25 if !s.Quiet {
26 fmt.Println("=====================================================")
27 }
14 type stringSet struct {
15 Set map[string]bool
2816 }
2917
30 func Banner(s *State) {
31 if s.Quiet {
32 return
33 }
34
35 fmt.Println("")
36 fmt.Println("Gobuster v1.4.1 OJ Reeves (@TheColonial)")
37 Ruler(s)
38 }
39
40 func ShowConfig(s *State) {
41 if s.Quiet {
42 return
43 }
44
45 if s != nil {
46 fmt.Printf("[+] Mode : %s\n", s.Mode)
47 fmt.Printf("[+] Url/Domain : %s\n", s.Url)
48 fmt.Printf("[+] Threads : %d\n", s.Threads)
49
50 wordlist := "stdin (pipe)"
51 if !s.StdIn {
52 wordlist = s.Wordlist
53 }
54 fmt.Printf("[+] Wordlist : %s\n", wordlist)
55
56 if s.OutputFileName != "" {
57 fmt.Printf("[+] Output file : %s\n", s.OutputFileName)
58 }
59
60 if s.Mode == "dir" {
61 fmt.Printf("[+] Status codes : %s\n", s.StatusCodes.Stringify())
62
63 if s.ProxyUrl != nil {
64 fmt.Printf("[+] Proxy : %s\n", s.ProxyUrl)
65 }
66
67 if s.Cookies != "" {
68 fmt.Printf("[+] Cookies : %s\n", s.Cookies)
69 }
70
71 if s.UserAgent != "" {
72 fmt.Printf("[+] User Agent : %s\n", s.UserAgent)
73 }
74
75 if s.IncludeLength {
76 fmt.Printf("[+] Show length : true\n")
77 }
78
79 if s.Username != "" {
80 fmt.Printf("[+] Auth User : %s\n", s.Username)
81 }
82
83 if len(s.Extensions) > 0 {
84 fmt.Printf("[+] Extensions : %s\n", strings.Join(s.Extensions, ","))
85 }
86
87 if s.UseSlash {
88 fmt.Printf("[+] Add Slash : true\n")
89 }
90
91 if s.FollowRedirect {
92 fmt.Printf("[+] Follow Redir : true\n")
93 }
94
95 if s.Expanded {
96 fmt.Printf("[+] Expanded : true\n")
97 }
98
99 if s.NoStatus {
100 fmt.Printf("[+] No status : true\n")
101 }
102
103 if s.Verbose {
104 fmt.Printf("[+] Verbose : true\n")
105 }
106 }
107
108 Ruler(s)
109 }
18 func newStringSet() stringSet {
19 return stringSet{Set: map[string]bool{}}
11020 }
11121
11222 // Add an element to a set
113 func (set *StringSet) Add(s string) bool {
23 func (set *stringSet) Add(s string) bool {
11424 _, found := set.Set[s]
11525 set.Set[s] = true
11626 return !found
11727 }
11828
11929 // Add a list of elements to a set
120 func (set *StringSet) AddRange(ss []string) {
30 func (set *stringSet) AddRange(ss []string) {
12131 for _, s := range ss {
12232 set.Set[s] = true
12333 }
12434 }
12535
12636 // Test if an element is in a set
127 func (set *StringSet) Contains(s string) bool {
37 func (set *stringSet) Contains(s string) bool {
12838 _, found := set.Set[s]
12939 return found
13040 }
13141
13242 // Check if any of the elements exist
133 func (set *StringSet) ContainsAny(ss []string) bool {
43 func (set *stringSet) ContainsAny(ss []string) bool {
13444 for _, s := range ss {
13545 if set.Set[s] {
13646 return true
14050 }
14151
14252 // Stringify the set
143 func (set *StringSet) Stringify() string {
53 func (set *stringSet) Stringify() string {
14454 values := []string{}
145 for s, _ := range set.Set {
55 for s := range set.Set {
14656 values = append(values, s)
14757 }
14858 return strings.Join(values, ",")
14959 }
15060
61 func newIntSet() intSet {
62 return intSet{Set: map[int]bool{}}
63 }
64
15165 // Add an element to a set
152 func (set *IntSet) Add(i int) bool {
66 func (set *intSet) Add(i int) bool {
15367 _, found := set.Set[i]
15468 set.Set[i] = true
15569 return !found
15670 }
15771
15872 // Test if an element is in a set
159 func (set *IntSet) Contains(i int) bool {
73 func (set *intSet) Contains(i int) bool {
16074 _, found := set.Set[i]
16175 return found
16276 }
16377
16478 // Stringify the set
165 func (set *IntSet) Stringify() string {
166 values := []string{}
167 for s, _ := range set.Set {
168 values = append(values, strconv.Itoa(s))
79 func (set *intSet) Stringify() string {
80 values := []int{}
81 for s := range set.Set {
82 values = append(values, s)
16983 }
170 return strings.Join(values, ",")
84 sort.Ints(values)
85
86 delim := ","
87 return strings.Trim(strings.Join(strings.Fields(fmt.Sprint(values)), delim), "[]")
17188 }
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 "net"
8 "os"
9 "strings"
10 "sync"
11 )
12
13 const (
14 // VERSION contains the current gobuster version
15 VERSION = "2.0.1"
16 )
17
18 // SetupFunc is the "setup" function prototype for implementations
19 type SetupFunc func(*Gobuster) error
20
21 // ProcessFunc is the "process" function prototype for implementations
22 type ProcessFunc func(*Gobuster, string) ([]Result, error)
23
24 // ResultToStringFunc is the "to string" function prototype for implementations
25 type ResultToStringFunc func(*Gobuster, *Result) (*string, error)
26
27 // Gobuster is the main object when creating a new run
28 type Gobuster struct {
29 Opts *Options
30 http *httpClient
31 WildcardIps stringSet
32 context context.Context
33 requestsExpected int
34 requestsIssued int
35 mu *sync.RWMutex
36 plugin GobusterPlugin
37 IsWildcard bool
38 resultChan chan Result
39 errorChan chan error
40 }
41
42 // GobusterPlugin is an interface which plugins must implement
43 type GobusterPlugin interface {
44 Setup(*Gobuster) error
45 Process(*Gobuster, string) ([]Result, error)
46 ResultToString(*Gobuster, *Result) (*string, error)
47 }
48
49 // NewGobuster returns a new Gobuster object
50 func NewGobuster(c context.Context, opts *Options, plugin GobusterPlugin) (*Gobuster, error) {
51 // validate given options
52 multiErr := opts.validate()
53 if multiErr != nil {
54 return nil, multiErr
55 }
56
57 var g Gobuster
58 g.WildcardIps = newStringSet()
59 g.context = c
60 g.Opts = opts
61 h, err := newHTTPClient(c, opts)
62 if err != nil {
63 return nil, err
64 }
65 g.http = h
66
67 g.plugin = plugin
68 g.mu = new(sync.RWMutex)
69
70 g.resultChan = make(chan Result)
71 g.errorChan = make(chan error)
72
73 return &g, nil
74 }
75
76 // Results returns a channel of Results
77 func (g *Gobuster) Results() <-chan Result {
78 return g.resultChan
79 }
80
81 // Errors returns a channel of errors
82 func (g *Gobuster) Errors() <-chan error {
83 return g.errorChan
84 }
85
86 func (g *Gobuster) incrementRequests() {
87 g.mu.Lock()
88 g.requestsIssued++
89 g.mu.Unlock()
90 }
91
92 // PrintProgress outputs the current wordlist progress to stderr
93 func (g *Gobuster) PrintProgress() {
94 if !g.Opts.Quiet && !g.Opts.NoProgress {
95 g.mu.RLock()
96 if g.Opts.Wordlist == "-" {
97 fmt.Fprintf(os.Stderr, "\rProgress: %d", g.requestsIssued)
98 // only print status if we already read in the wordlist
99 } else if g.requestsExpected > 0 {
100 fmt.Fprintf(os.Stderr, "\rProgress: %d / %d (%3.2f%%)", g.requestsIssued, g.requestsExpected, float32(g.requestsIssued)*100.0/float32(g.requestsExpected))
101 }
102 g.mu.RUnlock()
103 }
104 }
105
106 // ClearProgress removes the last status line from stderr
107 func (g *Gobuster) ClearProgress() {
108 fmt.Fprint(os.Stderr, resetTerminal())
109 }
110
111 // GetRequest issues a GET request to the target and returns
112 // the status code, length and an error
113 func (g *Gobuster) GetRequest(url string) (*int, *int64, error) {
114 return g.http.makeRequest(url, g.Opts.Cookies)
115 }
116
117 // DNSLookup looks up a domain via system default DNS servers
118 func (g *Gobuster) DNSLookup(domain string) ([]string, error) {
119 return net.LookupHost(domain)
120 }
121
122 // DNSLookupCname looks up a CNAME record via system default DNS servers
123 func (g *Gobuster) DNSLookupCname(domain string) (string, error) {
124 return net.LookupCNAME(domain)
125 }
126
127 func (g *Gobuster) worker(wordChan <-chan string, wg *sync.WaitGroup) {
128 defer wg.Done()
129 for {
130 select {
131 case <-g.context.Done():
132 return
133 case word, ok := <-wordChan:
134 // worker finished
135 if !ok {
136 return
137 }
138 g.incrementRequests()
139 // Mode-specific processing
140 res, err := g.plugin.Process(g, word)
141 if err != nil {
142 // do not exit and continue
143 g.errorChan <- err
144 continue
145 } else {
146 for _, r := range res {
147 g.resultChan <- r
148 }
149 }
150 }
151 }
152 }
153
154 func (g *Gobuster) getWordlist() (*bufio.Scanner, error) {
155 if g.Opts.Wordlist == "-" {
156 // Read directly from stdin
157 return bufio.NewScanner(os.Stdin), nil
158 }
159 // Pull content from the wordlist
160 wordlist, err := os.Open(g.Opts.Wordlist)
161 if err != nil {
162 return nil, fmt.Errorf("failed to open wordlist: %v", err)
163 }
164
165 lines, err := lineCounter(wordlist)
166 if err != nil {
167 return nil, fmt.Errorf("failed to get number of lines: %v", err)
168 }
169
170 g.requestsExpected = lines
171 g.requestsIssued = 0
172
173 // rewind wordlist
174 _, err = wordlist.Seek(0, 0)
175 if err != nil {
176 return nil, fmt.Errorf("failed to rewind wordlist: %v", err)
177 }
178 return bufio.NewScanner(wordlist), nil
179 }
180
181 // Start the busting of the website with the given
182 // set of settings from the command line.
183 func (g *Gobuster) Start() error {
184 if err := g.plugin.Setup(g); err != nil {
185 return err
186 }
187
188 var workerGroup sync.WaitGroup
189 workerGroup.Add(g.Opts.Threads)
190
191 wordChan := make(chan string, g.Opts.Threads)
192
193 // Create goroutines for each of the number of threads
194 // specified.
195 for i := 0; i < g.Opts.Threads; i++ {
196 go g.worker(wordChan, &workerGroup)
197 }
198
199 scanner, err := g.getWordlist()
200 if err != nil {
201 return err
202 }
203
204 Scan:
205 for scanner.Scan() {
206 select {
207 case <-g.context.Done():
208 break Scan
209 default:
210 word := strings.TrimSpace(scanner.Text())
211 // Skip "comment" (starts with #), as well as empty lines
212 if !strings.HasPrefix(word, "#") && len(word) > 0 {
213 wordChan <- word
214 }
215 }
216 }
217 close(wordChan)
218 workerGroup.Wait()
219 close(g.resultChan)
220 close(g.errorChan)
221 return nil
222 }
223
224 // GetConfigString returns the current config as a printable string
225 func (g *Gobuster) GetConfigString() (string, error) {
226 buf := &bytes.Buffer{}
227 o := g.Opts
228 if _, err := fmt.Fprintf(buf, "[+] Mode : %s\n", o.Mode); err != nil {
229 return "", err
230 }
231 if _, err := fmt.Fprintf(buf, "[+] Url/Domain : %s\n", o.URL); err != nil {
232 return "", err
233 }
234 if _, err := fmt.Fprintf(buf, "[+] Threads : %d\n", o.Threads); err != nil {
235 return "", err
236 }
237
238 wordlist := "stdin (pipe)"
239 if o.Wordlist != "-" {
240 wordlist = o.Wordlist
241 }
242 if _, err := fmt.Fprintf(buf, "[+] Wordlist : %s\n", wordlist); err != nil {
243 return "", err
244 }
245
246 if o.Mode == ModeDir {
247 if _, err := fmt.Fprintf(buf, "[+] Status codes : %s\n", o.StatusCodesParsed.Stringify()); err != nil {
248 return "", err
249 }
250
251 if o.Proxy != "" {
252 if _, err := fmt.Fprintf(buf, "[+] Proxy : %s\n", o.Proxy); err != nil {
253 return "", err
254 }
255 }
256
257 if o.Cookies != "" {
258 if _, err := fmt.Fprintf(buf, "[+] Cookies : %s\n", o.Cookies); err != nil {
259 return "", err
260 }
261 }
262
263 if o.UserAgent != "" {
264 if _, err := fmt.Fprintf(buf, "[+] User Agent : %s\n", o.UserAgent); err != nil {
265 return "", err
266 }
267 }
268
269 if o.IncludeLength {
270 if _, err := fmt.Fprintf(buf, "[+] Show length : true\n"); err != nil {
271 return "", err
272 }
273 }
274
275 if o.Username != "" {
276 if _, err := fmt.Fprintf(buf, "[+] Auth User : %s\n", o.Username); err != nil {
277 return "", err
278 }
279 }
280
281 if len(o.Extensions) > 0 {
282 if _, err := fmt.Fprintf(buf, "[+] Extensions : %s\n", o.ExtensionsParsed.Stringify()); err != nil {
283 return "", err
284 }
285 }
286
287 if o.UseSlash {
288 if _, err := fmt.Fprintf(buf, "[+] Add Slash : true\n"); err != nil {
289 return "", err
290 }
291 }
292
293 if o.FollowRedirect {
294 if _, err := fmt.Fprintf(buf, "[+] Follow Redir : true\n"); err != nil {
295 return "", err
296 }
297 }
298
299 if o.Expanded {
300 if _, err := fmt.Fprintf(buf, "[+] Expanded : true\n"); err != nil {
301 return "", err
302 }
303 }
304
305 if o.NoStatus {
306 if _, err := fmt.Fprintf(buf, "[+] No status : true\n"); err != nil {
307 return "", err
308 }
309 }
310
311 if o.Verbose {
312 if _, err := fmt.Fprintf(buf, "[+] Verbose : true\n"); err != nil {
313 return "", err
314 }
315 }
316
317 if _, err := fmt.Fprintf(buf, "[+] Timeout : %s\n", o.Timeout.String()); err != nil {
318 return "", err
319 }
320 }
321
322 return strings.TrimSpace(buf.String()), nil
323 }
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
-8
libgobuster/output.go less more
0 package libgobuster
1
2 func WriteToFile(output string, s *State) {
3 _, err := s.OutputFile.WriteString(output)
4 if err != nil {
5 panic("[!] Unable to write to file " + s.OutputFileName)
6 }
7 }
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 }
+0
-178
libgobuster/state.go less more
0 package libgobuster
1
2 import (
3 "bufio"
4 "fmt"
5 "net/http"
6 "net/url"
7 "os"
8 "strings"
9 "sync"
10 )
11
12 // A single result which comes from an individual web
13 // request.
14 type Result struct {
15 Entity string
16 Status int
17 Extra string
18 Size *int64
19 }
20
21 type PrintResultFunc func(s *State, r *Result)
22 type ProcessorFunc func(s *State, entity string, resultChan chan<- Result)
23 type SetupFunc func(s *State) bool
24
25 // Shim type for "set" containing ints
26 type IntSet struct {
27 Set map[int]bool
28 }
29
30 // Shim type for "set" containing strings
31 type StringSet struct {
32 Set map[string]bool
33 }
34
35 // Contains State that are read in from the command
36 // line when the program is invoked.
37 type State struct {
38 Client *http.Client
39 Cookies string
40 Expanded bool
41 Extensions []string
42 FollowRedirect bool
43 IncludeLength bool
44 Mode string
45 NoStatus bool
46 Password string
47 Printer PrintResultFunc
48 Processor ProcessorFunc
49 ProxyUrl *url.URL
50 Quiet bool
51 Setup SetupFunc
52 ShowIPs bool
53 ShowCNAME bool
54 StatusCodes IntSet
55 Threads int
56 Url string
57 UseSlash bool
58 UserAgent string
59 Username string
60 Verbose bool
61 Wordlist string
62 OutputFileName string
63 OutputFile *os.File
64 IsWildcard bool
65 WildcardForced bool
66 WildcardIps StringSet
67 SignalChan chan os.Signal
68 Terminate bool
69 StdIn bool
70 InsecureSSL bool
71 }
72
73 // Process the busting of the website with the given
74 // set of settings from the command line.
75 func Process(s *State) {
76
77 ShowConfig(s)
78
79 if s.Setup(s) == false {
80 Ruler(s)
81 return
82 }
83
84 PrepareSignalHandler(s)
85
86 // channels used for comms
87 wordChan := make(chan string, s.Threads)
88 resultChan := make(chan Result)
89
90 // Use a wait group for waiting for all threads
91 // to finish
92 processorGroup := new(sync.WaitGroup)
93 processorGroup.Add(s.Threads)
94 printerGroup := new(sync.WaitGroup)
95 printerGroup.Add(1)
96
97 // Create goroutines for each of the number of threads
98 // specified.
99 for i := 0; i < s.Threads; i++ {
100 go func() {
101 for {
102 word := <-wordChan
103
104 // Did we reach the end? If so break.
105 if word == "" {
106 break
107 }
108
109 // Mode-specific processing
110 s.Processor(s, word, resultChan)
111 }
112
113 // Indicate to the wait group that the thread
114 // has finished.
115 processorGroup.Done()
116 }()
117 }
118
119 // Single goroutine which handles the results as they
120 // appear from the worker threads.
121 go func() {
122 for r := range resultChan {
123 s.Printer(s, &r)
124 }
125 printerGroup.Done()
126 }()
127
128 var scanner *bufio.Scanner
129
130 if s.StdIn {
131 // Read directly from stdin
132 scanner = bufio.NewScanner(os.Stdin)
133 } else {
134 // Pull content from the wordlist
135 wordlist, err := os.Open(s.Wordlist)
136 if err != nil {
137 panic("Failed to open wordlist")
138 }
139 defer wordlist.Close()
140
141 // Lazy reading of the wordlist line by line
142 scanner = bufio.NewScanner(wordlist)
143 }
144
145 var outputFile *os.File
146 if s.OutputFileName != "" {
147 outputFile, err := os.Create(s.OutputFileName)
148 if err != nil {
149 fmt.Printf("[!] Unable to write to %s, falling back to stdout.\n", s.OutputFileName)
150 s.OutputFileName = ""
151 s.OutputFile = nil
152 } else {
153 s.OutputFile = outputFile
154 }
155 }
156
157 for scanner.Scan() {
158 if s.Terminate {
159 break
160 }
161 word := strings.TrimSpace(scanner.Text())
162
163 // Skip "comment" (starts with #), as well as empty lines
164 if !strings.HasPrefix(word, "#") && len(word) > 0 {
165 wordChan <- word
166 }
167 }
168
169 close(wordChan)
170 processorGroup.Wait()
171 close(resultChan)
172 printerGroup.Wait()
173 if s.OutputFile != nil {
174 outputFile.Close()
175 }
176 Ruler(s)
177 }
+0
-205
libgobuster/statehelpers.go less more
0 package libgobuster
1
2 //----------------------------------------------------
3 // Gobuster -- by OJ Reeves
4 //
5 // A crap attempt at building something that resembles
6 // dirbuster or dirb using Go. The goal was to build
7 // a tool that would help learn Go and to actually do
8 // something useful. The idea of having this compile
9 // to native code is also appealing.
10 //
11 // Run: gobuster -h
12 //
13 // Please see THANKS file for contributors.
14 // Please see LICENSE file for license details.
15 //
16 //----------------------------------------------------
17
18 import (
19 "crypto/tls"
20 "fmt"
21 "github.com/hashicorp/go-multierror"
22 "golang.org/x/crypto/ssh/terminal"
23 "net/http"
24 "net/url"
25 "os"
26 "regexp"
27 "strconv"
28 "strings"
29 "syscall"
30 )
31
32 func InitState() State {
33 return State{
34 StatusCodes: IntSet{Set: map[int]bool{}},
35 WildcardIps: StringSet{Set: map[string]bool{}},
36 IsWildcard: false,
37 StdIn: false,
38 }
39 }
40
41 func ValidateState(
42 s *State,
43 extensions string,
44 codes string,
45 proxy string) *multierror.Error {
46
47 var errorList *multierror.Error
48
49 switch strings.ToLower(s.Mode) {
50 case "dir":
51 s.Printer = PrintDirResult
52 s.Processor = ProcessDirEntry
53 s.Setup = SetupDir
54 case "dns":
55 s.Printer = PrintDnsResult
56 s.Processor = ProcessDnsEntry
57 s.Setup = SetupDns
58 default:
59 errorList = multierror.Append(errorList, fmt.Errorf("[!] Mode (-m): Invalid value: %s", s.Mode))
60 }
61
62 if s.Threads < 0 {
63 errorList = multierror.Append(errorList, fmt.Errorf("[!] Threads (-t): Invalid value: %s", s.Threads))
64 }
65
66 stdin, err := os.Stdin.Stat()
67 if err != nil {
68 fmt.Println("[!] Unable to stat stdin, falling back to wordlist file.")
69 } else if (stdin.Mode()&os.ModeCharDevice) == 0 && stdin.Size() > 0 {
70 s.StdIn = true
71 }
72
73 if !s.StdIn {
74 if s.Wordlist == "" {
75 errorList = multierror.Append(errorList, fmt.Errorf("[!] WordList (-w): Must be specified"))
76 } else if _, err := os.Stat(s.Wordlist); os.IsNotExist(err) {
77 errorList = multierror.Append(errorList, fmt.Errorf("[!] Wordlist (-w): File does not exist: %s", s.Wordlist))
78 }
79 } else if s.Wordlist != "" {
80 errorList = multierror.Append(errorList, fmt.Errorf("[!] Wordlist (-w) specified with pipe from stdin. Can't have both!"))
81 }
82
83 if s.Url == "" {
84 errorList = multierror.Append(errorList, fmt.Errorf("[!] Url/Domain (-u): Must be specified"))
85 }
86
87 if s.Mode == "dir" {
88 if err := ValidateDirModeState(s, extensions, codes, proxy, errorList); err.ErrorOrNil() != nil {
89 errorList = err
90 }
91 }
92
93 return errorList
94 }
95
96 func ValidateDirModeState(
97 s *State,
98 extensions string,
99 codes string,
100 proxy string,
101 previousErrors *multierror.Error) *multierror.Error {
102
103 // If we had previous errors, copy them into the current errorList.
104 // This is an easier to understand solution compared to double pointer black magick
105 var errorList *multierror.Error
106 if previousErrors.ErrorOrNil() != nil {
107 errorList = multierror.Append(errorList, previousErrors)
108 }
109
110 if strings.HasSuffix(s.Url, "/") == false {
111 s.Url = s.Url + "/"
112 }
113
114 if strings.HasPrefix(s.Url, "http") == false {
115 // check to see if a port was specified
116 re := regexp.MustCompile(`^[^/]+:(\d+)`)
117 match := re.FindStringSubmatch(s.Url)
118
119 if len(match) < 2 {
120 // no port, default to http on 80
121 s.Url = "http://" + s.Url
122 } else {
123 port, err := strconv.Atoi(match[1])
124 if err != nil || (port != 80 && port != 443) {
125 errorList = multierror.Append(errorList, fmt.Errorf("[!] Url/Domain (-u): Scheme not specified."))
126 } else if port == 80 {
127 s.Url = "http://" + s.Url
128 } else {
129 s.Url = "https://" + s.Url
130 }
131 }
132 }
133
134 // extensions are comma separated
135 if extensions != "" {
136 s.Extensions = strings.Split(extensions, ",")
137 for i := range s.Extensions {
138 if s.Extensions[i][0] != '.' {
139 s.Extensions[i] = "." + s.Extensions[i]
140 }
141 }
142 }
143
144 // status codes are comma separated
145 if codes != "" {
146 for _, c := range strings.Split(codes, ",") {
147 i, err := strconv.Atoi(c)
148 if err != nil {
149 errorList = multierror.Append(errorList, fmt.Errorf("[!] Invalid status code given: %s", c))
150 } else {
151 s.StatusCodes.Add(i)
152 }
153 }
154 }
155
156 // prompt for password if needed
157 if errorList.ErrorOrNil() == nil && s.Username != "" && s.Password == "" {
158 fmt.Printf("[?] Auth Password: ")
159 passBytes, err := terminal.ReadPassword(int(syscall.Stdin))
160
161 // print a newline to simulate the newline that was entered
162 // this means that formatting/printing after doesn't look bad.
163 fmt.Println("")
164
165 if err == nil {
166 s.Password = string(passBytes)
167 } else {
168 errorList = multierror.Append(errorList, fmt.Errorf("[!] Auth username given but reading of password failed"))
169 }
170 }
171
172 if errorList.ErrorOrNil() == nil {
173 var proxyUrlFunc func(*http.Request) (*url.URL, error)
174 proxyUrlFunc = http.ProxyFromEnvironment
175
176 if proxy != "" {
177 proxyUrl, err := url.Parse(proxy)
178 if err != nil {
179 errorList = multierror.Append(errorList, fmt.Errorf("[!] Proxy URL is invalid"))
180 panic("[!] Proxy URL is invalid") // TODO: Does this need to be a panic? Could be a standard error?
181 }
182 s.ProxyUrl = proxyUrl
183 proxyUrlFunc = http.ProxyURL(s.ProxyUrl)
184 }
185
186 s.Client = &http.Client{
187 Transport: &RedirectHandler{
188 State: s,
189 Transport: &http.Transport{
190 Proxy: proxyUrlFunc,
191 TLSClientConfig: &tls.Config{
192 InsecureSkipVerify: s.InsecureSSL,
193 },
194 },
195 }}
196
197 code, _ := GoGet(s, s.Url, "", s.Cookies)
198 if code == nil {
199 errorList = multierror.Append(errorList, fmt.Errorf("[-] Unable to connect: %s", s.Url))
200 }
201 }
202
203 return errorList
204 }
1616 //----------------------------------------------------
1717
1818 import (
19 "context"
1920 "flag"
2021 "fmt"
21
22 "log"
23 "os"
24 "os/signal"
25 "strings"
26 "sync"
27 "syscall"
28 "time"
29
30 "github.com/OJ/gobuster/gobusterdir"
31 "github.com/OJ/gobuster/gobusterdns"
2232 "github.com/OJ/gobuster/libgobuster"
33 "golang.org/x/crypto/ssh/terminal"
2334 )
2435
25 // Parse all the command line options into a settings
26 // instance for future use.
27 func ParseCmdLine() *libgobuster.State {
28 var extensions string
29 var codes string
30 var proxy string
31
32 s := libgobuster.InitState()
33
34 // Set up the variables we're interested in parsing.
35 flag.IntVar(&s.Threads, "t", 10, "Number of concurrent threads")
36 flag.StringVar(&s.Mode, "m", "dir", "Directory/File mode (dir) or DNS mode (dns)")
37 flag.StringVar(&s.Wordlist, "w", "", "Path to the wordlist")
38 flag.StringVar(&codes, "s", "200,204,301,302,307", "Positive status codes (dir mode only)")
39 flag.StringVar(&s.OutputFileName, "o", "", "Output file to write results to (defaults to stdout)")
40 flag.StringVar(&s.Url, "u", "", "The target URL or Domain")
41 flag.StringVar(&s.Cookies, "c", "", "Cookies to use for the requests (dir mode only)")
42 flag.StringVar(&s.Username, "U", "", "Username for Basic Auth (dir mode only)")
43 flag.StringVar(&s.Password, "P", "", "Password for Basic Auth (dir mode only)")
44 flag.StringVar(&extensions, "x", "", "File extension(s) to search for (dir mode only)")
45 flag.StringVar(&s.UserAgent, "a", "", "Set the User-Agent string (dir mode only)")
46 flag.StringVar(&proxy, "p", "", "Proxy to use for requests [http(s)://host:port] (dir mode only)")
47 flag.BoolVar(&s.Verbose, "v", false, "Verbose output (errors)")
48 flag.BoolVar(&s.ShowIPs, "i", false, "Show IP addresses (dns mode only)")
49 flag.BoolVar(&s.ShowCNAME, "cn", false, "Show CNAME records (dns mode only, cannot be used with '-i' option)")
50 flag.BoolVar(&s.FollowRedirect, "r", false, "Follow redirects")
51 flag.BoolVar(&s.Quiet, "q", false, "Don't print the banner and other noise")
52 flag.BoolVar(&s.Expanded, "e", false, "Expanded mode, print full URLs")
53 flag.BoolVar(&s.NoStatus, "n", false, "Don't print status codes")
54 flag.BoolVar(&s.IncludeLength, "l", false, "Include the length of the body in the output (dir mode only)")
55 flag.BoolVar(&s.UseSlash, "f", false, "Append a forward-slash to each directory request (dir mode only)")
56 flag.BoolVar(&s.WildcardForced, "fw", false, "Force continued operation when wildcard found")
57 flag.BoolVar(&s.InsecureSSL, "k", false, "Skip SSL certificate verification")
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)
67 }
68 }
69 }
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")
58132
59133 flag.Parse()
60134
61 libgobuster.Banner(&s)
62
63 if err := libgobuster.ValidateState(&s, extensions, codes, proxy); err.ErrorOrNil() != nil {
64 fmt.Printf("%s\n", err.Error())
65 return nil
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.")
186 }
187 cancel()
188 }
189 }()
190
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)
66202 } else {
67 libgobuster.Ruler(&s)
68 return &s
69 }
70 }
71
72 func main() {
73 state := ParseCmdLine()
74 if state != nil {
75 libgobuster.Process(state)
76 }
77 }
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
2 SET ARG=%1
3 SET TARGET=.\build
4
5 IF "%ARG%"=="test" (
6 go test -v -race ./...
7 echo Done.
8 GOTO Done
9 )
10
11 IF "%ARG%"=="clean" (
12 del /F /Q %TARGET%\*.*
13 echo Done.
14 GOTO Done
15 )
16
17 IF "%ARG%"=="windows" (
18 CALL :Windows
19 GOTO Done
20 )
21
22 IF "%ARG%"=="darwin" (
23 CALL :Darwin
24 GOTO Done
25 )
26
27 IF "%ARG%"=="linux" (
28 CALL :Linux
29 GOTO Done
30 )
31
32 IF "%ARG%"=="all" (
33 CALL :Darwin
34 CALL :Linux
35 CALL :Windows
36 GOTO Done
37 )
38
39 IF "%ARG%"=="" (
40 go build -o .\gobuster.exe
41 GOTO Done
42 )
43
44 GOTO Done
45
46 :Darwin
47 set GOOS=darwin
48 set GOARCH=amd64
49 echo Building for %GOOS% %GOARCH% ...
50 go build -o %TARGET%\gobuster-%GOOS%-%GOARCH%
51 set GOARCH=386
52 echo Building for %GOOS% %GOARCH% ...
53 go build -o %TARGET%\gobuster-%GOOS%-%GOARCH%
54 echo Done.
55 EXIT /B 0
56
57 :Linux
58 set GOOS=linux
59 set GOARCH=amd64
60 echo Building for %GOOS% %GOARCH% ...
61 go build -o %TARGET%\gobuster-%GOOS%-%GOARCH%
62 set GOARCH=386
63 echo Building for %GOOS% %GOARCH% ...
64 go build -o %TARGET%\gobuster-%GOOS%-%GOARCH%
65 echo Done.
66 EXIT /B 0
67
68 :Windows
69 set GOOS=windows
70 set GOARCH=amd64
71 echo Building for %GOOS% %GOARCH% ...
72 go build -o %TARGET%\gobuster-%GOARCH%.exe
73 set GOARCH=386
74 echo Building for %GOOS% %GOARCH% ...
75 go build -o %TARGET%\gobuster-%GOARCH%.exe
76 echo Done.
77 EXIT /B 0
78
79 :Done