New upstream version 3.2.0
Sophie Brun
1 year, 7 months ago
0 | 0 | # These are supported funding model platforms |
1 | 1 | |
2 | github: OJ | |
2 | github: [OJ, firefart] | |
3 | 3 | patreon: OJReeves |
4 | 4 | open_collective: gobuster |
5 | 5 | ko_fi: OJReeves |
0 | # To get started with Dependabot version updates, you'll need to specify which | |
1 | # package ecosystems to update and where the package manifests are located. | |
2 | # Please see the documentation for all configuration options: | |
3 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates | |
4 | ||
5 | version: 2 | |
6 | updates: | |
7 | - package-ecosystem: "gomod" | |
8 | directory: "/" | |
9 | schedule: | |
10 | interval: "weekly" | |
11 | ||
12 | - package-ecosystem: "github-actions" | |
13 | directory: "/" | |
14 | schedule: | |
15 | # Check for updates to GitHub Actions every weekday | |
16 | interval: "daily" |
5 | 5 | runs-on: ubuntu-latest |
6 | 6 | strategy: |
7 | 7 | matrix: |
8 | go: ["1.13.10", "1.14.2"] | |
8 | go: ["1.18", "1.19"] | |
9 | 9 | steps: |
10 | 10 | - name: Set up Go ${{ matrix.go }} |
11 | uses: actions/setup-go@v1 | |
11 | uses: actions/setup-go@v3 | |
12 | 12 | with: |
13 | 13 | go-version: ${{ matrix.go }} |
14 | id: go | |
15 | 14 | |
16 | - name: Check out code into the Go module directory | |
17 | uses: actions/checkout@v2 | |
15 | - name: Check out code | |
16 | uses: actions/[email protected] | |
17 | ||
18 | - name: build cache | |
19 | uses: actions/cache@v3 | |
20 | with: | |
21 | path: ~/go/pkg/mod | |
22 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} | |
23 | restore-keys: | | |
24 | ${{ runner.os }}-go- | |
18 | 25 | |
19 | 26 | - name: Get dependencies |
20 | 27 | run: | |
21 | 28 | go get -v -t -d ./... |
22 | if [ -f Gopkg.toml ]; then | |
23 | curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh | |
24 | dep ensure | |
25 | fi | |
26 | 29 | |
27 | - name: Build | |
28 | run: go build -v . | |
30 | - name: Build linux | |
31 | run: make linux | |
32 | ||
33 | - name: Build windows | |
34 | run: make windows | |
29 | 35 | |
30 | 36 | - name: Test |
31 | 37 | run: make test |
32 | ||
33 | - name: Lint | |
34 | run: make lint |
0 | name: golangci-lint | |
1 | on: [push, pull_request] | |
2 | jobs: | |
3 | golangci: | |
4 | name: lint | |
5 | runs-on: ubuntu-latest | |
6 | steps: | |
7 | - uses: actions/[email protected] | |
8 | ||
9 | - uses: actions/setup-go@v3 | |
10 | with: | |
11 | go-version: "^1.19" | |
12 | ||
13 | - name: golangci-lint | |
14 | uses: golangci/golangci-lint-action@v3 | |
15 | with: | |
16 | version: latest |
0 | name: goreleaser | |
1 | ||
2 | on: | |
3 | push: | |
4 | tags: | |
5 | - "*" | |
6 | ||
7 | permissions: | |
8 | contents: write | |
9 | ||
10 | jobs: | |
11 | goreleaser: | |
12 | runs-on: ubuntu-latest | |
13 | steps: | |
14 | - name: Checkout | |
15 | uses: actions/[email protected] | |
16 | with: | |
17 | fetch-depth: 0 | |
18 | - name: Fetch all tags | |
19 | run: git fetch --force --tags | |
20 | - name: Set up Go | |
21 | uses: actions/setup-go@v3 | |
22 | with: | |
23 | go-version: 1.19 | |
24 | - name: Run GoReleaser | |
25 | uses: goreleaser/goreleaser-action@v3 | |
26 | with: | |
27 | distribution: goreleaser | |
28 | version: latest | |
29 | args: release --rm-dist | |
30 | env: | |
31 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |
0 | # Compiled Object files, Static and Dynamic libs (Shared Objects) | |
1 | *.o | |
2 | *.a | |
0 | # Binaries for programs and plugins | |
1 | *.exe | |
2 | *.exe~ | |
3 | *.dll | |
3 | 4 | *.so |
5 | *.dylib | |
4 | 6 | |
5 | # Folders | |
6 | _obj | |
7 | _test | |
7 | # Test binary, built with `go test -c` | |
8 | *.test | |
8 | 9 | |
9 | # Architecture specific extensions/prefixes | |
10 | *.[568vq] | |
11 | [568vq].out | |
10 | # Output of the go coverage tool, specifically when used with LiteIDE | |
11 | *.out | |
12 | 12 | |
13 | *.cgo1.go | |
14 | *.cgo2.c | |
15 | _cgo_defun.c | |
16 | _cgo_gotypes.go | |
17 | _cgo_export.* | |
13 | # Dependency directories (remove the comment below to include it) | |
14 | # vendor/ | |
18 | 15 | |
19 | _testmain.go | |
20 | ||
21 | *.exe | |
22 | *.test | |
23 | 16 | *.prof |
24 | 17 | *.txt |
25 | 18 | *.swp |
27 | 20 | .vscode/ |
28 | 21 | gobuster |
29 | 22 | build |
30 | v3 | |
31 | 23 | |
32 | .idea/ | |
24 | dist/ |
0 | 0 | linters: |
1 | enable-all: true | |
2 | disable: | |
3 | - wsl | |
4 | - gocyclo | |
5 | - gocognit | |
6 | - funlen | |
7 | - lll | |
8 | - dogsled | |
9 | - gomnd | |
10 | - nestif | |
11 | - testpackage | |
12 | - godot | |
13 | - goerr113 | |
14 | - gofumpt | |
15 | ||
16 | issues: | |
17 | exclude-rules: | |
18 | - text: "TLS InsecureSkipVerify may be true" | |
19 | linters: | |
20 | - gosec | |
21 | ||
22 | - text: ifElseChain | |
23 | linters: | |
24 | - gocritic | |
25 | ||
26 | - path: cli\\cmd\\.+\.go | |
27 | linters: | |
28 | - gochecknoinits | |
29 | - gochecknoglobals | |
30 | ||
31 | - path: cli/cmd/.+\.go | |
32 | linters: | |
33 | - gochecknoinits | |
34 | - gochecknoglobals | |
35 | ||
36 | - path: helper/useragents.go | |
37 | linters: | |
38 | - gochecknoglobals | |
39 | ||
40 | - path: _test\.go | |
41 | linters: | |
42 | - scopelint | |
1 | enable: | |
2 | - nonamedreturns |
0 | # This is an example .goreleaser.yml file with some sensible defaults. | |
1 | # Make sure to check the documentation at https://goreleaser.com | |
2 | before: | |
3 | hooks: | |
4 | # You may remove this if you don't use go modules. | |
5 | - go mod tidy | |
6 | # you may remove this if you don't need go generate | |
7 | - go generate ./... | |
8 | builds: | |
9 | - env: | |
10 | - CGO_ENABLED=0 | |
11 | goos: | |
12 | - linux | |
13 | - windows | |
14 | - darwin | |
15 | archives: | |
16 | - format: tar.gz | |
17 | format_overrides: | |
18 | - goos: windows | |
19 | format: zip | |
20 | replacements: | |
21 | darwin: Darwin | |
22 | linux: Linux | |
23 | windows: Windows | |
24 | 386: i386 | |
25 | amd64: x86_64 | |
26 | checksum: | |
27 | name_template: "checksums.txt" | |
28 | snapshot: | |
29 | name_template: "{{ incpatch .Version }}-dev" | |
30 | changelog: | |
31 | sort: asc | |
32 | filters: | |
33 | exclude: | |
34 | - "^docs:" | |
35 | - "^test:" |
0 | 0 | FROM golang:latest AS build-env |
1 | 1 | WORKDIR /src |
2 | ENV GO111MODULE=on | |
2 | ENV CGO_ENABLED=0 | |
3 | 3 | COPY go.mod /src/ |
4 | 4 | RUN go mod download |
5 | 5 | COPY . . |
6 | RUN CGO_ENABLED=0 GOOS=linux go build -a -o gobuster -ldflags="-s -w" -gcflags="all=-trimpath=/src" -asmflags="all=-trimpath=/src" | |
6 | RUN go build -a -o gobuster -ldflags="-s -w" -gcflags="all=-trimpath=/src" -asmflags="all=-trimpath=/src" | |
7 | 7 | |
8 | 8 | FROM alpine:latest |
9 | 9 |
0 | TARGET=./build | |
1 | ARCHS=amd64 386 | |
2 | LDFLAGS="-s -w" | |
0 | .DEFAULT_GOAL := linux | |
3 | 1 | |
4 | current: | |
5 | @go build -o ./gobuster; \ | |
6 | echo "Done." | |
2 | .PHONY: linux | |
3 | linux: | |
4 | go build -o ./gobuster | |
7 | 5 | |
6 | .PHONY: windows | |
7 | windows: | |
8 | GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build -o ./gobuster.exe | |
9 | ||
10 | .PHONY: fmt | |
8 | 11 | fmt: |
9 | @go fmt ./...; \ | |
10 | echo "Done." | |
12 | go fmt ./... | |
11 | 13 | |
14 | .PHONY: update | |
12 | 15 | update: |
13 | @go get -u; \ | |
14 | go mod tidy -v; \ | |
15 | echo "Done." | |
16 | go get -u | |
17 | go mod tidy -v | |
16 | 18 | |
17 | windows: | |
18 | @for GOARCH in ${ARCHS}; do \ | |
19 | echo "Building for windows $${GOARCH} ..." ; \ | |
20 | mkdir -p ${TARGET}/gobuster-windows-$${GOARCH} ; \ | |
21 | GOOS=windows GOARCH=$${GOARCH} GO111MODULE=on CGO_ENABLED=0 go build -ldflags=${LDFLAGS} -trimpath -o ${TARGET}/gobuster-windows-$${GOARCH}/gobuster.exe ; \ | |
22 | done; \ | |
23 | echo "Done." | |
19 | .PHONY: all | |
20 | all: fmt update linux windows test lint | |
24 | 21 | |
25 | linux: | |
26 | @for GOARCH in ${ARCHS}; do \ | |
27 | echo "Building for linux $${GOARCH} ..." ; \ | |
28 | mkdir -p ${TARGET}/gobuster-linux-$${GOARCH} ; \ | |
29 | GOOS=linux GOARCH=$${GOARCH} GO111MODULE=on CGO_ENABLED=0 go build -ldflags=${LDFLAGS} -trimpath -o ${TARGET}/gobuster-linux-$${GOARCH}/gobuster ; \ | |
30 | done; \ | |
31 | echo "Done." | |
22 | .PHONY: test | |
23 | test: | |
24 | go test -v -race ./... | |
32 | 25 | |
33 | darwin: | |
34 | @for GOARCH in ${ARCHS}; do \ | |
35 | echo "Building for darwin $${GOARCH} ..." ; \ | |
36 | mkdir -p ${TARGET}/gobuster-darwin-$${GOARCH} ; \ | |
37 | GOOS=darwin GOARCH=$${GOARCH} GO111MODULE=on CGO_ENABLED=0 go build -ldflags=${LDFLAGS} -trimpath -o ${TARGET}/gobuster-darwin-$${GOARCH}/gobuster ; \ | |
38 | done; \ | |
39 | echo "Done." | |
40 | ||
41 | all: clean fmt update test lint darwin linux windows | |
42 | ||
43 | test: | |
44 | @go test -v -race ./... ; \ | |
45 | echo "Done." | |
46 | ||
26 | .PHONY: lint | |
47 | 27 | lint: |
48 | @if [ ! -f "$$(go env GOPATH)/bin/golangci-lint" ]; then \ | |
49 | curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $$(go env GOPATH)/bin v1.29.0; \ | |
50 | fi | |
51 | 28 | "$$(go env GOPATH)/bin/golangci-lint" run ./... |
52 | 29 | go mod tidy |
53 | 30 | |
54 | clean: | |
55 | @rm -rf ${TARGET}/* ; \ | |
56 | go clean ./... ; \ | |
57 | echo "Done." | |
31 | .PHONY: lint-update | |
32 | lint-update: | |
33 | curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $$(go env GOPATH)/bin | |
34 | $$(go env GOPATH)/bin/golangci-lint --version | |
35 | ||
36 | .PHONY: tag | |
37 | tag: | |
38 | @[ "${TAG}" ] && echo "Tagging a new version ${TAG}" || ( echo "TAG is not set"; exit 1 ) | |
39 | git tag -a "${TAG}" -m "${TAG}" | |
40 | git push origin "${TAG}" |
0 | # Gobuster v3.1.0 | |
0 | # Gobuster v3.2.0 | |
1 | 1 | |
2 | 2 | Gobuster is a tool used to brute-force: |
3 | 3 | |
4 | * URIs (directories and files) in web sites. | |
5 | * DNS subdomains (with wildcard support). | |
6 | * Virtual Host names on target web servers. | |
7 | * Open Amazon S3 buckets | |
4 | - URIs (directories and files) in web sites. | |
5 | - DNS subdomains (with wildcard support). | |
6 | - Virtual Host names on target web servers. | |
7 | - Open Amazon S3 buckets | |
8 | 8 | |
9 | 9 | ## Tags, Statuses, etc |
10 | 10 | |
11 | [![Build Status](https://travis-ci.com/OJ/gobuster.svg?branch=master)](https://travis-ci.com/OJ/gobuster) [![Backers on Open Collective](https://opencollective.com/gobuster/backers/badge.svg)] [![Sponsors on Open Collective](https://opencollective.com/gobuster/sponsors/badge.svg)] | |
12 | ||
13 | ## Oh dear God.. WHY!? | |
14 | ||
15 | Because I wanted: | |
16 | ||
17 | 1. ... something that didn't have a fat Java GUI (console FTW). | |
18 | 1. ... to build something that just worked on the command line. | |
19 | 1. ... something that did not do recursive brute force. | |
20 | 1. ... something that allowed me to brute force folders and multiple extensions at once. | |
21 | 1. ... something that compiled to native on multiple platforms. | |
22 | 1. ... something that was faster than an interpreted script (such as Python). | |
23 | 1. ... something that didn't require a runtime. | |
24 | 1. ... use something that was good with concurrency (hence Go). | |
25 | 1. ... to build something in Go that wasn't totally useless. | |
26 | ||
27 | ## But it's shit! And your implementation sucks! | |
28 | ||
29 | Yes, you're probably correct. Feel free to: | |
30 | ||
31 | * Not use it. | |
32 | * Show me how to do it better. | |
11 | [![Build Status](https://travis-ci.com/OJ/gobuster.svg?branch=master)](https://travis-ci.com/OJ/gobuster) [![Backers on Open Collective](https://opencollective.com/gobuster/backers/badge.svg)](https://opencollective.com/gobuster) [![Sponsors on Open Collective](https://opencollective.com/gobuster/sponsors/badge.svg)](https://opencollective.com/gobuster) | |
12 | ||
33 | 13 | |
34 | 14 | ## Love this tool? Back it! |
35 | 15 | |
39 | 19 | |
40 | 20 | All funds that are donated to this project will be donated to charity. A full log of charity donations will be available in this repository as they are processed. |
41 | 21 | |
42 | ## Changes in 3.1 | |
43 | ||
44 | * enumerate public AWS S3 buckets | |
45 | * fuzzing mode | |
46 | * specify HTTP method | |
47 | * added support for patterns. You can now specify a file containing patterns that are applied to every word, one by line. Every occurrence of the term `{GOBUSTER}` in it will be replaced with the current wordlist item. Please use with caution as this can cause increase the number of requests issued a lot. | |
48 | * The shorthand `p` flag which was assigned to proxy is now used by the pattern flag | |
49 | ||
50 | ## Changes in 3.0 | |
51 | ||
52 | * New CLI options so modes are strictly separated (`-m` is now gone!) | |
53 | * Performance Optimizations and better connection handling | |
54 | * Ability to enumerate vhost names | |
55 | * Option to supply custom HTTP headers | |
22 | # Changes | |
23 | ||
24 | ## 3.2 | |
25 | ||
26 | - Use go 1.19 | |
27 | - use contexts in the correct way | |
28 | - get rid of the wildcard flag (except in DNS mode) | |
29 | - color output | |
30 | - retry on timeout | |
31 | - google cloud bucket enumeration | |
32 | - fix nil reference errors | |
33 | ||
34 | ## 3.1 | |
35 | ||
36 | - enumerate public AWS S3 buckets | |
37 | - fuzzing mode | |
38 | - specify HTTP method | |
39 | - added support for patterns. You can now specify a file containing patterns that are applied to every word, one by line. Every occurrence of the term `{GOBUSTER}` in it will be replaced with the current wordlist item. Please use with caution as this can cause increase the number of requests issued a lot. | |
40 | - The shorthand `p` flag which was assigned to proxy is now used by the pattern flag | |
41 | ||
42 | ## 3.0 | |
43 | ||
44 | - New CLI options so modes are strictly separated (`-m` is now gone!) | |
45 | - Performance Optimizations and better connection handling | |
46 | - Ability to enumerate vhost names | |
47 | - Option to supply custom HTTP headers | |
48 | ||
49 | # License | |
50 | ||
51 | See the LICENSE file. | |
52 | ||
53 | # Manual | |
56 | 54 | |
57 | 55 | ## Available Modes |
58 | 56 | |
59 | * dir - the classic directory brute-forcing mode | |
60 | * dns - DNS subdomain brute-forcing mode | |
61 | * s3 - Enumerate open S3 buckets and look for existence and bucket listings | |
62 | * vhost - virtual host brute-forcing mode (not the same as DNS!) | |
63 | ||
64 | ## Built-in Help | |
57 | - dir - the classic directory brute-forcing mode | |
58 | - dns - DNS subdomain brute-forcing mode | |
59 | - s3 - Enumerate open S3 buckets and look for existence and bucket listings | |
60 | - gcs - Enumerate open google cloud buckets | |
61 | - vhost - virtual host brute-forcing mode (not the same as DNS!) | |
62 | - fuzz - some basic fuzzing, replaces the `FUZZ` keyword | |
63 | ||
64 | ## Easy Installation | |
65 | ||
66 | ### Binary Releases | |
67 | ||
68 | We are now shipping binaries for each of the releases so that you don't even have to build them yourself! How wonderful is that! | |
69 | ||
70 | If you're stupid enough to trust binaries that I've put together, you can download them from the [releases](https://github.com/OJ/gobuster/releases) page. | |
71 | ||
72 | ### Using `go install` | |
73 | ||
74 | If you have a [Go](https://golang.org/) environment ready to go (at least go 1.19), it's as easy as: | |
75 | ||
76 | ```bash | |
77 | go install github.com/OJ/gobuster/v3@latest | |
78 | ``` | |
79 | ||
80 | PS: You need at least go 1.19 to compile gobuster. | |
81 | ||
82 | ### Building From Source | |
83 | ||
84 | Since this tool is written in [Go](https://golang.org/) you need to install the Go language/compiler/etc. Full details of installation and set up can be found [on the Go language website](https://golang.org/doc/install). Once installed you have two options. You need at least go 1.19 to compile gobuster. | |
85 | ||
86 | ### Compiling | |
87 | ||
88 | `gobuster` has external dependencies, and so they need to be pulled in first: | |
89 | ||
90 | ```bash | |
91 | go get && go build | |
92 | ``` | |
93 | ||
94 | This will create a `gobuster` binary for you. If you want to install it in the `$GOPATH/bin` folder you can run: | |
95 | ||
96 | ```bash | |
97 | go install | |
98 | ``` | |
99 | ||
100 | ## Modes | |
65 | 101 | |
66 | 102 | Help is built-in! |
67 | 103 | |
68 | * `gobuster help` - outputs the top-level help. | |
69 | * `gobuster help <mode>` - outputs the help specific to that mode. | |
70 | ||
71 | ## `dns` Mode Help | |
72 | ||
73 | ```text | |
104 | - `gobuster help` - outputs the top-level help. | |
105 | - `gobuster help <mode>` - outputs the help specific to that mode. | |
106 | ||
107 | ## `dns` Mode | |
108 | ||
109 | ### Options | |
110 | ||
111 | ```text | |
112 | Uses DNS subdomain enumeration mode | |
113 | ||
74 | 114 | Usage: |
75 | 115 | gobuster dns [flags] |
76 | 116 | |
84 | 124 | --wildcard Force continued operation when wildcard found |
85 | 125 | |
86 | 126 | Global Flags: |
127 | --delay duration Time each thread waits between requests (e.g. 1500ms) | |
128 | --no-color Disable color output | |
129 | --no-error Don't display errors | |
87 | 130 | -z, --no-progress Don't display progress |
88 | 131 | -o, --output string Output file to write results to (defaults to stdout) |
132 | -p, --pattern string File containing replacement patterns | |
89 | 133 | -q, --quiet Don't print the banner and other noise |
90 | 134 | -t, --threads int Number of concurrent threads (default 10) |
91 | --delay duration Time each thread waits between requests (e.g. 1500ms) | |
92 | 135 | -v, --verbose Verbose output (errors) |
93 | 136 | -w, --wordlist string Path to the wordlist |
94 | 137 | ``` |
95 | 138 | |
96 | ## `dir` Mode Options | |
97 | ||
98 | ```text | |
99 | Usage: | |
100 | gobuster dir [flags] | |
101 | ||
102 | Flags: | |
103 | -f, --add-slash Append / to each request | |
104 | -c, --cookies string Cookies to use for the requests | |
105 | -e, --expanded Expanded mode, print full URLs | |
106 | -x, --extensions string File extension(s) to search for | |
107 | -r, --follow-redirect Follow redirects | |
108 | -H, --headers stringArray Specify HTTP headers, -H 'Header1: val1' -H 'Header2: val2' | |
109 | -h, --help help for dir | |
110 | -l, --include-length Include the length of the body in the output | |
111 | -k, --no-tls-validation Skip TLS certificate verification | |
112 | -n, --no-status Don't print status codes | |
113 | -P, --password string Password for Basic Auth | |
114 | -p, --proxy string Proxy to use for requests [http(s)://host:port] | |
115 | -s, --status-codes string Positive status codes (will be overwritten with status-codes-blacklist if set) (default "200,204,301,302,307,401,403") | |
116 | -b, --status-codes-blacklist string Negative status codes (will override status-codes if set) | |
117 | --timeout duration HTTP Timeout (default 10s) | |
118 | -u, --url string The target URL | |
119 | -a, --useragent string Set the User-Agent string (default "gobuster/3.1.0") | |
120 | -U, --username string Username for Basic Auth | |
121 | -d, --discover-backup Upon finding a file search for backup files | |
122 | --wildcard Force continued operation when wildcard found | |
123 | ||
124 | Global Flags: | |
125 | -z, --no-progress Don't display progress | |
126 | -o, --output string Output file to write results to (defaults to stdout) | |
127 | -q, --quiet Don't print the banner and other noise | |
128 | -t, --threads int Number of concurrent threads (default 10) | |
129 | --delay duration Time each thread waits between requests (e.g. 1500ms) | |
130 | -v, --verbose Verbose output (errors) | |
131 | -w, --wordlist string Path to the wordlist | |
132 | ``` | |
133 | ||
134 | ## `vhost` Mode Options | |
135 | ||
136 | ```text | |
137 | Usage: | |
138 | gobuster vhost [flags] | |
139 | ||
140 | Flags: | |
141 | -c, --cookies string Cookies to use for the requests | |
142 | -r, --follow-redirect Follow redirects | |
143 | -H, --headers stringArray Specify HTTP headers, -H 'Header1: val1' -H 'Header2: val2' | |
144 | -h, --help help for vhost | |
145 | -k, --no-tls-validation Skip TLS certificate verification | |
146 | -P, --password string Password for Basic Auth | |
147 | -p, --proxy string Proxy to use for requests [http(s)://host:port] | |
148 | --timeout duration HTTP Timeout (default 10s) | |
149 | -u, --url string The target URL | |
150 | -a, --useragent string Set the User-Agent string (default "gobuster/3.1.0") | |
151 | -U, --username string Username for Basic Auth | |
152 | ||
153 | Global Flags: | |
154 | -z, --no-progress Don't display progress | |
155 | -o, --output string Output file to write results to (defaults to stdout) | |
156 | -q, --quiet Don't print the banner and other noise | |
157 | -t, --threads int Number of concurrent threads (default 10) | |
158 | --delay duration Time each thread waits between requests (e.g. 1500ms) | |
159 | -v, --verbose Verbose output (errors) | |
160 | -w, --wordlist string Path to the wordlist | |
161 | ``` | |
162 | ||
163 | ## Easy Installation | |
164 | ||
165 | ### Binary Releases | |
166 | ||
167 | We are now shipping binaries for each of the releases so that you don't even have to build them yourself! How wonderful is that! | |
168 | ||
169 | If you're stupid enough to trust binaries that I've put together, you can download them from the [releases](https://github.com/OJ/gobuster/releases) page. | |
170 | ||
171 | ### Using `go get` | |
172 | ||
173 | If you have a [Go](https://golang.org/) environment ready to go, it's as easy as: | |
174 | ||
175 | ```bash | |
176 | go get github.com/OJ/gobuster | |
177 | ``` | |
178 | ||
179 | ## Building From Source | |
180 | ||
181 | Since this tool is written in [Go](https://golang.org/) you need to install the Go language/compiler/etc. Full details of installation and set up can be found [on the Go language website](https://golang.org/doc/install). Once installed you have two options. | |
182 | ||
183 | ### Compiling | |
184 | ||
185 | `gobuster` now has external dependencies, and so they need to be pulled in first: | |
186 | ||
187 | ```bash | |
188 | go get && go build | |
189 | ``` | |
190 | ||
191 | This will create a `gobuster` binary for you. If you want to install it in the `$GOPATH/bin` folder you can run: | |
192 | ||
193 | ```bash | |
194 | go install | |
195 | ``` | |
196 | ||
197 | If you have all the dependencies already, you can make use of the build scripts: | |
198 | ||
199 | * `make` - builds for the current Go configuration (ie. runs `go build`). | |
200 | * `make windows` - builds 32 and 64 bit binaries for windows, and writes them to the `build` folder. | |
201 | * `make linux` - builds 32 and 64 bit binaries for linux, and writes them to the `build` folder. | |
202 | * `make darwin` - builds 32 and 64 bit binaries for darwin, and writes them to the `build` folder. | |
203 | * `make all` - builds for all platforms and architectures, and writes the resulting binaries to the `build` folder. | |
204 | * `make clean` - clears out the `build` folder. | |
205 | * `make test` - runs the tests. | |
206 | ||
207 | ## Wordlists via STDIN | |
208 | ||
209 | Wordlists can be piped into `gobuster` via stdin by providing a `-` to the `-w` option: | |
210 | ||
211 | ```bash | |
212 | hashcat -a 3 --stdout ?l | gobuster dir -u https://mysite.com -w - | |
213 | ``` | |
214 | ||
215 | 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. | |
216 | ||
217 | ## Patterns | |
218 | ||
219 | You can supply pattern files that will be applied to every word from the wordlist. | |
220 | Just place the string `{GOBUSTER}` in it and this will be replaced with the word. | |
221 | This feature is also handy in s3 mode to pre- or postfix certain patterns. | |
222 | ||
223 | **Caution:** Using a big pattern file can cause a lot of request as every pattern is applied to every word in the wordlist. | |
224 | ||
225 | ### Example file | |
226 | ||
227 | ```text | |
228 | {GOBUSTER}Partial | |
229 | {GOBUSTER}Service | |
230 | PRE{GOBUSTER}POST | |
231 | {GOBUSTER}-prod | |
232 | {GOBUSTER}-dev | |
233 | ``` | |
234 | ||
235 | ## Examples | |
236 | ||
237 | ### `dir` Mode | |
238 | ||
239 | Command line might look like this: | |
240 | ||
241 | ```bash | |
242 | gobuster dir -u https://mysite.com/path/to/folder -c 'session=123456' -t 50 -w common-files.txt -x .php,.html | |
243 | ``` | |
244 | ||
245 | Default options looks like this: | |
246 | ||
247 | ```bash | |
248 | gobuster dir -u https://buffered.io -w ~/wordlists/shortlist.txt | |
249 | ||
250 | =============================================================== | |
251 | Gobuster v3.1.0 | |
252 | by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart) | |
253 | =============================================================== | |
254 | [+] Mode : dir | |
255 | [+] Url/Domain : https://buffered.io/ | |
256 | [+] Threads : 10 | |
257 | [+] Wordlist : /home/oj/wordlists/shortlist.txt | |
258 | [+] Status codes : 200,204,301,302,307,401,403 | |
259 | [+] User Agent : gobuster/3.1.0 | |
260 | [+] Timeout : 10s | |
261 | =============================================================== | |
262 | 2019/06/21 11:49:43 Starting gobuster | |
263 | =============================================================== | |
264 | /categories (Status: 301) | |
265 | /contact (Status: 301) | |
266 | /posts (Status: 301) | |
267 | /index (Status: 200) | |
268 | =============================================================== | |
269 | 2019/06/21 11:49:44 Finished | |
270 | =============================================================== | |
271 | ``` | |
272 | ||
273 | Default options with status codes disabled looks like this: | |
274 | ||
275 | ```bash | |
276 | gobuster dir -u https://buffered.io -w ~/wordlists/shortlist.txt -n | |
277 | ||
278 | =============================================================== | |
279 | Gobuster v3.1.0 | |
280 | by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart) | |
281 | =============================================================== | |
282 | [+] Mode : dir | |
283 | [+] Url/Domain : https://buffered.io/ | |
284 | [+] Threads : 10 | |
285 | [+] Wordlist : /home/oj/wordlists/shortlist.txt | |
286 | [+] Status codes : 200,204,301,302,307,401,403 | |
287 | [+] User Agent : gobuster/3.1.0 | |
288 | [+] No status : true | |
289 | [+] Timeout : 10s | |
290 | =============================================================== | |
291 | 2019/06/21 11:50:18 Starting gobuster | |
292 | =============================================================== | |
293 | /categories | |
294 | /contact | |
295 | /index | |
296 | /posts | |
297 | =============================================================== | |
298 | 2019/06/21 11:50:18 Finished | |
299 | =============================================================== | |
300 | ``` | |
301 | ||
302 | Verbose output looks like this: | |
303 | ||
304 | ```bash | |
305 | gobuster dir -u https://buffered.io -w ~/wordlists/shortlist.txt -v | |
306 | ||
307 | =============================================================== | |
308 | Gobuster v3.1.0 | |
309 | by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart) | |
310 | =============================================================== | |
311 | [+] Mode : dir | |
312 | [+] Url/Domain : https://buffered.io/ | |
313 | [+] Threads : 10 | |
314 | [+] Wordlist : /home/oj/wordlists/shortlist.txt | |
315 | [+] Status codes : 200,204,301,302,307,401,403 | |
316 | [+] User Agent : gobuster/3.1.0 | |
317 | [+] Verbose : true | |
318 | [+] Timeout : 10s | |
319 | =============================================================== | |
320 | 2019/06/21 11:50:51 Starting gobuster | |
321 | =============================================================== | |
322 | Missed: /alsodoesnotexist (Status: 404) | |
323 | Found: /index (Status: 200) | |
324 | Missed: /doesnotexist (Status: 404) | |
325 | Found: /categories (Status: 301) | |
326 | Found: /posts (Status: 301) | |
327 | Found: /contact (Status: 301) | |
328 | =============================================================== | |
329 | 2019/06/21 11:50:51 Finished | |
330 | =============================================================== | |
331 | ``` | |
332 | ||
333 | Example showing content length: | |
334 | ||
335 | ```bash | |
336 | gobuster dir -u https://buffered.io -w ~/wordlists/shortlist.txt -l | |
337 | ||
338 | =============================================================== | |
339 | Gobuster v3.1.0 | |
340 | by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart) | |
341 | =============================================================== | |
342 | [+] Mode : dir | |
343 | [+] Url/Domain : https://buffered.io/ | |
344 | [+] Threads : 10 | |
345 | [+] Wordlist : /home/oj/wordlists/shortlist.txt | |
346 | [+] Status codes : 200,204,301,302,307,401,403 | |
347 | [+] User Agent : gobuster/3.1.0 | |
348 | [+] Show length : true | |
349 | [+] Timeout : 10s | |
350 | =============================================================== | |
351 | 2019/06/21 11:51:16 Starting gobuster | |
352 | =============================================================== | |
353 | /categories (Status: 301) [Size: 178] | |
354 | /posts (Status: 301) [Size: 178] | |
355 | /contact (Status: 301) [Size: 178] | |
356 | /index (Status: 200) [Size: 51759] | |
357 | =============================================================== | |
358 | 2019/06/21 11:51:17 Finished | |
359 | =============================================================== | |
360 | ``` | |
361 | ||
362 | Quiet output, with status disabled and expanded mode looks like this ("grep mode"): | |
363 | ||
364 | ```bash | |
365 | gobuster dir -u https://buffered.io -w ~/wordlists/shortlist.txt -q -n -e | |
366 | https://buffered.io/index | |
367 | https://buffered.io/contact | |
368 | https://buffered.io/posts | |
369 | https://buffered.io/categories | |
370 | ``` | |
371 | ||
372 | ### `dns` Mode | |
373 | ||
374 | Command line might look like this: | |
375 | ||
376 | ```bash | |
139 | ### Examples | |
140 | ||
141 | ||
142 | ```text | |
377 | 143 | gobuster dns -d mysite.com -t 50 -w common-names.txt |
378 | 144 | ``` |
379 | 145 | |
380 | 146 | Normal sample run goes like this: |
381 | 147 | |
382 | ```bash | |
148 | ```text | |
383 | 149 | gobuster dns -d google.com -w ~/wordlists/subdomains.txt |
384 | 150 | |
385 | 151 | =============================================================== |
386 | Gobuster v3.1.0 | |
152 | Gobuster v3.2.0 | |
387 | 153 | by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart) |
388 | 154 | =============================================================== |
389 | 155 | [+] Mode : dns |
418 | 184 | |
419 | 185 | Show IP sample run goes like this: |
420 | 186 | |
421 | ```bash | |
187 | ```text | |
422 | 188 | gobuster dns -d google.com -w ~/wordlists/subdomains.txt -i |
423 | 189 | |
424 | 190 | =============================================================== |
425 | Gobuster v3.1.0 | |
191 | Gobuster v3.2.0 | |
426 | 192 | by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart) |
427 | 193 | =============================================================== |
428 | 194 | [+] Mode : dns |
457 | 223 | |
458 | 224 | 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. |
459 | 225 | |
460 | ```bash | |
226 | ```text | |
461 | 227 | gobuster dns -d yp.to -w ~/wordlists/subdomains.txt -i |
462 | 228 | |
463 | 229 | =============================================================== |
464 | Gobuster v3.1.0 | |
230 | Gobuster v3.2.0 | |
465 | 231 | by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart) |
466 | 232 | =============================================================== |
467 | 233 | [+] Mode : dns |
480 | 246 | |
481 | 247 | Wildcard DNS is also detected properly: |
482 | 248 | |
483 | ```bash | |
249 | ```text | |
484 | 250 | gobuster dns -d 0.0.1.xip.io -w ~/wordlists/subdomains.txt |
485 | 251 | |
486 | 252 | =============================================================== |
487 | Gobuster v3.1.0 | |
253 | Gobuster v3.2.0 | |
488 | 254 | by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart) |
489 | 255 | =============================================================== |
490 | 256 | [+] Mode : dns |
503 | 269 | |
504 | 270 | If the user wants to force processing of a domain that has wildcard entries, use `--wildcard`: |
505 | 271 | |
506 | ```bash | |
272 | ```text | |
507 | 273 | gobuster dns -d 0.0.1.xip.io -w ~/wordlists/subdomains.txt --wildcard |
508 | 274 | |
509 | 275 | =============================================================== |
510 | Gobuster v3.1.0 | |
276 | Gobuster v3.2.0 | |
511 | 277 | by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart) |
512 | 278 | =============================================================== |
513 | 279 | [+] Mode : dns |
525 | 291 | =============================================================== |
526 | 292 | ``` |
527 | 293 | |
528 | ### `vhost` Mode | |
529 | ||
530 | Command line might look like this: | |
531 | ||
532 | ```bash | |
294 | ## `dir` Mode | |
295 | ||
296 | ### Options | |
297 | ||
298 | ```text | |
299 | Uses directory/file enumeration mode | |
300 | ||
301 | Usage: | |
302 | gobuster dir [flags] | |
303 | ||
304 | Flags: | |
305 | -f, --add-slash Append / to each request | |
306 | -c, --cookies string Cookies to use for the requests | |
307 | -d, --discover-backup Also search for backup files by appending multiple backup extensions | |
308 | --exclude-length ints exclude the following content length (completely ignores the status). Supply multiple times to exclude multiple sizes. | |
309 | -e, --expanded Expanded mode, print full URLs | |
310 | -x, --extensions string File extension(s) to search for | |
311 | -r, --follow-redirect Follow redirects | |
312 | -H, --headers stringArray Specify HTTP headers, -H 'Header1: val1' -H 'Header2: val2' | |
313 | -h, --help help for dir | |
314 | --hide-length Hide the length of the body in the output | |
315 | -m, --method string Use the following HTTP method (default "GET") | |
316 | -n, --no-status Don't print status codes | |
317 | -k, --no-tls-validation Skip TLS certificate verification | |
318 | -P, --password string Password for Basic Auth | |
319 | --proxy string Proxy to use for requests [http(s)://host:port] | |
320 | --random-agent Use a random User-Agent string | |
321 | --retry Should retry on request timeout | |
322 | --retry-attempts int Times to retry on request timeout (default 3) | |
323 | -s, --status-codes string Positive status codes (will be overwritten with status-codes-blacklist if set) | |
324 | -b, --status-codes-blacklist string Negative status codes (will override status-codes if set) (default "404") | |
325 | --timeout duration HTTP Timeout (default 10s) | |
326 | -u, --url string The target URL | |
327 | -a, --useragent string Set the User-Agent string (default "gobuster/3.2.0") | |
328 | -U, --username string Username for Basic Auth | |
329 | ||
330 | Global Flags: | |
331 | --delay duration Time each thread waits between requests (e.g. 1500ms) | |
332 | --no-color Disable color output | |
333 | --no-error Don't display errors | |
334 | -z, --no-progress Don't display progress | |
335 | -o, --output string Output file to write results to (defaults to stdout) | |
336 | -p, --pattern string File containing replacement patterns | |
337 | -q, --quiet Don't print the banner and other noise | |
338 | -t, --threads int Number of concurrent threads (default 10) | |
339 | -v, --verbose Verbose output (errors) | |
340 | -w, --wordlist string Path to the wordlist | |
341 | ``` | |
342 | ||
343 | ### Examples | |
344 | ||
345 | ```text | |
346 | gobuster dir -u https://mysite.com/path/to/folder -c 'session=123456' -t 50 -w common-files.txt -x .php,.html | |
347 | ``` | |
348 | ||
349 | Default options looks like this: | |
350 | ||
351 | ```text | |
352 | gobuster dir -u https://buffered.io -w ~/wordlists/shortlist.txt | |
353 | ||
354 | =============================================================== | |
355 | Gobuster v3.2.0 | |
356 | by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart) | |
357 | =============================================================== | |
358 | [+] Mode : dir | |
359 | [+] Url/Domain : https://buffered.io/ | |
360 | [+] Threads : 10 | |
361 | [+] Wordlist : /home/oj/wordlists/shortlist.txt | |
362 | [+] Status codes : 200,204,301,302,307,401,403 | |
363 | [+] User Agent : gobuster/3.2.0 | |
364 | [+] Timeout : 10s | |
365 | =============================================================== | |
366 | 2019/06/21 11:49:43 Starting gobuster | |
367 | =============================================================== | |
368 | /categories (Status: 301) | |
369 | /contact (Status: 301) | |
370 | /posts (Status: 301) | |
371 | /index (Status: 200) | |
372 | =============================================================== | |
373 | 2019/06/21 11:49:44 Finished | |
374 | =============================================================== | |
375 | ``` | |
376 | ||
377 | Default options with status codes disabled looks like this: | |
378 | ||
379 | ```text | |
380 | gobuster dir -u https://buffered.io -w ~/wordlists/shortlist.txt -n | |
381 | ||
382 | =============================================================== | |
383 | Gobuster v3.2.0 | |
384 | by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart) | |
385 | =============================================================== | |
386 | [+] Mode : dir | |
387 | [+] Url/Domain : https://buffered.io/ | |
388 | [+] Threads : 10 | |
389 | [+] Wordlist : /home/oj/wordlists/shortlist.txt | |
390 | [+] Status codes : 200,204,301,302,307,401,403 | |
391 | [+] User Agent : gobuster/3.2.0 | |
392 | [+] No status : true | |
393 | [+] Timeout : 10s | |
394 | =============================================================== | |
395 | 2019/06/21 11:50:18 Starting gobuster | |
396 | =============================================================== | |
397 | /categories | |
398 | /contact | |
399 | /index | |
400 | /posts | |
401 | =============================================================== | |
402 | 2019/06/21 11:50:18 Finished | |
403 | =============================================================== | |
404 | ``` | |
405 | ||
406 | Verbose output looks like this: | |
407 | ||
408 | ```text | |
409 | gobuster dir -u https://buffered.io -w ~/wordlists/shortlist.txt -v | |
410 | ||
411 | =============================================================== | |
412 | Gobuster v3.2.0 | |
413 | by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart) | |
414 | =============================================================== | |
415 | [+] Mode : dir | |
416 | [+] Url/Domain : https://buffered.io/ | |
417 | [+] Threads : 10 | |
418 | [+] Wordlist : /home/oj/wordlists/shortlist.txt | |
419 | [+] Status codes : 200,204,301,302,307,401,403 | |
420 | [+] User Agent : gobuster/3.2.0 | |
421 | [+] Verbose : true | |
422 | [+] Timeout : 10s | |
423 | =============================================================== | |
424 | 2019/06/21 11:50:51 Starting gobuster | |
425 | =============================================================== | |
426 | Missed: /alsodoesnotexist (Status: 404) | |
427 | Found: /index (Status: 200) | |
428 | Missed: /doesnotexist (Status: 404) | |
429 | Found: /categories (Status: 301) | |
430 | Found: /posts (Status: 301) | |
431 | Found: /contact (Status: 301) | |
432 | =============================================================== | |
433 | 2019/06/21 11:50:51 Finished | |
434 | =============================================================== | |
435 | ``` | |
436 | ||
437 | Example showing content length: | |
438 | ||
439 | ```text | |
440 | gobuster dir -u https://buffered.io -w ~/wordlists/shortlist.txt -l | |
441 | ||
442 | =============================================================== | |
443 | Gobuster v3.2.0 | |
444 | by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart) | |
445 | =============================================================== | |
446 | [+] Mode : dir | |
447 | [+] Url/Domain : https://buffered.io/ | |
448 | [+] Threads : 10 | |
449 | [+] Wordlist : /home/oj/wordlists/shortlist.txt | |
450 | [+] Status codes : 200,204,301,302,307,401,403 | |
451 | [+] User Agent : gobuster/3.2.0 | |
452 | [+] Show length : true | |
453 | [+] Timeout : 10s | |
454 | =============================================================== | |
455 | 2019/06/21 11:51:16 Starting gobuster | |
456 | =============================================================== | |
457 | /categories (Status: 301) [Size: 178] | |
458 | /posts (Status: 301) [Size: 178] | |
459 | /contact (Status: 301) [Size: 178] | |
460 | /index (Status: 200) [Size: 51759] | |
461 | =============================================================== | |
462 | 2019/06/21 11:51:17 Finished | |
463 | =============================================================== | |
464 | ``` | |
465 | ||
466 | Quiet output, with status disabled and expanded mode looks like this ("grep mode"): | |
467 | ||
468 | ```text | |
469 | gobuster dir -u https://buffered.io -w ~/wordlists/shortlist.txt -q -n -e | |
470 | https://buffered.io/index | |
471 | https://buffered.io/contact | |
472 | https://buffered.io/posts | |
473 | https://buffered.io/categories | |
474 | ``` | |
475 | ||
476 | ## `vhost` Mode | |
477 | ||
478 | ### Options | |
479 | ||
480 | ```text | |
481 | Uses VHOST enumeration mode (you most probably want to use the IP adress as the URL parameter | |
482 | ||
483 | Usage: | |
484 | gobuster vhost [flags] | |
485 | ||
486 | Flags: | |
487 | --append-domain Append main domain from URL to words from wordlist. Otherwise the fully qualified domains need to be specified in the wordlist. | |
488 | -c, --cookies string Cookies to use for the requests | |
489 | --domain string the domain to append when using an IP address as URL. If left empty and you specify a domain based URL the hostname from the URL is extracted | |
490 | --exclude-length ints exclude the following content length (completely ignores the status). Supply multiple times to exclude multiple sizes. | |
491 | -r, --follow-redirect Follow redirects | |
492 | -H, --headers stringArray Specify HTTP headers, -H 'Header1: val1' -H 'Header2: val2' | |
493 | -h, --help help for vhost | |
494 | -m, --method string Use the following HTTP method (default "GET") | |
495 | -k, --no-tls-validation Skip TLS certificate verification | |
496 | -P, --password string Password for Basic Auth | |
497 | --proxy string Proxy to use for requests [http(s)://host:port] | |
498 | --random-agent Use a random User-Agent string | |
499 | --retry Should retry on request timeout | |
500 | --retry-attempts int Times to retry on request timeout (default 3) | |
501 | --timeout duration HTTP Timeout (default 10s) | |
502 | -u, --url string The target URL | |
503 | -a, --useragent string Set the User-Agent string (default "gobuster/3.2.0") | |
504 | -U, --username string Username for Basic Auth | |
505 | ||
506 | Global Flags: | |
507 | --delay duration Time each thread waits between requests (e.g. 1500ms) | |
508 | --no-color Disable color output | |
509 | --no-error Don't display errors | |
510 | -z, --no-progress Don't display progress | |
511 | -o, --output string Output file to write results to (defaults to stdout) | |
512 | -p, --pattern string File containing replacement patterns | |
513 | -q, --quiet Don't print the banner and other noise | |
514 | -t, --threads int Number of concurrent threads (default 10) | |
515 | -v, --verbose Verbose output (errors) | |
516 | -w, --wordlist string Path to the wordlist | |
517 | ``` | |
518 | ||
519 | ### Examples | |
520 | ||
521 | ||
522 | ```text | |
533 | 523 | gobuster vhost -u https://mysite.com -w common-vhosts.txt |
534 | 524 | ``` |
535 | 525 | |
536 | 526 | Normal sample run goes like this: |
537 | 527 | |
538 | ```bash | |
528 | ```text | |
539 | 529 | gobuster vhost -u https://mysite.com -w common-vhosts.txt |
540 | 530 | |
541 | 531 | =============================================================== |
542 | Gobuster v3.1.0 | |
532 | Gobuster v3.2.0 | |
543 | 533 | by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart) |
544 | 534 | =============================================================== |
545 | 535 | [+] Url: https://mysite.com |
546 | 536 | [+] Threads: 10 |
547 | 537 | [+] Wordlist: common-vhosts.txt |
548 | [+] User Agent: gobuster/3.1.0 | |
538 | [+] User Agent: gobuster/3.2.0 | |
549 | 539 | [+] Timeout: 10s |
550 | 540 | =============================================================== |
551 | 541 | 2019/06/21 08:36:00 Starting gobuster |
558 | 548 | =============================================================== |
559 | 549 | ``` |
560 | 550 | |
561 | ### `s3` Mode | |
562 | ||
563 | Command line might look like this: | |
551 | ## `fuzz` Mode | |
552 | ||
553 | ### Options | |
554 | ||
555 | ```text | |
556 | Uses fuzzing mode | |
557 | ||
558 | Usage: | |
559 | gobuster fuzz [flags] | |
560 | ||
561 | Flags: | |
562 | -c, --cookies string Cookies to use for the requests | |
563 | --exclude-length ints exclude the following content length (completely ignores the status). Supply multiple times to exclude multiple sizes. | |
564 | -b, --excludestatuscodes string Negative status codes (will override statuscodes if set) | |
565 | -r, --follow-redirect Follow redirects | |
566 | -H, --headers stringArray Specify HTTP headers, -H 'Header1: val1' -H 'Header2: val2' | |
567 | -h, --help help for fuzz | |
568 | -m, --method string Use the following HTTP method (default "GET") | |
569 | -k, --no-tls-validation Skip TLS certificate verification | |
570 | -P, --password string Password for Basic Auth | |
571 | --proxy string Proxy to use for requests [http(s)://host:port] | |
572 | --random-agent Use a random User-Agent string | |
573 | --retry Should retry on request timeout | |
574 | --retry-attempts int Times to retry on request timeout (default 3) | |
575 | --timeout duration HTTP Timeout (default 10s) | |
576 | -u, --url string The target URL | |
577 | -a, --useragent string Set the User-Agent string (default "gobuster/3.2.0") | |
578 | -U, --username string Username for Basic Auth | |
579 | ||
580 | Global Flags: | |
581 | --delay duration Time each thread waits between requests (e.g. 1500ms) | |
582 | --no-color Disable color output | |
583 | --no-error Don't display errors | |
584 | -z, --no-progress Don't display progress | |
585 | -o, --output string Output file to write results to (defaults to stdout) | |
586 | -p, --pattern string File containing replacement patterns | |
587 | -q, --quiet Don't print the banner and other noise | |
588 | -t, --threads int Number of concurrent threads (default 10) | |
589 | -v, --verbose Verbose output (errors) | |
590 | -w, --wordlist string Path to the wordlist | |
591 | ``` | |
592 | ||
593 | ### Examples | |
594 | ||
595 | ```text | |
596 | gobuster fuzz -u https://example.com?FUZZ=test -w parameter-names.txt | |
597 | ``` | |
598 | ||
599 | ## `s3` Mode | |
600 | ||
601 | ### Options | |
602 | ||
603 | ```text | |
604 | Uses aws bucket enumeration mode | |
605 | ||
606 | Usage: | |
607 | gobuster s3 [flags] | |
608 | ||
609 | Flags: | |
610 | -h, --help help for s3 | |
611 | -m, --maxfiles int max files to list when listing buckets (only shown in verbose mode) (default 5) | |
612 | -k, --no-tls-validation Skip TLS certificate verification | |
613 | --proxy string Proxy to use for requests [http(s)://host:port] | |
614 | --random-agent Use a random User-Agent string | |
615 | --retry Should retry on request timeout | |
616 | --retry-attempts int Times to retry on request timeout (default 3) | |
617 | --timeout duration HTTP Timeout (default 10s) | |
618 | -a, --useragent string Set the User-Agent string (default "gobuster/3.2.0") | |
619 | ||
620 | Global Flags: | |
621 | --delay duration Time each thread waits between requests (e.g. 1500ms) | |
622 | --no-color Disable color output | |
623 | --no-error Don't display errors | |
624 | -z, --no-progress Don't display progress | |
625 | -o, --output string Output file to write results to (defaults to stdout) | |
626 | -p, --pattern string File containing replacement patterns | |
627 | -q, --quiet Don't print the banner and other noise | |
628 | -t, --threads int Number of concurrent threads (default 10) | |
629 | -v, --verbose Verbose output (errors) | |
630 | -w, --wordlist string Path to the wordlist | |
631 | ``` | |
632 | ||
633 | ### Examples | |
634 | ||
635 | ```text | |
636 | gobuster s3 -w bucket-names.txt | |
637 | ``` | |
638 | ||
639 | ## `gcs` Mode | |
640 | ||
641 | ### Options | |
642 | ||
643 | ```text | |
644 | Uses gcs bucket enumeration mode | |
645 | ||
646 | Usage: | |
647 | gobuster gcs [flags] | |
648 | ||
649 | Flags: | |
650 | -h, --help help for gcs | |
651 | -m, --maxfiles int max files to list when listing buckets (only shown in verbose mode) (default 5) | |
652 | -k, --no-tls-validation Skip TLS certificate verification | |
653 | --proxy string Proxy to use for requests [http(s)://host:port] | |
654 | --random-agent Use a random User-Agent string | |
655 | --retry Should retry on request timeout | |
656 | --retry-attempts int Times to retry on request timeout (default 3) | |
657 | --timeout duration HTTP Timeout (default 10s) | |
658 | -a, --useragent string Set the User-Agent string (default "gobuster/3.2.0") | |
659 | ||
660 | Global Flags: | |
661 | --delay duration Time each thread waits between requests (e.g. 1500ms) | |
662 | --no-color Disable color output | |
663 | --no-error Don't display errors | |
664 | -z, --no-progress Don't display progress | |
665 | -o, --output string Output file to write results to (defaults to stdout) | |
666 | -p, --pattern string File containing replacement patterns | |
667 | -q, --quiet Don't print the banner and other noise | |
668 | -t, --threads int Number of concurrent threads (default 10) | |
669 | -v, --verbose Verbose output (errors) | |
670 | -w, --wordlist string Path to the wordlist | |
671 | ``` | |
672 | ||
673 | ### Examples | |
674 | ||
675 | ```text | |
676 | gobuster gcs -w bucket-names.txt | |
677 | ``` | |
678 | ||
679 | ## Wordlists via STDIN | |
680 | ||
681 | Wordlists can be piped into `gobuster` via stdin by providing a `-` to the `-w` option: | |
564 | 682 | |
565 | 683 | ```bash |
566 | gobuster s3 -w bucket-names.txt | |
567 | ``` | |
568 | ||
569 | ### `fuzzing` Mode | |
570 | ||
571 | Command line might look like this: | |
572 | ||
573 | ```bash | |
574 | gobuster fuzz -u https://example.com?FUZZ=test -w parameter-names.txt | |
684 | hashcat -a 3 --stdout ?l | gobuster dir -u https://mysite.com -w - | |
685 | ``` | |
686 | ||
687 | 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. | |
688 | ||
689 | ## Patterns | |
690 | ||
691 | You can supply pattern files that will be applied to every word from the wordlist. | |
692 | Just place the string `{GOBUSTER}` in it and this will be replaced with the word. | |
693 | This feature is also handy in s3 mode to pre- or postfix certain patterns. | |
694 | ||
695 | **Caution:** Using a big pattern file can cause a lot of request as every pattern is applied to every word in the wordlist. | |
696 | ||
697 | ### Example file | |
698 | ||
699 | ```text | |
700 | {GOBUSTER}Partial | |
701 | {GOBUSTER}Service | |
702 | PRE{GOBUSTER}POST | |
703 | {GOBUSTER}-prod | |
704 | {GOBUSTER}-dev | |
575 | 705 | ``` |
576 | 706 | |
577 | 707 | #### Use case in combination with patterns |
578 | 708 | |
579 | * Create a custom wordlist for the target containing company names and so on | |
580 | * Create a pattern file to use for common bucket names. | |
709 | - Create a custom wordlist for the target containing company names and so on | |
710 | - Create a pattern file to use for common bucket names. | |
581 | 711 | |
582 | 712 | ```bash |
583 | 713 | curl -s --output - https://raw.githubusercontent.com/eth0izzle/bucket-stream/master/permutations/extended.txt | sed -s 's/%s/{GOBUSTER}/' > patterns.txt |
584 | 714 | ``` |
585 | 715 | |
586 | * Run gobuster with the custom input. Be sure to turn verbose mode on to see the bucket details | |
587 | ||
588 | ```bash | |
716 | - Run gobuster with the custom input. Be sure to turn verbose mode on to see the bucket details | |
717 | ||
718 | ```text | |
589 | 719 | gobuster s3 --wordlist my.custom.wordlist -p patterns.txt -v |
590 | 720 | ``` |
591 | 721 | |
594 | 724 | ```text |
595 | 725 | PS C:\Users\firefart\Documents\code\gobuster> .\gobuster.exe s3 --wordlist .\wordlist.txt |
596 | 726 | =============================================================== |
597 | Gobuster v3.1.0 | |
727 | Gobuster v3.2.0 | |
598 | 728 | by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart) |
599 | 729 | =============================================================== |
600 | 730 | [+] Threads: 10 |
601 | 731 | [+] Wordlist: .\wordlist.txt |
602 | [+] User Agent: gobuster/3.1.0 | |
732 | [+] User Agent: gobuster/3.2.0 | |
603 | 733 | [+] Timeout: 10s |
604 | 734 | [+] Maximum files to list: 5 |
605 | 735 | =============================================================== |
623 | 753 | ```text |
624 | 754 | PS C:\Users\firefart\Documents\code\gobuster> .\gobuster.exe s3 --wordlist .\wordlist.txt -v |
625 | 755 | =============================================================== |
626 | Gobuster v3.1.0 | |
756 | Gobuster v3.2.0 | |
627 | 757 | by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart) |
628 | 758 | =============================================================== |
629 | 759 | [+] Threads: 10 |
630 | 760 | [+] Wordlist: .\wordlist.txt |
631 | [+] User Agent: gobuster/3.1.0 | |
761 | [+] User Agent: gobuster/3.2.0 | |
632 | 762 | [+] Verbose: true |
633 | 763 | [+] Timeout: 10s |
634 | 764 | [+] Maximum files to list: 5 |
653 | 783 | ```text |
654 | 784 | PS C:\Users\firefart\Documents\code\gobuster> .\gobuster.exe s3 --wordlist .\wordlist.txt -e |
655 | 785 | =============================================================== |
656 | Gobuster v3.1.0 | |
786 | Gobuster v3.2.0 | |
657 | 787 | by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart) |
658 | 788 | =============================================================== |
659 | 789 | [+] Threads: 10 |
660 | 790 | [+] Wordlist: .\wordlist.txt |
661 | [+] User Agent: gobuster/3.1.0 | |
791 | [+] User Agent: gobuster/3.2.0 | |
662 | 792 | [+] Timeout: 10s |
663 | 793 | [+] Expanded: true |
664 | 794 | [+] Maximum files to list: 5 |
677 | 807 | 2019/08/12 21:48:38 Finished |
678 | 808 | =============================================================== |
679 | 809 | ``` |
680 | ||
681 | ## License | |
682 | ||
683 | See the LICENSE file. | |
684 | ||
685 | ## Thanks | |
686 | ||
687 | See the THANKS file for people who helped out. |
0 | 0 | package cmd |
1 | 1 | |
2 | 2 | import ( |
3 | "errors" | |
3 | 4 | "fmt" |
4 | 5 | "log" |
5 | 6 | |
10 | 11 | "github.com/spf13/cobra" |
11 | 12 | ) |
12 | 13 | |
14 | // nolint:gochecknoglobals | |
13 | 15 | var cmdDir *cobra.Command |
14 | 16 | |
15 | 17 | func runDir(cmd *cobra.Command, args []string) error { |
18 | 20 | return fmt.Errorf("error on parsing arguments: %w", err) |
19 | 21 | } |
20 | 22 | |
21 | plugin, err := gobusterdir.NewGobusterDir(mainContext, globalopts, pluginopts) | |
23 | plugin, err := gobusterdir.NewGobusterDir(globalopts, pluginopts) | |
22 | 24 | if err != nil { |
23 | 25 | return fmt.Errorf("error on creating gobusterdir: %w", err) |
24 | 26 | } |
25 | 27 | |
26 | 28 | if err := cli.Gobuster(mainContext, globalopts, plugin); err != nil { |
27 | if goberr, ok := err.(*gobusterdir.ErrWildcard); ok { | |
28 | return fmt.Errorf("%s. To continue please exclude the status code, the length or use the --wildcard switch", goberr.Error()) | |
29 | var wErr *gobusterdir.ErrWildcard | |
30 | if errors.As(err, &wErr) { | |
31 | return fmt.Errorf("%w. To continue please exclude the status code or the length", wErr) | |
29 | 32 | } |
30 | 33 | return fmt.Errorf("error on running gobuster: %w", err) |
31 | 34 | } |
55 | 58 | plugin.NoTLSValidation = httpOpts.NoTLSValidation |
56 | 59 | plugin.Headers = httpOpts.Headers |
57 | 60 | plugin.Method = httpOpts.Method |
61 | plugin.RetryOnTimeout = httpOpts.RetryOnTimeout | |
62 | plugin.RetryAttempts = httpOpts.RetryAttempts | |
58 | 63 | |
59 | 64 | plugin.Extensions, err = cmdDir.Flags().GetString("extensions") |
60 | 65 | if err != nil { |
61 | 66 | return nil, nil, fmt.Errorf("invalid value for extensions: %w", err) |
62 | 67 | } |
68 | ret, err := helper.ParseExtensions(plugin.Extensions) | |
69 | if err != nil { | |
70 | return nil, nil, fmt.Errorf("invalid value for extensions: %w", err) | |
71 | } | |
72 | plugin.ExtensionsParsed = ret | |
63 | 73 | |
64 | if plugin.Extensions != "" { | |
65 | ret, err := helper.ParseExtensions(plugin.Extensions) | |
66 | if err != nil { | |
67 | return nil, nil, fmt.Errorf("invalid value for extensions: %w", err) | |
68 | } | |
69 | plugin.ExtensionsParsed = ret | |
74 | // parse normal status codes | |
75 | plugin.StatusCodes, err = cmdDir.Flags().GetString("status-codes") | |
76 | if err != nil { | |
77 | return nil, nil, fmt.Errorf("invalid value for status-codes: %w", err) | |
70 | 78 | } |
79 | ret2, err := helper.ParseCommaSeparatedInt(plugin.StatusCodes) | |
80 | if err != nil { | |
81 | return nil, nil, fmt.Errorf("invalid value for status-codes: %w", err) | |
82 | } | |
83 | plugin.StatusCodesParsed = ret2 | |
71 | 84 | |
85 | // blacklist will override the normal status codes | |
72 | 86 | plugin.StatusCodesBlacklist, err = cmdDir.Flags().GetString("status-codes-blacklist") |
73 | 87 | if err != nil { |
74 | 88 | return nil, nil, fmt.Errorf("invalid value for status-codes-blacklist: %w", err) |
75 | 89 | } |
90 | ret3, err := helper.ParseCommaSeparatedInt(plugin.StatusCodesBlacklist) | |
91 | if err != nil { | |
92 | return nil, nil, fmt.Errorf("invalid value for status-codes-blacklist: %w", err) | |
93 | } | |
94 | plugin.StatusCodesBlacklistParsed = ret3 | |
76 | 95 | |
77 | // blacklist will override the normal status codes | |
78 | if plugin.StatusCodesBlacklist != "" { | |
79 | ret, err := helper.ParseCommaSeparatedInt(plugin.StatusCodesBlacklist) | |
80 | if err != nil { | |
81 | return nil, nil, fmt.Errorf("invalid value for status-codes-blacklist: %w", err) | |
82 | } | |
83 | plugin.StatusCodesBlacklistParsed = ret | |
84 | } else { | |
85 | // parse normal status codes | |
86 | plugin.StatusCodes, err = cmdDir.Flags().GetString("status-codes") | |
87 | if err != nil { | |
88 | return nil, nil, fmt.Errorf("invalid value for status-codes: %w", err) | |
89 | } | |
90 | ret, err := helper.ParseCommaSeparatedInt(plugin.StatusCodes) | |
91 | if err != nil { | |
92 | return nil, nil, fmt.Errorf("invalid value for status-codes: %w", err) | |
93 | } | |
94 | plugin.StatusCodesParsed = ret | |
96 | if plugin.StatusCodes != "" && plugin.StatusCodesBlacklist != "" { | |
97 | return nil, nil, fmt.Errorf("status-codes (%q) and status-codes-blacklist (%q) are both set - please set only one. status-codes-blacklist is set by default so you might want to disable it by supplying an empty string.", | |
98 | plugin.StatusCodes, plugin.StatusCodesBlacklist) | |
99 | } | |
100 | ||
101 | if plugin.StatusCodes == "" && plugin.StatusCodesBlacklist == "" { | |
102 | return nil, nil, fmt.Errorf("status-codes and status-codes-blacklist are both not set, please set one") | |
95 | 103 | } |
96 | 104 | |
97 | 105 | plugin.UseSlash, err = cmdDir.Flags().GetBool("add-slash") |
114 | 122 | return nil, nil, fmt.Errorf("invalid value for hide-length: %w", err) |
115 | 123 | } |
116 | 124 | |
117 | plugin.WildcardForced, err = cmdDir.Flags().GetBool("wildcard") | |
118 | if err != nil { | |
119 | return nil, nil, fmt.Errorf("invalid value for wildcard: %w", err) | |
120 | } | |
121 | ||
122 | 125 | plugin.DiscoverBackup, err = cmdDir.Flags().GetBool("discover-backup") |
123 | 126 | if err != nil { |
124 | 127 | return nil, nil, fmt.Errorf("invalid value for discover-backup: %w", err) |
132 | 135 | return globalopts, plugin, nil |
133 | 136 | } |
134 | 137 | |
138 | // nolint:gochecknoinits | |
135 | 139 | func init() { |
136 | 140 | cmdDir = &cobra.Command{ |
137 | 141 | Use: "dir", |
149 | 153 | cmdDir.Flags().BoolP("no-status", "n", false, "Don't print status codes") |
150 | 154 | cmdDir.Flags().Bool("hide-length", false, "Hide the length of the body in the output") |
151 | 155 | cmdDir.Flags().BoolP("add-slash", "f", false, "Append / to each request") |
152 | cmdDir.Flags().Bool("wildcard", false, "Force continued operation when wildcard found") | |
153 | cmdDir.Flags().BoolP("discover-backup", "d", false, "Upon finding a file search for backup files") | |
156 | cmdDir.Flags().BoolP("discover-backup", "d", false, "Also search for backup files by appending multiple backup extensions") | |
154 | 157 | cmdDir.Flags().IntSlice("exclude-length", []int{}, "exclude the following content length (completely ignores the status). Supply multiple times to exclude multiple sizes.") |
155 | 158 | |
156 | 159 | cmdDir.PersistentPreRun = func(cmd *cobra.Command, args []string) { |
2 | 2 | import ( |
3 | 3 | "context" |
4 | 4 | "fmt" |
5 | "io/ioutil" | |
5 | "io" | |
6 | 6 | "log" |
7 | 7 | "net/http" |
8 | 8 | "net/http/httptest" |
30 | 30 | pluginopts := gobusterdir.NewOptionsDir() |
31 | 31 | pluginopts.URL = h.URL |
32 | 32 | pluginopts.Timeout = 10 * time.Second |
33 | pluginopts.WildcardForced = true | |
34 | 33 | |
35 | 34 | pluginopts.Extensions = ".php,.csv" |
36 | 35 | tmpExt, err := helper.ParseExtensions(pluginopts.Extensions) |
46 | 45 | } |
47 | 46 | pluginopts.StatusCodesParsed = tmpStat |
48 | 47 | |
49 | wordlist, err := ioutil.TempFile("", "") | |
48 | wordlist, err := os.CreateTemp("", "") | |
50 | 49 | if err != nil { |
51 | 50 | b.Fatalf("could not create tempfile: %v", err) |
52 | 51 | } |
72 | 71 | } |
73 | 72 | defer devnull.Close() |
74 | 73 | log.SetFlags(0) |
75 | log.SetOutput(ioutil.Discard) | |
74 | log.SetOutput(io.Discard) | |
76 | 75 | |
77 | 76 | // Run the real benchmark |
78 | 77 | for x := 0; x < b.N; x++ { |
79 | 78 | os.Stdout = devnull |
80 | 79 | os.Stderr = devnull |
81 | plugin, err := gobusterdir.NewGobusterDir(ctx, &globalopts, pluginopts) | |
80 | plugin, err := gobusterdir.NewGobusterDir(&globalopts, pluginopts) | |
82 | 81 | if err != nil { |
83 | 82 | b.Fatalf("error on creating gobusterdir: %v", err) |
84 | 83 | } |
0 | 0 | package cmd |
1 | 1 | |
2 | 2 | import ( |
3 | "errors" | |
3 | 4 | "fmt" |
4 | 5 | "log" |
5 | 6 | "runtime" |
11 | 12 | "github.com/spf13/cobra" |
12 | 13 | ) |
13 | 14 | |
15 | // nolint:gochecknoglobals | |
14 | 16 | var cmdDNS *cobra.Command |
15 | 17 | |
16 | 18 | func runDNS(cmd *cobra.Command, args []string) error { |
25 | 27 | } |
26 | 28 | |
27 | 29 | if err := cli.Gobuster(mainContext, globalopts, plugin); err != nil { |
28 | if goberr, ok := err.(*gobusterdns.ErrWildcard); ok { | |
29 | return fmt.Errorf("%s. To force processing of Wildcard DNS, specify the '--wildcard' switch", goberr.Error()) | |
30 | var wErr *gobusterdns.ErrWildcard | |
31 | if errors.As(err, &wErr) { | |
32 | return fmt.Errorf("%w. To force processing of Wildcard DNS, specify the '--wildcard' switch", wErr) | |
30 | 33 | } |
31 | 34 | return fmt.Errorf("error on running gobuster: %w", err) |
32 | 35 | } |
77 | 80 | return globalopts, plugin, nil |
78 | 81 | } |
79 | 82 | |
83 | // nolint:gochecknoinits | |
80 | 84 | func init() { |
81 | 85 | cmdDNS = &cobra.Command{ |
82 | 86 | Use: "dns", |
0 | 0 | package cmd |
1 | 1 | |
2 | 2 | import ( |
3 | "errors" | |
3 | 4 | "fmt" |
4 | 5 | "log" |
5 | 6 | |
10 | 11 | "github.com/spf13/cobra" |
11 | 12 | ) |
12 | 13 | |
14 | // nolint:gochecknoglobals | |
13 | 15 | var cmdFuzz *cobra.Command |
14 | 16 | |
15 | 17 | func runFuzz(cmd *cobra.Command, args []string) error { |
18 | 20 | return fmt.Errorf("error on parsing arguments: %w", err) |
19 | 21 | } |
20 | 22 | |
21 | plugin, err := gobusterfuzz.NewGobusterFuzz(mainContext, globalopts, pluginopts) | |
23 | plugin, err := gobusterfuzz.NewGobusterFuzz(globalopts, pluginopts) | |
22 | 24 | if err != nil { |
23 | 25 | return fmt.Errorf("error on creating gobusterfuzz: %w", err) |
24 | 26 | } |
25 | 27 | |
26 | 28 | if err := cli.Gobuster(mainContext, globalopts, plugin); err != nil { |
27 | if goberr, ok := err.(*gobusterfuzz.ErrWildcard); ok { | |
28 | return fmt.Errorf("%s. To force processing of Wildcard responses, specify the '--wildcard' switch", goberr.Error()) | |
29 | var wErr *gobusterfuzz.ErrWildcard | |
30 | if errors.As(err, &wErr) { | |
31 | return fmt.Errorf("%w. To continue please exclude the status code or the length", wErr) | |
29 | 32 | } |
30 | 33 | return fmt.Errorf("error on running gobuster: %w", err) |
31 | 34 | } |
55 | 58 | plugin.NoTLSValidation = httpOpts.NoTLSValidation |
56 | 59 | plugin.Headers = httpOpts.Headers |
57 | 60 | plugin.Method = httpOpts.Method |
61 | plugin.RetryOnTimeout = httpOpts.RetryOnTimeout | |
62 | plugin.RetryAttempts = httpOpts.RetryAttempts | |
58 | 63 | |
64 | // blacklist will override the normal status codes | |
59 | 65 | plugin.ExcludedStatusCodes, err = cmdFuzz.Flags().GetString("excludestatuscodes") |
60 | 66 | if err != nil { |
61 | 67 | return nil, nil, fmt.Errorf("invalid value for excludestatuscodes: %w", err) |
62 | 68 | } |
63 | ||
64 | // blacklist will override the normal status codes | |
65 | if plugin.ExcludedStatusCodes != "" { | |
66 | ret, err := helper.ParseCommaSeparatedInt(plugin.ExcludedStatusCodes) | |
67 | if err != nil { | |
68 | return nil, nil, fmt.Errorf("invalid value for excludestatuscodes: %w", err) | |
69 | } | |
70 | plugin.ExcludedStatusCodesParsed = ret | |
69 | ret, err := helper.ParseCommaSeparatedInt(plugin.ExcludedStatusCodes) | |
70 | if err != nil { | |
71 | return nil, nil, fmt.Errorf("invalid value for excludestatuscodes: %w", err) | |
71 | 72 | } |
72 | ||
73 | plugin.WildcardForced, err = cmdFuzz.Flags().GetBool("wildcard") | |
74 | if err != nil { | |
75 | return nil, nil, fmt.Errorf("invalid value for wildcard: %w", err) | |
76 | } | |
73 | plugin.ExcludedStatusCodesParsed = ret | |
77 | 74 | |
78 | 75 | plugin.ExcludeLength, err = cmdFuzz.Flags().GetIntSlice("exclude-length") |
79 | 76 | if err != nil { |
83 | 80 | return globalopts, plugin, nil |
84 | 81 | } |
85 | 82 | |
83 | // nolint:gochecknoinits | |
86 | 84 | func init() { |
87 | 85 | cmdFuzz = &cobra.Command{ |
88 | 86 | Use: "fuzz", |
95 | 93 | } |
96 | 94 | cmdFuzz.Flags().StringP("excludestatuscodes", "b", "", "Negative status codes (will override statuscodes if set)") |
97 | 95 | cmdFuzz.Flags().IntSlice("exclude-length", []int{}, "exclude the following content length (completely ignores the status). Supply multiple times to exclude multiple sizes.") |
98 | cmdFuzz.Flags().BoolP("wildcard", "", false, "Force continued operation when wildcard found") | |
99 | 96 | |
100 | 97 | cmdFuzz.PersistentPreRun = func(cmd *cobra.Command, args []string) { |
101 | 98 | configureGlobalOptions() |
0 | package cmd | |
1 | ||
2 | import ( | |
3 | "fmt" | |
4 | ||
5 | "github.com/OJ/gobuster/v3/cli" | |
6 | "github.com/OJ/gobuster/v3/gobustergcs" | |
7 | "github.com/OJ/gobuster/v3/libgobuster" | |
8 | "github.com/spf13/cobra" | |
9 | ) | |
10 | ||
11 | // nolint:gochecknoglobals | |
12 | var cmdGCS *cobra.Command | |
13 | ||
14 | func runGCS(cmd *cobra.Command, args []string) error { | |
15 | globalopts, pluginopts, err := parseGCSOptions() | |
16 | if err != nil { | |
17 | return fmt.Errorf("error on parsing arguments: %w", err) | |
18 | } | |
19 | ||
20 | plugin, err := gobustergcs.NewGobusterGCS(globalopts, pluginopts) | |
21 | if err != nil { | |
22 | return fmt.Errorf("error on creating gobustergcs: %w", err) | |
23 | } | |
24 | ||
25 | if err := cli.Gobuster(mainContext, globalopts, plugin); err != nil { | |
26 | return fmt.Errorf("error on running gobuster: %w", err) | |
27 | } | |
28 | return nil | |
29 | } | |
30 | ||
31 | func parseGCSOptions() (*libgobuster.Options, *gobustergcs.OptionsGCS, error) { | |
32 | globalopts, err := parseGlobalOptions() | |
33 | if err != nil { | |
34 | return nil, nil, err | |
35 | } | |
36 | ||
37 | pluginopts := gobustergcs.NewOptionsGCS() | |
38 | ||
39 | httpOpts, err := parseBasicHTTPOptions(cmdGCS) | |
40 | if err != nil { | |
41 | return nil, nil, err | |
42 | } | |
43 | ||
44 | pluginopts.UserAgent = httpOpts.UserAgent | |
45 | pluginopts.Proxy = httpOpts.Proxy | |
46 | pluginopts.Timeout = httpOpts.Timeout | |
47 | pluginopts.NoTLSValidation = httpOpts.NoTLSValidation | |
48 | pluginopts.RetryOnTimeout = httpOpts.RetryOnTimeout | |
49 | pluginopts.RetryAttempts = httpOpts.RetryAttempts | |
50 | ||
51 | pluginopts.MaxFilesToList, err = cmdGCS.Flags().GetInt("maxfiles") | |
52 | if err != nil { | |
53 | return nil, nil, fmt.Errorf("invalid value for maxfiles: %w", err) | |
54 | } | |
55 | ||
56 | return globalopts, pluginopts, nil | |
57 | } | |
58 | ||
59 | // nolint:gochecknoinits | |
60 | func init() { | |
61 | cmdGCS = &cobra.Command{ | |
62 | Use: "gcs", | |
63 | Short: "Uses gcs bucket enumeration mode", | |
64 | RunE: runGCS, | |
65 | } | |
66 | ||
67 | addBasicHTTPOptions(cmdGCS) | |
68 | cmdGCS.Flags().IntP("maxfiles", "m", 5, "max files to list when listing buckets (only shown in verbose mode)") | |
69 | ||
70 | cmdGCS.PersistentPreRun = func(cmd *cobra.Command, args []string) { | |
71 | configureGlobalOptions() | |
72 | } | |
73 | ||
74 | rootCmd.AddCommand(cmdGCS) | |
75 | } |
10 | 10 | "github.com/OJ/gobuster/v3/helper" |
11 | 11 | "github.com/OJ/gobuster/v3/libgobuster" |
12 | 12 | "github.com/spf13/cobra" |
13 | "golang.org/x/crypto/ssh/terminal" | |
13 | "golang.org/x/term" | |
14 | 14 | ) |
15 | 15 | |
16 | 16 | func addBasicHTTPOptions(cmd *cobra.Command) { |
18 | 18 | cmd.Flags().BoolP("random-agent", "", false, "Use a random User-Agent string") |
19 | 19 | cmd.Flags().StringP("proxy", "", "", "Proxy to use for requests [http(s)://host:port]") |
20 | 20 | cmd.Flags().DurationP("timeout", "", 10*time.Second, "HTTP Timeout") |
21 | cmd.Flags().BoolP("no-tls-validation", "k", false, "Skip TLS certificate verification") | |
22 | cmd.Flags().BoolP("retry", "", false, "Should retry on request timeout") | |
23 | cmd.Flags().IntP("retry-attempts", "", 3, "Times to retry on request timeout") | |
21 | 24 | } |
22 | 25 | |
23 | 26 | func addCommonHTTPOptions(cmd *cobra.Command) error { |
27 | 30 | cmd.Flags().StringP("username", "U", "", "Username for Basic Auth") |
28 | 31 | cmd.Flags().StringP("password", "P", "", "Password for Basic Auth") |
29 | 32 | cmd.Flags().BoolP("follow-redirect", "r", false, "Follow redirects") |
30 | cmd.Flags().BoolP("no-tls-validation", "k", false, "Skip TLS certificate verification") | |
31 | 33 | cmd.Flags().StringArrayP("headers", "H", []string{""}, "Specify HTTP headers, -H 'Header1: val1' -H 'Header2: val2'") |
32 | 34 | cmd.Flags().StringP("method", "m", "GET", "Use the following HTTP method") |
33 | 35 | |
51 | 53 | return options, fmt.Errorf("invalid value for random-agent: %w", err) |
52 | 54 | } |
53 | 55 | if randomUA { |
54 | options.UserAgent = helper.GetRandomUserAgent() | |
56 | ua, err := helper.GetRandomUserAgent() | |
57 | if err != nil { | |
58 | return options, err | |
59 | } | |
60 | options.UserAgent = ua | |
55 | 61 | } |
56 | 62 | |
57 | 63 | options.Proxy, err = cmd.Flags().GetString("proxy") |
62 | 68 | options.Timeout, err = cmd.Flags().GetDuration("timeout") |
63 | 69 | if err != nil { |
64 | 70 | return options, fmt.Errorf("invalid value for timeout: %w", err) |
71 | } | |
72 | ||
73 | options.RetryOnTimeout, err = cmd.Flags().GetBool("retry") | |
74 | if err != nil { | |
75 | return options, fmt.Errorf("invalid value for retry: %w", err) | |
76 | } | |
77 | ||
78 | options.RetryAttempts, err = cmd.Flags().GetInt("retry-attempts") | |
79 | if err != nil { | |
80 | return options, fmt.Errorf("invalid value for retry-attempts: %w", err) | |
81 | } | |
82 | ||
83 | options.NoTLSValidation, err = cmd.Flags().GetBool("no-tls-validation") | |
84 | if err != nil { | |
85 | return options, fmt.Errorf("invalid value for no-tls-validation: %w", err) | |
65 | 86 | } |
66 | 87 | return options, nil |
67 | 88 | } |
77 | 98 | options.Proxy = basic.Proxy |
78 | 99 | options.Timeout = basic.Timeout |
79 | 100 | options.UserAgent = basic.UserAgent |
101 | options.NoTLSValidation = basic.NoTLSValidation | |
102 | options.RetryOnTimeout = basic.RetryOnTimeout | |
103 | options.RetryAttempts = basic.RetryAttempts | |
80 | 104 | |
81 | 105 | options.URL, err = cmd.Flags().GetString("url") |
82 | 106 | if err != nil { |
123 | 147 | return options, fmt.Errorf("invalid value for follow-redirect: %w", err) |
124 | 148 | } |
125 | 149 | |
126 | options.NoTLSValidation, err = cmd.Flags().GetBool("no-tls-validation") | |
127 | if err != nil { | |
128 | return options, fmt.Errorf("invalid value for no-tls-validation: %w", err) | |
129 | } | |
130 | ||
131 | 150 | options.Method, err = cmd.Flags().GetString("method") |
132 | 151 | if err != nil { |
133 | 152 | return options, fmt.Errorf("invalid value for method: %w", err) |
156 | 175 | if options.Username != "" && options.Password == "" { |
157 | 176 | fmt.Printf("[?] Auth Password: ") |
158 | 177 | // please don't remove the int cast here as it is sadly needed on windows :/ |
159 | passBytes, err := terminal.ReadPassword(int(syscall.Stdin)) //nolint:unconvert | |
178 | passBytes, err := term.ReadPassword(int(syscall.Stdin)) //nolint:unconvert | |
160 | 179 | // print a newline to simulate the newline that was entered |
161 | 180 | // this means that formatting/printing after doesn't look bad. |
162 | 181 | fmt.Println("") |
8 | 8 | "os/signal" |
9 | 9 | |
10 | 10 | "github.com/OJ/gobuster/v3/libgobuster" |
11 | ||
11 | "github.com/fatih/color" | |
12 | 12 | "github.com/spf13/cobra" |
13 | 13 | ) |
14 | 14 | |
15 | // nolint:gochecknoglobals | |
15 | 16 | var rootCmd = &cobra.Command{ |
16 | 17 | Use: "gobuster", |
17 | 18 | SilenceUsage: true, |
18 | 19 | } |
19 | 20 | |
21 | // nolint:gochecknoglobals | |
20 | 22 | var mainContext context.Context |
21 | 23 | |
22 | 24 | // Execute is the main cobra method |
46 | 48 | // Once before and once after the help output. Not sure if |
47 | 49 | // this is going to be needed to output other errors that |
48 | 50 | // aren't automatically outputted. |
49 | //fmt.Println(err) | |
51 | // fmt.Println(err) | |
50 | 52 | os.Exit(1) |
51 | 53 | } |
52 | 54 | } |
134 | 136 | return nil, fmt.Errorf("invalid value for no-error: %w", err) |
135 | 137 | } |
136 | 138 | |
139 | noColor, err := rootCmd.Flags().GetBool("no-color") | |
140 | if err != nil { | |
141 | return nil, fmt.Errorf("invalid value for no-color: %w", err) | |
142 | } | |
143 | if noColor { | |
144 | color.NoColor = true | |
145 | } | |
146 | ||
137 | 147 | return globalopts, nil |
138 | 148 | } |
139 | 149 | |
147 | 157 | } |
148 | 158 | } |
149 | 159 | |
160 | // nolint:gochecknoinits | |
150 | 161 | func init() { |
151 | 162 | rootCmd.PersistentFlags().DurationP("delay", "", 0, "Time each thread waits between requests (e.g. 1500ms)") |
152 | 163 | rootCmd.PersistentFlags().IntP("threads", "t", 10, "Number of concurrent threads") |
157 | 168 | rootCmd.PersistentFlags().BoolP("no-progress", "z", false, "Don't display progress") |
158 | 169 | rootCmd.PersistentFlags().Bool("no-error", false, "Don't display errors") |
159 | 170 | rootCmd.PersistentFlags().StringP("pattern", "p", "", "File containing replacement patterns") |
171 | rootCmd.PersistentFlags().Bool("no-color", false, "Disable color output") | |
160 | 172 | } |
8 | 8 | "github.com/spf13/cobra" |
9 | 9 | ) |
10 | 10 | |
11 | // nolint:gochecknoglobals | |
11 | 12 | var cmdS3 *cobra.Command |
12 | 13 | |
13 | 14 | func runS3(cmd *cobra.Command, args []string) error { |
16 | 17 | return fmt.Errorf("error on parsing arguments: %w", err) |
17 | 18 | } |
18 | 19 | |
19 | plugin, err := gobusters3.NewGobusterS3(mainContext, globalopts, pluginopts) | |
20 | plugin, err := gobusters3.NewGobusterS3(globalopts, pluginopts) | |
20 | 21 | if err != nil { |
21 | 22 | return fmt.Errorf("error on creating gobusters3: %w", err) |
22 | 23 | } |
43 | 44 | plugin.UserAgent = httpOpts.UserAgent |
44 | 45 | plugin.Proxy = httpOpts.Proxy |
45 | 46 | plugin.Timeout = httpOpts.Timeout |
47 | plugin.NoTLSValidation = httpOpts.NoTLSValidation | |
48 | plugin.RetryOnTimeout = httpOpts.RetryOnTimeout | |
49 | plugin.RetryAttempts = httpOpts.RetryAttempts | |
46 | 50 | |
47 | 51 | plugin.MaxFilesToList, err = cmdS3.Flags().GetInt("maxfiles") |
48 | 52 | if err != nil { |
52 | 56 | return globalopts, plugin, nil |
53 | 57 | } |
54 | 58 | |
59 | // nolint:gochecknoinits | |
55 | 60 | func init() { |
56 | 61 | cmdS3 = &cobra.Command{ |
57 | 62 | Use: "s3", |
6 | 6 | "github.com/spf13/cobra" |
7 | 7 | ) |
8 | 8 | |
9 | // nolint:gochecknoglobals | |
9 | 10 | var cmdVersion *cobra.Command |
10 | 11 | |
11 | 12 | func runVersion(cmd *cobra.Command, args []string) error { |
13 | 14 | return nil |
14 | 15 | } |
15 | 16 | |
17 | // nolint:gochecknoinits | |
16 | 18 | func init() { |
17 | 19 | cmdVersion = &cobra.Command{ |
18 | 20 | Use: "version", |
9 | 9 | "github.com/spf13/cobra" |
10 | 10 | ) |
11 | 11 | |
12 | // nolint:gochecknoglobals | |
12 | 13 | var cmdVhost *cobra.Command |
13 | 14 | |
14 | 15 | func runVhost(cmd *cobra.Command, args []string) error { |
17 | 18 | return fmt.Errorf("error on parsing arguments: %w", err) |
18 | 19 | } |
19 | 20 | |
20 | plugin, err := gobustervhost.NewGobusterVhost(mainContext, globalopts, pluginopts) | |
21 | plugin, err := gobustervhost.NewGobusterVhost(globalopts, pluginopts) | |
21 | 22 | if err != nil { |
22 | 23 | return fmt.Errorf("error on creating gobustervhost: %w", err) |
23 | 24 | } |
50 | 51 | plugin.NoTLSValidation = httpOpts.NoTLSValidation |
51 | 52 | plugin.Headers = httpOpts.Headers |
52 | 53 | plugin.Method = httpOpts.Method |
54 | plugin.RetryOnTimeout = httpOpts.RetryOnTimeout | |
55 | plugin.RetryAttempts = httpOpts.RetryAttempts | |
56 | ||
57 | plugin.AppendDomain, err = cmdVhost.Flags().GetBool("append-domain") | |
58 | if err != nil { | |
59 | return nil, nil, fmt.Errorf("invalid value for append-domain: %w", err) | |
60 | } | |
61 | ||
62 | plugin.ExcludeLength, err = cmdVhost.Flags().GetIntSlice("exclude-length") | |
63 | if err != nil { | |
64 | return nil, nil, fmt.Errorf("invalid value for excludelength: %w", err) | |
65 | } | |
66 | ||
67 | plugin.Domain, err = cmdVhost.Flags().GetString("domain") | |
68 | if err != nil { | |
69 | return nil, nil, fmt.Errorf("invalid value for domain: %w", err) | |
70 | } | |
53 | 71 | |
54 | 72 | return globalopts, &plugin, nil |
55 | 73 | } |
56 | 74 | |
75 | // nolint:gochecknoinits | |
57 | 76 | func init() { |
58 | 77 | cmdVhost = &cobra.Command{ |
59 | 78 | Use: "vhost", |
60 | Short: "Uses VHOST enumeration mode", | |
79 | Short: "Uses VHOST enumeration mode (you most probably want to use the IP adress as the URL parameter", | |
61 | 80 | RunE: runVhost, |
62 | 81 | } |
63 | 82 | if err := addCommonHTTPOptions(cmdVhost); err != nil { |
64 | 83 | log.Fatalf("%v", err) |
65 | 84 | } |
85 | cmdVhost.Flags().BoolP("append-domain", "", false, "Append main domain from URL to words from wordlist. Otherwise the fully qualified domains need to be specified in the wordlist.") | |
86 | cmdVhost.Flags().IntSlice("exclude-length", []int{}, "exclude the following content length (completely ignores the status). Supply multiple times to exclude multiple sizes.") | |
87 | cmdVhost.Flags().String("domain", "", "the domain to append when using an IP address as URL. If left empty and you specify a domain based URL the hostname from the URL is extracted") | |
66 | 88 | |
67 | 89 | cmdVhost.PersistentPreRun = func(cmd *cobra.Command, args []string) { |
68 | 90 | configureGlobalOptions() |
2 | 2 | import ( |
3 | 3 | "context" |
4 | 4 | "fmt" |
5 | "io/ioutil" | |
5 | "io" | |
6 | 6 | "log" |
7 | 7 | "os" |
8 | 8 | "testing" |
21 | 21 | pluginopts.URL = h.URL |
22 | 22 | pluginopts.Timeout = 10 * time.Second |
23 | 23 | |
24 | wordlist, err := ioutil.TempFile("", "") | |
24 | wordlist, err := os.CreateTemp("", "") | |
25 | 25 | if err != nil { |
26 | 26 | b.Fatalf("could not create tempfile: %v", err) |
27 | 27 | } |
47 | 47 | } |
48 | 48 | defer devnull.Close() |
49 | 49 | log.SetFlags(0) |
50 | log.SetOutput(ioutil.Discard) | |
50 | log.SetOutput(io.Discard) | |
51 | 51 | |
52 | 52 | // Run the real benchmark |
53 | 53 | for x := 0; x < b.N; x++ { |
54 | 54 | os.Stdout = devnull |
55 | 55 | os.Stderr = devnull |
56 | plugin, err := gobustervhost.NewGobusterVhost(ctx, &globalopts, &pluginopts) | |
56 | plugin, err := gobustervhost.NewGobusterVhost(&globalopts, &pluginopts) | |
57 | 57 | if err != nil { |
58 | 58 | b.Fatalf("error on creating gobusterdir: %v", err) |
59 | 59 | } |
18 | 18 | fmt.Println("by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)") |
19 | 19 | } |
20 | 20 | |
21 | type outputType struct { | |
22 | Mu *sync.RWMutex | |
23 | MaxCharsWritten int | |
24 | } | |
25 | ||
26 | // right pad a string | |
27 | // nolint:unparam | |
28 | func rightPad(s string, padStr string, overallLen int) string { | |
29 | strLen := len(s) | |
30 | if overallLen <= strLen { | |
31 | return s | |
32 | } | |
33 | ||
34 | toPad := overallLen - strLen - 1 | |
35 | pad := strings.Repeat(padStr, toPad) | |
36 | return fmt.Sprintf("%s%s", s, pad) | |
37 | } | |
38 | ||
39 | 21 | // resultWorker outputs the results as they come in. This needs to be a range and should not handle |
40 | 22 | // the context so the channel always has a receiver and libgobuster will not block. |
41 | func resultWorker(g *libgobuster.Gobuster, filename string, wg *sync.WaitGroup, output *outputType) { | |
23 | func resultWorker(g *libgobuster.Gobuster, filename string, wg *sync.WaitGroup) { | |
42 | 24 | defer wg.Done() |
43 | 25 | |
44 | 26 | var f *os.File |
51 | 33 | defer f.Close() |
52 | 34 | } |
53 | 35 | |
54 | for r := range g.Results() { | |
36 | for r := range g.Progress.ResultChan { | |
55 | 37 | s, err := r.ResultToString() |
56 | 38 | if err != nil { |
57 | 39 | g.LogError.Fatal(err) |
58 | 40 | } |
59 | 41 | if s != "" { |
60 | 42 | s = strings.TrimSpace(s) |
61 | output.Mu.Lock() | |
62 | w, _ := fmt.Printf("\r%s\n", rightPad(s, " ", output.MaxCharsWritten)) | |
63 | // -1 to remove the newline, otherwise it's always bigger | |
64 | if (w - 1) > output.MaxCharsWritten { | |
65 | output.MaxCharsWritten = w - 1 | |
66 | } | |
67 | output.Mu.Unlock() | |
43 | _, _ = fmt.Printf("%s%s\n", TERMINAL_CLEAR_LINE, s) | |
68 | 44 | if f != nil { |
69 | 45 | err = writeToFile(f, s) |
70 | 46 | if err != nil { |
77 | 53 | |
78 | 54 | // errorWorker outputs the errors as they come in. This needs to be a range and should not handle |
79 | 55 | // the context so the channel always has a receiver and libgobuster will not block. |
80 | func errorWorker(g *libgobuster.Gobuster, wg *sync.WaitGroup, output *outputType) { | |
56 | func errorWorker(g *libgobuster.Gobuster, wg *sync.WaitGroup) { | |
81 | 57 | defer wg.Done() |
82 | 58 | |
83 | for e := range g.Errors() { | |
59 | for e := range g.Progress.ErrorChan { | |
84 | 60 | if !g.Opts.Quiet && !g.Opts.NoError { |
85 | output.Mu.Lock() | |
86 | g.LogError.Printf("[!] %v", e) | |
87 | output.Mu.Unlock() | |
61 | g.LogError.Printf("[!] %s\n", e.Error()) | |
88 | 62 | } |
89 | 63 | } |
90 | 64 | } |
91 | 65 | |
92 | 66 | // progressWorker outputs the progress every tick. It will stop once cancel() is called |
93 | 67 | // on the context |
94 | func progressWorker(c context.Context, g *libgobuster.Gobuster, wg *sync.WaitGroup, output *outputType) { | |
68 | func progressWorker(ctx context.Context, g *libgobuster.Gobuster, wg *sync.WaitGroup) { | |
95 | 69 | defer wg.Done() |
96 | 70 | |
97 | 71 | tick := time.NewTicker(cliProgressUpdate) |
100 | 74 | select { |
101 | 75 | case <-tick.C: |
102 | 76 | if !g.Opts.Quiet && !g.Opts.NoProgress { |
103 | g.RequestsCountMutex.RLock() | |
104 | output.Mu.Lock() | |
105 | var charsWritten int | |
77 | requestsIssued := g.Progress.RequestsIssued() | |
78 | requestsExpected := g.Progress.RequestsExpected() | |
106 | 79 | if g.Opts.Wordlist == "-" { |
107 | s := fmt.Sprintf("\rProgress: %d", g.RequestsIssued) | |
108 | s = rightPad(s, " ", output.MaxCharsWritten) | |
109 | charsWritten, _ = fmt.Fprint(os.Stderr, s) | |
80 | s := fmt.Sprintf("%sProgress: %d", TERMINAL_CLEAR_LINE, requestsIssued) | |
81 | _, _ = fmt.Fprint(os.Stderr, s) | |
110 | 82 | // only print status if we already read in the wordlist |
111 | } else if g.RequestsExpected > 0 { | |
112 | s := fmt.Sprintf("\rProgress: %d / %d (%3.2f%%)", g.RequestsIssued, g.RequestsExpected, float32(g.RequestsIssued)*100.0/float32(g.RequestsExpected)) | |
113 | s = rightPad(s, " ", output.MaxCharsWritten) | |
114 | charsWritten, _ = fmt.Fprint(os.Stderr, s) | |
83 | } else if requestsExpected > 0 { | |
84 | s := fmt.Sprintf("%sProgress: %d / %d (%3.2f%%)", TERMINAL_CLEAR_LINE, requestsIssued, requestsExpected, float32(requestsIssued)*100.0/float32(requestsExpected)) | |
85 | _, _ = fmt.Fprint(os.Stderr, s) | |
115 | 86 | } |
116 | if charsWritten > output.MaxCharsWritten { | |
117 | output.MaxCharsWritten = charsWritten | |
118 | } | |
119 | ||
120 | output.Mu.Unlock() | |
121 | g.RequestsCountMutex.RUnlock() | |
122 | 87 | } |
123 | case <-c.Done(): | |
88 | case <-ctx.Done(): | |
124 | 89 | return |
125 | 90 | } |
126 | 91 | } |
135 | 100 | } |
136 | 101 | |
137 | 102 | // Gobuster is the main entry point for the CLI |
138 | func Gobuster(prevCtx context.Context, opts *libgobuster.Options, plugin libgobuster.GobusterPlugin) error { | |
103 | func Gobuster(ctx context.Context, opts *libgobuster.Options, plugin libgobuster.GobusterPlugin) error { | |
139 | 104 | // Sanity checks |
140 | 105 | if opts == nil { |
141 | 106 | return fmt.Errorf("please provide valid options") |
145 | 110 | return fmt.Errorf("please provide a valid plugin") |
146 | 111 | } |
147 | 112 | |
148 | ctx, cancel := context.WithCancel(prevCtx) | |
113 | ctxCancel, cancel := context.WithCancel(ctx) | |
149 | 114 | defer cancel() |
150 | 115 | |
151 | gobuster, err := libgobuster.NewGobuster(ctx, opts, plugin) | |
116 | gobuster, err := libgobuster.NewGobuster(opts, plugin) | |
152 | 117 | if err != nil { |
153 | 118 | return err |
154 | 119 | } |
172 | 137 | // when we call wg.Wait() |
173 | 138 | var wg sync.WaitGroup |
174 | 139 | |
175 | outputMutex := new(sync.RWMutex) | |
176 | o := &outputType{ | |
177 | Mu: outputMutex, | |
178 | MaxCharsWritten: 0, | |
179 | } | |
140 | wg.Add(1) | |
141 | go resultWorker(gobuster, opts.OutputFilename, &wg) | |
180 | 142 | |
181 | 143 | wg.Add(1) |
182 | go resultWorker(gobuster, opts.OutputFilename, &wg, o) | |
183 | ||
184 | wg.Add(1) | |
185 | go errorWorker(gobuster, &wg, o) | |
144 | go errorWorker(gobuster, &wg) | |
186 | 145 | |
187 | 146 | if !opts.Quiet && !opts.NoProgress { |
188 | 147 | // if not quiet add a new workgroup entry and start the goroutine |
189 | 148 | wg.Add(1) |
190 | go progressWorker(ctx, gobuster, &wg, o) | |
149 | go progressWorker(ctxCancel, gobuster, &wg) | |
191 | 150 | } |
192 | 151 | |
193 | err = gobuster.Start() | |
152 | err = gobuster.Run(ctxCancel) | |
194 | 153 | |
195 | 154 | // call cancel func so progressWorker will exit (the only goroutine in this |
196 | 155 | // file using the context) and to free resources |
204 | 163 | } |
205 | 164 | |
206 | 165 | if !opts.Quiet { |
207 | // clear stderr progress | |
208 | fmt.Fprintf(os.Stderr, "\r%s\n", rightPad("", " ", o.MaxCharsWritten)) | |
209 | 166 | fmt.Println(ruler) |
210 | 167 | gobuster.LogInfo.Println("Finished") |
211 | 168 | fmt.Println(ruler) |
0 | 0 | // cSpell Settings |
1 | 1 | { |
2 | // Version of the setting file. Always 0.1 | |
3 | "version": "0.1", | |
2 | // Version of the setting file. Always 0.2 | |
3 | "version": "0.2", | |
4 | 4 | // language - current active spelling language |
5 | 5 | "language": "en", |
6 | 6 | // words - list of words to be always considered correct |
12 | 12 | "gobusterdns", |
13 | 13 | "gobusterfuzz", |
14 | 14 | "gobustervhost", |
15 | "gobustergcs", | |
15 | 16 | "vhost", |
16 | 17 | "vhosts", |
17 | 18 | "cname", |
27 | 28 | "unconvert", |
28 | 29 | "unparam", |
29 | 30 | "prealloc", |
30 | "gochecknoglobals" | |
31 | "gochecknoglobals", | |
32 | "gochecknoinits", | |
33 | "fatih", | |
34 | "netip" | |
31 | 35 | ], |
32 | 36 | // flagWords - list of words to be always considered incorrect |
33 | 37 | // This is useful for offensive words and common spelling errors. |
0 | 0 | module github.com/OJ/gobuster/v3 |
1 | 1 | |
2 | go 1.19 | |
3 | ||
2 | 4 | require ( |
3 | github.com/google/uuid v1.1.1 | |
4 | github.com/spf13/cobra v1.0.0 | |
5 | github.com/spf13/pflag v1.0.5 // indirect | |
6 | golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de | |
7 | golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed // indirect | |
5 | github.com/fatih/color v1.13.0 | |
6 | github.com/google/uuid v1.3.0 | |
7 | github.com/spf13/cobra v1.5.0 | |
8 | golang.org/x/term v0.0.0-20220919170432-7a66f970e087 | |
8 | 9 | ) |
9 | 10 | |
10 | go 1.15 | |
11 | require ( | |
12 | github.com/inconshreveable/mousetrap v1.0.1 // indirect | |
13 | github.com/mattn/go-colorable v0.1.13 // indirect | |
14 | github.com/mattn/go-isatty v0.0.16 // indirect | |
15 | github.com/spf13/pflag v1.0.5 // indirect | |
16 | golang.org/x/sys v0.0.0-20221006211917-84dc82d7e875 // indirect | |
17 | ) |
0 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= | |
1 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= | |
2 | github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= | |
3 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= | |
4 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= | |
5 | github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= | |
6 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= | |
7 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= | |
8 | github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= | |
9 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= | |
10 | github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= | |
11 | github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= | |
12 | github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= | |
13 | github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= | |
14 | github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= | |
15 | github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= | |
16 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= | |
17 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | |
18 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= | |
19 | github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= | |
20 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= | |
21 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= | |
22 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= | |
23 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= | |
24 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= | |
25 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= | |
26 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= | |
27 | github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= | |
28 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= | |
29 | github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= | |
30 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= | |
31 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= | |
32 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= | |
33 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= | |
34 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= | |
35 | github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= | |
36 | github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= | |
37 | github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= | |
38 | github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= | |
39 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= | |
40 | github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= | |
41 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= | |
42 | github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= | |
0 | github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= | |
1 | github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= | |
2 | github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= | |
3 | github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= | |
4 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= | |
43 | 5 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= |
44 | github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= | |
45 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= | |
46 | github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= | |
47 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= | |
48 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= | |
49 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= | |
50 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= | |
51 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= | |
52 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= | |
53 | github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= | |
54 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= | |
55 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= | |
56 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= | |
57 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= | |
58 | github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= | |
59 | github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= | |
60 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= | |
61 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | |
62 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | |
63 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= | |
64 | github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= | |
65 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= | |
66 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= | |
67 | github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= | |
68 | github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= | |
69 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= | |
70 | github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= | |
71 | github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= | |
72 | github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= | |
73 | github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= | |
74 | github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= | |
75 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= | |
76 | github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= | |
77 | github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= | |
78 | github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= | |
79 | github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= | |
80 | github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8= | |
81 | github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= | |
82 | github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= | |
83 | github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= | |
84 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= | |
6 | github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= | |
7 | github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= | |
8 | github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= | |
9 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= | |
10 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= | |
11 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= | |
12 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= | |
13 | github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= | |
14 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= | |
15 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= | |
16 | github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU= | |
17 | github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM= | |
85 | 18 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= |
86 | 19 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= |
87 | github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= | |
88 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | |
89 | github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= | |
90 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= | |
91 | github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= | |
92 | github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= | |
93 | github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= | |
94 | github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= | |
95 | go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= | |
96 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= | |
97 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= | |
98 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= | |
99 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= | |
100 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= | |
101 | golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de h1:ikNHVSjEfnvz6sxdSPCaPt572qowuyMDMJLLm3Db3ig= | |
102 | golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= | |
103 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= | |
104 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= | |
105 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | |
106 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | |
107 | golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | |
108 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= | |
109 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ= | |
110 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= | |
111 | golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= | |
112 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= | |
113 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | |
114 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | |
115 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | |
116 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | |
117 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | |
118 | golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | |
119 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | |
120 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | |
121 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | |
122 | golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed h1:WBkVNH1zd9jg/dK4HCM4lNANnmd12EHC9z+LmcCG4ns= | |
123 | golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | |
124 | golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= | |
125 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | |
126 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= | |
127 | golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= | |
128 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= | |
129 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= | |
130 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= | |
131 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= | |
132 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= | |
133 | google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= | |
134 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= | |
20 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | |
21 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | |
22 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | |
23 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | |
24 | golang.org/x/sys v0.0.0-20221006211917-84dc82d7e875 h1:AzgQNqF+FKwyQ5LbVrVqOcuuFB67N47F9+htZYH0wFM= | |
25 | golang.org/x/sys v0.0.0-20221006211917-84dc82d7e875/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | |
26 | golang.org/x/term v0.0.0-20220919170432-7a66f970e087 h1:tPwmk4vmvVCMdr98VgL4JH+qZxPL8fqlUOHnyOM8N3w= | |
27 | golang.org/x/term v0.0.0-20220919170432-7a66f970e087/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= | |
135 | 28 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= |
136 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | |
137 | gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= | |
138 | gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= | |
139 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | |
140 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | |
141 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= | |
29 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= |
4 | 4 | "bytes" |
5 | 5 | "context" |
6 | 6 | "fmt" |
7 | "net" | |
8 | "net/http" | |
7 | 9 | "strings" |
8 | 10 | "text/tabwriter" |
9 | 11 | |
32 | 34 | |
33 | 35 | // GobusterDir is the main type to implement the interface |
34 | 36 | type GobusterDir struct { |
35 | options *OptionsDir | |
36 | globalopts *libgobuster.Options | |
37 | http *libgobuster.HTTPClient | |
38 | requestsPerRun *int // helper variable so we do not recalculate this over and over | |
37 | options *OptionsDir | |
38 | globalopts *libgobuster.Options | |
39 | http *libgobuster.HTTPClient | |
39 | 40 | } |
40 | 41 | |
41 | 42 | // NewGobusterDir creates a new initialized GobusterDir |
42 | func NewGobusterDir(cont context.Context, globalopts *libgobuster.Options, opts *OptionsDir) (*GobusterDir, error) { | |
43 | func NewGobusterDir(globalopts *libgobuster.Options, opts *OptionsDir) (*GobusterDir, error) { | |
43 | 44 | if globalopts == nil { |
44 | 45 | return nil, fmt.Errorf("please provide valid global options") |
45 | 46 | } |
54 | 55 | } |
55 | 56 | |
56 | 57 | basicOptions := libgobuster.BasicHTTPOptions{ |
57 | Proxy: opts.Proxy, | |
58 | Timeout: opts.Timeout, | |
59 | UserAgent: opts.UserAgent, | |
58 | Proxy: opts.Proxy, | |
59 | Timeout: opts.Timeout, | |
60 | UserAgent: opts.UserAgent, | |
61 | NoTLSValidation: opts.NoTLSValidation, | |
62 | RetryOnTimeout: opts.RetryOnTimeout, | |
63 | RetryAttempts: opts.RetryAttempts, | |
60 | 64 | } |
61 | 65 | |
62 | 66 | httpOpts := libgobuster.HTTPOptions{ |
63 | 67 | BasicHTTPOptions: basicOptions, |
64 | 68 | FollowRedirect: opts.FollowRedirect, |
65 | NoTLSValidation: opts.NoTLSValidation, | |
66 | 69 | Username: opts.Username, |
67 | 70 | Password: opts.Password, |
68 | 71 | Headers: opts.Headers, |
70 | 73 | Method: opts.Method, |
71 | 74 | } |
72 | 75 | |
73 | h, err := libgobuster.NewHTTPClient(cont, &httpOpts) | |
76 | h, err := libgobuster.NewHTTPClient(&httpOpts) | |
74 | 77 | if err != nil { |
75 | 78 | return nil, err |
76 | 79 | } |
84 | 87 | return "directory enumeration" |
85 | 88 | } |
86 | 89 | |
87 | // RequestsPerRun returns the number of requests this plugin makes per single wordlist item | |
88 | func (d *GobusterDir) RequestsPerRun() int { | |
89 | if d.requestsPerRun != nil { | |
90 | return *d.requestsPerRun | |
91 | } | |
92 | ||
93 | num := 1 + len(d.options.ExtensionsParsed.Set) | |
94 | if d.options.DiscoverBackup { | |
95 | // default word | |
96 | num += len(backupExtensions) | |
97 | num += len(backupDotExtensions) | |
98 | // backups of filenames | |
99 | num += len(d.options.ExtensionsParsed.Set) * len(backupExtensions) | |
100 | num += len(d.options.ExtensionsParsed.Set) * len(backupDotExtensions) | |
101 | } | |
102 | d.requestsPerRun = &num | |
103 | ||
104 | return *d.requestsPerRun | |
105 | } | |
106 | ||
107 | 90 | // PreRun is the pre run implementation of gobusterdir |
108 | func (d *GobusterDir) PreRun() error { | |
91 | func (d *GobusterDir) PreRun(ctx context.Context) error { | |
109 | 92 | // add trailing slash |
110 | 93 | if !strings.HasSuffix(d.options.URL, "/") { |
111 | 94 | d.options.URL = fmt.Sprintf("%s/", d.options.URL) |
112 | 95 | } |
113 | 96 | |
114 | _, _, _, _, err := d.http.Request(d.options.URL, libgobuster.RequestOptions{}) | |
97 | _, _, _, _, err := d.http.Request(ctx, d.options.URL, libgobuster.RequestOptions{}) | |
115 | 98 | if err != nil { |
116 | 99 | return fmt.Errorf("unable to connect to %s: %w", d.options.URL, err) |
117 | 100 | } |
122 | 105 | url = fmt.Sprintf("%s/", url) |
123 | 106 | } |
124 | 107 | |
125 | wildcardResp, wildcardLength, _, _, err := d.http.Request(url, libgobuster.RequestOptions{}) | |
108 | wildcardResp, wildcardLength, _, _, err := d.http.Request(ctx, url, libgobuster.RequestOptions{}) | |
126 | 109 | if err != nil { |
127 | 110 | return err |
128 | 111 | } |
133 | 116 | } |
134 | 117 | |
135 | 118 | if d.options.StatusCodesBlacklistParsed.Length() > 0 { |
136 | if !d.options.StatusCodesBlacklistParsed.Contains(*wildcardResp) && !d.options.WildcardForced { | |
137 | return &ErrWildcard{url: url, statusCode: *wildcardResp, length: wildcardLength} | |
119 | if !d.options.StatusCodesBlacklistParsed.Contains(wildcardResp) { | |
120 | return &ErrWildcard{url: url, statusCode: wildcardResp, length: wildcardLength} | |
138 | 121 | } |
139 | 122 | } else if d.options.StatusCodesParsed.Length() > 0 { |
140 | if d.options.StatusCodesParsed.Contains(*wildcardResp) && !d.options.WildcardForced { | |
141 | return &ErrWildcard{url: url, statusCode: *wildcardResp, length: wildcardLength} | |
123 | if d.options.StatusCodesParsed.Contains(wildcardResp) { | |
124 | return &ErrWildcard{url: url, statusCode: wildcardResp, length: wildcardLength} | |
142 | 125 | } |
143 | 126 | } else { |
144 | 127 | return fmt.Errorf("StatusCodes and StatusCodesBlacklist are both not set which should not happen") |
162 | 145 | return ret |
163 | 146 | } |
164 | 147 | |
165 | // Run is the process implementation of gobusterdir | |
166 | func (d *GobusterDir) Run(word string, resChannel chan<- libgobuster.Result) error { | |
167 | suffix := "" | |
168 | if d.options.UseSlash { | |
169 | suffix = "/" | |
170 | } | |
171 | ||
148 | func (d *GobusterDir) AdditionalWords(word string) []string { | |
149 | var words []string | |
172 | 150 | // build list of urls to check |
173 | 151 | // 1: No extension |
174 | 152 | // 2: With extension |
175 | 153 | // 3: backupextension |
176 | urlsToCheck := make(map[string]string) | |
177 | entity := fmt.Sprintf("%s%s", word, suffix) | |
178 | dirURL := fmt.Sprintf("%s%s", d.options.URL, entity) | |
179 | urlsToCheck[entity] = dirURL | |
180 | 154 | if d.options.DiscoverBackup { |
181 | for _, u := range getBackupFilenames(word) { | |
182 | url := fmt.Sprintf("%s%s", d.options.URL, u) | |
183 | urlsToCheck[u] = url | |
184 | } | |
155 | words = append(words, getBackupFilenames(word)...) | |
185 | 156 | } |
186 | 157 | for ext := range d.options.ExtensionsParsed.Set { |
187 | 158 | filename := fmt.Sprintf("%s.%s", word, ext) |
188 | url := fmt.Sprintf("%s%s", d.options.URL, filename) | |
189 | urlsToCheck[filename] = url | |
159 | words = append(words, filename) | |
190 | 160 | if d.options.DiscoverBackup { |
191 | for _, u := range getBackupFilenames(filename) { | |
192 | url2 := fmt.Sprintf("%s%s", d.options.URL, u) | |
193 | urlsToCheck[u] = url2 | |
161 | words = append(words, getBackupFilenames(filename)...) | |
162 | } | |
163 | } | |
164 | return words | |
165 | } | |
166 | ||
167 | // ProcessWord is the process implementation of gobusterdir | |
168 | func (d *GobusterDir) ProcessWord(ctx context.Context, word string, progress *libgobuster.Progress) error { | |
169 | suffix := "" | |
170 | if d.options.UseSlash { | |
171 | suffix = "/" | |
172 | } | |
173 | entity := fmt.Sprintf("%s%s", word, suffix) | |
174 | url := fmt.Sprintf("%s%s", d.options.URL, entity) | |
175 | ||
176 | tries := 1 | |
177 | if d.options.RetryOnTimeout && d.options.RetryAttempts > 0 { | |
178 | // add it so it will be the overall max requests | |
179 | tries += d.options.RetryAttempts | |
180 | } | |
181 | ||
182 | var statusCode int | |
183 | var size int64 | |
184 | var header http.Header | |
185 | for i := 1; i <= tries; i++ { | |
186 | var err error | |
187 | statusCode, size, header, _, err = d.http.Request(ctx, url, libgobuster.RequestOptions{}) | |
188 | if err != nil { | |
189 | // check if it's a timeout and if we should try again and try again | |
190 | // otherwise the timeout error is raised | |
191 | if netErr, ok := err.(net.Error); ok && netErr.Timeout() && i != tries { | |
192 | continue | |
193 | } else if strings.Contains(err.Error(), "invalid control character in URL") { | |
194 | // put error in error chan so it's printed out and ignore it | |
195 | // so gobuster will not quit | |
196 | progress.ErrorChan <- err | |
197 | continue | |
198 | } else { | |
199 | return err | |
194 | 200 | } |
195 | 201 | } |
196 | } | |
197 | ||
198 | for entity, url := range urlsToCheck { | |
199 | statusCode, size, header, _, err := d.http.Request(url, libgobuster.RequestOptions{}) | |
200 | if err != nil { | |
201 | return err | |
202 | } | |
203 | if statusCode != nil { | |
204 | resultStatus := false | |
205 | ||
206 | if d.options.StatusCodesBlacklistParsed.Length() > 0 { | |
207 | if !d.options.StatusCodesBlacklistParsed.Contains(*statusCode) { | |
208 | resultStatus = true | |
209 | } | |
210 | } else if d.options.StatusCodesParsed.Length() > 0 { | |
211 | if d.options.StatusCodesParsed.Contains(*statusCode) { | |
212 | resultStatus = true | |
213 | } | |
214 | } else { | |
215 | return fmt.Errorf("StatusCodes and StatusCodesBlacklist are both not set which should not happen") | |
202 | break | |
203 | } | |
204 | ||
205 | if statusCode != 0 { | |
206 | resultStatus := false | |
207 | ||
208 | if d.options.StatusCodesBlacklistParsed.Length() > 0 { | |
209 | if !d.options.StatusCodesBlacklistParsed.Contains(statusCode) { | |
210 | resultStatus = true | |
216 | 211 | } |
217 | ||
218 | if (resultStatus && !helper.SliceContains(d.options.ExcludeLength, int(size))) || d.globalopts.Verbose { | |
219 | resChannel <- Result{ | |
220 | URL: d.options.URL, | |
221 | Path: entity, | |
222 | Verbose: d.globalopts.Verbose, | |
223 | Expanded: d.options.Expanded, | |
224 | NoStatus: d.options.NoStatus, | |
225 | HideLength: d.options.HideLength, | |
226 | Found: resultStatus, | |
227 | Header: header, | |
228 | StatusCode: *statusCode, | |
229 | Size: size, | |
230 | } | |
212 | } else if d.options.StatusCodesParsed.Length() > 0 { | |
213 | if d.options.StatusCodesParsed.Contains(statusCode) { | |
214 | resultStatus = true | |
215 | } | |
216 | } else { | |
217 | return fmt.Errorf("StatusCodes and StatusCodesBlacklist are both not set which should not happen") | |
218 | } | |
219 | ||
220 | if (resultStatus && !helper.SliceContains(d.options.ExcludeLength, int(size))) || d.globalopts.Verbose { | |
221 | progress.ResultChan <- Result{ | |
222 | URL: d.options.URL, | |
223 | Path: entity, | |
224 | Verbose: d.globalopts.Verbose, | |
225 | Expanded: d.options.Expanded, | |
226 | NoStatus: d.options.NoStatus, | |
227 | HideLength: d.options.HideLength, | |
228 | Found: resultStatus, | |
229 | Header: header, | |
230 | StatusCode: statusCode, | |
231 | Size: size, | |
231 | 232 | } |
232 | 233 | } |
233 | 234 | } |
7 | 7 | type OptionsDir struct { |
8 | 8 | libgobuster.HTTPOptions |
9 | 9 | Extensions string |
10 | ExtensionsParsed libgobuster.StringSet | |
10 | ExtensionsParsed libgobuster.Set[string] | |
11 | 11 | StatusCodes string |
12 | StatusCodesParsed libgobuster.IntSet | |
12 | StatusCodesParsed libgobuster.Set[int] | |
13 | 13 | StatusCodesBlacklist string |
14 | StatusCodesBlacklistParsed libgobuster.IntSet | |
14 | StatusCodesBlacklistParsed libgobuster.Set[int] | |
15 | 15 | UseSlash bool |
16 | WildcardForced bool | |
17 | 16 | HideLength bool |
18 | 17 | Expanded bool |
19 | 18 | NoStatus bool |
24 | 23 | // NewOptionsDir returns a new initialized OptionsDir |
25 | 24 | func NewOptionsDir() *OptionsDir { |
26 | 25 | return &OptionsDir{ |
27 | StatusCodesParsed: libgobuster.NewIntSet(), | |
28 | StatusCodesBlacklistParsed: libgobuster.NewIntSet(), | |
29 | ExtensionsParsed: libgobuster.NewStringSet(), | |
26 | StatusCodesParsed: libgobuster.NewSet[int](), | |
27 | StatusCodesBlacklistParsed: libgobuster.NewSet[int](), | |
28 | ExtensionsParsed: libgobuster.NewSet[string](), | |
30 | 29 | } |
31 | 30 | } |
3 | 3 | "bytes" |
4 | 4 | "fmt" |
5 | 5 | "net/http" |
6 | ||
7 | "github.com/fatih/color" | |
8 | ) | |
9 | ||
10 | var ( | |
11 | white = color.New(color.FgWhite).FprintfFunc() | |
12 | yellow = color.New(color.FgYellow).FprintfFunc() | |
13 | green = color.New(color.FgGreen).FprintfFunc() | |
14 | blue = color.New(color.FgBlue).FprintfFunc() | |
15 | red = color.New(color.FgRed).FprintfFunc() | |
16 | cyan = color.New(color.FgCyan).FprintfFunc() | |
6 | 17 | ) |
7 | 18 | |
8 | 19 | // Result represents a single result |
19 | 30 | Size int64 |
20 | 31 | } |
21 | 32 | |
22 | // ToString converts the Result to it's textual representation | |
33 | // ResultToString converts the Result to it's textual representation | |
23 | 34 | func (r Result) ResultToString() (string, error) { |
24 | 35 | buf := &bytes.Buffer{} |
25 | 36 | |
50 | 61 | } |
51 | 62 | |
52 | 63 | if !r.NoStatus { |
53 | if _, err := fmt.Fprintf(buf, " (Status: %d)", r.StatusCode); err != nil { | |
54 | return "", err | |
64 | color := white | |
65 | if r.StatusCode == 200 { | |
66 | color = green | |
67 | } else if r.StatusCode >= 300 && r.StatusCode < 400 { | |
68 | color = cyan | |
69 | } else if r.StatusCode >= 400 && r.StatusCode < 500 { | |
70 | color = yellow | |
71 | } else if r.StatusCode >= 500 && r.StatusCode < 600 { | |
72 | color = red | |
55 | 73 | } |
74 | ||
75 | color(buf, " (Status: %d)", r.StatusCode) | |
56 | 76 | } |
57 | 77 | |
58 | 78 | if !r.HideLength { |
63 | 83 | |
64 | 84 | location := r.Header.Get("Location") |
65 | 85 | if location != "" { |
66 | if _, err := fmt.Fprintf(buf, " [--> %s]", location); err != nil { | |
67 | return "", err | |
68 | } | |
86 | blue(buf, " [--> %s]", location) | |
69 | 87 | } |
70 | 88 | |
71 | 89 | if _, err := fmt.Fprintf(buf, "\n"); err != nil { |
6 | 6 | "fmt" |
7 | 7 | "log" |
8 | 8 | "net" |
9 | "net/netip" | |
9 | 10 | "strings" |
10 | 11 | "text/tabwriter" |
11 | 12 | "time" |
16 | 17 | |
17 | 18 | // ErrWildcard is returned if a wildcard response is found |
18 | 19 | type ErrWildcard struct { |
19 | wildcardIps libgobuster.StringSet | |
20 | wildcardIps libgobuster.Set[netip.Addr] | |
20 | 21 | } |
21 | 22 | |
22 | 23 | // Error is the implementation of the error interface |
30 | 31 | globalopts *libgobuster.Options |
31 | 32 | options *OptionsDNS |
32 | 33 | isWildcard bool |
33 | wildcardIps libgobuster.StringSet | |
34 | wildcardIps libgobuster.Set[netip.Addr] | |
34 | 35 | } |
35 | 36 | |
36 | 37 | func newCustomDialer(server string) func(ctx context.Context, network, address string) (net.Conn, error) { |
64 | 65 | g := GobusterDNS{ |
65 | 66 | options: opts, |
66 | 67 | globalopts: globalopts, |
67 | wildcardIps: libgobuster.NewStringSet(), | |
68 | wildcardIps: libgobuster.NewSet[netip.Addr](), | |
68 | 69 | resolver: resolver, |
69 | 70 | } |
70 | 71 | return &g, nil |
75 | 76 | return "DNS enumeration" |
76 | 77 | } |
77 | 78 | |
78 | // RequestsPerRun returns the number of requests this plugin makes per single wordlist item | |
79 | func (d *GobusterDNS) RequestsPerRun() int { | |
80 | return 1 | |
81 | } | |
82 | ||
83 | 79 | // PreRun is the pre run implementation of gobusterdns |
84 | func (d *GobusterDNS) PreRun() error { | |
80 | func (d *GobusterDNS) PreRun(ctx context.Context) error { | |
85 | 81 | // Resolve a subdomain that probably shouldn't exist |
86 | 82 | guid := uuid.New() |
87 | wildcardIps, err := d.dnsLookup(fmt.Sprintf("%s.%s", guid, d.options.Domain)) | |
83 | wildcardIps, err := d.dnsLookup(ctx, fmt.Sprintf("%s.%s", guid, d.options.Domain)) | |
88 | 84 | if err == nil { |
89 | 85 | d.isWildcard = true |
90 | 86 | d.wildcardIps.AddRange(wildcardIps) |
95 | 91 | |
96 | 92 | if !d.globalopts.Quiet { |
97 | 93 | // Provide a warning if the base domain doesn't resolve (in case of typo) |
98 | _, err = d.dnsLookup(d.options.Domain) | |
94 | _, err = d.dnsLookup(ctx, d.options.Domain) | |
99 | 95 | if err != nil { |
100 | 96 | // Not an error, just a warning. Eg. `yp.to` doesn't resolve, but `cr.yp.to` does! |
101 | 97 | log.Printf("[-] Unable to validate base domain: %s (%v)", d.options.Domain, err) |
105 | 101 | return nil |
106 | 102 | } |
107 | 103 | |
108 | // Run is the process implementation of gobusterdns | |
109 | func (d *GobusterDNS) Run(word string, resChannel chan<- libgobuster.Result) error { | |
104 | // ProcessWord is the process implementation of gobusterdns | |
105 | func (d *GobusterDNS) ProcessWord(ctx context.Context, word string, progress *libgobuster.Progress) error { | |
110 | 106 | subdomain := fmt.Sprintf("%s.%s", word, d.options.Domain) |
111 | ips, err := d.dnsLookup(subdomain) | |
107 | ips, err := d.dnsLookup(ctx, subdomain) | |
112 | 108 | if err == nil { |
113 | 109 | if !d.isWildcard || !d.wildcardIps.ContainsAny(ips) { |
114 | 110 | result := Result{ |
120 | 116 | if d.options.ShowIPs { |
121 | 117 | result.IPs = ips |
122 | 118 | } else if d.options.ShowCNAME { |
123 | cname, err := d.dnsLookupCname(subdomain) | |
119 | cname, err := d.dnsLookupCname(ctx, subdomain) | |
124 | 120 | if err == nil { |
125 | 121 | result.CNAME = cname |
126 | 122 | } |
127 | 123 | } |
128 | resChannel <- result | |
124 | progress.ResultChan <- result | |
129 | 125 | } |
130 | 126 | } else if d.globalopts.Verbose { |
131 | resChannel <- Result{ | |
127 | progress.ResultChan <- Result{ | |
132 | 128 | Subdomain: subdomain, |
133 | 129 | Found: false, |
134 | 130 | ShowIPs: d.options.ShowIPs, |
136 | 132 | } |
137 | 133 | } |
138 | 134 | return nil |
135 | } | |
136 | ||
137 | func (d *GobusterDNS) AdditionalWords(word string) []string { | |
138 | return []string{} | |
139 | 139 | } |
140 | 140 | |
141 | 141 | // GetConfigString returns the string representation of the current config |
218 | 218 | return strings.TrimSpace(buffer.String()), nil |
219 | 219 | } |
220 | 220 | |
221 | func (d *GobusterDNS) dnsLookup(domain string) ([]string, error) { | |
222 | ctx, cancel := context.WithTimeout(context.Background(), d.options.Timeout) | |
221 | func (d *GobusterDNS) dnsLookup(ctx context.Context, domain string) ([]netip.Addr, error) { | |
222 | ctx2, cancel := context.WithTimeout(ctx, d.options.Timeout) | |
223 | 223 | defer cancel() |
224 | return d.resolver.LookupHost(ctx, domain) | |
225 | } | |
226 | ||
227 | func (d *GobusterDNS) dnsLookupCname(domain string) (string, error) { | |
228 | ctx, cancel := context.WithTimeout(context.Background(), d.options.Timeout) | |
224 | return d.resolver.LookupNetIP(ctx2, "ip", domain) | |
225 | } | |
226 | ||
227 | func (d *GobusterDNS) dnsLookupCname(ctx context.Context, domain string) (string, error) { | |
228 | ctx2, cancel := context.WithTimeout(ctx, d.options.Timeout) | |
229 | 229 | defer cancel() |
230 | 230 | time.Sleep(time.Second) |
231 | return d.resolver.LookupCNAME(ctx, domain) | |
232 | } | |
231 | return d.resolver.LookupCNAME(ctx2, domain) | |
232 | } |
1 | 1 | |
2 | 2 | import ( |
3 | 3 | "bytes" |
4 | "fmt" | |
4 | "net/netip" | |
5 | 5 | "strings" |
6 | ||
7 | "github.com/fatih/color" | |
8 | ) | |
9 | ||
10 | var ( | |
11 | yellow = color.New(color.FgYellow).FprintfFunc() | |
12 | green = color.New(color.FgGreen).FprintfFunc() | |
6 | 13 | ) |
7 | 14 | |
8 | 15 | // Result represents a single result |
11 | 18 | ShowCNAME bool |
12 | 19 | Found bool |
13 | 20 | Subdomain string |
14 | IPs []string | |
21 | IPs []netip.Addr | |
15 | 22 | CNAME string |
16 | 23 | } |
17 | 24 | |
18 | // ToString converts the Result to it's textual representation | |
25 | // ResultToString converts the Result to it's textual representation | |
19 | 26 | func (r Result) ResultToString() (string, error) { |
20 | 27 | buf := &bytes.Buffer{} |
21 | 28 | |
29 | c := green | |
30 | ||
22 | 31 | if r.Found { |
23 | if _, err := fmt.Fprintf(buf, "Found: "); err != nil { | |
24 | return "", err | |
25 | } | |
32 | c(buf, "Found: ") | |
26 | 33 | } else { |
27 | if _, err := fmt.Fprintf(buf, "Missed: "); err != nil { | |
28 | return "", err | |
29 | } | |
34 | c = yellow | |
35 | c(buf, "Missed: ") | |
30 | 36 | } |
31 | 37 | |
32 | 38 | if r.ShowIPs && r.Found { |
33 | if _, err := fmt.Fprintf(buf, "%s [%s]\n", r.Subdomain, strings.Join(r.IPs, ",")); err != nil { | |
34 | return "", err | |
39 | ips := make([]string, len(r.IPs)) | |
40 | for i := range r.IPs { | |
41 | ips[i] = r.IPs[i].String() | |
35 | 42 | } |
43 | c(buf, "%s [%s]\n", r.Subdomain, strings.Join(ips, ",")) | |
36 | 44 | } else if r.ShowCNAME && r.Found && r.CNAME != "" { |
37 | if _, err := fmt.Fprintf(buf, "%s [%s]\n", r.Subdomain, r.CNAME); err != nil { | |
38 | return "", err | |
39 | } | |
45 | c(buf, "%s [%s]\n", r.Subdomain, r.CNAME) | |
40 | 46 | } else { |
41 | if _, err := fmt.Fprintf(buf, "%s\n", r.Subdomain); err != nil { | |
42 | return "", err | |
43 | } | |
47 | c(buf, "%s\n", r.Subdomain) | |
44 | 48 | } |
45 | 49 | |
46 | 50 | s := buf.String() |
4 | 4 | "bytes" |
5 | 5 | "context" |
6 | 6 | "fmt" |
7 | "net" | |
7 | 8 | "strings" |
8 | 9 | "text/tabwriter" |
9 | 10 | |
30 | 31 | } |
31 | 32 | |
32 | 33 | // NewGobusterFuzz creates a new initialized GobusterFuzz |
33 | func NewGobusterFuzz(cont context.Context, globalopts *libgobuster.Options, opts *OptionsFuzz) (*GobusterFuzz, error) { | |
34 | func NewGobusterFuzz(globalopts *libgobuster.Options, opts *OptionsFuzz) (*GobusterFuzz, error) { | |
34 | 35 | if globalopts == nil { |
35 | 36 | return nil, fmt.Errorf("please provide valid global options") |
36 | 37 | } |
45 | 46 | } |
46 | 47 | |
47 | 48 | basicOptions := libgobuster.BasicHTTPOptions{ |
48 | Proxy: opts.Proxy, | |
49 | Timeout: opts.Timeout, | |
50 | UserAgent: opts.UserAgent, | |
49 | Proxy: opts.Proxy, | |
50 | Timeout: opts.Timeout, | |
51 | UserAgent: opts.UserAgent, | |
52 | NoTLSValidation: opts.NoTLSValidation, | |
53 | RetryOnTimeout: opts.RetryOnTimeout, | |
54 | RetryAttempts: opts.RetryAttempts, | |
51 | 55 | } |
52 | 56 | |
53 | 57 | httpOpts := libgobuster.HTTPOptions{ |
54 | 58 | BasicHTTPOptions: basicOptions, |
55 | 59 | FollowRedirect: opts.FollowRedirect, |
56 | NoTLSValidation: opts.NoTLSValidation, | |
57 | 60 | Username: opts.Username, |
58 | 61 | Password: opts.Password, |
59 | 62 | Headers: opts.Headers, |
61 | 64 | Method: opts.Method, |
62 | 65 | } |
63 | 66 | |
64 | h, err := libgobuster.NewHTTPClient(cont, &httpOpts) | |
67 | h, err := libgobuster.NewHTTPClient(&httpOpts) | |
65 | 68 | if err != nil { |
66 | 69 | return nil, err |
67 | 70 | } |
74 | 77 | return "fuzzing" |
75 | 78 | } |
76 | 79 | |
77 | // RequestsPerRun returns the number of requests this plugin makes per single wordlist item | |
78 | func (d *GobusterFuzz) RequestsPerRun() int { | |
79 | return 1 | |
80 | } | |
81 | ||
82 | 80 | // PreRun is the pre run implementation of gobusterfuzz |
83 | func (d *GobusterFuzz) PreRun() error { | |
81 | func (d *GobusterFuzz) PreRun(ctx context.Context) error { | |
84 | 82 | return nil |
85 | 83 | } |
86 | 84 | |
87 | // Run is the process implementation of gobusterfuzz | |
88 | func (d *GobusterFuzz) Run(word string, resChannel chan<- libgobuster.Result) error { | |
89 | workingURL := strings.ReplaceAll(d.options.URL, "FUZZ", word) | |
90 | statusCode, size, _, _, err := d.http.Request(workingURL, libgobuster.RequestOptions{}) | |
91 | if err != nil { | |
92 | return err | |
93 | } | |
94 | if statusCode != nil { | |
85 | // ProcessWord is the process implementation of gobusterfuzz | |
86 | func (d *GobusterFuzz) ProcessWord(ctx context.Context, word string, progress *libgobuster.Progress) error { | |
87 | url := strings.ReplaceAll(d.options.URL, "FUZZ", word) | |
88 | ||
89 | tries := 1 | |
90 | if d.options.RetryOnTimeout && d.options.RetryAttempts > 0 { | |
91 | // add it so it will be the overall max requests | |
92 | tries += d.options.RetryAttempts | |
93 | } | |
94 | ||
95 | var statusCode int | |
96 | var size int64 | |
97 | for i := 1; i <= tries; i++ { | |
98 | var err error | |
99 | statusCode, size, _, _, err = d.http.Request(ctx, url, libgobuster.RequestOptions{}) | |
100 | if err != nil { | |
101 | // check if it's a timeout and if we should try again and try again | |
102 | // otherwise the timeout error is raised | |
103 | if netErr, ok := err.(net.Error); ok && netErr.Timeout() && i != tries { | |
104 | continue | |
105 | } else if strings.Contains(err.Error(), "invalid control character in URL") { | |
106 | // put error in error chan so it's printed out and ignore it | |
107 | // so gobuster will not quit | |
108 | progress.ErrorChan <- err | |
109 | continue | |
110 | } else { | |
111 | return err | |
112 | } | |
113 | } | |
114 | break | |
115 | } | |
116 | ||
117 | if statusCode != 0 { | |
95 | 118 | resultStatus := true |
96 | 119 | |
97 | 120 | if helper.SliceContains(d.options.ExcludeLength, int(size)) { |
99 | 122 | } |
100 | 123 | |
101 | 124 | if d.options.ExcludedStatusCodesParsed.Length() > 0 { |
102 | if d.options.ExcludedStatusCodesParsed.Contains(*statusCode) { | |
125 | if d.options.ExcludedStatusCodesParsed.Contains(statusCode) { | |
103 | 126 | resultStatus = false |
104 | 127 | } |
105 | 128 | } |
106 | 129 | |
107 | 130 | if resultStatus || d.globalopts.Verbose { |
108 | resChannel <- Result{ | |
131 | progress.ResultChan <- Result{ | |
109 | 132 | Verbose: d.globalopts.Verbose, |
110 | 133 | Found: resultStatus, |
111 | Path: workingURL, | |
112 | StatusCode: *statusCode, | |
134 | Path: url, | |
135 | StatusCode: statusCode, | |
113 | 136 | Size: size, |
114 | 137 | } |
115 | 138 | } |
116 | 139 | } |
117 | 140 | return nil |
141 | } | |
142 | ||
143 | func (d *GobusterFuzz) AdditionalWords(word string) []string { | |
144 | return []string{} | |
118 | 145 | } |
119 | 146 | |
120 | 147 | // GetConfigString returns the string representation of the current config |
7 | 7 | type OptionsFuzz struct { |
8 | 8 | libgobuster.HTTPOptions |
9 | 9 | ExcludedStatusCodes string |
10 | ExcludedStatusCodesParsed libgobuster.IntSet | |
11 | WildcardForced bool | |
10 | ExcludedStatusCodesParsed libgobuster.Set[int] | |
12 | 11 | ExcludeLength []int |
13 | 12 | } |
14 | 13 | |
15 | 14 | // NewOptionsFuzz returns a new initialized OptionsFuzz |
16 | 15 | func NewOptionsFuzz() *OptionsFuzz { |
17 | 16 | return &OptionsFuzz{ |
18 | ExcludedStatusCodesParsed: libgobuster.NewIntSet(), | |
17 | ExcludedStatusCodesParsed: libgobuster.NewSet[int](), | |
19 | 18 | } |
20 | 19 | } |
1 | 1 | |
2 | 2 | import ( |
3 | 3 | "bytes" |
4 | "fmt" | |
4 | ||
5 | "github.com/fatih/color" | |
6 | ) | |
7 | ||
8 | var ( | |
9 | yellow = color.New(color.FgYellow).FprintfFunc() | |
10 | green = color.New(color.FgGreen).FprintfFunc() | |
5 | 11 | ) |
6 | 12 | |
7 | 13 | // Result represents a single result |
13 | 19 | Size int64 |
14 | 20 | } |
15 | 21 | |
16 | // ToString converts the Result to it's textual representation | |
22 | // ResultToString converts the Result to it's textual representation | |
17 | 23 | func (r Result) ResultToString() (string, error) { |
18 | 24 | buf := &bytes.Buffer{} |
25 | ||
26 | c := green | |
19 | 27 | |
20 | 28 | // Prefix if we're in verbose mode |
21 | 29 | if r.Verbose { |
22 | 30 | if r.Found { |
23 | if _, err := fmt.Fprintf(buf, "Found: "); err != nil { | |
24 | return "", err | |
25 | } | |
31 | c(buf, "Found: ") | |
26 | 32 | } else { |
27 | if _, err := fmt.Fprintf(buf, "Missed: "); err != nil { | |
28 | return "", err | |
29 | } | |
33 | c = yellow | |
34 | c(buf, "Missed: ") | |
30 | 35 | } |
31 | 36 | } else if r.Found { |
32 | if _, err := fmt.Fprintf(buf, "Found: "); err != nil { | |
33 | return "", err | |
34 | } | |
37 | c(buf, "Found: ") | |
35 | 38 | } |
36 | 39 | |
37 | if _, err := fmt.Fprintf(buf, "[Status=%d] [Length=%d] %s", r.StatusCode, r.Size, r.Path); err != nil { | |
38 | return "", err | |
39 | } | |
40 | ||
41 | if _, err := fmt.Fprintf(buf, "\n"); err != nil { | |
42 | return "", err | |
43 | } | |
40 | c(buf, "[Status=%d] [Length=%d] %s", r.StatusCode, r.Size, r.Path) | |
41 | c(buf, "\n") | |
44 | 42 | |
45 | 43 | s := buf.String() |
46 | 44 | return s, nil |
0 | package gobustergcs | |
1 | ||
2 | import ( | |
3 | "bufio" | |
4 | "bytes" | |
5 | "context" | |
6 | "encoding/json" | |
7 | "fmt" | |
8 | "net" | |
9 | "net/http" | |
10 | "regexp" | |
11 | "strings" | |
12 | "text/tabwriter" | |
13 | ||
14 | "github.com/OJ/gobuster/v3/libgobuster" | |
15 | ) | |
16 | ||
17 | // GobusterGCS is the main type to implement the interface | |
18 | type GobusterGCS struct { | |
19 | options *OptionsGCS | |
20 | globalopts *libgobuster.Options | |
21 | http *libgobuster.HTTPClient | |
22 | bucketRegex *regexp.Regexp | |
23 | } | |
24 | ||
25 | // NewGobusterGCS creates a new initialized GobusterGCS | |
26 | func NewGobusterGCS(globalopts *libgobuster.Options, opts *OptionsGCS) (*GobusterGCS, error) { | |
27 | if globalopts == nil { | |
28 | return nil, fmt.Errorf("please provide valid global options") | |
29 | } | |
30 | ||
31 | if opts == nil { | |
32 | return nil, fmt.Errorf("please provide valid plugin options") | |
33 | } | |
34 | ||
35 | g := GobusterGCS{ | |
36 | options: opts, | |
37 | globalopts: globalopts, | |
38 | } | |
39 | ||
40 | basicOptions := libgobuster.BasicHTTPOptions{ | |
41 | Proxy: opts.Proxy, | |
42 | Timeout: opts.Timeout, | |
43 | UserAgent: opts.UserAgent, | |
44 | NoTLSValidation: opts.NoTLSValidation, | |
45 | RetryOnTimeout: opts.RetryOnTimeout, | |
46 | RetryAttempts: opts.RetryAttempts, | |
47 | } | |
48 | ||
49 | httpOpts := libgobuster.HTTPOptions{ | |
50 | BasicHTTPOptions: basicOptions, | |
51 | // needed so we can list bucket contents | |
52 | FollowRedirect: true, | |
53 | } | |
54 | ||
55 | h, err := libgobuster.NewHTTPClient(&httpOpts) | |
56 | if err != nil { | |
57 | return nil, err | |
58 | } | |
59 | g.http = h | |
60 | // https://cloud.google.com/storage/docs/naming-buckets | |
61 | g.bucketRegex = regexp.MustCompile(`^[a-z0-9][a-z0-9\-_.]{1,61}[a-z0-9](\.[a-z0-9][a-z0-9\-_.]{1,61}[a-z0-9])*$`) | |
62 | ||
63 | return &g, nil | |
64 | } | |
65 | ||
66 | // Name should return the name of the plugin | |
67 | func (s *GobusterGCS) Name() string { | |
68 | return "GCS bucket enumeration" | |
69 | } | |
70 | ||
71 | // PreRun is the pre run implementation of GobusterS3 | |
72 | func (s *GobusterGCS) PreRun(ctx context.Context) error { | |
73 | return nil | |
74 | } | |
75 | ||
76 | // ProcessWord is the process implementation of GobusterS3 | |
77 | func (s *GobusterGCS) ProcessWord(ctx context.Context, word string, progress *libgobuster.Progress) error { | |
78 | // only check for valid bucket names | |
79 | if !s.isValidBucketName(word) { | |
80 | return nil | |
81 | } | |
82 | ||
83 | bucketURL := fmt.Sprintf("https://storage.googleapis.com/storage/v1/b/%s/o?maxResults=%d", word, s.options.MaxFilesToList) | |
84 | ||
85 | tries := 1 | |
86 | if s.options.RetryOnTimeout && s.options.RetryAttempts > 0 { | |
87 | // add it so it will be the overall max requests | |
88 | tries += s.options.RetryAttempts | |
89 | } | |
90 | ||
91 | var statusCode int | |
92 | var body []byte | |
93 | for i := 1; i <= tries; i++ { | |
94 | var err error | |
95 | statusCode, _, _, body, err = s.http.Request(ctx, bucketURL, libgobuster.RequestOptions{ReturnBody: true}) | |
96 | if err != nil { | |
97 | // check if it's a timeout and if we should try again and try again | |
98 | // otherwise the timeout error is raised | |
99 | if netErr, ok := err.(net.Error); ok && netErr.Timeout() && i != tries { | |
100 | continue | |
101 | } else if strings.Contains(err.Error(), "invalid control character in URL") { | |
102 | // put error in error chan so it's printed out and ignore it | |
103 | // so gobuster will not quit | |
104 | progress.ErrorChan <- err | |
105 | continue | |
106 | } else { | |
107 | return err | |
108 | } | |
109 | } | |
110 | break | |
111 | } | |
112 | ||
113 | if statusCode == 0 || body == nil { | |
114 | return nil | |
115 | } | |
116 | ||
117 | // looks like 401, 403, and 404 are the only negative status codes | |
118 | found := false | |
119 | switch statusCode { | |
120 | case http.StatusUnauthorized, | |
121 | http.StatusForbidden, | |
122 | http.StatusNotFound: | |
123 | found = false | |
124 | case http.StatusOK: | |
125 | // listing enabled | |
126 | found = true | |
127 | default: | |
128 | // default to found as we use negative status codes | |
129 | found = true | |
130 | } | |
131 | ||
132 | // nothing found, bail out | |
133 | // may add the result later if we want to enable verbose output | |
134 | if !found { | |
135 | return nil | |
136 | } | |
137 | ||
138 | extraStr := "" | |
139 | if s.globalopts.Verbose { | |
140 | // get status | |
141 | var result map[string]interface{} | |
142 | err := json.Unmarshal(body, &result) | |
143 | ||
144 | if err != nil { | |
145 | return fmt.Errorf("could not parse response json: %w", err) | |
146 | } | |
147 | ||
148 | if _, exist := result["error"]; exist { | |
149 | // https://cloud.google.com/storage/docs/json_api/v1/status-codes | |
150 | gcsError := GCSError{} | |
151 | err := json.Unmarshal(body, &gcsError) | |
152 | if err != nil { | |
153 | return fmt.Errorf("could not parse error json: %w", err) | |
154 | } | |
155 | extraStr = fmt.Sprintf("Error: %s (%d)", gcsError.Error.Message, gcsError.Error.Code) | |
156 | } else if v, exist := result["kind"]; exist && v == "storage#objects" { | |
157 | // https://cloud.google.com/storage/docs/json_api/v1/status-codes | |
158 | // bucket listing enabled | |
159 | gcsListing := GCSListing{} | |
160 | err := json.Unmarshal(body, &gcsListing) | |
161 | if err != nil { | |
162 | return fmt.Errorf("could not parse result json: %w", err) | |
163 | } | |
164 | extraStr = "Bucket Listing enabled: " | |
165 | for _, x := range gcsListing.Items { | |
166 | extraStr += fmt.Sprintf("%s (%sb), ", x.Name, x.Size) | |
167 | } | |
168 | extraStr = strings.TrimRight(extraStr, ", ") | |
169 | } | |
170 | } | |
171 | ||
172 | progress.ResultChan <- Result{ | |
173 | Found: found, | |
174 | BucketName: word, | |
175 | Status: extraStr, | |
176 | } | |
177 | ||
178 | return nil | |
179 | } | |
180 | ||
181 | func (s *GobusterGCS) AdditionalWords(word string) []string { | |
182 | return []string{} | |
183 | } | |
184 | ||
185 | // GetConfigString returns the string representation of the current config | |
186 | func (s *GobusterGCS) GetConfigString() (string, error) { | |
187 | var buffer bytes.Buffer | |
188 | bw := bufio.NewWriter(&buffer) | |
189 | tw := tabwriter.NewWriter(bw, 0, 5, 3, ' ', 0) | |
190 | o := s.options | |
191 | ||
192 | if _, err := fmt.Fprintf(tw, "[+] Threads:\t%d\n", s.globalopts.Threads); err != nil { | |
193 | return "", err | |
194 | } | |
195 | ||
196 | if s.globalopts.Delay > 0 { | |
197 | if _, err := fmt.Fprintf(tw, "[+] Delay:\t%s\n", s.globalopts.Delay); err != nil { | |
198 | return "", err | |
199 | } | |
200 | } | |
201 | ||
202 | wordlist := "stdin (pipe)" | |
203 | if s.globalopts.Wordlist != "-" { | |
204 | wordlist = s.globalopts.Wordlist | |
205 | } | |
206 | if _, err := fmt.Fprintf(tw, "[+] Wordlist:\t%s\n", wordlist); err != nil { | |
207 | return "", err | |
208 | } | |
209 | ||
210 | if s.globalopts.PatternFile != "" { | |
211 | if _, err := fmt.Fprintf(tw, "[+] Patterns:\t%s (%d entries)\n", s.globalopts.PatternFile, len(s.globalopts.Patterns)); err != nil { | |
212 | return "", err | |
213 | } | |
214 | } | |
215 | ||
216 | if o.Proxy != "" { | |
217 | if _, err := fmt.Fprintf(tw, "[+] Proxy:\t%s\n", o.Proxy); err != nil { | |
218 | return "", err | |
219 | } | |
220 | } | |
221 | ||
222 | if o.UserAgent != "" { | |
223 | if _, err := fmt.Fprintf(tw, "[+] User Agent:\t%s\n", o.UserAgent); err != nil { | |
224 | return "", err | |
225 | } | |
226 | } | |
227 | ||
228 | if _, err := fmt.Fprintf(tw, "[+] Timeout:\t%s\n", o.Timeout.String()); err != nil { | |
229 | return "", err | |
230 | } | |
231 | ||
232 | if s.globalopts.Verbose { | |
233 | if _, err := fmt.Fprintf(tw, "[+] Verbose:\ttrue\n"); err != nil { | |
234 | return "", err | |
235 | } | |
236 | } | |
237 | ||
238 | if _, err := fmt.Fprintf(tw, "[+] Maximum files to list:\t%d\n", o.MaxFilesToList); err != nil { | |
239 | return "", err | |
240 | } | |
241 | ||
242 | if err := tw.Flush(); err != nil { | |
243 | return "", fmt.Errorf("error on tostring: %w", err) | |
244 | } | |
245 | ||
246 | if err := bw.Flush(); err != nil { | |
247 | return "", fmt.Errorf("error on tostring: %w", err) | |
248 | } | |
249 | ||
250 | return strings.TrimSpace(buffer.String()), nil | |
251 | } | |
252 | ||
253 | // https://docs.aws.amazon.com/AmazonS3/latest/dev/BucketRestrictions.html | |
254 | func (s *GobusterGCS) isValidBucketName(bucketName string) bool { | |
255 | if len(bucketName) > 222 || !s.bucketRegex.MatchString(bucketName) { | |
256 | return false | |
257 | } | |
258 | if strings.HasPrefix(bucketName, "-") || strings.HasSuffix(bucketName, "-") || | |
259 | strings.HasPrefix(bucketName, "_") || strings.HasSuffix(bucketName, "_") || | |
260 | strings.HasPrefix(bucketName, ".") || strings.HasSuffix(bucketName, ".") { | |
261 | return false | |
262 | } | |
263 | return true | |
264 | } |
0 | package gobustergcs | |
1 | ||
2 | import ( | |
3 | "github.com/OJ/gobuster/v3/libgobuster" | |
4 | ) | |
5 | ||
6 | // OptionsGCS is the struct to hold all options for this plugin | |
7 | type OptionsGCS struct { | |
8 | libgobuster.BasicHTTPOptions | |
9 | MaxFilesToList int | |
10 | } | |
11 | ||
12 | // NewOptionsGCS returns a new initialized OptionsS3 | |
13 | func NewOptionsGCS() *OptionsGCS { | |
14 | return &OptionsGCS{} | |
15 | } |
0 | package gobustergcs | |
1 | ||
2 | import ( | |
3 | "bytes" | |
4 | ||
5 | "github.com/fatih/color" | |
6 | ) | |
7 | ||
8 | var ( | |
9 | green = color.New(color.FgGreen).FprintfFunc() | |
10 | ) | |
11 | ||
12 | // Result represents a single result | |
13 | type Result struct { | |
14 | Found bool | |
15 | BucketName string | |
16 | Status string | |
17 | } | |
18 | ||
19 | // ResultToString converts the Result to it's textual representation | |
20 | func (r Result) ResultToString() (string, error) { | |
21 | buf := &bytes.Buffer{} | |
22 | ||
23 | c := green | |
24 | ||
25 | c(buf, "https://storage.googleapis.com/storage/v1/b/%s/o", r.BucketName) | |
26 | ||
27 | if r.Status != "" { | |
28 | c(buf, " [%s]", r.Status) | |
29 | } | |
30 | c(buf, "\n") | |
31 | ||
32 | str := buf.String() | |
33 | return str, nil | |
34 | } |
0 | package gobustergcs | |
1 | ||
2 | // GCSError represents a returned error from GCS | |
3 | type GCSError struct { | |
4 | Error struct { | |
5 | Code int `json:"code"` | |
6 | Message string `json:"message"` | |
7 | Errors []struct { | |
8 | Message string `json:"message"` | |
9 | Reason string `json:"reason"` | |
10 | LocationType string `json:"locationType"` | |
11 | Location string `json:"location"` | |
12 | } `json:"errors"` | |
13 | } `json:"error"` | |
14 | } | |
15 | ||
16 | // GCSListing contains only a subset of returned properties | |
17 | type GCSListing struct { | |
18 | IsTruncated string `json:"nextPageToken"` | |
19 | Items []struct { | |
20 | Name string `json:"name"` | |
21 | LastModified string `json:"updated"` | |
22 | Size string `json:"size"` | |
23 | } `json:"items"` | |
24 | } |
5 | 5 | "context" |
6 | 6 | "encoding/xml" |
7 | 7 | "fmt" |
8 | "net" | |
8 | 9 | "net/http" |
9 | 10 | "regexp" |
10 | 11 | "strings" |
22 | 23 | } |
23 | 24 | |
24 | 25 | // NewGobusterS3 creates a new initialized GobusterS3 |
25 | func NewGobusterS3(cont context.Context, globalopts *libgobuster.Options, opts *OptionsS3) (*GobusterS3, error) { | |
26 | func NewGobusterS3(globalopts *libgobuster.Options, opts *OptionsS3) (*GobusterS3, error) { | |
26 | 27 | if globalopts == nil { |
27 | 28 | return nil, fmt.Errorf("please provide valid global options") |
28 | 29 | } |
37 | 38 | } |
38 | 39 | |
39 | 40 | basicOptions := libgobuster.BasicHTTPOptions{ |
40 | Proxy: opts.Proxy, | |
41 | Timeout: opts.Timeout, | |
42 | UserAgent: opts.UserAgent, | |
41 | Proxy: opts.Proxy, | |
42 | Timeout: opts.Timeout, | |
43 | UserAgent: opts.UserAgent, | |
44 | NoTLSValidation: opts.NoTLSValidation, | |
45 | RetryOnTimeout: opts.RetryOnTimeout, | |
46 | RetryAttempts: opts.RetryAttempts, | |
43 | 47 | } |
44 | 48 | |
45 | 49 | httpOpts := libgobuster.HTTPOptions{ |
48 | 52 | FollowRedirect: true, |
49 | 53 | } |
50 | 54 | |
51 | h, err := libgobuster.NewHTTPClient(cont, &httpOpts) | |
55 | h, err := libgobuster.NewHTTPClient(&httpOpts) | |
52 | 56 | if err != nil { |
53 | 57 | return nil, err |
54 | 58 | } |
63 | 67 | return "S3 bucket enumeration" |
64 | 68 | } |
65 | 69 | |
66 | // RequestsPerRun returns the number of requests this plugin makes per single wordlist item | |
67 | func (s *GobusterS3) RequestsPerRun() int { | |
68 | return 1 | |
69 | } | |
70 | ||
71 | 70 | // PreRun is the pre run implementation of GobusterS3 |
72 | func (s *GobusterS3) PreRun() error { | |
71 | func (s *GobusterS3) PreRun(ctx context.Context) error { | |
73 | 72 | return nil |
74 | 73 | } |
75 | 74 | |
76 | // Run is the process implementation of GobusterS3 | |
77 | func (s *GobusterS3) Run(word string, resChannel chan<- libgobuster.Result) error { | |
75 | // ProcessWord is the process implementation of GobusterS3 | |
76 | func (s *GobusterS3) ProcessWord(ctx context.Context, word string, progress *libgobuster.Progress) error { | |
78 | 77 | // only check for valid bucket names |
79 | 78 | if !s.isValidBucketName(word) { |
80 | 79 | return nil |
81 | 80 | } |
82 | 81 | |
83 | bucketURL := fmt.Sprintf("http://%s.s3.amazonaws.com/?max-keys=%d", word, s.options.MaxFilesToList) | |
84 | status, _, _, body, err := s.http.Request(bucketURL, libgobuster.RequestOptions{ReturnBody: true}) | |
85 | if err != nil { | |
86 | return err | |
87 | } | |
88 | ||
89 | if status == nil || body == nil { | |
82 | bucketURL := fmt.Sprintf("https://%s.s3.amazonaws.com/?max-keys=%d", word, s.options.MaxFilesToList) | |
83 | ||
84 | tries := 1 | |
85 | if s.options.RetryOnTimeout && s.options.RetryAttempts > 0 { | |
86 | // add it so it will be the overall max requests | |
87 | tries += s.options.RetryAttempts | |
88 | } | |
89 | ||
90 | var statusCode int | |
91 | var body []byte | |
92 | for i := 1; i <= tries; i++ { | |
93 | var err error | |
94 | statusCode, _, _, body, err = s.http.Request(ctx, bucketURL, libgobuster.RequestOptions{ReturnBody: true}) | |
95 | if err != nil { | |
96 | // check if it's a timeout and if we should try again and try again | |
97 | // otherwise the timeout error is raised | |
98 | if netErr, ok := err.(net.Error); ok && netErr.Timeout() && i != tries { | |
99 | continue | |
100 | } else if strings.Contains(err.Error(), "invalid control character in URL") { | |
101 | // put error in error chan so it's printed out and ignore it | |
102 | // so gobuster will not quit | |
103 | progress.ErrorChan <- err | |
104 | continue | |
105 | } else { | |
106 | return err | |
107 | } | |
108 | } | |
109 | break | |
110 | } | |
111 | ||
112 | if statusCode == 0 || body == nil { | |
90 | 113 | return nil |
91 | 114 | } |
92 | 115 | |
93 | 116 | // looks like 404 and 400 are the only negative status codes |
94 | 117 | found := false |
95 | switch *status { | |
118 | switch statusCode { | |
96 | 119 | case http.StatusBadRequest: |
97 | 120 | case http.StatusNotFound: |
98 | 121 | found = false |
137 | 160 | } |
138 | 161 | } |
139 | 162 | |
140 | resChannel <- Result{ | |
163 | progress.ResultChan <- Result{ | |
141 | 164 | Found: found, |
142 | 165 | BucketName: word, |
143 | 166 | Status: extraStr, |
144 | 167 | } |
145 | 168 | |
146 | 169 | return nil |
170 | } | |
171 | ||
172 | func (d *GobusterS3) AdditionalWords(word string) []string { | |
173 | return []string{} | |
147 | 174 | } |
148 | 175 | |
149 | 176 | // GetConfigString returns the string representation of the current config |
1 | 1 | |
2 | 2 | import ( |
3 | 3 | "bytes" |
4 | "fmt" | |
4 | ||
5 | "github.com/fatih/color" | |
6 | ) | |
7 | ||
8 | var ( | |
9 | green = color.New(color.FgGreen).FprintfFunc() | |
5 | 10 | ) |
6 | 11 | |
7 | 12 | // Result represents a single result |
11 | 16 | Status string |
12 | 17 | } |
13 | 18 | |
14 | // ToString converts the Result to it's textual representation | |
19 | // ResultToString converts the Result to it's textual representation | |
15 | 20 | func (r Result) ResultToString() (string, error) { |
16 | 21 | buf := &bytes.Buffer{} |
17 | 22 | |
18 | if _, err := fmt.Fprintf(buf, "http://%s.s3.amazonaws.com/", r.BucketName); err != nil { | |
19 | return "", err | |
20 | } | |
23 | c := green | |
24 | ||
25 | c(buf, "http://%s.s3.amazonaws.com/", r.BucketName) | |
21 | 26 | |
22 | 27 | if r.Status != "" { |
23 | if _, err := fmt.Fprintf(buf, " [%s]", r.Status); err != nil { | |
24 | return "", err | |
25 | } | |
28 | c(buf, " [%s]", r.Status) | |
26 | 29 | } |
27 | ||
28 | if _, err := fmt.Fprintf(buf, "\n"); err != nil { | |
29 | return "", err | |
30 | } | |
30 | c(buf, "\n") | |
31 | 31 | |
32 | 32 | str := buf.String() |
33 | 33 | return str, nil |
4 | 4 | "bytes" |
5 | 5 | "context" |
6 | 6 | "fmt" |
7 | "net" | |
8 | "net/http" | |
7 | 9 | "net/url" |
8 | 10 | "strings" |
9 | 11 | "text/tabwriter" |
10 | 12 | |
13 | "github.com/OJ/gobuster/v3/helper" | |
11 | 14 | "github.com/OJ/gobuster/v3/libgobuster" |
12 | 15 | "github.com/google/uuid" |
13 | 16 | ) |
14 | 17 | |
15 | 18 | // GobusterVhost is the main type to implement the interface |
16 | 19 | type GobusterVhost struct { |
17 | options *OptionsVhost | |
18 | globalopts *libgobuster.Options | |
19 | http *libgobuster.HTTPClient | |
20 | domain string | |
21 | baseline1 []byte | |
22 | baseline2 []byte | |
20 | options *OptionsVhost | |
21 | globalopts *libgobuster.Options | |
22 | http *libgobuster.HTTPClient | |
23 | domain string | |
24 | normalBody []byte | |
25 | abnormalBody []byte | |
23 | 26 | } |
24 | 27 | |
25 | 28 | // NewGobusterVhost creates a new initialized GobusterDir |
26 | func NewGobusterVhost(cont context.Context, globalopts *libgobuster.Options, opts *OptionsVhost) (*GobusterVhost, error) { | |
29 | func NewGobusterVhost(globalopts *libgobuster.Options, opts *OptionsVhost) (*GobusterVhost, error) { | |
27 | 30 | if globalopts == nil { |
28 | 31 | return nil, fmt.Errorf("please provide valid global options") |
29 | 32 | } |
38 | 41 | } |
39 | 42 | |
40 | 43 | basicOptions := libgobuster.BasicHTTPOptions{ |
41 | Proxy: opts.Proxy, | |
42 | Timeout: opts.Timeout, | |
43 | UserAgent: opts.UserAgent, | |
44 | Proxy: opts.Proxy, | |
45 | Timeout: opts.Timeout, | |
46 | UserAgent: opts.UserAgent, | |
47 | NoTLSValidation: opts.NoTLSValidation, | |
48 | RetryOnTimeout: opts.RetryOnTimeout, | |
49 | RetryAttempts: opts.RetryAttempts, | |
44 | 50 | } |
45 | 51 | |
46 | 52 | httpOpts := libgobuster.HTTPOptions{ |
47 | 53 | BasicHTTPOptions: basicOptions, |
48 | 54 | FollowRedirect: opts.FollowRedirect, |
49 | NoTLSValidation: opts.NoTLSValidation, | |
50 | 55 | Username: opts.Username, |
51 | 56 | Password: opts.Password, |
52 | 57 | Headers: opts.Headers, |
54 | 59 | Method: opts.Method, |
55 | 60 | } |
56 | 61 | |
57 | h, err := libgobuster.NewHTTPClient(cont, &httpOpts) | |
62 | h, err := libgobuster.NewHTTPClient(&httpOpts) | |
58 | 63 | if err != nil { |
59 | 64 | return nil, err |
60 | 65 | } |
67 | 72 | return "VHOST enumeration" |
68 | 73 | } |
69 | 74 | |
70 | // RequestsPerRun returns the number of requests this plugin makes per single wordlist item | |
71 | func (v *GobusterVhost) RequestsPerRun() int { | |
72 | return 1 | |
73 | } | |
74 | ||
75 | 75 | // PreRun is the pre run implementation of gobusterdir |
76 | func (v *GobusterVhost) PreRun() error { | |
76 | func (v *GobusterVhost) PreRun(ctx context.Context) error { | |
77 | 77 | // add trailing slash |
78 | 78 | if !strings.HasSuffix(v.options.URL, "/") { |
79 | 79 | v.options.URL = fmt.Sprintf("%s/", v.options.URL) |
83 | 83 | if err != nil { |
84 | 84 | return fmt.Errorf("invalid url %s: %w", v.options.URL, err) |
85 | 85 | } |
86 | v.domain = urlParsed.Host | |
87 | ||
88 | // request default vhost for baseline1 | |
89 | _, _, _, tmp, err := v.http.Request(v.options.URL, libgobuster.RequestOptions{ReturnBody: true}) | |
86 | if v.options.Domain != "" { | |
87 | v.domain = v.options.Domain | |
88 | } else { | |
89 | v.domain = urlParsed.Host | |
90 | } | |
91 | ||
92 | // request default vhost for normalBody | |
93 | _, _, _, body, err := v.http.Request(ctx, v.options.URL, libgobuster.RequestOptions{ReturnBody: true}) | |
90 | 94 | if err != nil { |
91 | 95 | return fmt.Errorf("unable to connect to %s: %w", v.options.URL, err) |
92 | 96 | } |
93 | v.baseline1 = tmp | |
94 | ||
95 | // request non existent vhost for baseline2 | |
97 | v.normalBody = body | |
98 | ||
99 | // request non existent vhost for abnormalBody | |
96 | 100 | subdomain := fmt.Sprintf("%s.%s", uuid.New(), v.domain) |
97 | _, _, _, tmp, err = v.http.Request(v.options.URL, libgobuster.RequestOptions{Host: subdomain, ReturnBody: true}) | |
101 | _, _, _, body, err = v.http.Request(ctx, v.options.URL, libgobuster.RequestOptions{Host: subdomain, ReturnBody: true}) | |
98 | 102 | if err != nil { |
99 | 103 | return fmt.Errorf("unable to connect to %s: %w", v.options.URL, err) |
100 | 104 | } |
101 | v.baseline2 = tmp | |
105 | v.abnormalBody = body | |
102 | 106 | return nil |
103 | 107 | } |
104 | 108 | |
105 | // Run is the process implementation of gobusterdir | |
106 | func (v *GobusterVhost) Run(word string, resChannel chan<- libgobuster.Result) error { | |
107 | subdomain := fmt.Sprintf("%s.%s", word, v.domain) | |
108 | status, size, header, body, err := v.http.Request(v.options.URL, libgobuster.RequestOptions{Host: subdomain, ReturnBody: true}) | |
109 | if err != nil { | |
110 | return err | |
109 | // ProcessWord is the process implementation of gobusterdir | |
110 | func (v *GobusterVhost) ProcessWord(ctx context.Context, word string, progress *libgobuster.Progress) error { | |
111 | var subdomain string | |
112 | if v.options.AppendDomain { | |
113 | subdomain = fmt.Sprintf("%s.%s", word, v.domain) | |
114 | } else { | |
115 | // wordlist needs to include full domains | |
116 | subdomain = word | |
117 | } | |
118 | ||
119 | tries := 1 | |
120 | if v.options.RetryOnTimeout && v.options.RetryAttempts > 0 { | |
121 | // add it so it will be the overall max requests | |
122 | tries += v.options.RetryAttempts | |
123 | } | |
124 | ||
125 | var statusCode int | |
126 | var size int64 | |
127 | var header http.Header | |
128 | var body []byte | |
129 | for i := 1; i <= tries; i++ { | |
130 | var err error | |
131 | statusCode, size, header, body, err = v.http.Request(ctx, v.options.URL, libgobuster.RequestOptions{Host: subdomain, ReturnBody: true}) | |
132 | if err != nil { | |
133 | // check if it's a timeout and if we should try again and try again | |
134 | // otherwise the timeout error is raised | |
135 | if netErr, ok := err.(net.Error); ok && netErr.Timeout() && i != tries { | |
136 | continue | |
137 | } else if strings.Contains(err.Error(), "invalid control character in URL") { | |
138 | // put error in error chan so it's printed out and ignore it | |
139 | // so gobuster will not quit | |
140 | progress.ErrorChan <- err | |
141 | continue | |
142 | } else { | |
143 | return err | |
144 | } | |
145 | } | |
146 | break | |
111 | 147 | } |
112 | 148 | |
113 | 149 | // subdomain must not match default vhost and non existent vhost |
114 | 150 | // or verbose mode is enabled |
115 | found := !bytes.Equal(body, v.baseline1) && !bytes.Equal(body, v.baseline2) | |
116 | if found || v.globalopts.Verbose { | |
151 | found := body != nil && !bytes.Equal(body, v.normalBody) && !bytes.Equal(body, v.abnormalBody) | |
152 | if (found && !helper.SliceContains(v.options.ExcludeLength, int(size))) || v.globalopts.Verbose { | |
117 | 153 | resultStatus := false |
118 | 154 | if found { |
119 | 155 | resultStatus = true |
120 | 156 | } |
121 | resChannel <- Result{ | |
157 | progress.ResultChan <- Result{ | |
122 | 158 | Found: resultStatus, |
123 | 159 | Vhost: subdomain, |
124 | StatusCode: *status, | |
160 | StatusCode: statusCode, | |
125 | 161 | Size: size, |
126 | 162 | Header: header, |
127 | 163 | } |
128 | 164 | } |
129 | 165 | return nil |
166 | } | |
167 | ||
168 | func (v *GobusterVhost) AdditionalWords(word string) []string { | |
169 | return []string{} | |
130 | 170 | } |
131 | 171 | |
132 | 172 | // GetConfigString returns the string representation of the current config |
201 | 241 | return "", err |
202 | 242 | } |
203 | 243 | |
244 | if _, err := fmt.Fprintf(tw, "[+] Append Domain:\t%t\n", v.options.AppendDomain); err != nil { | |
245 | return "", err | |
246 | } | |
247 | ||
248 | if len(o.ExcludeLength) > 0 { | |
249 | if _, err := fmt.Fprintf(tw, "[+] Exclude Length:\t%s\n", helper.JoinIntSlice(v.options.ExcludeLength)); err != nil { | |
250 | return "", err | |
251 | } | |
252 | } | |
253 | ||
204 | 254 | if err := tw.Flush(); err != nil { |
205 | 255 | return "", fmt.Errorf("error on tostring: %w", err) |
206 | 256 | } |
6 | 6 | // OptionsVhost is the struct to hold all options for this plugin |
7 | 7 | type OptionsVhost struct { |
8 | 8 | libgobuster.HTTPOptions |
9 | AppendDomain bool | |
10 | ExcludeLength []int | |
11 | Domain string | |
9 | 12 | } |
0 | 0 | package gobustervhost |
1 | 1 | |
2 | 2 | import ( |
3 | "bytes" | |
4 | 3 | "fmt" |
5 | 4 | "net/http" |
5 | ||
6 | "github.com/fatih/color" | |
7 | ) | |
8 | ||
9 | var ( | |
10 | white = color.New(color.FgWhite).SprintFunc() | |
11 | yellow = color.New(color.FgYellow).SprintFunc() | |
12 | green = color.New(color.FgGreen).SprintFunc() | |
13 | blue = color.New(color.FgBlue).SprintFunc() | |
14 | red = color.New(color.FgRed).SprintFunc() | |
15 | cyan = color.New(color.FgCyan).SprintFunc() | |
6 | 16 | ) |
7 | 17 | |
8 | 18 | // Result represents a single result |
14 | 24 | Header http.Header |
15 | 25 | } |
16 | 26 | |
17 | // ToString converts the Result to it's textual representation | |
27 | // ResultToString converts the Result to it's textual representation | |
18 | 28 | func (r Result) ResultToString() (string, error) { |
19 | buf := &bytes.Buffer{} | |
20 | ||
21 | statusText := "Missed" | |
29 | statusText := yellow("Missed") | |
22 | 30 | if r.Found { |
23 | statusText = "Found" | |
31 | statusText = green("Found") | |
24 | 32 | } |
25 | 33 | |
26 | if _, err := fmt.Fprintf(buf, "%s: %s (Status: %d) [Size: %d]\n", statusText, r.Vhost, r.StatusCode, r.Size); err != nil { | |
27 | return "", err | |
34 | statusCodeColor := white | |
35 | if r.StatusCode == 200 { | |
36 | statusCodeColor = green | |
37 | } else if r.StatusCode >= 300 && r.StatusCode < 400 { | |
38 | statusCodeColor = cyan | |
39 | } else if r.StatusCode >= 400 && r.StatusCode < 500 { | |
40 | statusCodeColor = yellow | |
41 | } else if r.StatusCode >= 500 && r.StatusCode < 600 { | |
42 | statusCodeColor = red | |
28 | 43 | } |
29 | 44 | |
30 | s := buf.String() | |
31 | return s, nil | |
45 | statusCode := statusCodeColor(fmt.Sprintf("Status: %d", r.StatusCode)) | |
46 | ||
47 | location := r.Header.Get("Location") | |
48 | locationString := "" | |
49 | if location != "" { | |
50 | locationString = blue(fmt.Sprintf(" [--> %s]", location)) | |
51 | } | |
52 | ||
53 | return fmt.Sprintf("%s: %s %s [Size: %d]%s\n", statusText, r.Vhost, statusCode, r.Size, locationString), nil | |
32 | 54 | } |
8 | 8 | ) |
9 | 9 | |
10 | 10 | // ParseExtensions parses the extensions provided as a comma separated list |
11 | func ParseExtensions(extensions string) (libgobuster.StringSet, error) { | |
11 | func ParseExtensions(extensions string) (libgobuster.Set[string], error) { | |
12 | ret := libgobuster.NewSet[string]() | |
13 | ||
12 | 14 | if extensions == "" { |
13 | return libgobuster.StringSet{}, fmt.Errorf("invalid extension string provided") | |
15 | return ret, nil | |
14 | 16 | } |
15 | 17 | |
16 | ret := libgobuster.NewStringSet() | |
17 | 18 | for _, e := range strings.Split(extensions, ",") { |
18 | 19 | e = strings.TrimSpace(e) |
19 | 20 | // remove leading . from extensions |
23 | 24 | } |
24 | 25 | |
25 | 26 | // ParseCommaSeparatedInt parses the status codes provided as a comma separated list |
26 | func ParseCommaSeparatedInt(inputString string) (libgobuster.IntSet, error) { | |
27 | func ParseCommaSeparatedInt(inputString string) (libgobuster.Set[int], error) { | |
28 | ret := libgobuster.NewSet[int]() | |
29 | ||
27 | 30 | if inputString == "" { |
28 | return libgobuster.IntSet{}, fmt.Errorf("invalid string provided") | |
31 | return ret, nil | |
29 | 32 | } |
30 | 33 | |
31 | ret := libgobuster.NewIntSet() | |
32 | 34 | for _, c := range strings.Split(inputString, ",") { |
33 | 35 | c = strings.TrimSpace(c) |
34 | 36 | i, err := strconv.Atoi(c) |
35 | 37 | if err != nil { |
36 | return libgobuster.IntSet{}, fmt.Errorf("invalid string given: %s", c) | |
38 | return libgobuster.NewSet[int](), fmt.Errorf("invalid string given: %s", c) | |
37 | 39 | } |
38 | 40 | ret.Add(i) |
39 | 41 | } |
8 | 8 | |
9 | 9 | func TestParseExtensions(t *testing.T) { |
10 | 10 | t.Parallel() |
11 | ||
12 | 11 | var tt = []struct { |
13 | 12 | testName string |
14 | 13 | extensions string |
15 | expectedExtensions libgobuster.StringSet | |
14 | expectedExtensions libgobuster.Set[string] | |
16 | 15 | expectedError string |
17 | 16 | }{ |
18 | {"Valid extensions", "php,asp,txt", libgobuster.StringSet{Set: map[string]bool{"php": true, "asp": true, "txt": true}}, ""}, | |
19 | {"Spaces", "php, asp , txt", libgobuster.StringSet{Set: map[string]bool{"php": true, "asp": true, "txt": true}}, ""}, | |
20 | {"Double extensions", "php,asp,txt,php,asp,txt", libgobuster.StringSet{Set: map[string]bool{"php": true, "asp": true, "txt": true}}, ""}, | |
21 | {"Leading dot", ".php,asp,.txt", libgobuster.StringSet{Set: map[string]bool{"php": true, "asp": true, "txt": true}}, ""}, | |
22 | {"Empty string", "", libgobuster.NewStringSet(), "invalid extension string provided"}, | |
17 | {"Valid extensions", "php,asp,txt", libgobuster.Set[string]{Set: map[string]bool{"php": true, "asp": true, "txt": true}}, ""}, | |
18 | {"Spaces", "php, asp , txt", libgobuster.Set[string]{Set: map[string]bool{"php": true, "asp": true, "txt": true}}, ""}, | |
19 | {"Double extensions", "php,asp,txt,php,asp,txt", libgobuster.Set[string]{Set: map[string]bool{"php": true, "asp": true, "txt": true}}, ""}, | |
20 | {"Leading dot", ".php,asp,.txt", libgobuster.Set[string]{Set: map[string]bool{"php": true, "asp": true, "txt": true}}, ""}, | |
21 | {"Empty string", "", libgobuster.NewSet[string](), "invalid extension string provided"}, | |
23 | 22 | } |
24 | 23 | |
25 | 24 | for _, x := range tt { |
25 | x := x // NOTE: https://github.com/golang/go/wiki/CommonMistakes#using-goroutines-on-loop-iterator-variables | |
26 | 26 | t.Run(x.testName, func(t *testing.T) { |
27 | t.Parallel() | |
27 | 28 | ret, err := ParseExtensions(x.extensions) |
28 | 29 | if x.expectedError != "" { |
29 | 30 | if err != nil && err.Error() != x.expectedError { |
38 | 39 | |
39 | 40 | func TestParseCommaSeparatedInt(t *testing.T) { |
40 | 41 | t.Parallel() |
41 | ||
42 | 42 | var tt = []struct { |
43 | 43 | testName string |
44 | 44 | stringCodes string |
45 | expectedCodes libgobuster.IntSet | |
45 | expectedCodes libgobuster.Set[int] | |
46 | 46 | expectedError string |
47 | 47 | }{ |
48 | {"Valid codes", "200,100,202", libgobuster.IntSet{Set: map[int]bool{100: true, 200: true, 202: true}}, ""}, | |
49 | {"Spaces", "200, 100 , 202", libgobuster.IntSet{Set: map[int]bool{100: true, 200: true, 202: true}}, ""}, | |
50 | {"Double codes", "200, 100, 202, 100", libgobuster.IntSet{Set: map[int]bool{100: true, 200: true, 202: true}}, ""}, | |
51 | {"Invalid code", "200,AAA", libgobuster.NewIntSet(), "invalid string given: AAA"}, | |
52 | {"Invalid integer", "2000000000000000000000000000000", libgobuster.NewIntSet(), "invalid string given: 2000000000000000000000000000000"}, | |
53 | {"Empty string", "", libgobuster.NewIntSet(), "invalid string provided"}, | |
48 | {"Valid codes", "200,100,202", libgobuster.Set[int]{Set: map[int]bool{100: true, 200: true, 202: true}}, ""}, | |
49 | {"Spaces", "200, 100 , 202", libgobuster.Set[int]{Set: map[int]bool{100: true, 200: true, 202: true}}, ""}, | |
50 | {"Double codes", "200, 100, 202, 100", libgobuster.Set[int]{Set: map[int]bool{100: true, 200: true, 202: true}}, ""}, | |
51 | {"Invalid code", "200,AAA", libgobuster.NewSet[int](), "invalid string given: AAA"}, | |
52 | {"Invalid integer", "2000000000000000000000000000000", libgobuster.NewSet[int](), "invalid string given: 2000000000000000000000000000000"}, | |
53 | {"Empty string", "", libgobuster.NewSet[int](), "invalid string provided"}, | |
54 | 54 | } |
55 | 55 | |
56 | 56 | for _, x := range tt { |
57 | x := x // NOTE: https://github.com/golang/go/wiki/CommonMistakes#using-goroutines-on-loop-iterator-variables | |
57 | 58 | t.Run(x.testName, func(t *testing.T) { |
59 | t.Parallel() | |
58 | 60 | ret, err := ParseCommaSeparatedInt(x.stringCodes) |
59 | 61 | if x.expectedError != "" { |
60 | 62 | if err != nil && err.Error() != x.expectedError { |
71 | 73 | var tt = []struct { |
72 | 74 | testName string |
73 | 75 | extensions string |
74 | expectedExtensions libgobuster.StringSet | |
76 | expectedExtensions libgobuster.Set[string] | |
75 | 77 | expectedError string |
76 | 78 | }{ |
77 | {"Valid extensions", "php,asp,txt", libgobuster.StringSet{Set: map[string]bool{"php": true, "asp": true, "txt": true}}, ""}, | |
78 | {"Spaces", "php, asp , txt", libgobuster.StringSet{Set: map[string]bool{"php": true, "asp": true, "txt": true}}, ""}, | |
79 | {"Double extensions", "php,asp,txt,php,asp,txt", libgobuster.StringSet{Set: map[string]bool{"php": true, "asp": true, "txt": true}}, ""}, | |
80 | {"Leading dot", ".php,asp,.txt", libgobuster.StringSet{Set: map[string]bool{"php": true, "asp": true, "txt": true}}, ""}, | |
81 | {"Empty string", "", libgobuster.NewStringSet(), "invalid extension string provided"}, | |
79 | {"Valid extensions", "php,asp,txt", libgobuster.Set[string]{Set: map[string]bool{"php": true, "asp": true, "txt": true}}, ""}, | |
80 | {"Spaces", "php, asp , txt", libgobuster.Set[string]{Set: map[string]bool{"php": true, "asp": true, "txt": true}}, ""}, | |
81 | {"Double extensions", "php,asp,txt,php,asp,txt", libgobuster.Set[string]{Set: map[string]bool{"php": true, "asp": true, "txt": true}}, ""}, | |
82 | {"Leading dot", ".php,asp,.txt", libgobuster.Set[string]{Set: map[string]bool{"php": true, "asp": true, "txt": true}}, ""}, | |
83 | {"Empty string", "", libgobuster.NewSet[string](), "invalid extension string provided"}, | |
82 | 84 | } |
83 | 85 | |
84 | 86 | for _, x := range tt { |
87 | x := x // NOTE: https://github.com/golang/go/wiki/CommonMistakes#using-goroutines-on-loop-iterator-variables | |
85 | 88 | b.Run(x.testName, func(b2 *testing.B) { |
86 | 89 | for y := 0; y < b2.N; y++ { |
87 | 90 | _, _ = ParseExtensions(x.extensions) |
94 | 97 | var tt = []struct { |
95 | 98 | testName string |
96 | 99 | stringCodes string |
97 | expectedCodes libgobuster.IntSet | |
100 | expectedCodes libgobuster.Set[int] | |
98 | 101 | expectedError string |
99 | 102 | }{ |
100 | {"Valid codes", "200,100,202", libgobuster.IntSet{Set: map[int]bool{100: true, 200: true, 202: true}}, ""}, | |
101 | {"Spaces", "200, 100 , 202", libgobuster.IntSet{Set: map[int]bool{100: true, 200: true, 202: true}}, ""}, | |
102 | {"Double codes", "200, 100, 202, 100", libgobuster.IntSet{Set: map[int]bool{100: true, 200: true, 202: true}}, ""}, | |
103 | {"Invalid code", "200,AAA", libgobuster.NewIntSet(), "invalid string given: AAA"}, | |
104 | {"Invalid integer", "2000000000000000000000000000000", libgobuster.NewIntSet(), "invalid string given: 2000000000000000000000000000000"}, | |
105 | {"Empty string", "", libgobuster.NewIntSet(), "invalid string string provided"}, | |
103 | {"Valid codes", "200,100,202", libgobuster.Set[int]{Set: map[int]bool{100: true, 200: true, 202: true}}, ""}, | |
104 | {"Spaces", "200, 100 , 202", libgobuster.Set[int]{Set: map[int]bool{100: true, 200: true, 202: true}}, ""}, | |
105 | {"Double codes", "200, 100, 202, 100", libgobuster.Set[int]{Set: map[int]bool{100: true, 200: true, 202: true}}, ""}, | |
106 | {"Invalid code", "200,AAA", libgobuster.NewSet[int](), "invalid string given: AAA"}, | |
107 | {"Invalid integer", "2000000000000000000000000000000", libgobuster.NewSet[int](), "invalid string given: 2000000000000000000000000000000"}, | |
108 | {"Empty string", "", libgobuster.NewSet[int](), "invalid string string provided"}, | |
106 | 109 | } |
107 | 110 | |
108 | 111 | for _, x := range tt { |
112 | x := x // NOTE: https://github.com/golang/go/wiki/CommonMistakes#using-goroutines-on-loop-iterator-variables | |
109 | 113 | b.Run(x.testName, func(b2 *testing.B) { |
110 | 114 | for y := 0; y < b2.N; y++ { |
111 | 115 | _, _ = ParseCommaSeparatedInt(x.stringCodes) |
0 | 0 | package helper |
1 | 1 | |
2 | 2 | import ( |
3 | "math/rand" | |
4 | "time" | |
3 | "crypto/rand" | |
4 | "math/big" | |
5 | 5 | ) |
6 | 6 | |
7 | // molint:gochecknoglobals | |
7 | 8 | var userAgents = [...]string{ |
8 | 9 | "Mozilla/5.0 (X11; Linux i686; rv:64.0) Gecko/20100101 Firefox/64.0", |
9 | 10 | "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:64.0) Gecko/20100101 Firefox/64.0", |
4665 | 4666 | } |
4666 | 4667 | |
4667 | 4668 | // GetRandomUserAgent picks a random user agent from a predefined list |
4668 | func GetRandomUserAgent() string { | |
4669 | rand.Seed(time.Now().Unix()) | |
4670 | return userAgents[rand.Intn(len(userAgents))] | |
4669 | func GetRandomUserAgent() (string, error) { | |
4670 | n, err := rand.Int(rand.Reader, big.NewInt(int64(len(userAgents)))) | |
4671 | if err != nil { | |
4672 | return "", err | |
4673 | } | |
4674 | return userAgents[n.Int64()], err | |
4671 | 4675 | } |
1 | 1 | |
2 | 2 | import ( |
3 | 3 | "bytes" |
4 | "errors" | |
4 | 5 | "fmt" |
5 | 6 | "io" |
6 | "sort" | |
7 | 7 | "strings" |
8 | 8 | ) |
9 | 9 | |
10 | // IntSet is a set of Ints | |
11 | type IntSet struct { | |
12 | Set map[int]bool | |
10 | // Set is a set of Ts | |
11 | type Set[T comparable] struct { | |
12 | Set map[T]bool | |
13 | 13 | } |
14 | 14 | |
15 | // StringSet is a set of Strings | |
16 | type StringSet struct { | |
17 | Set map[string]bool | |
18 | } | |
19 | ||
20 | // NewStringSet creates a new initialized StringSet | |
21 | func NewStringSet() StringSet { | |
22 | return StringSet{Set: map[string]bool{}} | |
15 | // NewSSet creates a new initialized Set | |
16 | func NewSet[T comparable]() Set[T] { | |
17 | return Set[T]{Set: map[T]bool{}} | |
23 | 18 | } |
24 | 19 | |
25 | 20 | // Add an element to a set |
26 | func (set *StringSet) Add(s string) bool { | |
21 | func (set *Set[T]) Add(s T) bool { | |
27 | 22 | _, found := set.Set[s] |
28 | 23 | set.Set[s] = true |
29 | 24 | return !found |
30 | 25 | } |
31 | 26 | |
32 | 27 | // AddRange adds a list of elements to a set |
33 | func (set *StringSet) AddRange(ss []string) { | |
28 | func (set *Set[T]) AddRange(ss []T) { | |
34 | 29 | for _, s := range ss { |
35 | 30 | set.Set[s] = true |
36 | 31 | } |
37 | 32 | } |
38 | 33 | |
39 | 34 | // Contains tests if an element is in a set |
40 | func (set *StringSet) Contains(s string) bool { | |
35 | func (set *Set[T]) Contains(s T) bool { | |
41 | 36 | _, found := set.Set[s] |
42 | 37 | return found |
43 | 38 | } |
44 | 39 | |
45 | 40 | // ContainsAny checks if any of the elements exist |
46 | func (set *StringSet) ContainsAny(ss []string) bool { | |
41 | func (set *Set[T]) ContainsAny(ss []T) bool { | |
47 | 42 | for _, s := range ss { |
48 | 43 | if set.Set[s] { |
49 | 44 | return true |
53 | 48 | } |
54 | 49 | |
55 | 50 | // Length returns the length of the Set |
56 | func (set *StringSet) Length() int { | |
51 | func (set *Set[T]) Length() int { | |
57 | 52 | return len(set.Set) |
58 | 53 | } |
59 | 54 | |
60 | 55 | // Stringify the set |
61 | func (set *StringSet) Stringify() string { | |
56 | func (set *Set[T]) Stringify() string { | |
62 | 57 | values := make([]string, len(set.Set)) |
63 | 58 | i := 0 |
64 | 59 | for s := range set.Set { |
65 | values[i] = s | |
60 | values[i] = fmt.Sprint(s) | |
66 | 61 | i++ |
67 | 62 | } |
68 | 63 | return strings.Join(values, ",") |
69 | } | |
70 | ||
71 | // NewIntSet creates a new initialized IntSet | |
72 | func NewIntSet() IntSet { | |
73 | return IntSet{Set: map[int]bool{}} | |
74 | } | |
75 | ||
76 | // Add adds an element to a set | |
77 | func (set *IntSet) Add(i int) bool { | |
78 | _, found := set.Set[i] | |
79 | set.Set[i] = true | |
80 | return !found | |
81 | } | |
82 | ||
83 | // Contains tests if an element is in a set | |
84 | func (set *IntSet) Contains(i int) bool { | |
85 | _, found := set.Set[i] | |
86 | return found | |
87 | } | |
88 | ||
89 | // Stringify the set | |
90 | func (set *IntSet) Stringify() string { | |
91 | values := make([]int, len(set.Set)) | |
92 | i := 0 | |
93 | for s := range set.Set { | |
94 | values[i] = s | |
95 | i++ | |
96 | } | |
97 | sort.Ints(values) | |
98 | ||
99 | delim := "," | |
100 | return strings.Trim(strings.Join(strings.Fields(fmt.Sprint(values)), delim), "[]") | |
101 | } | |
102 | ||
103 | // Length returns the length of the Set | |
104 | func (set *IntSet) Length() int { | |
105 | return len(set.Set) | |
106 | 64 | } |
107 | 65 | |
108 | 66 | func lineCounter(r io.Reader) (int, error) { |
115 | 73 | count += bytes.Count(buf[:c], lineSep) |
116 | 74 | |
117 | 75 | switch { |
118 | case err == io.EOF: | |
76 | case errors.Is(err, io.EOF): | |
119 | 77 | return count, nil |
120 | 78 | |
121 | 79 | case err != nil: |
1 | 1 | |
2 | 2 | import ( |
3 | 3 | "errors" |
4 | "fmt" | |
4 | 5 | "strings" |
5 | 6 | "testing" |
6 | 7 | "testing/iotest" |
7 | 8 | ) |
8 | 9 | |
9 | func TestNewStringSet(t *testing.T) { | |
10 | if NewStringSet().Set == nil { | |
11 | t.Fatal("newStringSet returned nil Set") | |
10 | func TestNewSet(t *testing.T) { | |
11 | t.Parallel() | |
12 | if NewSet[string]().Set == nil { | |
13 | t.Fatal("NewSet[string] returned nil Set") | |
14 | } | |
15 | ||
16 | if NewSet[int]().Set == nil { | |
17 | t.Fatal("NewSet[int] returned nil Set") | |
12 | 18 | } |
13 | 19 | } |
14 | 20 | |
15 | func TestNewIntSet(t *testing.T) { | |
16 | if NewIntSet().Set == nil { | |
17 | t.Fatal("newIntSet returned nil Set") | |
21 | func TestSetAdd(t *testing.T) { | |
22 | t.Parallel() | |
23 | x := NewSet[string]() | |
24 | x.Add("test") | |
25 | if len(x.Set) != 1 { | |
26 | t.Fatalf("Unexpected string size. Should have 1 Got %v", len(x.Set)) | |
27 | } | |
28 | ||
29 | y := NewSet[int]() | |
30 | y.Add(1) | |
31 | if len(y.Set) != 1 { | |
32 | t.Fatalf("Unexpected int size. Should have 1 Got %v", len(y.Set)) | |
18 | 33 | } |
19 | 34 | } |
20 | 35 | |
21 | func TestStringSetAdd(t *testing.T) { | |
22 | x := NewStringSet() | |
36 | func TestSetAddDouble(t *testing.T) { | |
37 | t.Parallel() | |
38 | x := NewSet[string]() | |
39 | x.Add("test") | |
23 | 40 | x.Add("test") |
24 | 41 | if len(x.Set) != 1 { |
25 | t.Fatalf("Unexpected size. Should have 1 Got %v", len(x.Set)) | |
42 | t.Fatalf("Unexpected string size. Should be 1 (unique) Got %v", len(x.Set)) | |
43 | } | |
44 | ||
45 | y := NewSet[int]() | |
46 | y.Add(1) | |
47 | y.Add(1) | |
48 | if len(y.Set) != 1 { | |
49 | t.Fatalf("Unexpected int size. Should be 1 (unique) Got %v", len(y.Set)) | |
26 | 50 | } |
27 | 51 | } |
28 | 52 | |
29 | func TestStringSetAddDouble(t *testing.T) { | |
30 | x := NewStringSet() | |
31 | x.Add("test") | |
32 | x.Add("test") | |
33 | if len(x.Set) != 1 { | |
34 | t.Fatalf("Unexpected size. Should have 1 Got %d", len(x.Set)) | |
53 | func TestSetAddRange(t *testing.T) { | |
54 | t.Parallel() | |
55 | x := NewSet[string]() | |
56 | x.AddRange([]string{"string1", "string2"}) | |
57 | if len(x.Set) != 2 { | |
58 | t.Fatalf("Unexpected string size. Should have 2 Got %v", len(x.Set)) | |
59 | } | |
60 | ||
61 | y := NewSet[int]() | |
62 | y.AddRange([]int{1, 2}) | |
63 | if len(y.Set) != 2 { | |
64 | t.Fatalf("Unexpected int size. Should have 2 Got %v", len(y.Set)) | |
35 | 65 | } |
36 | 66 | } |
37 | 67 | |
38 | func TestStringSetAddRange(t *testing.T) { | |
39 | x := NewStringSet() | |
40 | x.AddRange([]string{"string1", "string2"}) | |
68 | func TestSetAddRangeDouble(t *testing.T) { | |
69 | t.Parallel() | |
70 | x := NewSet[string]() | |
71 | x.AddRange([]string{"string1", "string2", "string1", "string2"}) | |
41 | 72 | if len(x.Set) != 2 { |
42 | t.Fatalf("Unexpected size. Should have 2 Got %d", len(x.Set)) | |
73 | t.Fatalf("Unexpected string size. Should be 2 (unique) Got %v", len(x.Set)) | |
74 | } | |
75 | ||
76 | y := NewSet[int]() | |
77 | y.AddRange([]int{1, 2, 1, 2}) | |
78 | if len(y.Set) != 2 { | |
79 | t.Fatalf("Unexpected int size. Should be 2 (unique) Got %v", len(y.Set)) | |
43 | 80 | } |
44 | 81 | } |
45 | 82 | |
46 | func TestStringSetAddRangeDouble(t *testing.T) { | |
47 | x := NewStringSet() | |
48 | x.AddRange([]string{"string1", "string2", "string1", "string2"}) | |
49 | if len(x.Set) != 2 { | |
50 | t.Fatalf("Unexpected size. Should have 2 Got %d", len(x.Set)) | |
51 | } | |
52 | } | |
53 | ||
54 | func TestStringSetContains(t *testing.T) { | |
55 | x := NewStringSet() | |
83 | func TestSetContains(t *testing.T) { | |
84 | t.Parallel() | |
85 | x := NewSet[string]() | |
56 | 86 | v := []string{"string1", "string2", "1234", "5678"} |
57 | 87 | x.AddRange(v) |
58 | for _, y := range v { | |
59 | if !x.Contains(y) { | |
60 | t.Fatalf("Did not find value %s in array. %v", y, x.Set) | |
88 | for _, i := range v { | |
89 | if !x.Contains(i) { | |
90 | t.Fatalf("Did not find value %s in array. %v", i, x.Set) | |
91 | } | |
92 | } | |
93 | ||
94 | y := NewSet[int]() | |
95 | v2 := []int{1, 2312, 123121, 999, -99} | |
96 | y.AddRange(v2) | |
97 | for _, i := range v2 { | |
98 | if !y.Contains(i) { | |
99 | t.Fatalf("Did not find value %d in array. %v", i, y.Set) | |
61 | 100 | } |
62 | 101 | } |
63 | 102 | } |
64 | 103 | |
65 | func TestStringSetContainsAny(t *testing.T) { | |
66 | x := NewStringSet() | |
104 | func TestSetContainsAny(t *testing.T) { | |
105 | t.Parallel() | |
106 | x := NewSet[string]() | |
67 | 107 | v := []string{"string1", "string2", "1234", "5678"} |
68 | 108 | x.AddRange(v) |
69 | 109 | if !x.ContainsAny(v) { |
74 | 114 | if x.ContainsAny([]string{"mmmm", "nnnnn"}) { |
75 | 115 | t.Fatal("Found unexpected values") |
76 | 116 | } |
117 | ||
118 | y := NewSet[int]() | |
119 | v2 := []int{1, 2312, 123121, 999, -99} | |
120 | y.AddRange(v2) | |
121 | if !y.ContainsAny(v2) { | |
122 | t.Fatalf("Did not find any") | |
123 | } | |
124 | ||
125 | // test not found | |
126 | if y.ContainsAny([]int{9235, 2398532}) { | |
127 | t.Fatal("Found unexpected values") | |
128 | } | |
77 | 129 | } |
78 | 130 | |
79 | func TestStringSetStringify(t *testing.T) { | |
80 | x := NewStringSet() | |
131 | func TestSetStringify(t *testing.T) { | |
132 | t.Parallel() | |
133 | x := NewSet[string]() | |
81 | 134 | v := []string{"string1", "string2", "1234", "5678"} |
82 | 135 | x.AddRange(v) |
83 | 136 | z := x.Stringify() |
84 | 137 | // order is random |
85 | for _, y := range v { | |
86 | if !strings.Contains(z, y) { | |
87 | t.Fatalf("Did not find value %q in %q", y, z) | |
138 | for _, i := range v { | |
139 | if !strings.Contains(z, i) { | |
140 | t.Fatalf("Did not find value %q in %q", i, z) | |
141 | } | |
142 | } | |
143 | ||
144 | y := NewSet[int]() | |
145 | v2 := []int{1, 2312, 123121, 999, -99} | |
146 | y.AddRange(v2) | |
147 | z = y.Stringify() | |
148 | // order is random | |
149 | for _, i := range v2 { | |
150 | if !strings.Contains(z, fmt.Sprint(i)) { | |
151 | t.Fatalf("Did not find value %q in %q", i, z) | |
88 | 152 | } |
89 | 153 | } |
90 | 154 | } |
91 | 155 | |
92 | func TestIntSetAdd(t *testing.T) { | |
93 | x := NewIntSet() | |
94 | x.Add(1) | |
95 | if len(x.Set) != 1 { | |
96 | t.Fatalf("Unexpected size. Should have 1 Got %d", len(x.Set)) | |
97 | } | |
98 | } | |
99 | ||
100 | func TestIntSetAddDouble(t *testing.T) { | |
101 | x := NewIntSet() | |
102 | x.Add(1) | |
103 | x.Add(1) | |
104 | if len(x.Set) != 1 { | |
105 | t.Fatalf("Unexpected size. Should have 1 Got %d", len(x.Set)) | |
106 | } | |
107 | } | |
108 | ||
109 | func TestIntSetContains(t *testing.T) { | |
110 | x := NewIntSet() | |
111 | v := []int{1, 2, 3, 4} | |
112 | for _, y := range v { | |
113 | x.Add(y) | |
114 | } | |
115 | for _, y := range v { | |
116 | if !x.Contains(y) { | |
117 | t.Fatalf("Did not find value %d in array. %v", y, x.Set) | |
118 | } | |
119 | } | |
120 | } | |
121 | ||
122 | func TestIntSetStringify(t *testing.T) { | |
123 | x := NewIntSet() | |
124 | v := []int{1, 3, 2, 4} | |
125 | expected := "1,2,3,4" | |
126 | for _, y := range v { | |
127 | x.Add(y) | |
128 | } | |
129 | z := x.Stringify() | |
130 | // should be sorted | |
131 | if expected != z { | |
132 | t.Fatalf("Expected %q got %q", expected, z) | |
133 | } | |
134 | } | |
135 | ||
136 | 156 | func TestLineCounter(t *testing.T) { |
157 | t.Parallel() | |
137 | 158 | var tt = []struct { |
138 | 159 | testName string |
139 | 160 | s string |
146 | 167 | {"Empty", "", 1}, |
147 | 168 | } |
148 | 169 | for _, x := range tt { |
170 | x := x // NOTE: https://github.com/golang/go/wiki/CommonMistakes#using-goroutines-on-loop-iterator-variables | |
149 | 171 | t.Run(x.testName, func(t *testing.T) { |
172 | t.Parallel() | |
150 | 173 | r := strings.NewReader(x.s) |
151 | 174 | l, err := lineCounter(r) |
152 | 175 | if err != nil { |
160 | 183 | } |
161 | 184 | |
162 | 185 | func TestLineCounterError(t *testing.T) { |
186 | t.Parallel() | |
163 | 187 | r := iotest.TimeoutReader(strings.NewReader("test")) |
164 | 188 | _, err := lineCounter(r) |
165 | 189 | if !errors.Is(err, iotest.ErrTimeout) { |
5 | 5 | "errors" |
6 | 6 | "fmt" |
7 | 7 | "io" |
8 | "io/ioutil" | |
9 | 8 | "net/http" |
10 | 9 | "net/url" |
11 | 10 | "strings" |
20 | 19 | // HTTPClient represents a http object |
21 | 20 | type HTTPClient struct { |
22 | 21 | client *http.Client |
23 | context context.Context | |
24 | 22 | userAgent string |
25 | 23 | defaultUserAgent string |
26 | 24 | username string |
28 | 26 | headers []HTTPHeader |
29 | 27 | cookies string |
30 | 28 | method string |
29 | host string | |
31 | 30 | } |
32 | 31 | |
33 | 32 | // RequestOptions is used to pass options to a single individual request |
38 | 37 | } |
39 | 38 | |
40 | 39 | // NewHTTPClient returns a new HTTPClient |
41 | func NewHTTPClient(c context.Context, opt *HTTPOptions) (*HTTPClient, error) { | |
40 | func NewHTTPClient(opt *HTTPOptions) (*HTTPClient, error) { | |
42 | 41 | var proxyURLFunc func(*http.Request) (*url.URL, error) |
43 | 42 | var client HTTPClient |
44 | 43 | proxyURLFunc = http.ProxyFromEnvironment |
75 | 74 | InsecureSkipVerify: opt.NoTLSValidation, |
76 | 75 | }, |
77 | 76 | }} |
78 | client.context = c | |
79 | 77 | client.username = opt.Username |
80 | 78 | client.password = opt.Password |
81 | 79 | client.userAgent = opt.UserAgent |
86 | 84 | if client.method == "" { |
87 | 85 | client.method = http.MethodGet |
88 | 86 | } |
87 | // Host header needs to be set separately | |
88 | for _, h := range opt.Headers { | |
89 | if h.Name == "Host" { | |
90 | client.host = h.Value | |
91 | break | |
92 | } | |
93 | } | |
89 | 94 | return &client, nil |
90 | 95 | } |
91 | 96 | |
92 | 97 | // Request makes an http request and returns the status, the content length, the headers, the body and an error |
93 | 98 | // if you want the body returned set the corresponding property inside RequestOptions |
94 | func (client *HTTPClient) Request(fullURL string, opts RequestOptions) (*int, int64, http.Header, []byte, error) { | |
95 | resp, err := client.makeRequest(fullURL, opts.Host, opts.Body) | |
99 | func (client *HTTPClient) Request(ctx context.Context, fullURL string, opts RequestOptions) (int, int64, http.Header, []byte, error) { | |
100 | resp, err := client.makeRequest(ctx, fullURL, opts.Host, opts.Body) | |
96 | 101 | if err != nil { |
97 | 102 | // ignore context canceled errors |
98 | if errors.Is(client.context.Err(), context.Canceled) { | |
99 | return nil, 0, nil, nil, nil | |
103 | if errors.Is(ctx.Err(), context.Canceled) { | |
104 | return 0, 0, nil, nil, nil | |
100 | 105 | } |
101 | return nil, 0, nil, nil, err | |
106 | return 0, 0, nil, nil, err | |
102 | 107 | } |
103 | 108 | defer resp.Body.Close() |
104 | 109 | |
105 | 110 | var body []byte |
106 | 111 | var length int64 |
107 | 112 | if opts.ReturnBody { |
108 | body, err = ioutil.ReadAll(resp.Body) | |
113 | body, err = io.ReadAll(resp.Body) | |
109 | 114 | if err != nil { |
110 | return nil, 0, nil, nil, fmt.Errorf("could not read body %w", err) | |
115 | return 0, 0, nil, nil, fmt.Errorf("could not read body %w", err) | |
111 | 116 | } |
112 | 117 | length = int64(len(body)) |
113 | 118 | } else { |
114 | 119 | // DO NOT REMOVE! |
115 | 120 | // absolutely needed so golang will reuse connections! |
116 | length, err = io.Copy(ioutil.Discard, resp.Body) | |
121 | length, err = io.Copy(io.Discard, resp.Body) | |
117 | 122 | if err != nil { |
118 | return nil, 0, nil, nil, err | |
123 | return 0, 0, nil, nil, err | |
119 | 124 | } |
120 | 125 | } |
121 | 126 | |
122 | return &resp.StatusCode, length, resp.Header, body, nil | |
127 | return resp.StatusCode, length, resp.Header, body, nil | |
123 | 128 | } |
124 | 129 | |
125 | func (client *HTTPClient) makeRequest(fullURL, host string, data io.Reader) (*http.Response, error) { | |
130 | func (client *HTTPClient) makeRequest(ctx context.Context, fullURL, host string, data io.Reader) (*http.Response, error) { | |
126 | 131 | req, err := http.NewRequest(client.method, fullURL, data) |
127 | 132 | if err != nil { |
128 | 133 | return nil, err |
129 | 134 | } |
130 | 135 | |
131 | 136 | // add the context so we can easily cancel out |
132 | req = req.WithContext(client.context) | |
137 | req = req.WithContext(ctx) | |
133 | 138 | |
134 | 139 | if client.cookies != "" { |
135 | 140 | req.Header.Set("Cookie", client.cookies) |
136 | 141 | } |
137 | 142 | |
143 | // Use host for VHOST mode on a per request basis, otherwise the one provided from headers | |
138 | 144 | if host != "" { |
139 | 145 | req.Host = host |
146 | } else if client.host != "" { | |
147 | req.Host = client.host | |
140 | 148 | } |
141 | 149 | |
142 | 150 | if client.userAgent != "" { |
156 | 164 | |
157 | 165 | resp, err := client.client.Do(req) |
158 | 166 | if err != nil { |
159 | if ue, ok := err.(*url.Error); ok { | |
167 | var ue *url.Error | |
168 | if errors.As(err, &ue) { | |
160 | 169 | if strings.HasPrefix(ue.Err.Error(), "x509") { |
161 | 170 | return nil, fmt.Errorf("invalid certificate: %w", ue.Err) |
162 | 171 | } |
2 | 2 | import ( |
3 | 3 | "bytes" |
4 | 4 | "context" |
5 | "crypto/rand" | |
5 | 6 | "fmt" |
6 | "math/rand" | |
7 | "math/big" | |
7 | 8 | "net/http" |
8 | 9 | "net/http/httptest" |
9 | 10 | "testing" |
25 | 26 | return ts |
26 | 27 | } |
27 | 28 | |
28 | func randomString(length int) string { | |
29 | func randomString(length int) (string, error) { | |
29 | 30 | var letter = []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") |
30 | 31 | letterLen := len(letter) |
31 | 32 | |
32 | 33 | b := make([]byte, length) |
33 | 34 | for i := range b { |
34 | b[i] = letter[rand.Intn(letterLen)] | |
35 | n, err := rand.Int(rand.Reader, big.NewInt(int64(letterLen))) | |
36 | if err != nil { | |
37 | return "", err | |
38 | } | |
39 | b[i] = letter[n.Int64()] | |
35 | 40 | } |
36 | return string(b) | |
41 | return string(b), nil | |
37 | 42 | } |
38 | 43 | |
39 | 44 | func TestRequest(t *testing.T) { |
40 | ret := randomString(100) | |
45 | t.Parallel() | |
46 | ret, err := randomString(100) | |
47 | if err != nil { | |
48 | t.Fatal(err) | |
49 | } | |
41 | 50 | h := httpServerT(t, ret) |
42 | 51 | defer h.Close() |
43 | 52 | var o HTTPOptions |
44 | c, err := NewHTTPClient(context.Background(), &o) | |
53 | c, err := NewHTTPClient(&o) | |
45 | 54 | if err != nil { |
46 | 55 | t.Fatalf("Got Error: %v", err) |
47 | 56 | } |
48 | status, length, _, body, err := c.Request(h.URL, RequestOptions{ReturnBody: true}) | |
57 | status, length, _, body, err := c.Request(context.Background(), h.URL, RequestOptions{ReturnBody: true}) | |
49 | 58 | if err != nil { |
50 | 59 | t.Fatalf("Got Error: %v", err) |
51 | 60 | } |
52 | if *status != 200 { | |
61 | if status != 200 { | |
53 | 62 | t.Fatalf("Invalid status returned: %d", status) |
54 | 63 | } |
55 | 64 | if length != int64(len(ret)) { |
61 | 70 | } |
62 | 71 | |
63 | 72 | func BenchmarkRequestWithoutBody(b *testing.B) { |
64 | h := httpServerB(b, randomString(10000)) | |
73 | r, err := randomString(10000) | |
74 | if err != nil { | |
75 | b.Fatal(err) | |
76 | } | |
77 | h := httpServerB(b, r) | |
65 | 78 | defer h.Close() |
66 | 79 | var o HTTPOptions |
67 | c, err := NewHTTPClient(context.Background(), &o) | |
80 | c, err := NewHTTPClient(&o) | |
68 | 81 | if err != nil { |
69 | 82 | b.Fatalf("Got Error: %v", err) |
70 | 83 | } |
71 | 84 | for x := 0; x < b.N; x++ { |
72 | _, _, _, _, err := c.Request(h.URL, RequestOptions{ReturnBody: false}) | |
85 | _, _, _, _, err := c.Request(context.Background(), h.URL, RequestOptions{ReturnBody: false}) | |
73 | 86 | if err != nil { |
74 | 87 | b.Fatalf("Got Error: %v", err) |
75 | 88 | } |
77 | 90 | } |
78 | 91 | |
79 | 92 | func BenchmarkRequestWitBody(b *testing.B) { |
80 | h := httpServerB(b, randomString(10000)) | |
93 | r, err := randomString(10000) | |
94 | if err != nil { | |
95 | b.Fatal(err) | |
96 | } | |
97 | h := httpServerB(b, r) | |
81 | 98 | defer h.Close() |
82 | 99 | var o HTTPOptions |
83 | c, err := NewHTTPClient(context.Background(), &o) | |
100 | c, err := NewHTTPClient(&o) | |
84 | 101 | if err != nil { |
85 | 102 | b.Fatalf("Got Error: %v", err) |
86 | 103 | } |
87 | 104 | for x := 0; x < b.N; x++ { |
88 | _, _, _, _, err := c.Request(h.URL, RequestOptions{ReturnBody: true}) | |
105 | _, _, _, _, err := c.Request(context.Background(), h.URL, RequestOptions{ReturnBody: true}) | |
89 | 106 | if err != nil { |
90 | 107 | b.Fatalf("Got Error: %v", err) |
91 | 108 | } |
93 | 110 | } |
94 | 111 | |
95 | 112 | func BenchmarkNewHTTPClient(b *testing.B) { |
96 | h := httpServerB(b, randomString(500)) | |
113 | r, err := randomString(500) | |
114 | if err != nil { | |
115 | b.Fatal(err) | |
116 | } | |
117 | h := httpServerB(b, r) | |
97 | 118 | defer h.Close() |
98 | 119 | var o HTTPOptions |
99 | 120 | for x := 0; x < b.N; x++ { |
100 | _, err := NewHTTPClient(context.Background(), &o) | |
121 | _, err := NewHTTPClient(&o) | |
101 | 122 | if err != nil { |
102 | 123 | b.Fatalf("Got Error: %v", err) |
103 | 124 | } |
0 | 0 | package libgobuster |
1 | ||
2 | import "context" | |
1 | 3 | |
2 | 4 | // GobusterPlugin is an interface which plugins must implement |
3 | 5 | type GobusterPlugin interface { |
4 | 6 | Name() string |
5 | RequestsPerRun() int | |
6 | PreRun() error | |
7 | Run(string, chan<- Result) error | |
7 | PreRun(context.Context) error | |
8 | ProcessWord(context.Context, string, *Progress) error | |
9 | AdditionalWords(string) []string | |
8 | 10 | GetConfigString() (string, error) |
9 | 11 | } |
10 | 12 | |
13 | // Result is an interface for the Result object | |
11 | 14 | type Result interface { |
12 | 15 | ResultToString() (string, error) |
13 | 16 | } |
8 | 8 | "strings" |
9 | 9 | "sync" |
10 | 10 | "time" |
11 | ||
12 | "github.com/fatih/color" | |
11 | 13 | ) |
12 | 14 | |
13 | 15 | // PATTERN is the pattern for wordlist replacements in pattern file |
24 | 26 | |
25 | 27 | // Gobuster is the main object when creating a new run |
26 | 28 | type Gobuster struct { |
27 | Opts *Options | |
28 | context context.Context | |
29 | RequestsExpected int | |
30 | RequestsIssued int | |
31 | RequestsCountMutex *sync.RWMutex | |
32 | plugin GobusterPlugin | |
33 | resultChan chan Result | |
34 | errorChan chan error | |
35 | LogInfo *log.Logger | |
36 | LogError *log.Logger | |
29 | Opts *Options | |
30 | plugin GobusterPlugin | |
31 | LogInfo *log.Logger | |
32 | LogError *log.Logger | |
33 | Progress *Progress | |
37 | 34 | } |
38 | 35 | |
39 | 36 | // NewGobuster returns a new Gobuster object |
40 | func NewGobuster(c context.Context, opts *Options, plugin GobusterPlugin) (*Gobuster, error) { | |
37 | func NewGobuster(opts *Options, plugin GobusterPlugin) (*Gobuster, error) { | |
41 | 38 | var g Gobuster |
42 | 39 | g.Opts = opts |
43 | 40 | g.plugin = plugin |
44 | g.RequestsCountMutex = new(sync.RWMutex) | |
45 | g.context = c | |
46 | g.resultChan = make(chan Result) | |
47 | g.errorChan = make(chan error) | |
48 | 41 | g.LogInfo = log.New(os.Stdout, "", log.LstdFlags) |
49 | g.LogError = log.New(os.Stderr, "[ERROR] ", log.LstdFlags) | |
42 | g.LogError = log.New(os.Stderr, color.New(color.FgRed).Sprint("[ERROR] "), log.LstdFlags) | |
43 | g.Progress = NewProgress() | |
50 | 44 | |
51 | 45 | return &g, nil |
52 | 46 | } |
53 | 47 | |
54 | // Results returns a channel of Results | |
55 | func (g *Gobuster) Results() <-chan Result { | |
56 | return g.resultChan | |
57 | } | |
58 | ||
59 | // Errors returns a channel of errors | |
60 | func (g *Gobuster) Errors() <-chan error { | |
61 | return g.errorChan | |
62 | } | |
63 | ||
64 | func (g *Gobuster) incrementRequests() { | |
65 | g.RequestsCountMutex.Lock() | |
66 | g.RequestsIssued += g.plugin.RequestsPerRun() | |
67 | g.RequestsCountMutex.Unlock() | |
68 | } | |
69 | ||
70 | func (g *Gobuster) worker(wordChan <-chan string, wg *sync.WaitGroup) { | |
48 | func (g *Gobuster) worker(ctx context.Context, wordChan <-chan string, wg *sync.WaitGroup) { | |
71 | 49 | defer wg.Done() |
72 | 50 | for { |
73 | 51 | select { |
74 | case <-g.context.Done(): | |
52 | case <-ctx.Done(): | |
75 | 53 | return |
76 | 54 | case word, ok := <-wordChan: |
77 | 55 | // worker finished |
78 | 56 | if !ok { |
79 | 57 | return |
80 | 58 | } |
81 | g.incrementRequests() | |
59 | g.Progress.incrementRequests() | |
82 | 60 | |
83 | 61 | wordCleaned := strings.TrimSpace(word) |
84 | 62 | // Skip "comment" (starts with #), as well as empty lines |
87 | 65 | } |
88 | 66 | |
89 | 67 | // Mode-specific processing |
90 | err := g.plugin.Run(wordCleaned, g.resultChan) | |
68 | err := g.plugin.ProcessWord(ctx, wordCleaned, g.Progress) | |
91 | 69 | if err != nil { |
92 | 70 | // do not exit and continue |
93 | g.errorChan <- err | |
71 | g.Progress.ErrorChan <- err | |
94 | 72 | continue |
95 | 73 | } |
96 | 74 | |
97 | 75 | select { |
98 | case <-g.context.Done(): | |
76 | case <-ctx.Done(): | |
99 | 77 | case <-time.After(g.Opts.Delay): |
100 | 78 | } |
101 | 79 | } |
118 | 96 | return nil, fmt.Errorf("failed to get number of lines: %w", err) |
119 | 97 | } |
120 | 98 | |
121 | g.RequestsIssued = 0 | |
99 | // calcutate expected requests | |
100 | g.Progress.IncrementTotalRequests(lines) | |
122 | 101 | |
123 | // calcutate expected requests | |
124 | g.RequestsExpected = lines | |
125 | if g.Opts.PatternFile != "" { | |
126 | g.RequestsExpected += lines * len(g.Opts.Patterns) | |
102 | // call the function once with a dummy entry to receive the number | |
103 | // of custom words per wordlist word | |
104 | customWordsLen := len(g.plugin.AdditionalWords("dummy")) | |
105 | if customWordsLen > 0 { | |
106 | origExpected := g.Progress.RequestsExpected() | |
107 | inc := origExpected * customWordsLen | |
108 | g.Progress.IncrementTotalRequests(inc) | |
127 | 109 | } |
128 | ||
129 | g.RequestsExpected *= g.plugin.RequestsPerRun() | |
130 | 110 | |
131 | 111 | // rewind wordlist |
132 | 112 | _, err = wordlist.Seek(0, 0) |
136 | 116 | return bufio.NewScanner(wordlist), nil |
137 | 117 | } |
138 | 118 | |
139 | // Start the busting of the website with the given | |
119 | // Run the busting of the website with the given | |
140 | 120 | // set of settings from the command line. |
141 | func (g *Gobuster) Start() error { | |
142 | defer close(g.resultChan) | |
143 | defer close(g.errorChan) | |
121 | func (g *Gobuster) Run(ctx context.Context) error { | |
122 | defer close(g.Progress.ResultChan) | |
123 | defer close(g.Progress.ErrorChan) | |
144 | 124 | |
145 | if err := g.plugin.PreRun(); err != nil { | |
125 | if err := g.plugin.PreRun(ctx); err != nil { | |
146 | 126 | return err |
147 | 127 | } |
148 | 128 | |
154 | 134 | // Create goroutines for each of the number of threads |
155 | 135 | // specified. |
156 | 136 | for i := 0; i < g.Opts.Threads; i++ { |
157 | go g.worker(wordChan, &workerGroup) | |
137 | go g.worker(ctx, wordChan, &workerGroup) | |
158 | 138 | } |
159 | 139 | |
160 | 140 | scanner, err := g.getWordlist() |
165 | 145 | Scan: |
166 | 146 | for scanner.Scan() { |
167 | 147 | select { |
168 | case <-g.context.Done(): | |
148 | case <-ctx.Done(): | |
169 | 149 | break Scan |
170 | 150 | default: |
171 | 151 | word := scanner.Text() |
176 | 156 | for _, w := range perms { |
177 | 157 | select { |
178 | 158 | // need to check here too otherwise wordChan will block |
179 | case <-g.context.Done(): | |
159 | case <-ctx.Done(): | |
160 | break Scan | |
161 | case wordChan <- w: | |
162 | } | |
163 | } | |
164 | ||
165 | for _, w := range g.plugin.AdditionalWords(word) { | |
166 | select { | |
167 | // need to check here too otherwise wordChan will block | |
168 | case <-ctx.Done(): | |
180 | 169 | break Scan |
181 | 170 | case wordChan <- w: |
182 | 171 | } |
12 | 12 | NoProgress bool |
13 | 13 | NoError bool |
14 | 14 | Quiet bool |
15 | WildcardForced bool | |
16 | 15 | Verbose bool |
17 | 16 | Delay time.Duration |
18 | 17 | } |
5 | 5 | |
6 | 6 | // BasicHTTPOptions defines only core http options |
7 | 7 | type BasicHTTPOptions struct { |
8 | UserAgent string | |
9 | Proxy string | |
10 | Timeout time.Duration | |
8 | UserAgent string | |
9 | Proxy string | |
10 | NoTLSValidation bool | |
11 | Timeout time.Duration | |
12 | RetryOnTimeout bool | |
13 | RetryAttempts int | |
11 | 14 | } |
12 | 15 | |
13 | 16 | // HTTPOptions is the struct to pass in all http options to Gobuster |
14 | 17 | type HTTPOptions struct { |
15 | 18 | BasicHTTPOptions |
16 | Password string | |
17 | URL string | |
18 | Username string | |
19 | Cookies string | |
20 | Headers []HTTPHeader | |
21 | FollowRedirect bool | |
22 | NoTLSValidation bool | |
23 | Method string | |
19 | Password string | |
20 | URL string | |
21 | Username string | |
22 | Cookies string | |
23 | Headers []HTTPHeader | |
24 | FollowRedirect bool | |
25 | Method string | |
24 | 26 | } |
0 | package libgobuster | |
1 | ||
2 | import "sync" | |
3 | ||
4 | type Progress struct { | |
5 | requestsExpectedMutex *sync.RWMutex | |
6 | requestsExpected int | |
7 | requestsCountMutex *sync.RWMutex | |
8 | requestsIssued int | |
9 | ResultChan chan Result | |
10 | ErrorChan chan error | |
11 | } | |
12 | ||
13 | func NewProgress() *Progress { | |
14 | var p Progress | |
15 | p.requestsIssued = 0 | |
16 | p.requestsExpectedMutex = new(sync.RWMutex) | |
17 | p.requestsCountMutex = new(sync.RWMutex) | |
18 | p.ResultChan = make(chan Result) | |
19 | p.ErrorChan = make(chan error) | |
20 | return &p | |
21 | } | |
22 | ||
23 | func (p *Progress) RequestsExpected() int { | |
24 | p.requestsExpectedMutex.RLock() | |
25 | defer p.requestsExpectedMutex.RUnlock() | |
26 | return p.requestsExpected | |
27 | } | |
28 | ||
29 | func (p *Progress) RequestsIssued() int { | |
30 | p.requestsCountMutex.RLock() | |
31 | defer p.requestsCountMutex.RUnlock() | |
32 | return p.requestsIssued | |
33 | } | |
34 | ||
35 | func (p *Progress) incrementRequests() { | |
36 | p.requestsCountMutex.Lock() | |
37 | defer p.requestsCountMutex.Unlock() | |
38 | p.requestsIssued++ | |
39 | } | |
40 | ||
41 | func (p *Progress) IncrementTotalRequests(by int) { | |
42 | p.requestsCountMutex.Lock() | |
43 | defer p.requestsCountMutex.Unlock() | |
44 | p.requestsExpected += by | |
45 | } |
1 | 1 | |
2 | 2 | const ( |
3 | 3 | // VERSION contains the current gobuster version |
4 | VERSION = "3.1.0" | |
4 | VERSION = "3.2.0-dev" | |
5 | 5 | ) |
0 | @echo off | |
1 | ||
2 | SET ARG=%1 | |
3 | SET TARGET=.\build | |
4 | SET BUILDARGS=-ldflags="-s -w" -trimpath | |
5 | ||
6 | IF "%ARG%"=="test" ( | |
7 | CALL :Test | |
8 | GOTO Done | |
9 | ) | |
10 | ||
11 | IF "%ARG%"=="clean" ( | |
12 | del /F /Q %TARGET%\*.* | |
13 | go clean ./... | |
14 | echo Done. | |
15 | GOTO Done | |
16 | ) | |
17 | ||
18 | IF "%ARG%"=="windows" ( | |
19 | CALL :Windows | |
20 | GOTO Done | |
21 | ) | |
22 | ||
23 | IF "%ARG%"=="darwin" ( | |
24 | CALL :Darwin | |
25 | GOTO Done | |
26 | ) | |
27 | ||
28 | IF "%ARG%"=="linux" ( | |
29 | CALL :Linux | |
30 | GOTO Done | |
31 | ) | |
32 | ||
33 | IF "%ARG%"=="update" ( | |
34 | CALL :Update | |
35 | GOTO Done | |
36 | ) | |
37 | ||
38 | IF "%ARG%"=="fmt" ( | |
39 | CALL :Fmt | |
40 | GOTO Done | |
41 | ) | |
42 | ||
43 | IF "%ARG%"=="lint" ( | |
44 | CALL :Lint | |
45 | GOTO Done | |
46 | ) | |
47 | ||
48 | IF "%ARG%"=="all" ( | |
49 | CALL :Fmt | |
50 | CALL :Update | |
51 | CALL :Lint | |
52 | CALL :Test | |
53 | CALL :Darwin | |
54 | CALL :Linux | |
55 | CALL :Windows | |
56 | GOTO Done | |
57 | ) | |
58 | ||
59 | IF "%ARG%"=="" ( | |
60 | go build -o .\gobuster.exe | |
61 | GOTO Done | |
62 | ) | |
63 | ||
64 | GOTO Done | |
65 | ||
66 | :Test | |
67 | set GO111MODULE=on | |
68 | set CGO_ENABLED=0 | |
69 | echo Testing ... | |
70 | go test -v ./... | |
71 | echo Done | |
72 | EXIT /B 0 | |
73 | ||
74 | :Lint | |
75 | set GO111MODULE=on | |
76 | echo Linting ... | |
77 | go get -u github.com/golangci/golangci-lint@master | |
78 | golangci-lint run ./... | |
79 | rem remove test deps | |
80 | go mod tidy | |
81 | echo Done | |
82 | ||
83 | :Fmt | |
84 | set GO111MODULE=on | |
85 | echo Formatting ... | |
86 | go fmt ./... | |
87 | echo Done. | |
88 | EXIT /B 0 | |
89 | ||
90 | :Update | |
91 | set GO111MODULE=on | |
92 | echo Updating ... | |
93 | go get -u | |
94 | go mod tidy -v | |
95 | echo Done. | |
96 | EXIT /B 0 | |
97 | ||
98 | :Darwin | |
99 | set GOOS=darwin | |
100 | set GOARCH=amd64 | |
101 | set GO111MODULE=on | |
102 | set CGO_ENABLED=0 | |
103 | echo Building for %GOOS% %GOARCH% ... | |
104 | set DIR=%TARGET%\gobuster-%GOOS%-%GOARCH% | |
105 | mkdir %DIR% 2> NUL | |
106 | go build %BUILDARGS% -o %DIR%\gobuster | |
107 | set GOARCH=386 | |
108 | echo Building for %GOOS% %GOARCH% ... | |
109 | set DIR=%TARGET%\gobuster-%GOOS%-%GOARCH% | |
110 | mkdir %DIR% 2> NUL | |
111 | go build %BUILDARGS% -o %DIR%\gobuster | |
112 | echo Done. | |
113 | EXIT /B 0 | |
114 | ||
115 | :Linux | |
116 | set GOOS=linux | |
117 | set GOARCH=amd64 | |
118 | set GO111MODULE=on | |
119 | set CGO_ENABLED=0 | |
120 | echo Building for %GOOS% %GOARCH% ... | |
121 | set DIR=%TARGET%\gobuster-%GOOS%-%GOARCH% | |
122 | mkdir %DIR% 2> NUL | |
123 | go build %BUILDARGS% -o %DIR%\gobuster | |
124 | set GOARCH=386 | |
125 | echo Building for %GOOS% %GOARCH% ... | |
126 | set DIR=%TARGET%\gobuster-%GOOS%-%GOARCH% | |
127 | mkdir %DIR% 2> NUL | |
128 | go build %BUILDARGS% -o %DIR%\gobuster | |
129 | echo Done. | |
130 | EXIT /B 0 | |
131 | ||
132 | :Windows | |
133 | set GOOS=windows | |
134 | set GOARCH=amd64 | |
135 | set GO111MODULE=on | |
136 | set CGO_ENABLED=0 | |
137 | echo Building for %GOOS% %GOARCH% ... | |
138 | set DIR=%TARGET%\gobuster-%GOOS%-%GOARCH% | |
139 | mkdir %DIR% 2> NUL | |
140 | go build %BUILDARGS% -o %DIR%\gobuster.exe | |
141 | set GOARCH=386 | |
142 | echo Building for %GOOS% %GOARCH% ... | |
143 | set DIR=%TARGET%\gobuster-%GOOS%-%GOARCH% | |
144 | mkdir %DIR% 2> NUL | |
145 | go build %BUILDARGS% -o %DIR%\gobuster.exe | |
146 | echo Done. | |
147 | EXIT /B 0 | |
148 | ||
149 | :Done |