mirror of
https://github.com/OJ/gobuster.git
synced 2024-05-06 11:16:05 +02:00
Dev Updates (#305)
* retry on timeout * Google Cloud Bucket enumeration * colors in output * goreleaser * fix nil reference errors
This commit is contained in:
parent
9ea1da42f7
commit
0a0cab949f
|
@ -1,6 +1,6 @@
|
||||||
# These are supported funding model platforms
|
# These are supported funding model platforms
|
||||||
|
|
||||||
github: OJ
|
github: [OJ, firefart]
|
||||||
patreon: OJReeves
|
patreon: OJReeves
|
||||||
open_collective: gobuster
|
open_collective: gobuster
|
||||||
ko_fi: OJReeves
|
ko_fi: OJReeves
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
# To get started with Dependabot version updates, you'll need to specify which
|
||||||
|
# package ecosystems to update and where the package manifests are located.
|
||||||
|
# Please see the documentation for all configuration options:
|
||||||
|
# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||||
|
|
||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: "gomod"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "weekly"
|
||||||
|
|
||||||
|
- package-ecosystem: "github-actions"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
# Check for updates to GitHub Actions every weekday
|
||||||
|
interval: "daily"
|
|
@ -6,7 +6,7 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go: ["1.17", "1.18", "1.19"]
|
go: ["1.18", "1.19"]
|
||||||
steps:
|
steps:
|
||||||
- name: Set up Go ${{ matrix.go }}
|
- name: Set up Go ${{ matrix.go }}
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v2
|
||||||
|
@ -28,8 +28,11 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
go get -v -t -d ./...
|
go get -v -t -d ./...
|
||||||
|
|
||||||
- name: Build
|
- name: Build linux
|
||||||
run: go build -v .
|
run: make linux
|
||||||
|
|
||||||
|
- name: Build windows
|
||||||
|
run: make windows
|
||||||
|
|
||||||
- name: Test
|
- name: Test
|
||||||
run: make test
|
run: make test
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
name: goreleaser
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- "*"
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
goreleaser:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: Fetch all tags
|
||||||
|
run: git fetch --force --tags
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v2
|
||||||
|
with:
|
||||||
|
go-version: 1.19
|
||||||
|
- name: Run GoReleaser
|
||||||
|
uses: goreleaser/goreleaser-action@v2
|
||||||
|
with:
|
||||||
|
distribution: goreleaser
|
||||||
|
version: latest
|
||||||
|
args: release --rm-dist
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
@ -1,26 +1,19 @@
|
||||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
# Binaries for programs and plugins
|
||||||
*.o
|
|
||||||
*.a
|
|
||||||
*.so
|
|
||||||
|
|
||||||
# Folders
|
|
||||||
_obj
|
|
||||||
_test
|
|
||||||
|
|
||||||
# Architecture specific extensions/prefixes
|
|
||||||
*.[568vq]
|
|
||||||
[568vq].out
|
|
||||||
|
|
||||||
*.cgo1.go
|
|
||||||
*.cgo2.c
|
|
||||||
_cgo_defun.c
|
|
||||||
_cgo_gotypes.go
|
|
||||||
_cgo_export.*
|
|
||||||
|
|
||||||
_testmain.go
|
|
||||||
|
|
||||||
*.exe
|
*.exe
|
||||||
|
*.exe~
|
||||||
|
*.dll
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
|
||||||
|
# Test binary, built with `go test -c`
|
||||||
*.test
|
*.test
|
||||||
|
|
||||||
|
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||||
|
*.out
|
||||||
|
|
||||||
|
# Dependency directories (remove the comment below to include it)
|
||||||
|
# vendor/
|
||||||
|
|
||||||
*.prof
|
*.prof
|
||||||
*.txt
|
*.txt
|
||||||
*.swp
|
*.swp
|
||||||
|
@ -28,6 +21,5 @@ _testmain.go
|
||||||
.vscode/
|
.vscode/
|
||||||
gobuster
|
gobuster
|
||||||
build
|
build
|
||||||
v3
|
|
||||||
|
|
||||||
.idea/
|
dist/
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
linters:
|
||||||
|
enable:
|
||||||
|
- nonamedreturns
|
|
@ -0,0 +1,36 @@
|
||||||
|
# This is an example .goreleaser.yml file with some sensible defaults.
|
||||||
|
# Make sure to check the documentation at https://goreleaser.com
|
||||||
|
before:
|
||||||
|
hooks:
|
||||||
|
# You may remove this if you don't use go modules.
|
||||||
|
- go mod tidy
|
||||||
|
# you may remove this if you don't need go generate
|
||||||
|
- go generate ./...
|
||||||
|
builds:
|
||||||
|
- env:
|
||||||
|
- CGO_ENABLED=0
|
||||||
|
goos:
|
||||||
|
- linux
|
||||||
|
- windows
|
||||||
|
- darwin
|
||||||
|
archives:
|
||||||
|
- format: tar.gz
|
||||||
|
format_overrides:
|
||||||
|
- goos: windows
|
||||||
|
format: zip
|
||||||
|
replacements:
|
||||||
|
darwin: Darwin
|
||||||
|
linux: Linux
|
||||||
|
windows: Windows
|
||||||
|
386: i386
|
||||||
|
amd64: x86_64
|
||||||
|
checksum:
|
||||||
|
name_template: "checksums.txt"
|
||||||
|
snapshot:
|
||||||
|
name_template: "{{ incpatch .Version }}-dev"
|
||||||
|
changelog:
|
||||||
|
sort: asc
|
||||||
|
filters:
|
||||||
|
exclude:
|
||||||
|
- "^docs:"
|
||||||
|
- "^test:"
|
72
Makefile
72
Makefile
|
@ -1,57 +1,28 @@
|
||||||
TARGET=./build
|
.DEFAULT_GOAL := linux
|
||||||
ARCHS=amd64 386
|
|
||||||
LDFLAGS="-s -w"
|
|
||||||
|
|
||||||
.PHONY: current
|
|
||||||
current:
|
|
||||||
@go build -o ./gobuster; \
|
|
||||||
echo "Done."
|
|
||||||
|
|
||||||
.PHONY: fmt
|
|
||||||
fmt:
|
|
||||||
@go fmt ./...; \
|
|
||||||
echo "Done."
|
|
||||||
|
|
||||||
.PHONY: update
|
|
||||||
update:
|
|
||||||
@go get -u; \
|
|
||||||
go mod tidy -v; \
|
|
||||||
echo "Done."
|
|
||||||
|
|
||||||
.PHONY: windows
|
|
||||||
windows:
|
|
||||||
@for GOARCH in ${ARCHS}; do \
|
|
||||||
echo "Building for windows $${GOARCH} ..." ; \
|
|
||||||
mkdir -p ${TARGET}/gobuster-windows-$${GOARCH} ; \
|
|
||||||
GOOS=windows GOARCH=$${GOARCH} GO111MODULE=on CGO_ENABLED=0 go build -ldflags=${LDFLAGS} -trimpath -o ${TARGET}/gobuster-windows-$${GOARCH}/gobuster.exe ; \
|
|
||||||
done; \
|
|
||||||
echo "Done."
|
|
||||||
|
|
||||||
.PHONY: linux
|
.PHONY: linux
|
||||||
linux:
|
linux:
|
||||||
@for GOARCH in ${ARCHS}; do \
|
go build -o ./gobuster
|
||||||
echo "Building for linux $${GOARCH} ..." ; \
|
|
||||||
mkdir -p ${TARGET}/gobuster-linux-$${GOARCH} ; \
|
|
||||||
GOOS=linux GOARCH=$${GOARCH} GO111MODULE=on CGO_ENABLED=0 go build -ldflags=${LDFLAGS} -trimpath -o ${TARGET}/gobuster-linux-$${GOARCH}/gobuster ; \
|
|
||||||
done; \
|
|
||||||
echo "Done."
|
|
||||||
|
|
||||||
.PHONY: darwin
|
.PHONY: windows
|
||||||
darwin:
|
windows:
|
||||||
@for GOARCH in ${ARCHS}; do \
|
GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build -o ./gobuster.exe
|
||||||
echo "Building for darwin $${GOARCH} ..." ; \
|
|
||||||
mkdir -p ${TARGET}/gobuster-darwin-$${GOARCH} ; \
|
.PHONY: fmt
|
||||||
GOOS=darwin GOARCH=$${GOARCH} GO111MODULE=on CGO_ENABLED=0 go build -ldflags=${LDFLAGS} -trimpath -o ${TARGET}/gobuster-darwin-$${GOARCH}/gobuster ; \
|
fmt:
|
||||||
done; \
|
go fmt ./...
|
||||||
echo "Done."
|
|
||||||
|
.PHONY: update
|
||||||
|
update:
|
||||||
|
go get -u
|
||||||
|
go mod tidy -v
|
||||||
|
|
||||||
.PHONY: all
|
.PHONY: all
|
||||||
all: clean fmt update test lint darwin linux windows
|
all: fmt update linux windows test lint
|
||||||
|
|
||||||
.PHONY: test
|
.PHONY: test
|
||||||
test:
|
test:
|
||||||
@go test -v -race ./... ; \
|
go test -v -race ./...
|
||||||
echo "Done."
|
|
||||||
|
|
||||||
.PHONY: lint
|
.PHONY: lint
|
||||||
lint:
|
lint:
|
||||||
|
@ -62,14 +33,3 @@ lint:
|
||||||
lint-update:
|
lint-update:
|
||||||
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $$(go env GOPATH)/bin
|
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $$(go env GOPATH)/bin
|
||||||
$$(go env GOPATH)/bin/golangci-lint --version
|
$$(go env GOPATH)/bin/golangci-lint --version
|
||||||
|
|
||||||
.PHONY: lint-docker
|
|
||||||
lint-docker:
|
|
||||||
docker pull golangci/golangci-lint:latest
|
|
||||||
docker run --rm -v $$(pwd):/app -w /app golangci/golangci-lint:latest golangci-lint run
|
|
||||||
|
|
||||||
.PHONY: clean
|
|
||||||
clean:
|
|
||||||
@rm -rf ${TARGET}/* ; \
|
|
||||||
go clean ./... ; \
|
|
||||||
echo "Done."
|
|
||||||
|
|
786
README.md
786
README.md
|
@ -1,4 +1,4 @@
|
||||||
# Gobuster v3.1.0
|
# Gobuster v3.2.0
|
||||||
|
|
||||||
Gobuster is a tool used to brute-force:
|
Gobuster is a tool used to brute-force:
|
||||||
|
|
||||||
|
@ -9,28 +9,8 @@ Gobuster is a tool used to brute-force:
|
||||||
|
|
||||||
## Tags, Statuses, etc
|
## Tags, Statuses, etc
|
||||||
|
|
||||||
[![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)]
|
[![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)
|
||||||
|
|
||||||
## Oh dear God.. WHY!?
|
|
||||||
|
|
||||||
Because I wanted:
|
|
||||||
|
|
||||||
1. ... something that didn't have a fat Java GUI (console FTW).
|
|
||||||
1. ... to build something that just worked on the command line.
|
|
||||||
1. ... something that did not do recursive brute force.
|
|
||||||
1. ... something that allowed me to brute force folders and multiple extensions at once.
|
|
||||||
1. ... something that compiled to native on multiple platforms.
|
|
||||||
1. ... something that was faster than an interpreted script (such as Python).
|
|
||||||
1. ... something that didn't require a runtime.
|
|
||||||
1. ... use something that was good with concurrency (hence Go).
|
|
||||||
1. ... to build something in Go that wasn't totally useless.
|
|
||||||
|
|
||||||
## But it's shit! And your implementation sucks!
|
|
||||||
|
|
||||||
Yes, you're probably correct. Feel free to:
|
|
||||||
|
|
||||||
- Not use it.
|
|
||||||
- Show me how to do it better.
|
|
||||||
|
|
||||||
## Love this tool? Back it!
|
## Love this tool? Back it!
|
||||||
|
|
||||||
|
@ -40,13 +20,19 @@ If you're backing us already, you rock. If you're not, that's cool too! Want to
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
## Changes in 3.1-dev
|
# Changes
|
||||||
|
|
||||||
- Use go 1.16
|
## 3.2-dev
|
||||||
|
|
||||||
|
- Use go 1.19
|
||||||
- use contexts in the correct way
|
- use contexts in the correct way
|
||||||
- get rid of the wildcard flag (except in DNS mode)
|
- get rid of the wildcard flag (except in DNS mode)
|
||||||
|
- color output
|
||||||
|
- retry on timeout
|
||||||
|
- google cloud bucket enumeration
|
||||||
|
- fix nil reference errors
|
||||||
|
|
||||||
## Changes in 3.1
|
## 3.1
|
||||||
|
|
||||||
- enumerate public AWS S3 buckets
|
- enumerate public AWS S3 buckets
|
||||||
- fuzzing mode
|
- fuzzing mode
|
||||||
|
@ -54,118 +40,27 @@ All funds that are donated to this project will be donated to charity. A full lo
|
||||||
- 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.
|
- 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.
|
||||||
- The shorthand `p` flag which was assigned to proxy is now used by the pattern flag
|
- The shorthand `p` flag which was assigned to proxy is now used by the pattern flag
|
||||||
|
|
||||||
## Changes in 3.0
|
## 3.0
|
||||||
|
|
||||||
- New CLI options so modes are strictly separated (`-m` is now gone!)
|
- New CLI options so modes are strictly separated (`-m` is now gone!)
|
||||||
- Performance Optimizations and better connection handling
|
- Performance Optimizations and better connection handling
|
||||||
- Ability to enumerate vhost names
|
- Ability to enumerate vhost names
|
||||||
- Option to supply custom HTTP headers
|
- Option to supply custom HTTP headers
|
||||||
|
|
||||||
|
# License
|
||||||
|
|
||||||
|
See the LICENSE file.
|
||||||
|
|
||||||
|
# Manual
|
||||||
|
|
||||||
## Available Modes
|
## Available Modes
|
||||||
|
|
||||||
- dir - the classic directory brute-forcing mode
|
- dir - the classic directory brute-forcing mode
|
||||||
- dns - DNS subdomain brute-forcing mode
|
- dns - DNS subdomain brute-forcing mode
|
||||||
- s3 - Enumerate open S3 buckets and look for existence and bucket listings
|
- s3 - Enumerate open S3 buckets and look for existence and bucket listings
|
||||||
|
- gcs - Enumerate open google cloud buckets
|
||||||
- vhost - virtual host brute-forcing mode (not the same as DNS!)
|
- vhost - virtual host brute-forcing mode (not the same as DNS!)
|
||||||
|
- fuzz - some basic fuzzing, replaces the `FUZZ` keyword
|
||||||
## Built-in Help
|
|
||||||
|
|
||||||
Help is built-in!
|
|
||||||
|
|
||||||
- `gobuster help` - outputs the top-level help.
|
|
||||||
- `gobuster help <mode>` - outputs the help specific to that mode.
|
|
||||||
|
|
||||||
## `dns` Mode Help
|
|
||||||
|
|
||||||
```text
|
|
||||||
Usage:
|
|
||||||
gobuster dns [flags]
|
|
||||||
|
|
||||||
Flags:
|
|
||||||
-d, --domain string The target domain
|
|
||||||
-h, --help help for dns
|
|
||||||
-r, --resolver string Use custom DNS server (format server.com or server.com:port)
|
|
||||||
-c, --show-cname Show CNAME records (cannot be used with '-i' option)
|
|
||||||
-i, --show-ips Show IP addresses
|
|
||||||
--timeout duration DNS resolver timeout (default 1s)
|
|
||||||
--wildcard Force continued operation when wildcard found
|
|
||||||
|
|
||||||
Global Flags:
|
|
||||||
-z, --no-progress Don't display progress
|
|
||||||
-o, --output string Output file to write results to (defaults to stdout)
|
|
||||||
-q, --quiet Don't print the banner and other noise
|
|
||||||
-t, --threads int Number of concurrent threads (default 10)
|
|
||||||
--delay duration Time each thread waits between requests (e.g. 1500ms)
|
|
||||||
-v, --verbose Verbose output (errors)
|
|
||||||
-w, --wordlist string Path to the wordlist
|
|
||||||
```
|
|
||||||
|
|
||||||
## `dir` Mode Options
|
|
||||||
|
|
||||||
```text
|
|
||||||
Usage:
|
|
||||||
gobuster dir [flags]
|
|
||||||
|
|
||||||
Flags:
|
|
||||||
-f, --add-slash Append / to each request
|
|
||||||
-c, --cookies string Cookies to use for the requests
|
|
||||||
-e, --expanded Expanded mode, print full URLs
|
|
||||||
-x, --extensions string File extension(s) to search for
|
|
||||||
-r, --follow-redirect Follow redirects
|
|
||||||
-H, --headers stringArray Specify HTTP headers, -H 'Header1: val1' -H 'Header2: val2'
|
|
||||||
-h, --help help for dir
|
|
||||||
-l, --include-length Include the length of the body in the output
|
|
||||||
-k, --no-tls-validation Skip TLS certificate verification
|
|
||||||
-n, --no-status Don't print status codes
|
|
||||||
-P, --password string Password for Basic Auth
|
|
||||||
-p, --proxy string Proxy to use for requests [http(s)://host:port]
|
|
||||||
-s, --status-codes string Positive status codes (will be overwritten with status-codes-blacklist if set) (default "200,204,301,302,307,401,403")
|
|
||||||
-b, --status-codes-blacklist string Negative status codes (will override status-codes if set)
|
|
||||||
--timeout duration HTTP Timeout (default 10s)
|
|
||||||
-u, --url string The target URL
|
|
||||||
-a, --useragent string Set the User-Agent string (default "gobuster/3.1.0")
|
|
||||||
-U, --username string Username for Basic Auth
|
|
||||||
-d, --discover-backup Upon finding a file search for backup files
|
|
||||||
--wildcard Force continued operation when wildcard found
|
|
||||||
|
|
||||||
Global Flags:
|
|
||||||
-z, --no-progress Don't display progress
|
|
||||||
-o, --output string Output file to write results to (defaults to stdout)
|
|
||||||
-q, --quiet Don't print the banner and other noise
|
|
||||||
-t, --threads int Number of concurrent threads (default 10)
|
|
||||||
--delay duration Time each thread waits between requests (e.g. 1500ms)
|
|
||||||
-v, --verbose Verbose output (errors)
|
|
||||||
-w, --wordlist string Path to the wordlist
|
|
||||||
```
|
|
||||||
|
|
||||||
## `vhost` Mode Options
|
|
||||||
|
|
||||||
```text
|
|
||||||
Usage:
|
|
||||||
gobuster vhost [flags]
|
|
||||||
|
|
||||||
Flags:
|
|
||||||
-c, --cookies string Cookies to use for the requests
|
|
||||||
-r, --follow-redirect Follow redirects
|
|
||||||
-H, --headers stringArray Specify HTTP headers, -H 'Header1: val1' -H 'Header2: val2'
|
|
||||||
-h, --help help for vhost
|
|
||||||
-k, --no-tls-validation Skip TLS certificate verification
|
|
||||||
-P, --password string Password for Basic Auth
|
|
||||||
-p, --proxy string Proxy to use for requests [http(s)://host:port]
|
|
||||||
--timeout duration HTTP Timeout (default 10s)
|
|
||||||
-u, --url string The target URL
|
|
||||||
-a, --useragent string Set the User-Agent string (default "gobuster/3.1.0")
|
|
||||||
-U, --username string Username for Basic Auth
|
|
||||||
|
|
||||||
Global Flags:
|
|
||||||
-z, --no-progress Don't display progress
|
|
||||||
-o, --output string Output file to write results to (defaults to stdout)
|
|
||||||
-q, --quiet Don't print the banner and other noise
|
|
||||||
-t, --threads int Number of concurrent threads (default 10)
|
|
||||||
--delay duration Time each thread waits between requests (e.g. 1500ms)
|
|
||||||
-v, --verbose Verbose output (errors)
|
|
||||||
-w, --wordlist string Path to the wordlist
|
|
||||||
```
|
|
||||||
|
|
||||||
## Easy Installation
|
## Easy Installation
|
||||||
|
|
||||||
|
@ -177,17 +72,17 @@ If you're stupid enough to trust binaries that I've put together, you can downlo
|
||||||
|
|
||||||
### Using `go install`
|
### Using `go install`
|
||||||
|
|
||||||
If you have a [Go](https://golang.org/) environment ready to go (at least go 1.17), it's as easy as:
|
If you have a [Go](https://golang.org/) environment ready to go (at least go 1.19), it's as easy as:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
go install github.com/OJ/gobuster/v3@latest
|
go install github.com/OJ/gobuster/v3@latest
|
||||||
```
|
```
|
||||||
|
|
||||||
PS: You need at least go 1.17.0 to compile gobuster.
|
PS: You need at least go 1.19 to compile gobuster.
|
||||||
|
|
||||||
## Building From Source
|
### Building From Source
|
||||||
|
|
||||||
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.17.0 to compile gobuster.
|
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.
|
||||||
|
|
||||||
### Compiling
|
### Compiling
|
||||||
|
|
||||||
|
@ -203,196 +98,59 @@ This will create a `gobuster` binary for you. If you want to install it in the `
|
||||||
go install
|
go install
|
||||||
```
|
```
|
||||||
|
|
||||||
If you have all the dependencies already, you can make use of the build scripts:
|
## Modes
|
||||||
|
|
||||||
- `make` - builds for the current Go configuration (ie. runs `go build`).
|
Help is built-in!
|
||||||
- `make windows` - builds 32 and 64 bit binaries for windows, and writes them to the `build` folder.
|
|
||||||
- `make linux` - builds 32 and 64 bit binaries for linux, and writes them to the `build` folder.
|
|
||||||
- `make darwin` - builds 32 and 64 bit binaries for darwin, and writes them to the `build` folder.
|
|
||||||
- `make all` - builds for all platforms and architectures, and writes the resulting binaries to the `build` folder.
|
|
||||||
- `make clean` - clears out the `build` folder.
|
|
||||||
- `make test` - runs the tests.
|
|
||||||
|
|
||||||
## Wordlists via STDIN
|
- `gobuster help` - outputs the top-level help.
|
||||||
|
- `gobuster help <mode>` - outputs the help specific to that mode.
|
||||||
|
|
||||||
Wordlists can be piped into `gobuster` via stdin by providing a `-` to the `-w` option:
|
## `dns` Mode
|
||||||
|
|
||||||
```bash
|
### Options
|
||||||
hashcat -a 3 --stdout ?l | gobuster dir -u https://mysite.com -w -
|
|
||||||
```
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
## Patterns
|
|
||||||
|
|
||||||
You can supply pattern files that will be applied to every word from the wordlist.
|
|
||||||
Just place the string `{GOBUSTER}` in it and this will be replaced with the word.
|
|
||||||
This feature is also handy in s3 mode to pre- or postfix certain patterns.
|
|
||||||
|
|
||||||
**Caution:** Using a big pattern file can cause a lot of request as every pattern is applied to every word in the wordlist.
|
|
||||||
|
|
||||||
### Example file
|
|
||||||
|
|
||||||
```text
|
```text
|
||||||
{GOBUSTER}Partial
|
Uses DNS subdomain enumeration mode
|
||||||
{GOBUSTER}Service
|
|
||||||
PRE{GOBUSTER}POST
|
Usage:
|
||||||
{GOBUSTER}-prod
|
gobuster dns [flags]
|
||||||
{GOBUSTER}-dev
|
|
||||||
|
Flags:
|
||||||
|
-d, --domain string The target domain
|
||||||
|
-h, --help help for dns
|
||||||
|
-r, --resolver string Use custom DNS server (format server.com or server.com:port)
|
||||||
|
-c, --show-cname Show CNAME records (cannot be used with '-i' option)
|
||||||
|
-i, --show-ips Show IP addresses
|
||||||
|
--timeout duration DNS resolver timeout (default 1s)
|
||||||
|
--wildcard Force continued operation when wildcard found
|
||||||
|
|
||||||
|
Global Flags:
|
||||||
|
--delay duration Time each thread waits between requests (e.g. 1500ms)
|
||||||
|
--no-color Disable color output
|
||||||
|
--no-error Don't display errors
|
||||||
|
-z, --no-progress Don't display progress
|
||||||
|
-o, --output string Output file to write results to (defaults to stdout)
|
||||||
|
-p, --pattern string File containing replacement patterns
|
||||||
|
-q, --quiet Don't print the banner and other noise
|
||||||
|
-t, --threads int Number of concurrent threads (default 10)
|
||||||
|
-v, --verbose Verbose output (errors)
|
||||||
|
-w, --wordlist string Path to the wordlist
|
||||||
```
|
```
|
||||||
|
|
||||||
## Examples
|
### Examples
|
||||||
|
|
||||||
### `dir` Mode
|
|
||||||
|
|
||||||
Command line might look like this:
|
```text
|
||||||
|
|
||||||
```bash
|
|
||||||
gobuster dir -u https://mysite.com/path/to/folder -c 'session=123456' -t 50 -w common-files.txt -x .php,.html
|
|
||||||
```
|
|
||||||
|
|
||||||
Default options looks like this:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
gobuster dir -u https://buffered.io -w ~/wordlists/shortlist.txt
|
|
||||||
|
|
||||||
===============================================================
|
|
||||||
Gobuster v3.1.0
|
|
||||||
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
|
|
||||||
===============================================================
|
|
||||||
[+] Mode : dir
|
|
||||||
[+] Url/Domain : https://buffered.io/
|
|
||||||
[+] Threads : 10
|
|
||||||
[+] Wordlist : /home/oj/wordlists/shortlist.txt
|
|
||||||
[+] Status codes : 200,204,301,302,307,401,403
|
|
||||||
[+] User Agent : gobuster/3.1.0
|
|
||||||
[+] Timeout : 10s
|
|
||||||
===============================================================
|
|
||||||
2019/06/21 11:49:43 Starting gobuster
|
|
||||||
===============================================================
|
|
||||||
/categories (Status: 301)
|
|
||||||
/contact (Status: 301)
|
|
||||||
/posts (Status: 301)
|
|
||||||
/index (Status: 200)
|
|
||||||
===============================================================
|
|
||||||
2019/06/21 11:49:44 Finished
|
|
||||||
===============================================================
|
|
||||||
```
|
|
||||||
|
|
||||||
Default options with status codes disabled looks like this:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
gobuster dir -u https://buffered.io -w ~/wordlists/shortlist.txt -n
|
|
||||||
|
|
||||||
===============================================================
|
|
||||||
Gobuster v3.1.0
|
|
||||||
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
|
|
||||||
===============================================================
|
|
||||||
[+] Mode : dir
|
|
||||||
[+] Url/Domain : https://buffered.io/
|
|
||||||
[+] Threads : 10
|
|
||||||
[+] Wordlist : /home/oj/wordlists/shortlist.txt
|
|
||||||
[+] Status codes : 200,204,301,302,307,401,403
|
|
||||||
[+] User Agent : gobuster/3.1.0
|
|
||||||
[+] No status : true
|
|
||||||
[+] Timeout : 10s
|
|
||||||
===============================================================
|
|
||||||
2019/06/21 11:50:18 Starting gobuster
|
|
||||||
===============================================================
|
|
||||||
/categories
|
|
||||||
/contact
|
|
||||||
/index
|
|
||||||
/posts
|
|
||||||
===============================================================
|
|
||||||
2019/06/21 11:50:18 Finished
|
|
||||||
===============================================================
|
|
||||||
```
|
|
||||||
|
|
||||||
Verbose output looks like this:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
gobuster dir -u https://buffered.io -w ~/wordlists/shortlist.txt -v
|
|
||||||
|
|
||||||
===============================================================
|
|
||||||
Gobuster v3.1.0
|
|
||||||
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
|
|
||||||
===============================================================
|
|
||||||
[+] Mode : dir
|
|
||||||
[+] Url/Domain : https://buffered.io/
|
|
||||||
[+] Threads : 10
|
|
||||||
[+] Wordlist : /home/oj/wordlists/shortlist.txt
|
|
||||||
[+] Status codes : 200,204,301,302,307,401,403
|
|
||||||
[+] User Agent : gobuster/3.1.0
|
|
||||||
[+] Verbose : true
|
|
||||||
[+] Timeout : 10s
|
|
||||||
===============================================================
|
|
||||||
2019/06/21 11:50:51 Starting gobuster
|
|
||||||
===============================================================
|
|
||||||
Missed: /alsodoesnotexist (Status: 404)
|
|
||||||
Found: /index (Status: 200)
|
|
||||||
Missed: /doesnotexist (Status: 404)
|
|
||||||
Found: /categories (Status: 301)
|
|
||||||
Found: /posts (Status: 301)
|
|
||||||
Found: /contact (Status: 301)
|
|
||||||
===============================================================
|
|
||||||
2019/06/21 11:50:51 Finished
|
|
||||||
===============================================================
|
|
||||||
```
|
|
||||||
|
|
||||||
Example showing content length:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
gobuster dir -u https://buffered.io -w ~/wordlists/shortlist.txt -l
|
|
||||||
|
|
||||||
===============================================================
|
|
||||||
Gobuster v3.1.0
|
|
||||||
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
|
|
||||||
===============================================================
|
|
||||||
[+] Mode : dir
|
|
||||||
[+] Url/Domain : https://buffered.io/
|
|
||||||
[+] Threads : 10
|
|
||||||
[+] Wordlist : /home/oj/wordlists/shortlist.txt
|
|
||||||
[+] Status codes : 200,204,301,302,307,401,403
|
|
||||||
[+] User Agent : gobuster/3.1.0
|
|
||||||
[+] Show length : true
|
|
||||||
[+] Timeout : 10s
|
|
||||||
===============================================================
|
|
||||||
2019/06/21 11:51:16 Starting gobuster
|
|
||||||
===============================================================
|
|
||||||
/categories (Status: 301) [Size: 178]
|
|
||||||
/posts (Status: 301) [Size: 178]
|
|
||||||
/contact (Status: 301) [Size: 178]
|
|
||||||
/index (Status: 200) [Size: 51759]
|
|
||||||
===============================================================
|
|
||||||
2019/06/21 11:51:17 Finished
|
|
||||||
===============================================================
|
|
||||||
```
|
|
||||||
|
|
||||||
Quiet output, with status disabled and expanded mode looks like this ("grep mode"):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
gobuster dir -u https://buffered.io -w ~/wordlists/shortlist.txt -q -n -e
|
|
||||||
https://buffered.io/index
|
|
||||||
https://buffered.io/contact
|
|
||||||
https://buffered.io/posts
|
|
||||||
https://buffered.io/categories
|
|
||||||
```
|
|
||||||
|
|
||||||
### `dns` Mode
|
|
||||||
|
|
||||||
Command line might look like this:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
gobuster dns -d mysite.com -t 50 -w common-names.txt
|
gobuster dns -d mysite.com -t 50 -w common-names.txt
|
||||||
```
|
```
|
||||||
|
|
||||||
Normal sample run goes like this:
|
Normal sample run goes like this:
|
||||||
|
|
||||||
```bash
|
```text
|
||||||
gobuster dns -d google.com -w ~/wordlists/subdomains.txt
|
gobuster dns -d google.com -w ~/wordlists/subdomains.txt
|
||||||
|
|
||||||
===============================================================
|
===============================================================
|
||||||
Gobuster v3.1.0
|
Gobuster v3.2.0
|
||||||
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
|
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
|
||||||
===============================================================
|
===============================================================
|
||||||
[+] Mode : dns
|
[+] Mode : dns
|
||||||
|
@ -427,11 +185,11 @@ Found: blog.google.com
|
||||||
|
|
||||||
Show IP sample run goes like this:
|
Show IP sample run goes like this:
|
||||||
|
|
||||||
```bash
|
```text
|
||||||
gobuster dns -d google.com -w ~/wordlists/subdomains.txt -i
|
gobuster dns -d google.com -w ~/wordlists/subdomains.txt -i
|
||||||
|
|
||||||
===============================================================
|
===============================================================
|
||||||
Gobuster v3.1.0
|
Gobuster v3.2.0
|
||||||
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
|
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
|
||||||
===============================================================
|
===============================================================
|
||||||
[+] Mode : dns
|
[+] Mode : dns
|
||||||
|
@ -466,11 +224,11 @@ Found: mail.google.com [172.217.25.37, 2404:6800:4006:802::2005]
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
```bash
|
```text
|
||||||
gobuster dns -d yp.to -w ~/wordlists/subdomains.txt -i
|
gobuster dns -d yp.to -w ~/wordlists/subdomains.txt -i
|
||||||
|
|
||||||
===============================================================
|
===============================================================
|
||||||
Gobuster v3.1.0
|
Gobuster v3.2.0
|
||||||
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
|
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
|
||||||
===============================================================
|
===============================================================
|
||||||
[+] Mode : dns
|
[+] Mode : dns
|
||||||
|
@ -489,11 +247,11 @@ Found: cr.yp.to [131.193.32.108, 131.193.32.109]
|
||||||
|
|
||||||
Wildcard DNS is also detected properly:
|
Wildcard DNS is also detected properly:
|
||||||
|
|
||||||
```bash
|
```text
|
||||||
gobuster dns -d 0.0.1.xip.io -w ~/wordlists/subdomains.txt
|
gobuster dns -d 0.0.1.xip.io -w ~/wordlists/subdomains.txt
|
||||||
|
|
||||||
===============================================================
|
===============================================================
|
||||||
Gobuster v3.1.0
|
Gobuster v3.2.0
|
||||||
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
|
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
|
||||||
===============================================================
|
===============================================================
|
||||||
[+] Mode : dns
|
[+] Mode : dns
|
||||||
|
@ -512,11 +270,11 @@ by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
|
||||||
|
|
||||||
If the user wants to force processing of a domain that has wildcard entries, use `--wildcard`:
|
If the user wants to force processing of a domain that has wildcard entries, use `--wildcard`:
|
||||||
|
|
||||||
```bash
|
```text
|
||||||
gobuster dns -d 0.0.1.xip.io -w ~/wordlists/subdomains.txt --wildcard
|
gobuster dns -d 0.0.1.xip.io -w ~/wordlists/subdomains.txt --wildcard
|
||||||
|
|
||||||
===============================================================
|
===============================================================
|
||||||
Gobuster v3.1.0
|
Gobuster v3.2.0
|
||||||
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
|
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
|
||||||
===============================================================
|
===============================================================
|
||||||
[+] Mode : dns
|
[+] Mode : dns
|
||||||
|
@ -534,27 +292,251 @@ Found: test.127.0.0.1.xip.io
|
||||||
===============================================================
|
===============================================================
|
||||||
```
|
```
|
||||||
|
|
||||||
### `vhost` Mode
|
## `dir` Mode
|
||||||
|
|
||||||
Command line might look like this:
|
### Options
|
||||||
|
|
||||||
```bash
|
```text
|
||||||
|
Uses directory/file enumeration mode
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
gobuster dir [flags]
|
||||||
|
|
||||||
|
Flags:
|
||||||
|
-f, --add-slash Append / to each request
|
||||||
|
-c, --cookies string Cookies to use for the requests
|
||||||
|
-d, --discover-backup Also search for backup files by appending multiple backup extensions
|
||||||
|
--exclude-length ints exclude the following content length (completely ignores the status). Supply multiple times to exclude multiple sizes.
|
||||||
|
-e, --expanded Expanded mode, print full URLs
|
||||||
|
-x, --extensions string File extension(s) to search for
|
||||||
|
-r, --follow-redirect Follow redirects
|
||||||
|
-H, --headers stringArray Specify HTTP headers, -H 'Header1: val1' -H 'Header2: val2'
|
||||||
|
-h, --help help for dir
|
||||||
|
--hide-length Hide the length of the body in the output
|
||||||
|
-m, --method string Use the following HTTP method (default "GET")
|
||||||
|
-n, --no-status Don't print status codes
|
||||||
|
-k, --no-tls-validation Skip TLS certificate verification
|
||||||
|
-P, --password string Password for Basic Auth
|
||||||
|
--proxy string Proxy to use for requests [http(s)://host:port]
|
||||||
|
--random-agent Use a random User-Agent string
|
||||||
|
--retry Should retry on request timeout
|
||||||
|
--retry-attempts int Times to retry on request timeout (default 3)
|
||||||
|
-s, --status-codes string Positive status codes (will be overwritten with status-codes-blacklist if set)
|
||||||
|
-b, --status-codes-blacklist string Negative status codes (will override status-codes if set) (default "404")
|
||||||
|
--timeout duration HTTP Timeout (default 10s)
|
||||||
|
-u, --url string The target URL
|
||||||
|
-a, --useragent string Set the User-Agent string (default "gobuster/3.2.0")
|
||||||
|
-U, --username string Username for Basic Auth
|
||||||
|
|
||||||
|
Global Flags:
|
||||||
|
--delay duration Time each thread waits between requests (e.g. 1500ms)
|
||||||
|
--no-color Disable color output
|
||||||
|
--no-error Don't display errors
|
||||||
|
-z, --no-progress Don't display progress
|
||||||
|
-o, --output string Output file to write results to (defaults to stdout)
|
||||||
|
-p, --pattern string File containing replacement patterns
|
||||||
|
-q, --quiet Don't print the banner and other noise
|
||||||
|
-t, --threads int Number of concurrent threads (default 10)
|
||||||
|
-v, --verbose Verbose output (errors)
|
||||||
|
-w, --wordlist string Path to the wordlist
|
||||||
|
```
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
```text
|
||||||
|
gobuster dir -u https://mysite.com/path/to/folder -c 'session=123456' -t 50 -w common-files.txt -x .php,.html
|
||||||
|
```
|
||||||
|
|
||||||
|
Default options looks like this:
|
||||||
|
|
||||||
|
```text
|
||||||
|
gobuster dir -u https://buffered.io -w ~/wordlists/shortlist.txt
|
||||||
|
|
||||||
|
===============================================================
|
||||||
|
Gobuster v3.2.0
|
||||||
|
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
|
||||||
|
===============================================================
|
||||||
|
[+] Mode : dir
|
||||||
|
[+] Url/Domain : https://buffered.io/
|
||||||
|
[+] Threads : 10
|
||||||
|
[+] Wordlist : /home/oj/wordlists/shortlist.txt
|
||||||
|
[+] Status codes : 200,204,301,302,307,401,403
|
||||||
|
[+] User Agent : gobuster/3.2.0
|
||||||
|
[+] Timeout : 10s
|
||||||
|
===============================================================
|
||||||
|
2019/06/21 11:49:43 Starting gobuster
|
||||||
|
===============================================================
|
||||||
|
/categories (Status: 301)
|
||||||
|
/contact (Status: 301)
|
||||||
|
/posts (Status: 301)
|
||||||
|
/index (Status: 200)
|
||||||
|
===============================================================
|
||||||
|
2019/06/21 11:49:44 Finished
|
||||||
|
===============================================================
|
||||||
|
```
|
||||||
|
|
||||||
|
Default options with status codes disabled looks like this:
|
||||||
|
|
||||||
|
```text
|
||||||
|
gobuster dir -u https://buffered.io -w ~/wordlists/shortlist.txt -n
|
||||||
|
|
||||||
|
===============================================================
|
||||||
|
Gobuster v3.2.0
|
||||||
|
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
|
||||||
|
===============================================================
|
||||||
|
[+] Mode : dir
|
||||||
|
[+] Url/Domain : https://buffered.io/
|
||||||
|
[+] Threads : 10
|
||||||
|
[+] Wordlist : /home/oj/wordlists/shortlist.txt
|
||||||
|
[+] Status codes : 200,204,301,302,307,401,403
|
||||||
|
[+] User Agent : gobuster/3.2.0
|
||||||
|
[+] No status : true
|
||||||
|
[+] Timeout : 10s
|
||||||
|
===============================================================
|
||||||
|
2019/06/21 11:50:18 Starting gobuster
|
||||||
|
===============================================================
|
||||||
|
/categories
|
||||||
|
/contact
|
||||||
|
/index
|
||||||
|
/posts
|
||||||
|
===============================================================
|
||||||
|
2019/06/21 11:50:18 Finished
|
||||||
|
===============================================================
|
||||||
|
```
|
||||||
|
|
||||||
|
Verbose output looks like this:
|
||||||
|
|
||||||
|
```text
|
||||||
|
gobuster dir -u https://buffered.io -w ~/wordlists/shortlist.txt -v
|
||||||
|
|
||||||
|
===============================================================
|
||||||
|
Gobuster v3.2.0
|
||||||
|
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
|
||||||
|
===============================================================
|
||||||
|
[+] Mode : dir
|
||||||
|
[+] Url/Domain : https://buffered.io/
|
||||||
|
[+] Threads : 10
|
||||||
|
[+] Wordlist : /home/oj/wordlists/shortlist.txt
|
||||||
|
[+] Status codes : 200,204,301,302,307,401,403
|
||||||
|
[+] User Agent : gobuster/3.2.0
|
||||||
|
[+] Verbose : true
|
||||||
|
[+] Timeout : 10s
|
||||||
|
===============================================================
|
||||||
|
2019/06/21 11:50:51 Starting gobuster
|
||||||
|
===============================================================
|
||||||
|
Missed: /alsodoesnotexist (Status: 404)
|
||||||
|
Found: /index (Status: 200)
|
||||||
|
Missed: /doesnotexist (Status: 404)
|
||||||
|
Found: /categories (Status: 301)
|
||||||
|
Found: /posts (Status: 301)
|
||||||
|
Found: /contact (Status: 301)
|
||||||
|
===============================================================
|
||||||
|
2019/06/21 11:50:51 Finished
|
||||||
|
===============================================================
|
||||||
|
```
|
||||||
|
|
||||||
|
Example showing content length:
|
||||||
|
|
||||||
|
```text
|
||||||
|
gobuster dir -u https://buffered.io -w ~/wordlists/shortlist.txt -l
|
||||||
|
|
||||||
|
===============================================================
|
||||||
|
Gobuster v3.2.0
|
||||||
|
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
|
||||||
|
===============================================================
|
||||||
|
[+] Mode : dir
|
||||||
|
[+] Url/Domain : https://buffered.io/
|
||||||
|
[+] Threads : 10
|
||||||
|
[+] Wordlist : /home/oj/wordlists/shortlist.txt
|
||||||
|
[+] Status codes : 200,204,301,302,307,401,403
|
||||||
|
[+] User Agent : gobuster/3.2.0
|
||||||
|
[+] Show length : true
|
||||||
|
[+] Timeout : 10s
|
||||||
|
===============================================================
|
||||||
|
2019/06/21 11:51:16 Starting gobuster
|
||||||
|
===============================================================
|
||||||
|
/categories (Status: 301) [Size: 178]
|
||||||
|
/posts (Status: 301) [Size: 178]
|
||||||
|
/contact (Status: 301) [Size: 178]
|
||||||
|
/index (Status: 200) [Size: 51759]
|
||||||
|
===============================================================
|
||||||
|
2019/06/21 11:51:17 Finished
|
||||||
|
===============================================================
|
||||||
|
```
|
||||||
|
|
||||||
|
Quiet output, with status disabled and expanded mode looks like this ("grep mode"):
|
||||||
|
|
||||||
|
```text
|
||||||
|
gobuster dir -u https://buffered.io -w ~/wordlists/shortlist.txt -q -n -e
|
||||||
|
https://buffered.io/index
|
||||||
|
https://buffered.io/contact
|
||||||
|
https://buffered.io/posts
|
||||||
|
https://buffered.io/categories
|
||||||
|
```
|
||||||
|
|
||||||
|
## `vhost` Mode
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
```text
|
||||||
|
Uses VHOST enumeration mode (you most probably want to use the IP adress as the URL parameter
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
gobuster vhost [flags]
|
||||||
|
|
||||||
|
Flags:
|
||||||
|
--append-domain Append main domain from URL to words from wordlist. Otherwise the fully qualified domains need to be specified in the wordlist.
|
||||||
|
-c, --cookies string Cookies to use for the requests
|
||||||
|
--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
|
||||||
|
--exclude-length ints exclude the following content length (completely ignores the status). Supply multiple times to exclude multiple sizes.
|
||||||
|
-r, --follow-redirect Follow redirects
|
||||||
|
-H, --headers stringArray Specify HTTP headers, -H 'Header1: val1' -H 'Header2: val2'
|
||||||
|
-h, --help help for vhost
|
||||||
|
-m, --method string Use the following HTTP method (default "GET")
|
||||||
|
-k, --no-tls-validation Skip TLS certificate verification
|
||||||
|
-P, --password string Password for Basic Auth
|
||||||
|
--proxy string Proxy to use for requests [http(s)://host:port]
|
||||||
|
--random-agent Use a random User-Agent string
|
||||||
|
--retry Should retry on request timeout
|
||||||
|
--retry-attempts int Times to retry on request timeout (default 3)
|
||||||
|
--timeout duration HTTP Timeout (default 10s)
|
||||||
|
-u, --url string The target URL
|
||||||
|
-a, --useragent string Set the User-Agent string (default "gobuster/3.2.0")
|
||||||
|
-U, --username string Username for Basic Auth
|
||||||
|
|
||||||
|
Global Flags:
|
||||||
|
--delay duration Time each thread waits between requests (e.g. 1500ms)
|
||||||
|
--no-color Disable color output
|
||||||
|
--no-error Don't display errors
|
||||||
|
-z, --no-progress Don't display progress
|
||||||
|
-o, --output string Output file to write results to (defaults to stdout)
|
||||||
|
-p, --pattern string File containing replacement patterns
|
||||||
|
-q, --quiet Don't print the banner and other noise
|
||||||
|
-t, --threads int Number of concurrent threads (default 10)
|
||||||
|
-v, --verbose Verbose output (errors)
|
||||||
|
-w, --wordlist string Path to the wordlist
|
||||||
|
```
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
|
||||||
|
```text
|
||||||
gobuster vhost -u https://mysite.com -w common-vhosts.txt
|
gobuster vhost -u https://mysite.com -w common-vhosts.txt
|
||||||
```
|
```
|
||||||
|
|
||||||
Normal sample run goes like this:
|
Normal sample run goes like this:
|
||||||
|
|
||||||
```bash
|
```text
|
||||||
gobuster vhost -u https://mysite.com -w common-vhosts.txt
|
gobuster vhost -u https://mysite.com -w common-vhosts.txt
|
||||||
|
|
||||||
===============================================================
|
===============================================================
|
||||||
Gobuster v3.1.0
|
Gobuster v3.2.0
|
||||||
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
|
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
|
||||||
===============================================================
|
===============================================================
|
||||||
[+] Url: https://mysite.com
|
[+] Url: https://mysite.com
|
||||||
[+] Threads: 10
|
[+] Threads: 10
|
||||||
[+] Wordlist: common-vhosts.txt
|
[+] Wordlist: common-vhosts.txt
|
||||||
[+] User Agent: gobuster/3.1.0
|
[+] User Agent: gobuster/3.2.0
|
||||||
[+] Timeout: 10s
|
[+] Timeout: 10s
|
||||||
===============================================================
|
===============================================================
|
||||||
2019/06/21 08:36:00 Starting gobuster
|
2019/06/21 08:36:00 Starting gobuster
|
||||||
|
@ -567,20 +549,160 @@ Found: mail.mysite.com
|
||||||
===============================================================
|
===============================================================
|
||||||
```
|
```
|
||||||
|
|
||||||
### `s3` Mode
|
## `fuzz` Mode
|
||||||
|
|
||||||
Command line might look like this:
|
### Options
|
||||||
|
|
||||||
```bash
|
```text
|
||||||
|
Uses fuzzing mode
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
gobuster fuzz [flags]
|
||||||
|
|
||||||
|
Flags:
|
||||||
|
-c, --cookies string Cookies to use for the requests
|
||||||
|
--exclude-length ints exclude the following content length (completely ignores the status). Supply multiple times to exclude multiple sizes.
|
||||||
|
-b, --excludestatuscodes string Negative status codes (will override statuscodes if set)
|
||||||
|
-r, --follow-redirect Follow redirects
|
||||||
|
-H, --headers stringArray Specify HTTP headers, -H 'Header1: val1' -H 'Header2: val2'
|
||||||
|
-h, --help help for fuzz
|
||||||
|
-m, --method string Use the following HTTP method (default "GET")
|
||||||
|
-k, --no-tls-validation Skip TLS certificate verification
|
||||||
|
-P, --password string Password for Basic Auth
|
||||||
|
--proxy string Proxy to use for requests [http(s)://host:port]
|
||||||
|
--random-agent Use a random User-Agent string
|
||||||
|
--retry Should retry on request timeout
|
||||||
|
--retry-attempts int Times to retry on request timeout (default 3)
|
||||||
|
--timeout duration HTTP Timeout (default 10s)
|
||||||
|
-u, --url string The target URL
|
||||||
|
-a, --useragent string Set the User-Agent string (default "gobuster/3.2.0")
|
||||||
|
-U, --username string Username for Basic Auth
|
||||||
|
|
||||||
|
Global Flags:
|
||||||
|
--delay duration Time each thread waits between requests (e.g. 1500ms)
|
||||||
|
--no-color Disable color output
|
||||||
|
--no-error Don't display errors
|
||||||
|
-z, --no-progress Don't display progress
|
||||||
|
-o, --output string Output file to write results to (defaults to stdout)
|
||||||
|
-p, --pattern string File containing replacement patterns
|
||||||
|
-q, --quiet Don't print the banner and other noise
|
||||||
|
-t, --threads int Number of concurrent threads (default 10)
|
||||||
|
-v, --verbose Verbose output (errors)
|
||||||
|
-w, --wordlist string Path to the wordlist
|
||||||
|
```
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
```text
|
||||||
|
gobuster fuzz -u https://example.com?FUZZ=test -w parameter-names.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
## `s3` Mode
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
```text
|
||||||
|
Uses aws bucket enumeration mode
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
gobuster s3 [flags]
|
||||||
|
|
||||||
|
Flags:
|
||||||
|
-h, --help help for s3
|
||||||
|
-m, --maxfiles int max files to list when listing buckets (only shown in verbose mode) (default 5)
|
||||||
|
-k, --no-tls-validation Skip TLS certificate verification
|
||||||
|
--proxy string Proxy to use for requests [http(s)://host:port]
|
||||||
|
--random-agent Use a random User-Agent string
|
||||||
|
--retry Should retry on request timeout
|
||||||
|
--retry-attempts int Times to retry on request timeout (default 3)
|
||||||
|
--timeout duration HTTP Timeout (default 10s)
|
||||||
|
-a, --useragent string Set the User-Agent string (default "gobuster/3.2.0")
|
||||||
|
|
||||||
|
Global Flags:
|
||||||
|
--delay duration Time each thread waits between requests (e.g. 1500ms)
|
||||||
|
--no-color Disable color output
|
||||||
|
--no-error Don't display errors
|
||||||
|
-z, --no-progress Don't display progress
|
||||||
|
-o, --output string Output file to write results to (defaults to stdout)
|
||||||
|
-p, --pattern string File containing replacement patterns
|
||||||
|
-q, --quiet Don't print the banner and other noise
|
||||||
|
-t, --threads int Number of concurrent threads (default 10)
|
||||||
|
-v, --verbose Verbose output (errors)
|
||||||
|
-w, --wordlist string Path to the wordlist
|
||||||
|
```
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
```text
|
||||||
gobuster s3 -w bucket-names.txt
|
gobuster s3 -w bucket-names.txt
|
||||||
```
|
```
|
||||||
|
|
||||||
### `fuzzing` Mode
|
## `gcs` Mode
|
||||||
|
|
||||||
Command line might look like this:
|
### Options
|
||||||
|
|
||||||
|
```text
|
||||||
|
Uses gcs bucket enumeration mode
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
gobuster gcs [flags]
|
||||||
|
|
||||||
|
Flags:
|
||||||
|
-h, --help help for gcs
|
||||||
|
-m, --maxfiles int max files to list when listing buckets (only shown in verbose mode) (default 5)
|
||||||
|
-k, --no-tls-validation Skip TLS certificate verification
|
||||||
|
--proxy string Proxy to use for requests [http(s)://host:port]
|
||||||
|
--random-agent Use a random User-Agent string
|
||||||
|
--retry Should retry on request timeout
|
||||||
|
--retry-attempts int Times to retry on request timeout (default 3)
|
||||||
|
--timeout duration HTTP Timeout (default 10s)
|
||||||
|
-a, --useragent string Set the User-Agent string (default "gobuster/3.2.0")
|
||||||
|
|
||||||
|
Global Flags:
|
||||||
|
--delay duration Time each thread waits between requests (e.g. 1500ms)
|
||||||
|
--no-color Disable color output
|
||||||
|
--no-error Don't display errors
|
||||||
|
-z, --no-progress Don't display progress
|
||||||
|
-o, --output string Output file to write results to (defaults to stdout)
|
||||||
|
-p, --pattern string File containing replacement patterns
|
||||||
|
-q, --quiet Don't print the banner and other noise
|
||||||
|
-t, --threads int Number of concurrent threads (default 10)
|
||||||
|
-v, --verbose Verbose output (errors)
|
||||||
|
-w, --wordlist string Path to the wordlist
|
||||||
|
```
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
```text
|
||||||
|
gobuster gcs -w bucket-names.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
## Wordlists via STDIN
|
||||||
|
|
||||||
|
Wordlists can be piped into `gobuster` via stdin by providing a `-` to the `-w` option:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
gobuster fuzz -u https://example.com?FUZZ=test -w parameter-names.txt
|
hashcat -a 3 --stdout ?l | gobuster dir -u https://mysite.com -w -
|
||||||
|
```
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
## Patterns
|
||||||
|
|
||||||
|
You can supply pattern files that will be applied to every word from the wordlist.
|
||||||
|
Just place the string `{GOBUSTER}` in it and this will be replaced with the word.
|
||||||
|
This feature is also handy in s3 mode to pre- or postfix certain patterns.
|
||||||
|
|
||||||
|
**Caution:** Using a big pattern file can cause a lot of request as every pattern is applied to every word in the wordlist.
|
||||||
|
|
||||||
|
### Example file
|
||||||
|
|
||||||
|
```text
|
||||||
|
{GOBUSTER}Partial
|
||||||
|
{GOBUSTER}Service
|
||||||
|
PRE{GOBUSTER}POST
|
||||||
|
{GOBUSTER}-prod
|
||||||
|
{GOBUSTER}-dev
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Use case in combination with patterns
|
#### Use case in combination with patterns
|
||||||
|
@ -594,7 +716,7 @@ curl -s --output - https://raw.githubusercontent.com/eth0izzle/bucket-stream/mas
|
||||||
|
|
||||||
- Run gobuster with the custom input. Be sure to turn verbose mode on to see the bucket details
|
- Run gobuster with the custom input. Be sure to turn verbose mode on to see the bucket details
|
||||||
|
|
||||||
```bash
|
```text
|
||||||
gobuster s3 --wordlist my.custom.wordlist -p patterns.txt -v
|
gobuster s3 --wordlist my.custom.wordlist -p patterns.txt -v
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -603,12 +725,12 @@ Normal sample run goes like this:
|
||||||
```text
|
```text
|
||||||
PS C:\Users\firefart\Documents\code\gobuster> .\gobuster.exe s3 --wordlist .\wordlist.txt
|
PS C:\Users\firefart\Documents\code\gobuster> .\gobuster.exe s3 --wordlist .\wordlist.txt
|
||||||
===============================================================
|
===============================================================
|
||||||
Gobuster v3.1.0
|
Gobuster v3.2.0
|
||||||
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
|
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
|
||||||
===============================================================
|
===============================================================
|
||||||
[+] Threads: 10
|
[+] Threads: 10
|
||||||
[+] Wordlist: .\wordlist.txt
|
[+] Wordlist: .\wordlist.txt
|
||||||
[+] User Agent: gobuster/3.1.0
|
[+] User Agent: gobuster/3.2.0
|
||||||
[+] Timeout: 10s
|
[+] Timeout: 10s
|
||||||
[+] Maximum files to list: 5
|
[+] Maximum files to list: 5
|
||||||
===============================================================
|
===============================================================
|
||||||
|
@ -632,12 +754,12 @@ Verbose and sample run
|
||||||
```text
|
```text
|
||||||
PS C:\Users\firefart\Documents\code\gobuster> .\gobuster.exe s3 --wordlist .\wordlist.txt -v
|
PS C:\Users\firefart\Documents\code\gobuster> .\gobuster.exe s3 --wordlist .\wordlist.txt -v
|
||||||
===============================================================
|
===============================================================
|
||||||
Gobuster v3.1.0
|
Gobuster v3.2.0
|
||||||
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
|
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
|
||||||
===============================================================
|
===============================================================
|
||||||
[+] Threads: 10
|
[+] Threads: 10
|
||||||
[+] Wordlist: .\wordlist.txt
|
[+] Wordlist: .\wordlist.txt
|
||||||
[+] User Agent: gobuster/3.1.0
|
[+] User Agent: gobuster/3.2.0
|
||||||
[+] Verbose: true
|
[+] Verbose: true
|
||||||
[+] Timeout: 10s
|
[+] Timeout: 10s
|
||||||
[+] Maximum files to list: 5
|
[+] Maximum files to list: 5
|
||||||
|
@ -662,12 +784,12 @@ Extended sample run
|
||||||
```text
|
```text
|
||||||
PS C:\Users\firefart\Documents\code\gobuster> .\gobuster.exe s3 --wordlist .\wordlist.txt -e
|
PS C:\Users\firefart\Documents\code\gobuster> .\gobuster.exe s3 --wordlist .\wordlist.txt -e
|
||||||
===============================================================
|
===============================================================
|
||||||
Gobuster v3.1.0
|
Gobuster v3.2.0
|
||||||
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
|
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
|
||||||
===============================================================
|
===============================================================
|
||||||
[+] Threads: 10
|
[+] Threads: 10
|
||||||
[+] Wordlist: .\wordlist.txt
|
[+] Wordlist: .\wordlist.txt
|
||||||
[+] User Agent: gobuster/3.1.0
|
[+] User Agent: gobuster/3.2.0
|
||||||
[+] Timeout: 10s
|
[+] Timeout: 10s
|
||||||
[+] Expanded: true
|
[+] Expanded: true
|
||||||
[+] Maximum files to list: 5
|
[+] Maximum files to list: 5
|
||||||
|
@ -686,11 +808,3 @@ http://localhost.s3.amazonaws.com/
|
||||||
2019/08/12 21:48:38 Finished
|
2019/08/12 21:48:38 Finished
|
||||||
===============================================================
|
===============================================================
|
||||||
```
|
```
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
See the LICENSE file.
|
|
||||||
|
|
||||||
## Thanks
|
|
||||||
|
|
||||||
See the THANKS file for people who helped out.
|
|
||||||
|
|
|
@ -59,6 +59,8 @@ func parseDirOptions() (*libgobuster.Options, *gobusterdir.OptionsDir, error) {
|
||||||
plugin.NoTLSValidation = httpOpts.NoTLSValidation
|
plugin.NoTLSValidation = httpOpts.NoTLSValidation
|
||||||
plugin.Headers = httpOpts.Headers
|
plugin.Headers = httpOpts.Headers
|
||||||
plugin.Method = httpOpts.Method
|
plugin.Method = httpOpts.Method
|
||||||
|
plugin.RetryOnTimeout = httpOpts.RetryOnTimeout
|
||||||
|
plugin.RetryAttempts = httpOpts.RetryAttempts
|
||||||
|
|
||||||
plugin.Extensions, err = cmdDir.Flags().GetString("extensions")
|
plugin.Extensions, err = cmdDir.Flags().GetString("extensions")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -93,7 +95,8 @@ func parseDirOptions() (*libgobuster.Options, *gobusterdir.OptionsDir, error) {
|
||||||
plugin.StatusCodesBlacklistParsed = ret3
|
plugin.StatusCodesBlacklistParsed = ret3
|
||||||
|
|
||||||
if plugin.StatusCodes != "" && plugin.StatusCodesBlacklist != "" {
|
if plugin.StatusCodes != "" && plugin.StatusCodesBlacklist != "" {
|
||||||
return nil, nil, fmt.Errorf("status-codes and status-codes-blacklist are both set, please set only one")
|
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.",
|
||||||
|
plugin.StatusCodes, plugin.StatusCodesBlacklist)
|
||||||
}
|
}
|
||||||
|
|
||||||
if plugin.StatusCodes == "" && plugin.StatusCodesBlacklist == "" {
|
if plugin.StatusCodes == "" && plugin.StatusCodesBlacklist == "" {
|
||||||
|
@ -151,7 +154,7 @@ func init() {
|
||||||
cmdDir.Flags().BoolP("no-status", "n", false, "Don't print status codes")
|
cmdDir.Flags().BoolP("no-status", "n", false, "Don't print status codes")
|
||||||
cmdDir.Flags().Bool("hide-length", false, "Hide the length of the body in the output")
|
cmdDir.Flags().Bool("hide-length", false, "Hide the length of the body in the output")
|
||||||
cmdDir.Flags().BoolP("add-slash", "f", false, "Append / to each request")
|
cmdDir.Flags().BoolP("add-slash", "f", false, "Append / to each request")
|
||||||
cmdDir.Flags().BoolP("discover-backup", "d", false, "Upon finding a file search for backup files")
|
cmdDir.Flags().BoolP("discover-backup", "d", false, "Also search for backup files by appending multiple backup extensions")
|
||||||
cmdDir.Flags().IntSlice("exclude-length", []int{}, "exclude the following content length (completely ignores the status). Supply multiple times to exclude multiple sizes.")
|
cmdDir.Flags().IntSlice("exclude-length", []int{}, "exclude the following content length (completely ignores the status). Supply multiple times to exclude multiple sizes.")
|
||||||
|
|
||||||
cmdDir.PersistentPreRun = func(cmd *cobra.Command, args []string) {
|
cmdDir.PersistentPreRun = func(cmd *cobra.Command, args []string) {
|
||||||
|
|
|
@ -59,6 +59,8 @@ func parseFuzzOptions() (*libgobuster.Options, *gobusterfuzz.OptionsFuzz, error)
|
||||||
plugin.NoTLSValidation = httpOpts.NoTLSValidation
|
plugin.NoTLSValidation = httpOpts.NoTLSValidation
|
||||||
plugin.Headers = httpOpts.Headers
|
plugin.Headers = httpOpts.Headers
|
||||||
plugin.Method = httpOpts.Method
|
plugin.Method = httpOpts.Method
|
||||||
|
plugin.RetryOnTimeout = httpOpts.RetryOnTimeout
|
||||||
|
plugin.RetryAttempts = httpOpts.RetryAttempts
|
||||||
|
|
||||||
// blacklist will override the normal status codes
|
// blacklist will override the normal status codes
|
||||||
plugin.ExcludedStatusCodes, err = cmdFuzz.Flags().GetString("excludestatuscodes")
|
plugin.ExcludedStatusCodes, err = cmdFuzz.Flags().GetString("excludestatuscodes")
|
||||||
|
|
|
@ -0,0 +1,76 @@
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/OJ/gobuster/v3/cli"
|
||||||
|
"github.com/OJ/gobuster/v3/gobustergcs"
|
||||||
|
"github.com/OJ/gobuster/v3/libgobuster"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
// nolint:gochecknoglobals
|
||||||
|
var cmdGCS *cobra.Command
|
||||||
|
|
||||||
|
func runGCS(cmd *cobra.Command, args []string) error {
|
||||||
|
globalopts, pluginopts, err := parseGCSOptions()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error on parsing arguments: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
plugin, err := gobustergcs.NewGobusterGCS(globalopts, pluginopts)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error on creating gobustergcs: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := cli.Gobuster(mainContext, globalopts, plugin); err != nil {
|
||||||
|
return fmt.Errorf("error on running gobuster: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseGCSOptions() (*libgobuster.Options, *gobustergcs.OptionsGCS, error) {
|
||||||
|
globalopts, err := parseGlobalOptions()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pluginopts := gobustergcs.NewOptionsGCS()
|
||||||
|
|
||||||
|
httpOpts, err := parseBasicHTTPOptions(cmdGCS)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pluginopts.UserAgent = httpOpts.UserAgent
|
||||||
|
pluginopts.Proxy = httpOpts.Proxy
|
||||||
|
pluginopts.Timeout = httpOpts.Timeout
|
||||||
|
pluginopts.NoTLSValidation = httpOpts.NoTLSValidation
|
||||||
|
pluginopts.RetryOnTimeout = httpOpts.RetryOnTimeout
|
||||||
|
pluginopts.RetryAttempts = httpOpts.RetryAttempts
|
||||||
|
|
||||||
|
pluginopts.MaxFilesToList, err = cmdGCS.Flags().GetInt("maxfiles")
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("invalid value for maxfiles: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return globalopts, pluginopts, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// nolint:gochecknoinits
|
||||||
|
func init() {
|
||||||
|
cmdGCS = &cobra.Command{
|
||||||
|
Use: "gcs",
|
||||||
|
Short: "Uses gcs bucket enumeration mode",
|
||||||
|
RunE: runGCS,
|
||||||
|
}
|
||||||
|
|
||||||
|
addBasicHTTPOptions(cmdGCS)
|
||||||
|
cmdGCS.Flags().IntP("maxfiles", "m", 5, "max files to list when listing buckets (only shown in verbose mode)")
|
||||||
|
|
||||||
|
cmdGCS.PersistentPreRun = func(cmd *cobra.Command, args []string) {
|
||||||
|
configureGlobalOptions()
|
||||||
|
}
|
||||||
|
|
||||||
|
rootCmd.AddCommand(cmdGCS)
|
||||||
|
}
|
|
@ -20,6 +20,8 @@ func addBasicHTTPOptions(cmd *cobra.Command) {
|
||||||
cmd.Flags().StringP("proxy", "", "", "Proxy to use for requests [http(s)://host:port]")
|
cmd.Flags().StringP("proxy", "", "", "Proxy to use for requests [http(s)://host:port]")
|
||||||
cmd.Flags().DurationP("timeout", "", 10*time.Second, "HTTP Timeout")
|
cmd.Flags().DurationP("timeout", "", 10*time.Second, "HTTP Timeout")
|
||||||
cmd.Flags().BoolP("no-tls-validation", "k", false, "Skip TLS certificate verification")
|
cmd.Flags().BoolP("no-tls-validation", "k", false, "Skip TLS certificate verification")
|
||||||
|
cmd.Flags().BoolP("retry", "", false, "Should retry on request timeout")
|
||||||
|
cmd.Flags().IntP("retry-attempts", "", 3, "Times to retry on request timeout")
|
||||||
}
|
}
|
||||||
|
|
||||||
func addCommonHTTPOptions(cmd *cobra.Command) error {
|
func addCommonHTTPOptions(cmd *cobra.Command) error {
|
||||||
|
@ -69,6 +71,16 @@ func parseBasicHTTPOptions(cmd *cobra.Command) (libgobuster.BasicHTTPOptions, er
|
||||||
return options, fmt.Errorf("invalid value for timeout: %w", err)
|
return options, fmt.Errorf("invalid value for timeout: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
options.RetryOnTimeout, err = cmd.Flags().GetBool("retry")
|
||||||
|
if err != nil {
|
||||||
|
return options, fmt.Errorf("invalid value for retry: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
options.RetryAttempts, err = cmd.Flags().GetInt("retry-attempts")
|
||||||
|
if err != nil {
|
||||||
|
return options, fmt.Errorf("invalid value for retry-attempts: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
options.NoTLSValidation, err = cmd.Flags().GetBool("no-tls-validation")
|
options.NoTLSValidation, err = cmd.Flags().GetBool("no-tls-validation")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return options, fmt.Errorf("invalid value for no-tls-validation: %w", err)
|
return options, fmt.Errorf("invalid value for no-tls-validation: %w", err)
|
||||||
|
@ -88,6 +100,8 @@ func parseCommonHTTPOptions(cmd *cobra.Command) (libgobuster.HTTPOptions, error)
|
||||||
options.Timeout = basic.Timeout
|
options.Timeout = basic.Timeout
|
||||||
options.UserAgent = basic.UserAgent
|
options.UserAgent = basic.UserAgent
|
||||||
options.NoTLSValidation = basic.NoTLSValidation
|
options.NoTLSValidation = basic.NoTLSValidation
|
||||||
|
options.RetryOnTimeout = basic.RetryOnTimeout
|
||||||
|
options.RetryAttempts = basic.RetryAttempts
|
||||||
|
|
||||||
options.URL, err = cmd.Flags().GetString("url")
|
options.URL, err = cmd.Flags().GetString("url")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
|
||||||
"github.com/OJ/gobuster/v3/libgobuster"
|
"github.com/OJ/gobuster/v3/libgobuster"
|
||||||
|
"github.com/fatih/color"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -136,6 +137,14 @@ func parseGlobalOptions() (*libgobuster.Options, error) {
|
||||||
return nil, fmt.Errorf("invalid value for no-error: %w", err)
|
return nil, fmt.Errorf("invalid value for no-error: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
noColor, err := rootCmd.Flags().GetBool("no-color")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid value for no-color: %w", err)
|
||||||
|
}
|
||||||
|
if noColor {
|
||||||
|
color.NoColor = true
|
||||||
|
}
|
||||||
|
|
||||||
return globalopts, nil
|
return globalopts, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -160,4 +169,5 @@ func init() {
|
||||||
rootCmd.PersistentFlags().BoolP("no-progress", "z", false, "Don't display progress")
|
rootCmd.PersistentFlags().BoolP("no-progress", "z", false, "Don't display progress")
|
||||||
rootCmd.PersistentFlags().Bool("no-error", false, "Don't display errors")
|
rootCmd.PersistentFlags().Bool("no-error", false, "Don't display errors")
|
||||||
rootCmd.PersistentFlags().StringP("pattern", "p", "", "File containing replacement patterns")
|
rootCmd.PersistentFlags().StringP("pattern", "p", "", "File containing replacement patterns")
|
||||||
|
rootCmd.PersistentFlags().Bool("no-color", false, "Disable color output")
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,6 +46,8 @@ func parseS3Options() (*libgobuster.Options, *gobusters3.OptionsS3, error) {
|
||||||
plugin.Proxy = httpOpts.Proxy
|
plugin.Proxy = httpOpts.Proxy
|
||||||
plugin.Timeout = httpOpts.Timeout
|
plugin.Timeout = httpOpts.Timeout
|
||||||
plugin.NoTLSValidation = httpOpts.NoTLSValidation
|
plugin.NoTLSValidation = httpOpts.NoTLSValidation
|
||||||
|
plugin.RetryOnTimeout = httpOpts.RetryOnTimeout
|
||||||
|
plugin.RetryAttempts = httpOpts.RetryAttempts
|
||||||
|
|
||||||
plugin.MaxFilesToList, err = cmdS3.Flags().GetInt("maxfiles")
|
plugin.MaxFilesToList, err = cmdS3.Flags().GetInt("maxfiles")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -52,6 +52,8 @@ func parseVhostOptions() (*libgobuster.Options, *gobustervhost.OptionsVhost, err
|
||||||
plugin.NoTLSValidation = httpOpts.NoTLSValidation
|
plugin.NoTLSValidation = httpOpts.NoTLSValidation
|
||||||
plugin.Headers = httpOpts.Headers
|
plugin.Headers = httpOpts.Headers
|
||||||
plugin.Method = httpOpts.Method
|
plugin.Method = httpOpts.Method
|
||||||
|
plugin.RetryOnTimeout = httpOpts.RetryOnTimeout
|
||||||
|
plugin.RetryAttempts = httpOpts.RetryAttempts
|
||||||
|
|
||||||
plugin.AppendDomain, err = cmdVhost.Flags().GetBool("append-domain")
|
plugin.AppendDomain, err = cmdVhost.Flags().GetBool("append-domain")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
//go:build !windows
|
||||||
|
|
||||||
|
package cli
|
||||||
|
|
||||||
|
const (
|
||||||
|
TERMINAL_CLEAR_LINE = "\r\x1b[2K"
|
||||||
|
)
|
|
@ -0,0 +1,7 @@
|
||||||
|
//go:build windows
|
||||||
|
|
||||||
|
package cli
|
||||||
|
|
||||||
|
const (
|
||||||
|
TERMINAL_CLEAR_LINE = "\r\r"
|
||||||
|
)
|
|
@ -19,27 +19,9 @@ func banner() {
|
||||||
fmt.Println("by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)")
|
fmt.Println("by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)")
|
||||||
}
|
}
|
||||||
|
|
||||||
type outputType struct {
|
|
||||||
Mu *sync.RWMutex
|
|
||||||
MaxCharsWritten int
|
|
||||||
}
|
|
||||||
|
|
||||||
// right pad a string
|
|
||||||
// nolint:unparam
|
|
||||||
func rightPad(s string, padStr string, overallLen int) string {
|
|
||||||
strLen := len(s)
|
|
||||||
if overallLen <= strLen {
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
toPad := overallLen - strLen - 1
|
|
||||||
pad := strings.Repeat(padStr, toPad)
|
|
||||||
return fmt.Sprintf("%s%s", s, pad)
|
|
||||||
}
|
|
||||||
|
|
||||||
// resultWorker outputs the results as they come in. This needs to be a range and should not handle
|
// resultWorker outputs the results as they come in. This needs to be a range and should not handle
|
||||||
// the context so the channel always has a receiver and libgobuster will not block.
|
// the context so the channel always has a receiver and libgobuster will not block.
|
||||||
func resultWorker(g *libgobuster.Gobuster, filename string, wg *sync.WaitGroup, output *outputType) {
|
func resultWorker(g *libgobuster.Gobuster, filename string, wg *sync.WaitGroup) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
|
|
||||||
var f *os.File
|
var f *os.File
|
||||||
|
@ -52,20 +34,14 @@ func resultWorker(g *libgobuster.Gobuster, filename string, wg *sync.WaitGroup,
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
for r := range g.Results() {
|
for r := range g.Progress.ResultChan {
|
||||||
s, err := r.ResultToString()
|
s, err := r.ResultToString()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
g.LogError.Fatal(err)
|
g.LogError.Fatal(err)
|
||||||
}
|
}
|
||||||
if s != "" {
|
if s != "" {
|
||||||
s = strings.TrimSpace(s)
|
s = strings.TrimSpace(s)
|
||||||
output.Mu.Lock()
|
_, _ = fmt.Printf("%s%s\n", TERMINAL_CLEAR_LINE, s)
|
||||||
w, _ := fmt.Printf("\r%s\n", rightPad(s, " ", output.MaxCharsWritten))
|
|
||||||
// -1 to remove the newline, otherwise it's always bigger
|
|
||||||
if (w - 1) > output.MaxCharsWritten {
|
|
||||||
output.MaxCharsWritten = w - 1
|
|
||||||
}
|
|
||||||
output.Mu.Unlock()
|
|
||||||
if f != nil {
|
if f != nil {
|
||||||
err = writeToFile(f, s)
|
err = writeToFile(f, s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -78,21 +54,19 @@ func resultWorker(g *libgobuster.Gobuster, filename string, wg *sync.WaitGroup,
|
||||||
|
|
||||||
// errorWorker outputs the errors as they come in. This needs to be a range and should not handle
|
// errorWorker outputs the errors as they come in. This needs to be a range and should not handle
|
||||||
// the context so the channel always has a receiver and libgobuster will not block.
|
// the context so the channel always has a receiver and libgobuster will not block.
|
||||||
func errorWorker(g *libgobuster.Gobuster, wg *sync.WaitGroup, output *outputType) {
|
func errorWorker(g *libgobuster.Gobuster, wg *sync.WaitGroup) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
|
|
||||||
for e := range g.Errors() {
|
for e := range g.Progress.ErrorChan {
|
||||||
if !g.Opts.Quiet && !g.Opts.NoError {
|
if !g.Opts.Quiet && !g.Opts.NoError {
|
||||||
output.Mu.Lock()
|
g.LogError.Printf("[!] %s\n", e.Error())
|
||||||
g.LogError.Printf("[!] %v", e)
|
|
||||||
output.Mu.Unlock()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// progressWorker outputs the progress every tick. It will stop once cancel() is called
|
// progressWorker outputs the progress every tick. It will stop once cancel() is called
|
||||||
// on the context
|
// on the context
|
||||||
func progressWorker(ctx context.Context, g *libgobuster.Gobuster, wg *sync.WaitGroup, output *outputType) {
|
func progressWorker(ctx context.Context, g *libgobuster.Gobuster, wg *sync.WaitGroup) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
|
|
||||||
tick := time.NewTicker(cliProgressUpdate)
|
tick := time.NewTicker(cliProgressUpdate)
|
||||||
|
@ -101,25 +75,16 @@ func progressWorker(ctx context.Context, g *libgobuster.Gobuster, wg *sync.WaitG
|
||||||
select {
|
select {
|
||||||
case <-tick.C:
|
case <-tick.C:
|
||||||
if !g.Opts.Quiet && !g.Opts.NoProgress {
|
if !g.Opts.Quiet && !g.Opts.NoProgress {
|
||||||
g.RequestsCountMutex.RLock()
|
requestsIssued := g.Progress.RequestsIssued()
|
||||||
output.Mu.Lock()
|
requestsExpected := g.Progress.RequestsExpected()
|
||||||
var charsWritten int
|
|
||||||
if g.Opts.Wordlist == "-" {
|
if g.Opts.Wordlist == "-" {
|
||||||
s := fmt.Sprintf("\rProgress: %d", g.RequestsIssued)
|
s := fmt.Sprintf("%sProgress: %d", TERMINAL_CLEAR_LINE, requestsIssued)
|
||||||
s = rightPad(s, " ", output.MaxCharsWritten)
|
_, _ = fmt.Fprint(os.Stderr, s)
|
||||||
charsWritten, _ = fmt.Fprint(os.Stderr, s)
|
|
||||||
// only print status if we already read in the wordlist
|
// only print status if we already read in the wordlist
|
||||||
} else if g.RequestsExpected > 0 {
|
} else if requestsExpected > 0 {
|
||||||
s := fmt.Sprintf("\rProgress: %d / %d (%3.2f%%)", g.RequestsIssued, g.RequestsExpected, float32(g.RequestsIssued)*100.0/float32(g.RequestsExpected))
|
s := fmt.Sprintf("%sProgress: %d / %d (%3.2f%%)", TERMINAL_CLEAR_LINE, requestsIssued, requestsExpected, float32(requestsIssued)*100.0/float32(requestsExpected))
|
||||||
s = rightPad(s, " ", output.MaxCharsWritten)
|
_, _ = fmt.Fprint(os.Stderr, s)
|
||||||
charsWritten, _ = fmt.Fprint(os.Stderr, s)
|
|
||||||
}
|
}
|
||||||
if charsWritten > output.MaxCharsWritten {
|
|
||||||
output.MaxCharsWritten = charsWritten
|
|
||||||
}
|
|
||||||
|
|
||||||
output.Mu.Unlock()
|
|
||||||
g.RequestsCountMutex.RUnlock()
|
|
||||||
}
|
}
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return
|
return
|
||||||
|
@ -173,22 +138,16 @@ func Gobuster(ctx context.Context, opts *libgobuster.Options, plugin libgobuster
|
||||||
// when we call wg.Wait()
|
// when we call wg.Wait()
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
outputMutex := new(sync.RWMutex)
|
wg.Add(1)
|
||||||
o := &outputType{
|
go resultWorker(gobuster, opts.OutputFilename, &wg)
|
||||||
Mu: outputMutex,
|
|
||||||
MaxCharsWritten: 0,
|
|
||||||
}
|
|
||||||
|
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go resultWorker(gobuster, opts.OutputFilename, &wg, o)
|
go errorWorker(gobuster, &wg)
|
||||||
|
|
||||||
wg.Add(1)
|
|
||||||
go errorWorker(gobuster, &wg, o)
|
|
||||||
|
|
||||||
if !opts.Quiet && !opts.NoProgress {
|
if !opts.Quiet && !opts.NoProgress {
|
||||||
// if not quiet add a new workgroup entry and start the goroutine
|
// if not quiet add a new workgroup entry and start the goroutine
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go progressWorker(ctxCancel, gobuster, &wg, o)
|
go progressWorker(ctxCancel, gobuster, &wg)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = gobuster.Run(ctxCancel)
|
err = gobuster.Run(ctxCancel)
|
||||||
|
@ -205,8 +164,6 @@ func Gobuster(ctx context.Context, opts *libgobuster.Options, plugin libgobuster
|
||||||
}
|
}
|
||||||
|
|
||||||
if !opts.Quiet {
|
if !opts.Quiet {
|
||||||
// clear stderr progress
|
|
||||||
fmt.Fprintf(os.Stderr, "\r%s\n", rightPad("", " ", o.MaxCharsWritten))
|
|
||||||
fmt.Println(ruler)
|
fmt.Println(ruler)
|
||||||
gobuster.LogInfo.Println("Finished")
|
gobuster.LogInfo.Println("Finished")
|
||||||
fmt.Println(ruler)
|
fmt.Println(ruler)
|
||||||
|
|
10
cspell.json
10
cspell.json
|
@ -1,7 +1,7 @@
|
||||||
// cSpell Settings
|
// cSpell Settings
|
||||||
{
|
{
|
||||||
// Version of the setting file. Always 0.1
|
// Version of the setting file. Always 0.2
|
||||||
"version": "0.1",
|
"version": "0.2",
|
||||||
// language - current active spelling language
|
// language - current active spelling language
|
||||||
"language": "en",
|
"language": "en",
|
||||||
// words - list of words to be always considered correct
|
// words - list of words to be always considered correct
|
||||||
|
@ -13,6 +13,7 @@
|
||||||
"gobusterdns",
|
"gobusterdns",
|
||||||
"gobusterfuzz",
|
"gobusterfuzz",
|
||||||
"gobustervhost",
|
"gobustervhost",
|
||||||
|
"gobustergcs",
|
||||||
"vhost",
|
"vhost",
|
||||||
"vhosts",
|
"vhosts",
|
||||||
"cname",
|
"cname",
|
||||||
|
@ -28,7 +29,10 @@
|
||||||
"unconvert",
|
"unconvert",
|
||||||
"unparam",
|
"unparam",
|
||||||
"prealloc",
|
"prealloc",
|
||||||
"gochecknoglobals"
|
"gochecknoglobals",
|
||||||
|
"gochecknoinits",
|
||||||
|
"fatih",
|
||||||
|
"netip"
|
||||||
],
|
],
|
||||||
// flagWords - list of words to be always considered incorrect
|
// flagWords - list of words to be always considered incorrect
|
||||||
// This is useful for offensive words and common spelling errors.
|
// This is useful for offensive words and common spelling errors.
|
||||||
|
|
11
go.mod
11
go.mod
|
@ -1,15 +1,18 @@
|
||||||
module github.com/OJ/gobuster/v3
|
module github.com/OJ/gobuster/v3
|
||||||
|
|
||||||
|
go 1.19
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/fatih/color v1.13.0
|
||||||
github.com/google/uuid v1.3.0
|
github.com/google/uuid v1.3.0
|
||||||
github.com/spf13/cobra v1.5.0
|
github.com/spf13/cobra v1.5.0
|
||||||
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035
|
golang.org/x/term v0.0.0-20220919170432-7a66f970e087
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/inconshreveable/mousetrap v1.0.1 // indirect
|
github.com/inconshreveable/mousetrap v1.0.1 // indirect
|
||||||
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.16 // indirect
|
||||||
github.com/spf13/pflag v1.0.5 // indirect
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
golang.org/x/sys v0.0.0-20220909162455-aba9fc2a8ff2 // indirect
|
golang.org/x/sys v0.0.0-20221006211917-84dc82d7e875 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
go 1.17
|
|
||||||
|
|
22
go.sum
22
go.sum
|
@ -1,18 +1,30 @@
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||||
|
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
|
||||||
|
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
||||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||||
github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc=
|
github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc=
|
||||||
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||||
|
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||||
|
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
|
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||||
|
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||||
|
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||||
|
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
|
||||||
|
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU=
|
github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU=
|
||||||
github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM=
|
github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM=
|
||||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20220909162455-aba9fc2a8ff2 h1:wM1k/lXfpc5HdkJJyW9GELpd8ERGdnh8sMGL6Gzq3Ho=
|
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20220909162455-aba9fc2a8ff2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035 h1:Q5284mrmYTpACcm+eAKjKJH48BBwSyfJqmmGDTtT8Vc=
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/sys v0.0.0-20221006211917-84dc82d7e875 h1:AzgQNqF+FKwyQ5LbVrVqOcuuFB67N47F9+htZYH0wFM=
|
||||||
|
golang.org/x/sys v0.0.0-20221006211917-84dc82d7e875/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/term v0.0.0-20220919170432-7a66f970e087 h1:tPwmk4vmvVCMdr98VgL4JH+qZxPL8fqlUOHnyOM8N3w=
|
||||||
|
golang.org/x/term v0.0.0-20220919170432-7a66f970e087/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
|
|
|
@ -5,6 +5,8 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"text/tabwriter"
|
"text/tabwriter"
|
||||||
|
|
||||||
|
@ -33,10 +35,9 @@ func (e *ErrWildcard) Error() string {
|
||||||
|
|
||||||
// GobusterDir is the main type to implement the interface
|
// GobusterDir is the main type to implement the interface
|
||||||
type GobusterDir struct {
|
type GobusterDir struct {
|
||||||
options *OptionsDir
|
options *OptionsDir
|
||||||
globalopts *libgobuster.Options
|
globalopts *libgobuster.Options
|
||||||
http *libgobuster.HTTPClient
|
http *libgobuster.HTTPClient
|
||||||
requestsPerRun *int // helper variable so we do not recalculate this over and over
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewGobusterDir creates a new initialized GobusterDir
|
// NewGobusterDir creates a new initialized GobusterDir
|
||||||
|
@ -59,6 +60,8 @@ func NewGobusterDir(globalopts *libgobuster.Options, opts *OptionsDir) (*Gobuste
|
||||||
Timeout: opts.Timeout,
|
Timeout: opts.Timeout,
|
||||||
UserAgent: opts.UserAgent,
|
UserAgent: opts.UserAgent,
|
||||||
NoTLSValidation: opts.NoTLSValidation,
|
NoTLSValidation: opts.NoTLSValidation,
|
||||||
|
RetryOnTimeout: opts.RetryOnTimeout,
|
||||||
|
RetryAttempts: opts.RetryAttempts,
|
||||||
}
|
}
|
||||||
|
|
||||||
httpOpts := libgobuster.HTTPOptions{
|
httpOpts := libgobuster.HTTPOptions{
|
||||||
|
@ -85,26 +88,6 @@ func (d *GobusterDir) Name() string {
|
||||||
return "directory enumeration"
|
return "directory enumeration"
|
||||||
}
|
}
|
||||||
|
|
||||||
// RequestsPerRun returns the number of requests this plugin makes per single wordlist item
|
|
||||||
func (d *GobusterDir) RequestsPerRun() int {
|
|
||||||
if d.requestsPerRun != nil {
|
|
||||||
return *d.requestsPerRun
|
|
||||||
}
|
|
||||||
|
|
||||||
num := 1 + len(d.options.ExtensionsParsed.Set)
|
|
||||||
if d.options.DiscoverBackup {
|
|
||||||
// default word
|
|
||||||
num += len(backupExtensions)
|
|
||||||
num += len(backupDotExtensions)
|
|
||||||
// backups of filenames
|
|
||||||
num += len(d.options.ExtensionsParsed.Set) * len(backupExtensions)
|
|
||||||
num += len(d.options.ExtensionsParsed.Set) * len(backupDotExtensions)
|
|
||||||
}
|
|
||||||
d.requestsPerRun = &num
|
|
||||||
|
|
||||||
return *d.requestsPerRun
|
|
||||||
}
|
|
||||||
|
|
||||||
// PreRun is the pre run implementation of gobusterdir
|
// PreRun is the pre run implementation of gobusterdir
|
||||||
func (d *GobusterDir) PreRun(ctx context.Context) error {
|
func (d *GobusterDir) PreRun(ctx context.Context) error {
|
||||||
// add trailing slash
|
// add trailing slash
|
||||||
|
@ -134,12 +117,12 @@ func (d *GobusterDir) PreRun(ctx context.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if d.options.StatusCodesBlacklistParsed.Length() > 0 {
|
if d.options.StatusCodesBlacklistParsed.Length() > 0 {
|
||||||
if !d.options.StatusCodesBlacklistParsed.Contains(*wildcardResp) {
|
if !d.options.StatusCodesBlacklistParsed.Contains(wildcardResp) {
|
||||||
return &ErrWildcard{url: url, statusCode: *wildcardResp, length: wildcardLength}
|
return &ErrWildcard{url: url, statusCode: wildcardResp, length: wildcardLength}
|
||||||
}
|
}
|
||||||
} else if d.options.StatusCodesParsed.Length() > 0 {
|
} else if d.options.StatusCodesParsed.Length() > 0 {
|
||||||
if d.options.StatusCodesParsed.Contains(*wildcardResp) {
|
if d.options.StatusCodesParsed.Contains(wildcardResp) {
|
||||||
return &ErrWildcard{url: url, statusCode: *wildcardResp, length: wildcardLength}
|
return &ErrWildcard{url: url, statusCode: wildcardResp, length: wildcardLength}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return fmt.Errorf("StatusCodes and StatusCodesBlacklist are both not set which should not happen")
|
return fmt.Errorf("StatusCodes and StatusCodesBlacklist are both not set which should not happen")
|
||||||
|
@ -163,72 +146,90 @@ func getBackupFilenames(word string) []string {
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run is the process implementation of gobusterdir
|
func (d *GobusterDir) AdditionalWords(word string) []string {
|
||||||
func (d *GobusterDir) Run(ctx context.Context, word string, resChannel chan<- libgobuster.Result) error {
|
var words []string
|
||||||
suffix := ""
|
|
||||||
if d.options.UseSlash {
|
|
||||||
suffix = "/"
|
|
||||||
}
|
|
||||||
|
|
||||||
// build list of urls to check
|
// build list of urls to check
|
||||||
// 1: No extension
|
// 1: No extension
|
||||||
// 2: With extension
|
// 2: With extension
|
||||||
// 3: backupextension
|
// 3: backupextension
|
||||||
urlsToCheck := make(map[string]string)
|
|
||||||
entity := fmt.Sprintf("%s%s", word, suffix)
|
|
||||||
dirURL := fmt.Sprintf("%s%s", d.options.URL, entity)
|
|
||||||
urlsToCheck[entity] = dirURL
|
|
||||||
if d.options.DiscoverBackup {
|
if d.options.DiscoverBackup {
|
||||||
for _, u := range getBackupFilenames(word) {
|
words = append(words, getBackupFilenames(word)...)
|
||||||
url := fmt.Sprintf("%s%s", d.options.URL, u)
|
|
||||||
urlsToCheck[u] = url
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
for ext := range d.options.ExtensionsParsed.Set {
|
for ext := range d.options.ExtensionsParsed.Set {
|
||||||
filename := fmt.Sprintf("%s.%s", word, ext)
|
filename := fmt.Sprintf("%s.%s", word, ext)
|
||||||
url := fmt.Sprintf("%s%s", d.options.URL, filename)
|
words = append(words, filename)
|
||||||
urlsToCheck[filename] = url
|
|
||||||
if d.options.DiscoverBackup {
|
if d.options.DiscoverBackup {
|
||||||
for _, u := range getBackupFilenames(filename) {
|
words = append(words, getBackupFilenames(filename)...)
|
||||||
url2 := fmt.Sprintf("%s%s", d.options.URL, u)
|
|
||||||
urlsToCheck[u] = url2
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return words
|
||||||
|
}
|
||||||
|
|
||||||
for entity, url := range urlsToCheck {
|
// ProcessWord is the process implementation of gobusterdir
|
||||||
statusCode, size, header, _, err := d.http.Request(ctx, url, libgobuster.RequestOptions{})
|
func (d *GobusterDir) ProcessWord(ctx context.Context, word string, progress *libgobuster.Progress) error {
|
||||||
|
suffix := ""
|
||||||
|
if d.options.UseSlash {
|
||||||
|
suffix = "/"
|
||||||
|
}
|
||||||
|
entity := fmt.Sprintf("%s%s", word, suffix)
|
||||||
|
url := fmt.Sprintf("%s%s", d.options.URL, entity)
|
||||||
|
|
||||||
|
tries := 1
|
||||||
|
if d.options.RetryOnTimeout && d.options.RetryAttempts > 0 {
|
||||||
|
// add it so it will be the overall max requests
|
||||||
|
tries += d.options.RetryAttempts
|
||||||
|
}
|
||||||
|
|
||||||
|
var statusCode int
|
||||||
|
var size int64
|
||||||
|
var header http.Header
|
||||||
|
for i := 1; i <= tries; i++ {
|
||||||
|
var err error
|
||||||
|
statusCode, size, header, _, err = d.http.Request(ctx, url, libgobuster.RequestOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
// check if it's a timeout and if we should try again and try again
|
||||||
}
|
// otherwise the timeout error is raised
|
||||||
if statusCode != nil {
|
if netErr, ok := err.(net.Error); ok && netErr.Timeout() && i != tries {
|
||||||
resultStatus := false
|
continue
|
||||||
|
} else if strings.Contains(err.Error(), "invalid control character in URL") {
|
||||||
if d.options.StatusCodesBlacklistParsed.Length() > 0 {
|
// put error in error chan so it's printed out and ignore it
|
||||||
if !d.options.StatusCodesBlacklistParsed.Contains(*statusCode) {
|
// so gobuster will not quit
|
||||||
resultStatus = true
|
progress.ErrorChan <- err
|
||||||
}
|
continue
|
||||||
} else if d.options.StatusCodesParsed.Length() > 0 {
|
|
||||||
if d.options.StatusCodesParsed.Contains(*statusCode) {
|
|
||||||
resultStatus = true
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
return fmt.Errorf("StatusCodes and StatusCodesBlacklist are both not set which should not happen")
|
return err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
if (resultStatus && !helper.SliceContains(d.options.ExcludeLength, int(size))) || d.globalopts.Verbose {
|
if statusCode != 0 {
|
||||||
resChannel <- Result{
|
resultStatus := false
|
||||||
URL: d.options.URL,
|
|
||||||
Path: entity,
|
if d.options.StatusCodesBlacklistParsed.Length() > 0 {
|
||||||
Verbose: d.globalopts.Verbose,
|
if !d.options.StatusCodesBlacklistParsed.Contains(statusCode) {
|
||||||
Expanded: d.options.Expanded,
|
resultStatus = true
|
||||||
NoStatus: d.options.NoStatus,
|
}
|
||||||
HideLength: d.options.HideLength,
|
} else if d.options.StatusCodesParsed.Length() > 0 {
|
||||||
Found: resultStatus,
|
if d.options.StatusCodesParsed.Contains(statusCode) {
|
||||||
Header: header,
|
resultStatus = true
|
||||||
StatusCode: *statusCode,
|
}
|
||||||
Size: size,
|
} else {
|
||||||
}
|
return fmt.Errorf("StatusCodes and StatusCodesBlacklist are both not set which should not happen")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resultStatus && !helper.SliceContains(d.options.ExcludeLength, int(size))) || d.globalopts.Verbose {
|
||||||
|
progress.ResultChan <- Result{
|
||||||
|
URL: d.options.URL,
|
||||||
|
Path: entity,
|
||||||
|
Verbose: d.globalopts.Verbose,
|
||||||
|
Expanded: d.options.Expanded,
|
||||||
|
NoStatus: d.options.NoStatus,
|
||||||
|
HideLength: d.options.HideLength,
|
||||||
|
Found: resultStatus,
|
||||||
|
Header: header,
|
||||||
|
StatusCode: statusCode,
|
||||||
|
Size: size,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,11 +8,11 @@ import (
|
||||||
type OptionsDir struct {
|
type OptionsDir struct {
|
||||||
libgobuster.HTTPOptions
|
libgobuster.HTTPOptions
|
||||||
Extensions string
|
Extensions string
|
||||||
ExtensionsParsed libgobuster.StringSet
|
ExtensionsParsed libgobuster.Set[string]
|
||||||
StatusCodes string
|
StatusCodes string
|
||||||
StatusCodesParsed libgobuster.IntSet
|
StatusCodesParsed libgobuster.Set[int]
|
||||||
StatusCodesBlacklist string
|
StatusCodesBlacklist string
|
||||||
StatusCodesBlacklistParsed libgobuster.IntSet
|
StatusCodesBlacklistParsed libgobuster.Set[int]
|
||||||
UseSlash bool
|
UseSlash bool
|
||||||
HideLength bool
|
HideLength bool
|
||||||
Expanded bool
|
Expanded bool
|
||||||
|
@ -24,8 +24,8 @@ type OptionsDir struct {
|
||||||
// NewOptionsDir returns a new initialized OptionsDir
|
// NewOptionsDir returns a new initialized OptionsDir
|
||||||
func NewOptionsDir() *OptionsDir {
|
func NewOptionsDir() *OptionsDir {
|
||||||
return &OptionsDir{
|
return &OptionsDir{
|
||||||
StatusCodesParsed: libgobuster.NewIntSet(),
|
StatusCodesParsed: libgobuster.NewSet[int](),
|
||||||
StatusCodesBlacklistParsed: libgobuster.NewIntSet(),
|
StatusCodesBlacklistParsed: libgobuster.NewSet[int](),
|
||||||
ExtensionsParsed: libgobuster.NewStringSet(),
|
ExtensionsParsed: libgobuster.NewSet[string](),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,17 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/fatih/color"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
white = color.New(color.FgWhite).FprintfFunc()
|
||||||
|
yellow = color.New(color.FgYellow).FprintfFunc()
|
||||||
|
green = color.New(color.FgGreen).FprintfFunc()
|
||||||
|
blue = color.New(color.FgBlue).FprintfFunc()
|
||||||
|
red = color.New(color.FgRed).FprintfFunc()
|
||||||
|
cyan = color.New(color.FgCyan).FprintfFunc()
|
||||||
)
|
)
|
||||||
|
|
||||||
// Result represents a single result
|
// Result represents a single result
|
||||||
|
@ -51,9 +62,18 @@ func (r Result) ResultToString() (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if !r.NoStatus {
|
if !r.NoStatus {
|
||||||
if _, err := fmt.Fprintf(buf, " (Status: %d)", r.StatusCode); err != nil {
|
color := white
|
||||||
return "", err
|
if r.StatusCode == 200 {
|
||||||
|
color = green
|
||||||
|
} else if r.StatusCode >= 300 && r.StatusCode < 400 {
|
||||||
|
color = cyan
|
||||||
|
} else if r.StatusCode >= 400 && r.StatusCode < 500 {
|
||||||
|
color = yellow
|
||||||
|
} else if r.StatusCode >= 500 && r.StatusCode < 600 {
|
||||||
|
color = red
|
||||||
}
|
}
|
||||||
|
|
||||||
|
color(buf, " (Status: %d)", r.StatusCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !r.HideLength {
|
if !r.HideLength {
|
||||||
|
@ -64,9 +84,7 @@ func (r Result) ResultToString() (string, error) {
|
||||||
|
|
||||||
location := r.Header.Get("Location")
|
location := r.Header.Get("Location")
|
||||||
if location != "" {
|
if location != "" {
|
||||||
if _, err := fmt.Fprintf(buf, " [--> %s]", location); err != nil {
|
blue(buf, " [--> %s]", location)
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := fmt.Fprintf(buf, "\n"); err != nil {
|
if _, err := fmt.Fprintf(buf, "\n"); err != nil {
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
|
"net/netip"
|
||||||
"strings"
|
"strings"
|
||||||
"text/tabwriter"
|
"text/tabwriter"
|
||||||
"time"
|
"time"
|
||||||
|
@ -17,7 +18,7 @@ import (
|
||||||
|
|
||||||
// ErrWildcard is returned if a wildcard response is found
|
// ErrWildcard is returned if a wildcard response is found
|
||||||
type ErrWildcard struct {
|
type ErrWildcard struct {
|
||||||
wildcardIps libgobuster.StringSet
|
wildcardIps libgobuster.Set[netip.Addr]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Error is the implementation of the error interface
|
// Error is the implementation of the error interface
|
||||||
|
@ -31,7 +32,7 @@ type GobusterDNS struct {
|
||||||
globalopts *libgobuster.Options
|
globalopts *libgobuster.Options
|
||||||
options *OptionsDNS
|
options *OptionsDNS
|
||||||
isWildcard bool
|
isWildcard bool
|
||||||
wildcardIps libgobuster.StringSet
|
wildcardIps libgobuster.Set[netip.Addr]
|
||||||
}
|
}
|
||||||
|
|
||||||
func newCustomDialer(server string) func(ctx context.Context, network, address string) (net.Conn, error) {
|
func newCustomDialer(server string) func(ctx context.Context, network, address string) (net.Conn, error) {
|
||||||
|
@ -65,7 +66,7 @@ func NewGobusterDNS(globalopts *libgobuster.Options, opts *OptionsDNS) (*Gobuste
|
||||||
g := GobusterDNS{
|
g := GobusterDNS{
|
||||||
options: opts,
|
options: opts,
|
||||||
globalopts: globalopts,
|
globalopts: globalopts,
|
||||||
wildcardIps: libgobuster.NewStringSet(),
|
wildcardIps: libgobuster.NewSet[netip.Addr](),
|
||||||
resolver: resolver,
|
resolver: resolver,
|
||||||
}
|
}
|
||||||
return &g, nil
|
return &g, nil
|
||||||
|
@ -76,11 +77,6 @@ func (d *GobusterDNS) Name() string {
|
||||||
return "DNS enumeration"
|
return "DNS enumeration"
|
||||||
}
|
}
|
||||||
|
|
||||||
// RequestsPerRun returns the number of requests this plugin makes per single wordlist item
|
|
||||||
func (d *GobusterDNS) RequestsPerRun() int {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// PreRun is the pre run implementation of gobusterdns
|
// PreRun is the pre run implementation of gobusterdns
|
||||||
func (d *GobusterDNS) PreRun(ctx context.Context) error {
|
func (d *GobusterDNS) PreRun(ctx context.Context) error {
|
||||||
// Resolve a subdomain that probably shouldn't exist
|
// Resolve a subdomain that probably shouldn't exist
|
||||||
|
@ -106,8 +102,8 @@ func (d *GobusterDNS) PreRun(ctx context.Context) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run is the process implementation of gobusterdns
|
// ProcessWord is the process implementation of gobusterdns
|
||||||
func (d *GobusterDNS) Run(ctx context.Context, word string, resChannel chan<- libgobuster.Result) error {
|
func (d *GobusterDNS) ProcessWord(ctx context.Context, word string, progress *libgobuster.Progress) error {
|
||||||
subdomain := fmt.Sprintf("%s.%s", word, d.options.Domain)
|
subdomain := fmt.Sprintf("%s.%s", word, d.options.Domain)
|
||||||
ips, err := d.dnsLookup(ctx, subdomain)
|
ips, err := d.dnsLookup(ctx, subdomain)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
@ -126,10 +122,10 @@ func (d *GobusterDNS) Run(ctx context.Context, word string, resChannel chan<- li
|
||||||
result.CNAME = cname
|
result.CNAME = cname
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
resChannel <- result
|
progress.ResultChan <- result
|
||||||
}
|
}
|
||||||
} else if d.globalopts.Verbose {
|
} else if d.globalopts.Verbose {
|
||||||
resChannel <- Result{
|
progress.ResultChan <- Result{
|
||||||
Subdomain: subdomain,
|
Subdomain: subdomain,
|
||||||
Found: false,
|
Found: false,
|
||||||
ShowIPs: d.options.ShowIPs,
|
ShowIPs: d.options.ShowIPs,
|
||||||
|
@ -139,6 +135,10 @@ func (d *GobusterDNS) Run(ctx context.Context, word string, resChannel chan<- li
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *GobusterDNS) AdditionalWords(word string) []string {
|
||||||
|
return []string{}
|
||||||
|
}
|
||||||
|
|
||||||
// GetConfigString returns the string representation of the current config
|
// GetConfigString returns the string representation of the current config
|
||||||
func (d *GobusterDNS) GetConfigString() (string, error) {
|
func (d *GobusterDNS) GetConfigString() (string, error) {
|
||||||
var buffer bytes.Buffer
|
var buffer bytes.Buffer
|
||||||
|
@ -219,10 +219,10 @@ func (d *GobusterDNS) GetConfigString() (string, error) {
|
||||||
return strings.TrimSpace(buffer.String()), nil
|
return strings.TrimSpace(buffer.String()), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *GobusterDNS) dnsLookup(ctx context.Context, domain string) ([]string, error) {
|
func (d *GobusterDNS) dnsLookup(ctx context.Context, domain string) ([]netip.Addr, error) {
|
||||||
ctx2, cancel := context.WithTimeout(ctx, d.options.Timeout)
|
ctx2, cancel := context.WithTimeout(ctx, d.options.Timeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
return d.resolver.LookupHost(ctx2, domain)
|
return d.resolver.LookupNetIP(ctx2, "ip", domain)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *GobusterDNS) dnsLookupCname(ctx context.Context, domain string) (string, error) {
|
func (d *GobusterDNS) dnsLookupCname(ctx context.Context, domain string) (string, error) {
|
||||||
|
|
|
@ -2,8 +2,15 @@ package gobusterdns
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"net/netip"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/fatih/color"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
yellow = color.New(color.FgYellow).FprintfFunc()
|
||||||
|
green = color.New(color.FgGreen).FprintfFunc()
|
||||||
)
|
)
|
||||||
|
|
||||||
// Result represents a single result
|
// Result represents a single result
|
||||||
|
@ -12,7 +19,7 @@ type Result struct {
|
||||||
ShowCNAME bool
|
ShowCNAME bool
|
||||||
Found bool
|
Found bool
|
||||||
Subdomain string
|
Subdomain string
|
||||||
IPs []string
|
IPs []netip.Addr
|
||||||
CNAME string
|
CNAME string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,28 +27,25 @@ type Result struct {
|
||||||
func (r Result) ResultToString() (string, error) {
|
func (r Result) ResultToString() (string, error) {
|
||||||
buf := &bytes.Buffer{}
|
buf := &bytes.Buffer{}
|
||||||
|
|
||||||
|
c := green
|
||||||
|
|
||||||
if r.Found {
|
if r.Found {
|
||||||
if _, err := fmt.Fprintf(buf, "Found: "); err != nil {
|
c(buf, "Found: ")
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if _, err := fmt.Fprintf(buf, "Missed: "); err != nil {
|
c = yellow
|
||||||
return "", err
|
c(buf, "Missed: ")
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.ShowIPs && r.Found {
|
if r.ShowIPs && r.Found {
|
||||||
if _, err := fmt.Fprintf(buf, "%s [%s]\n", r.Subdomain, strings.Join(r.IPs, ",")); err != nil {
|
ips := make([]string, len(r.IPs))
|
||||||
return "", err
|
for i := range r.IPs {
|
||||||
|
ips[i] = r.IPs[i].String()
|
||||||
}
|
}
|
||||||
|
c(buf, "%s [%s]\n", r.Subdomain, strings.Join(ips, ","))
|
||||||
} else if r.ShowCNAME && r.Found && r.CNAME != "" {
|
} else if r.ShowCNAME && r.Found && r.CNAME != "" {
|
||||||
if _, err := fmt.Fprintf(buf, "%s [%s]\n", r.Subdomain, r.CNAME); err != nil {
|
c(buf, "%s [%s]\n", r.Subdomain, r.CNAME)
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if _, err := fmt.Fprintf(buf, "%s\n", r.Subdomain); err != nil {
|
c(buf, "%s\n", r.Subdomain)
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
s := buf.String()
|
s := buf.String()
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
"text/tabwriter"
|
"text/tabwriter"
|
||||||
|
|
||||||
|
@ -50,6 +51,8 @@ func NewGobusterFuzz(globalopts *libgobuster.Options, opts *OptionsFuzz) (*Gobus
|
||||||
Timeout: opts.Timeout,
|
Timeout: opts.Timeout,
|
||||||
UserAgent: opts.UserAgent,
|
UserAgent: opts.UserAgent,
|
||||||
NoTLSValidation: opts.NoTLSValidation,
|
NoTLSValidation: opts.NoTLSValidation,
|
||||||
|
RetryOnTimeout: opts.RetryOnTimeout,
|
||||||
|
RetryAttempts: opts.RetryAttempts,
|
||||||
}
|
}
|
||||||
|
|
||||||
httpOpts := libgobuster.HTTPOptions{
|
httpOpts := libgobuster.HTTPOptions{
|
||||||
|
@ -75,24 +78,44 @@ func (d *GobusterFuzz) Name() string {
|
||||||
return "fuzzing"
|
return "fuzzing"
|
||||||
}
|
}
|
||||||
|
|
||||||
// RequestsPerRun returns the number of requests this plugin makes per single wordlist item
|
|
||||||
func (d *GobusterFuzz) RequestsPerRun() int {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// PreRun is the pre run implementation of gobusterfuzz
|
// PreRun is the pre run implementation of gobusterfuzz
|
||||||
func (d *GobusterFuzz) PreRun(ctx context.Context) error {
|
func (d *GobusterFuzz) PreRun(ctx context.Context) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run is the process implementation of gobusterfuzz
|
// ProcessWord is the process implementation of gobusterfuzz
|
||||||
func (d *GobusterFuzz) Run(ctx context.Context, word string, resChannel chan<- libgobuster.Result) error {
|
func (d *GobusterFuzz) ProcessWord(ctx context.Context, word string, progress *libgobuster.Progress) error {
|
||||||
workingURL := strings.ReplaceAll(d.options.URL, "FUZZ", word)
|
url := strings.ReplaceAll(d.options.URL, "FUZZ", word)
|
||||||
statusCode, size, _, _, err := d.http.Request(ctx, workingURL, libgobuster.RequestOptions{})
|
|
||||||
if err != nil {
|
tries := 1
|
||||||
return err
|
if d.options.RetryOnTimeout && d.options.RetryAttempts > 0 {
|
||||||
|
// add it so it will be the overall max requests
|
||||||
|
tries += d.options.RetryAttempts
|
||||||
}
|
}
|
||||||
if statusCode != nil {
|
|
||||||
|
var statusCode int
|
||||||
|
var size int64
|
||||||
|
for i := 1; i <= tries; i++ {
|
||||||
|
var err error
|
||||||
|
statusCode, size, _, _, err = d.http.Request(ctx, url, libgobuster.RequestOptions{})
|
||||||
|
if err != nil {
|
||||||
|
// check if it's a timeout and if we should try again and try again
|
||||||
|
// otherwise the timeout error is raised
|
||||||
|
if netErr, ok := err.(net.Error); ok && netErr.Timeout() && i != tries {
|
||||||
|
continue
|
||||||
|
} else if strings.Contains(err.Error(), "invalid control character in URL") {
|
||||||
|
// put error in error chan so it's printed out and ignore it
|
||||||
|
// so gobuster will not quit
|
||||||
|
progress.ErrorChan <- err
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if statusCode != 0 {
|
||||||
resultStatus := true
|
resultStatus := true
|
||||||
|
|
||||||
if helper.SliceContains(d.options.ExcludeLength, int(size)) {
|
if helper.SliceContains(d.options.ExcludeLength, int(size)) {
|
||||||
|
@ -100,17 +123,17 @@ func (d *GobusterFuzz) Run(ctx context.Context, word string, resChannel chan<- l
|
||||||
}
|
}
|
||||||
|
|
||||||
if d.options.ExcludedStatusCodesParsed.Length() > 0 {
|
if d.options.ExcludedStatusCodesParsed.Length() > 0 {
|
||||||
if d.options.ExcludedStatusCodesParsed.Contains(*statusCode) {
|
if d.options.ExcludedStatusCodesParsed.Contains(statusCode) {
|
||||||
resultStatus = false
|
resultStatus = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if resultStatus || d.globalopts.Verbose {
|
if resultStatus || d.globalopts.Verbose {
|
||||||
resChannel <- Result{
|
progress.ResultChan <- Result{
|
||||||
Verbose: d.globalopts.Verbose,
|
Verbose: d.globalopts.Verbose,
|
||||||
Found: resultStatus,
|
Found: resultStatus,
|
||||||
Path: workingURL,
|
Path: url,
|
||||||
StatusCode: *statusCode,
|
StatusCode: statusCode,
|
||||||
Size: size,
|
Size: size,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -118,6 +141,10 @@ func (d *GobusterFuzz) Run(ctx context.Context, word string, resChannel chan<- l
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *GobusterFuzz) AdditionalWords(word string) []string {
|
||||||
|
return []string{}
|
||||||
|
}
|
||||||
|
|
||||||
// GetConfigString returns the string representation of the current config
|
// GetConfigString returns the string representation of the current config
|
||||||
func (d *GobusterFuzz) GetConfigString() (string, error) {
|
func (d *GobusterFuzz) GetConfigString() (string, error) {
|
||||||
var buffer bytes.Buffer
|
var buffer bytes.Buffer
|
||||||
|
|
|
@ -8,13 +8,13 @@ import (
|
||||||
type OptionsFuzz struct {
|
type OptionsFuzz struct {
|
||||||
libgobuster.HTTPOptions
|
libgobuster.HTTPOptions
|
||||||
ExcludedStatusCodes string
|
ExcludedStatusCodes string
|
||||||
ExcludedStatusCodesParsed libgobuster.IntSet
|
ExcludedStatusCodesParsed libgobuster.Set[int]
|
||||||
ExcludeLength []int
|
ExcludeLength []int
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewOptionsFuzz returns a new initialized OptionsFuzz
|
// NewOptionsFuzz returns a new initialized OptionsFuzz
|
||||||
func NewOptionsFuzz() *OptionsFuzz {
|
func NewOptionsFuzz() *OptionsFuzz {
|
||||||
return &OptionsFuzz{
|
return &OptionsFuzz{
|
||||||
ExcludedStatusCodesParsed: libgobuster.NewIntSet(),
|
ExcludedStatusCodesParsed: libgobuster.NewSet[int](),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,13 @@ package gobusterfuzz
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
|
||||||
|
"github.com/fatih/color"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
yellow = color.New(color.FgYellow).FprintfFunc()
|
||||||
|
green = color.New(color.FgGreen).FprintfFunc()
|
||||||
)
|
)
|
||||||
|
|
||||||
// Result represents a single result
|
// Result represents a single result
|
||||||
|
@ -18,30 +24,22 @@ type Result struct {
|
||||||
func (r Result) ResultToString() (string, error) {
|
func (r Result) ResultToString() (string, error) {
|
||||||
buf := &bytes.Buffer{}
|
buf := &bytes.Buffer{}
|
||||||
|
|
||||||
|
c := green
|
||||||
|
|
||||||
// Prefix if we're in verbose mode
|
// Prefix if we're in verbose mode
|
||||||
if r.Verbose {
|
if r.Verbose {
|
||||||
if r.Found {
|
if r.Found {
|
||||||
if _, err := fmt.Fprintf(buf, "Found: "); err != nil {
|
c(buf, "Found: ")
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if _, err := fmt.Fprintf(buf, "Missed: "); err != nil {
|
c = yellow
|
||||||
return "", err
|
c(buf, "Missed: ")
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else if r.Found {
|
} else if r.Found {
|
||||||
if _, err := fmt.Fprintf(buf, "Found: "); err != nil {
|
c(buf, "Found: ")
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := fmt.Fprintf(buf, "[Status=%d] [Length=%d] %s", r.StatusCode, r.Size, r.Path); err != nil {
|
c(buf, "[Status=%d] [Length=%d] %s", r.StatusCode, r.Size, r.Path)
|
||||||
return "", err
|
c(buf, "\n")
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := fmt.Fprintf(buf, "\n"); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
s := buf.String()
|
s := buf.String()
|
||||||
return s, nil
|
return s, nil
|
||||||
|
|
|
@ -0,0 +1,265 @@
|
||||||
|
package gobustergcs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"text/tabwriter"
|
||||||
|
|
||||||
|
"github.com/OJ/gobuster/v3/libgobuster"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GobusterGCS is the main type to implement the interface
|
||||||
|
type GobusterGCS struct {
|
||||||
|
options *OptionsGCS
|
||||||
|
globalopts *libgobuster.Options
|
||||||
|
http *libgobuster.HTTPClient
|
||||||
|
bucketRegex *regexp.Regexp
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGobusterGCS creates a new initialized GobusterGCS
|
||||||
|
func NewGobusterGCS(globalopts *libgobuster.Options, opts *OptionsGCS) (*GobusterGCS, error) {
|
||||||
|
if globalopts == nil {
|
||||||
|
return nil, fmt.Errorf("please provide valid global options")
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts == nil {
|
||||||
|
return nil, fmt.Errorf("please provide valid plugin options")
|
||||||
|
}
|
||||||
|
|
||||||
|
g := GobusterGCS{
|
||||||
|
options: opts,
|
||||||
|
globalopts: globalopts,
|
||||||
|
}
|
||||||
|
|
||||||
|
basicOptions := libgobuster.BasicHTTPOptions{
|
||||||
|
Proxy: opts.Proxy,
|
||||||
|
Timeout: opts.Timeout,
|
||||||
|
UserAgent: opts.UserAgent,
|
||||||
|
NoTLSValidation: opts.NoTLSValidation,
|
||||||
|
RetryOnTimeout: opts.RetryOnTimeout,
|
||||||
|
RetryAttempts: opts.RetryAttempts,
|
||||||
|
}
|
||||||
|
|
||||||
|
httpOpts := libgobuster.HTTPOptions{
|
||||||
|
BasicHTTPOptions: basicOptions,
|
||||||
|
// needed so we can list bucket contents
|
||||||
|
FollowRedirect: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
h, err := libgobuster.NewHTTPClient(&httpOpts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
g.http = h
|
||||||
|
// https://cloud.google.com/storage/docs/naming-buckets
|
||||||
|
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])*$`)
|
||||||
|
|
||||||
|
return &g, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name should return the name of the plugin
|
||||||
|
func (s *GobusterGCS) Name() string {
|
||||||
|
return "GCS bucket enumeration"
|
||||||
|
}
|
||||||
|
|
||||||
|
// PreRun is the pre run implementation of GobusterS3
|
||||||
|
func (s *GobusterGCS) PreRun(ctx context.Context) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProcessWord is the process implementation of GobusterS3
|
||||||
|
func (s *GobusterGCS) ProcessWord(ctx context.Context, word string, progress *libgobuster.Progress) error {
|
||||||
|
// only check for valid bucket names
|
||||||
|
if !s.isValidBucketName(word) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
bucketURL := fmt.Sprintf("https://storage.googleapis.com/storage/v1/b/%s/o?maxResults=%d", word, s.options.MaxFilesToList)
|
||||||
|
|
||||||
|
tries := 1
|
||||||
|
if s.options.RetryOnTimeout && s.options.RetryAttempts > 0 {
|
||||||
|
// add it so it will be the overall max requests
|
||||||
|
tries += s.options.RetryAttempts
|
||||||
|
}
|
||||||
|
|
||||||
|
var statusCode int
|
||||||
|
var body []byte
|
||||||
|
for i := 1; i <= tries; i++ {
|
||||||
|
var err error
|
||||||
|
statusCode, _, _, body, err = s.http.Request(ctx, bucketURL, libgobuster.RequestOptions{ReturnBody: true})
|
||||||
|
if err != nil {
|
||||||
|
// check if it's a timeout and if we should try again and try again
|
||||||
|
// otherwise the timeout error is raised
|
||||||
|
if netErr, ok := err.(net.Error); ok && netErr.Timeout() && i != tries {
|
||||||
|
continue
|
||||||
|
} else if strings.Contains(err.Error(), "invalid control character in URL") {
|
||||||
|
// put error in error chan so it's printed out and ignore it
|
||||||
|
// so gobuster will not quit
|
||||||
|
progress.ErrorChan <- err
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if statusCode == 0 || body == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// looks like 401, 403, and 404 are the only negative status codes
|
||||||
|
found := false
|
||||||
|
switch statusCode {
|
||||||
|
case http.StatusUnauthorized,
|
||||||
|
http.StatusForbidden,
|
||||||
|
http.StatusNotFound:
|
||||||
|
found = false
|
||||||
|
case http.StatusOK:
|
||||||
|
// listing enabled
|
||||||
|
found = true
|
||||||
|
default:
|
||||||
|
// default to found as we use negative status codes
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// nothing found, bail out
|
||||||
|
// may add the result later if we want to enable verbose output
|
||||||
|
if !found {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
extraStr := ""
|
||||||
|
if s.globalopts.Verbose {
|
||||||
|
// get status
|
||||||
|
var result map[string]interface{}
|
||||||
|
err := json.Unmarshal(body, &result)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not parse response json: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, exist := result["error"]; exist {
|
||||||
|
// https://cloud.google.com/storage/docs/json_api/v1/status-codes
|
||||||
|
gcsError := GCSError{}
|
||||||
|
err := json.Unmarshal(body, &gcsError)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not parse error json: %w", err)
|
||||||
|
}
|
||||||
|
extraStr = fmt.Sprintf("Error: %s (%d)", gcsError.Error.Message, gcsError.Error.Code)
|
||||||
|
} else if v, exist := result["kind"]; exist && v == "storage#objects" {
|
||||||
|
// https://cloud.google.com/storage/docs/json_api/v1/status-codes
|
||||||
|
// bucket listing enabled
|
||||||
|
gcsListing := GCSListing{}
|
||||||
|
err := json.Unmarshal(body, &gcsListing)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not parse result json: %w", err)
|
||||||
|
}
|
||||||
|
extraStr = "Bucket Listing enabled: "
|
||||||
|
for _, x := range gcsListing.Items {
|
||||||
|
extraStr += fmt.Sprintf("%s (%sb), ", x.Name, x.Size)
|
||||||
|
}
|
||||||
|
extraStr = strings.TrimRight(extraStr, ", ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
progress.ResultChan <- Result{
|
||||||
|
Found: found,
|
||||||
|
BucketName: word,
|
||||||
|
Status: extraStr,
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *GobusterGCS) AdditionalWords(word string) []string {
|
||||||
|
return []string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetConfigString returns the string representation of the current config
|
||||||
|
func (s *GobusterGCS) GetConfigString() (string, error) {
|
||||||
|
var buffer bytes.Buffer
|
||||||
|
bw := bufio.NewWriter(&buffer)
|
||||||
|
tw := tabwriter.NewWriter(bw, 0, 5, 3, ' ', 0)
|
||||||
|
o := s.options
|
||||||
|
|
||||||
|
if _, err := fmt.Fprintf(tw, "[+] Threads:\t%d\n", s.globalopts.Threads); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.globalopts.Delay > 0 {
|
||||||
|
if _, err := fmt.Fprintf(tw, "[+] Delay:\t%s\n", s.globalopts.Delay); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
wordlist := "stdin (pipe)"
|
||||||
|
if s.globalopts.Wordlist != "-" {
|
||||||
|
wordlist = s.globalopts.Wordlist
|
||||||
|
}
|
||||||
|
if _, err := fmt.Fprintf(tw, "[+] Wordlist:\t%s\n", wordlist); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.globalopts.PatternFile != "" {
|
||||||
|
if _, err := fmt.Fprintf(tw, "[+] Patterns:\t%s (%d entries)\n", s.globalopts.PatternFile, len(s.globalopts.Patterns)); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if o.Proxy != "" {
|
||||||
|
if _, err := fmt.Fprintf(tw, "[+] Proxy:\t%s\n", o.Proxy); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if o.UserAgent != "" {
|
||||||
|
if _, err := fmt.Fprintf(tw, "[+] User Agent:\t%s\n", o.UserAgent); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := fmt.Fprintf(tw, "[+] Timeout:\t%s\n", o.Timeout.String()); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.globalopts.Verbose {
|
||||||
|
if _, err := fmt.Fprintf(tw, "[+] Verbose:\ttrue\n"); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := fmt.Fprintf(tw, "[+] Maximum files to list:\t%d\n", o.MaxFilesToList); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tw.Flush(); err != nil {
|
||||||
|
return "", fmt.Errorf("error on tostring: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := bw.Flush(); err != nil {
|
||||||
|
return "", fmt.Errorf("error on tostring: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.TrimSpace(buffer.String()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://docs.aws.amazon.com/AmazonS3/latest/dev/BucketRestrictions.html
|
||||||
|
func (s *GobusterGCS) isValidBucketName(bucketName string) bool {
|
||||||
|
if len(bucketName) > 222 || !s.bucketRegex.MatchString(bucketName) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(bucketName, "-") || strings.HasSuffix(bucketName, "-") ||
|
||||||
|
strings.HasPrefix(bucketName, "_") || strings.HasSuffix(bucketName, "_") ||
|
||||||
|
strings.HasPrefix(bucketName, ".") || strings.HasSuffix(bucketName, ".") {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
package gobustergcs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/OJ/gobuster/v3/libgobuster"
|
||||||
|
)
|
||||||
|
|
||||||
|
// OptionsGCS is the struct to hold all options for this plugin
|
||||||
|
type OptionsGCS struct {
|
||||||
|
libgobuster.BasicHTTPOptions
|
||||||
|
MaxFilesToList int
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewOptionsGCS returns a new initialized OptionsS3
|
||||||
|
func NewOptionsGCS() *OptionsGCS {
|
||||||
|
return &OptionsGCS{}
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
package gobustergcs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
|
||||||
|
"github.com/fatih/color"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
green = color.New(color.FgGreen).FprintfFunc()
|
||||||
|
)
|
||||||
|
|
||||||
|
// Result represents a single result
|
||||||
|
type Result struct {
|
||||||
|
Found bool
|
||||||
|
BucketName string
|
||||||
|
Status string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResultToString converts the Result to it's textual representation
|
||||||
|
func (r Result) ResultToString() (string, error) {
|
||||||
|
buf := &bytes.Buffer{}
|
||||||
|
|
||||||
|
c := green
|
||||||
|
|
||||||
|
c(buf, "https://storage.googleapis.com/storage/v1/b/%s/o", r.BucketName)
|
||||||
|
|
||||||
|
if r.Status != "" {
|
||||||
|
c(buf, " [%s]", r.Status)
|
||||||
|
}
|
||||||
|
c(buf, "\n")
|
||||||
|
|
||||||
|
str := buf.String()
|
||||||
|
return str, nil
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
package gobustergcs
|
||||||
|
|
||||||
|
// GCSError represents a returned error from GCS
|
||||||
|
type GCSError struct {
|
||||||
|
Error struct {
|
||||||
|
Code int `json:"code"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
Errors []struct {
|
||||||
|
Message string `json:"message"`
|
||||||
|
Reason string `json:"reason"`
|
||||||
|
LocationType string `json:"locationType"`
|
||||||
|
Location string `json:"location"`
|
||||||
|
} `json:"errors"`
|
||||||
|
} `json:"error"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GCSListing contains only a subset of returned properties
|
||||||
|
type GCSListing struct {
|
||||||
|
IsTruncated string `json:"nextPageToken"`
|
||||||
|
Items []struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
LastModified string `json:"updated"`
|
||||||
|
Size string `json:"size"`
|
||||||
|
} `json:"items"`
|
||||||
|
}
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -42,6 +43,8 @@ func NewGobusterS3(globalopts *libgobuster.Options, opts *OptionsS3) (*GobusterS
|
||||||
Timeout: opts.Timeout,
|
Timeout: opts.Timeout,
|
||||||
UserAgent: opts.UserAgent,
|
UserAgent: opts.UserAgent,
|
||||||
NoTLSValidation: opts.NoTLSValidation,
|
NoTLSValidation: opts.NoTLSValidation,
|
||||||
|
RetryOnTimeout: opts.RetryOnTimeout,
|
||||||
|
RetryAttempts: opts.RetryAttempts,
|
||||||
}
|
}
|
||||||
|
|
||||||
httpOpts := libgobuster.HTTPOptions{
|
httpOpts := libgobuster.HTTPOptions{
|
||||||
|
@ -65,36 +68,55 @@ func (s *GobusterS3) Name() string {
|
||||||
return "S3 bucket enumeration"
|
return "S3 bucket enumeration"
|
||||||
}
|
}
|
||||||
|
|
||||||
// RequestsPerRun returns the number of requests this plugin makes per single wordlist item
|
|
||||||
func (s *GobusterS3) RequestsPerRun() int {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// PreRun is the pre run implementation of GobusterS3
|
// PreRun is the pre run implementation of GobusterS3
|
||||||
func (s *GobusterS3) PreRun(ctx context.Context) error {
|
func (s *GobusterS3) PreRun(ctx context.Context) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run is the process implementation of GobusterS3
|
// ProcessWord is the process implementation of GobusterS3
|
||||||
func (s *GobusterS3) Run(ctx context.Context, word string, resChannel chan<- libgobuster.Result) error {
|
func (s *GobusterS3) ProcessWord(ctx context.Context, word string, progress *libgobuster.Progress) error {
|
||||||
// only check for valid bucket names
|
// only check for valid bucket names
|
||||||
if !s.isValidBucketName(word) {
|
if !s.isValidBucketName(word) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
bucketURL := fmt.Sprintf("https://%s.s3.amazonaws.com/?max-keys=%d", word, s.options.MaxFilesToList)
|
bucketURL := fmt.Sprintf("https://%s.s3.amazonaws.com/?max-keys=%d", word, s.options.MaxFilesToList)
|
||||||
status, _, _, body, err := s.http.Request(ctx, bucketURL, libgobuster.RequestOptions{ReturnBody: true})
|
|
||||||
if err != nil {
|
tries := 1
|
||||||
return err
|
if s.options.RetryOnTimeout && s.options.RetryAttempts > 0 {
|
||||||
|
// add it so it will be the overall max requests
|
||||||
|
tries += s.options.RetryAttempts
|
||||||
}
|
}
|
||||||
|
|
||||||
if status == nil || body == nil {
|
var statusCode int
|
||||||
|
var body []byte
|
||||||
|
for i := 1; i <= tries; i++ {
|
||||||
|
var err error
|
||||||
|
statusCode, _, _, body, err = s.http.Request(ctx, bucketURL, libgobuster.RequestOptions{ReturnBody: true})
|
||||||
|
if err != nil {
|
||||||
|
// check if it's a timeout and if we should try again and try again
|
||||||
|
// otherwise the timeout error is raised
|
||||||
|
if netErr, ok := err.(net.Error); ok && netErr.Timeout() && i != tries {
|
||||||
|
continue
|
||||||
|
} else if strings.Contains(err.Error(), "invalid control character in URL") {
|
||||||
|
// put error in error chan so it's printed out and ignore it
|
||||||
|
// so gobuster will not quit
|
||||||
|
progress.ErrorChan <- err
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if statusCode == 0 || body == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// looks like 404 and 400 are the only negative status codes
|
// looks like 404 and 400 are the only negative status codes
|
||||||
found := false
|
found := false
|
||||||
switch *status {
|
switch statusCode {
|
||||||
case http.StatusBadRequest:
|
case http.StatusBadRequest:
|
||||||
case http.StatusNotFound:
|
case http.StatusNotFound:
|
||||||
found = false
|
found = false
|
||||||
|
@ -139,7 +161,7 @@ func (s *GobusterS3) Run(ctx context.Context, word string, resChannel chan<- lib
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
resChannel <- Result{
|
progress.ResultChan <- Result{
|
||||||
Found: found,
|
Found: found,
|
||||||
BucketName: word,
|
BucketName: word,
|
||||||
Status: extraStr,
|
Status: extraStr,
|
||||||
|
@ -148,6 +170,10 @@ func (s *GobusterS3) Run(ctx context.Context, word string, resChannel chan<- lib
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *GobusterS3) AdditionalWords(word string) []string {
|
||||||
|
return []string{}
|
||||||
|
}
|
||||||
|
|
||||||
// GetConfigString returns the string representation of the current config
|
// GetConfigString returns the string representation of the current config
|
||||||
func (s *GobusterS3) GetConfigString() (string, error) {
|
func (s *GobusterS3) GetConfigString() (string, error) {
|
||||||
var buffer bytes.Buffer
|
var buffer bytes.Buffer
|
||||||
|
|
|
@ -2,7 +2,12 @@ package gobusters3
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
|
||||||
|
"github.com/fatih/color"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
green = color.New(color.FgGreen).FprintfFunc()
|
||||||
)
|
)
|
||||||
|
|
||||||
// Result represents a single result
|
// Result represents a single result
|
||||||
|
@ -16,19 +21,14 @@ type Result struct {
|
||||||
func (r Result) ResultToString() (string, error) {
|
func (r Result) ResultToString() (string, error) {
|
||||||
buf := &bytes.Buffer{}
|
buf := &bytes.Buffer{}
|
||||||
|
|
||||||
if _, err := fmt.Fprintf(buf, "http://%s.s3.amazonaws.com/", r.BucketName); err != nil {
|
c := green
|
||||||
return "", err
|
|
||||||
}
|
c(buf, "http://%s.s3.amazonaws.com/", r.BucketName)
|
||||||
|
|
||||||
if r.Status != "" {
|
if r.Status != "" {
|
||||||
if _, err := fmt.Fprintf(buf, " [%s]", r.Status); err != nil {
|
c(buf, " [%s]", r.Status)
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := fmt.Fprintf(buf, "\n"); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
}
|
||||||
|
c(buf, "\n")
|
||||||
|
|
||||||
str := buf.String()
|
str := buf.String()
|
||||||
return str, nil
|
return str, nil
|
||||||
|
|
|
@ -5,6 +5,8 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
"text/tabwriter"
|
"text/tabwriter"
|
||||||
|
@ -16,12 +18,12 @@ import (
|
||||||
|
|
||||||
// GobusterVhost is the main type to implement the interface
|
// GobusterVhost is the main type to implement the interface
|
||||||
type GobusterVhost struct {
|
type GobusterVhost struct {
|
||||||
options *OptionsVhost
|
options *OptionsVhost
|
||||||
globalopts *libgobuster.Options
|
globalopts *libgobuster.Options
|
||||||
http *libgobuster.HTTPClient
|
http *libgobuster.HTTPClient
|
||||||
domain string
|
domain string
|
||||||
baseline1 []byte
|
normalBody []byte
|
||||||
baseline2 []byte
|
abnormalBody []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewGobusterVhost creates a new initialized GobusterDir
|
// NewGobusterVhost creates a new initialized GobusterDir
|
||||||
|
@ -44,6 +46,8 @@ func NewGobusterVhost(globalopts *libgobuster.Options, opts *OptionsVhost) (*Gob
|
||||||
Timeout: opts.Timeout,
|
Timeout: opts.Timeout,
|
||||||
UserAgent: opts.UserAgent,
|
UserAgent: opts.UserAgent,
|
||||||
NoTLSValidation: opts.NoTLSValidation,
|
NoTLSValidation: opts.NoTLSValidation,
|
||||||
|
RetryOnTimeout: opts.RetryOnTimeout,
|
||||||
|
RetryAttempts: opts.RetryAttempts,
|
||||||
}
|
}
|
||||||
|
|
||||||
httpOpts := libgobuster.HTTPOptions{
|
httpOpts := libgobuster.HTTPOptions{
|
||||||
|
@ -69,11 +73,6 @@ func (v *GobusterVhost) Name() string {
|
||||||
return "VHOST enumeration"
|
return "VHOST enumeration"
|
||||||
}
|
}
|
||||||
|
|
||||||
// RequestsPerRun returns the number of requests this plugin makes per single wordlist item
|
|
||||||
func (v *GobusterVhost) RequestsPerRun() int {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// PreRun is the pre run implementation of gobusterdir
|
// PreRun is the pre run implementation of gobusterdir
|
||||||
func (v *GobusterVhost) PreRun(ctx context.Context) error {
|
func (v *GobusterVhost) PreRun(ctx context.Context) error {
|
||||||
// add trailing slash
|
// add trailing slash
|
||||||
|
@ -91,25 +90,25 @@ func (v *GobusterVhost) PreRun(ctx context.Context) error {
|
||||||
v.domain = urlParsed.Host
|
v.domain = urlParsed.Host
|
||||||
}
|
}
|
||||||
|
|
||||||
// request default vhost for baseline1
|
// request default vhost for normalBody
|
||||||
_, _, _, tmp, err := v.http.Request(ctx, v.options.URL, libgobuster.RequestOptions{ReturnBody: true})
|
_, _, _, body, err := v.http.Request(ctx, v.options.URL, libgobuster.RequestOptions{ReturnBody: true})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to connect to %s: %w", v.options.URL, err)
|
return fmt.Errorf("unable to connect to %s: %w", v.options.URL, err)
|
||||||
}
|
}
|
||||||
v.baseline1 = tmp
|
v.normalBody = body
|
||||||
|
|
||||||
// request non existent vhost for baseline2
|
// request non existent vhost for abnormalBody
|
||||||
subdomain := fmt.Sprintf("%s.%s", uuid.New(), v.domain)
|
subdomain := fmt.Sprintf("%s.%s", uuid.New(), v.domain)
|
||||||
_, _, _, tmp, err = v.http.Request(ctx, v.options.URL, libgobuster.RequestOptions{Host: subdomain, ReturnBody: true})
|
_, _, _, body, err = v.http.Request(ctx, v.options.URL, libgobuster.RequestOptions{Host: subdomain, ReturnBody: true})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to connect to %s: %w", v.options.URL, err)
|
return fmt.Errorf("unable to connect to %s: %w", v.options.URL, err)
|
||||||
}
|
}
|
||||||
v.baseline2 = tmp
|
v.abnormalBody = body
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run is the process implementation of gobusterdir
|
// ProcessWord is the process implementation of gobusterdir
|
||||||
func (v *GobusterVhost) Run(ctx context.Context, word string, resChannel chan<- libgobuster.Result) error {
|
func (v *GobusterVhost) ProcessWord(ctx context.Context, word string, progress *libgobuster.Progress) error {
|
||||||
var subdomain string
|
var subdomain string
|
||||||
if v.options.AppendDomain {
|
if v.options.AppendDomain {
|
||||||
subdomain = fmt.Sprintf("%s.%s", word, v.domain)
|
subdomain = fmt.Sprintf("%s.%s", word, v.domain)
|
||||||
|
@ -117,23 +116,49 @@ func (v *GobusterVhost) Run(ctx context.Context, word string, resChannel chan<-
|
||||||
// wordlist needs to include full domains
|
// wordlist needs to include full domains
|
||||||
subdomain = word
|
subdomain = word
|
||||||
}
|
}
|
||||||
status, size, header, body, err := v.http.Request(ctx, v.options.URL, libgobuster.RequestOptions{Host: subdomain, ReturnBody: true})
|
|
||||||
if err != nil {
|
tries := 1
|
||||||
return err
|
if v.options.RetryOnTimeout && v.options.RetryAttempts > 0 {
|
||||||
|
// add it so it will be the overall max requests
|
||||||
|
tries += v.options.RetryAttempts
|
||||||
|
}
|
||||||
|
|
||||||
|
var statusCode int
|
||||||
|
var size int64
|
||||||
|
var header http.Header
|
||||||
|
var body []byte
|
||||||
|
for i := 1; i <= tries; i++ {
|
||||||
|
var err error
|
||||||
|
statusCode, size, header, body, err = v.http.Request(ctx, v.options.URL, libgobuster.RequestOptions{Host: subdomain, ReturnBody: true})
|
||||||
|
if err != nil {
|
||||||
|
// check if it's a timeout and if we should try again and try again
|
||||||
|
// otherwise the timeout error is raised
|
||||||
|
if netErr, ok := err.(net.Error); ok && netErr.Timeout() && i != tries {
|
||||||
|
continue
|
||||||
|
} else if strings.Contains(err.Error(), "invalid control character in URL") {
|
||||||
|
// put error in error chan so it's printed out and ignore it
|
||||||
|
// so gobuster will not quit
|
||||||
|
progress.ErrorChan <- err
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
// subdomain must not match default vhost and non existent vhost
|
// subdomain must not match default vhost and non existent vhost
|
||||||
// or verbose mode is enabled
|
// or verbose mode is enabled
|
||||||
found := !bytes.Equal(body, v.baseline1) && !bytes.Equal(body, v.baseline2)
|
found := body != nil && !bytes.Equal(body, v.normalBody) && !bytes.Equal(body, v.abnormalBody)
|
||||||
if (found && !helper.SliceContains(v.options.ExcludeLength, int(size))) || v.globalopts.Verbose {
|
if (found && !helper.SliceContains(v.options.ExcludeLength, int(size))) || v.globalopts.Verbose {
|
||||||
resultStatus := false
|
resultStatus := false
|
||||||
if found {
|
if found {
|
||||||
resultStatus = true
|
resultStatus = true
|
||||||
}
|
}
|
||||||
resChannel <- Result{
|
progress.ResultChan <- Result{
|
||||||
Found: resultStatus,
|
Found: resultStatus,
|
||||||
Vhost: subdomain,
|
Vhost: subdomain,
|
||||||
StatusCode: *status,
|
StatusCode: statusCode,
|
||||||
Size: size,
|
Size: size,
|
||||||
Header: header,
|
Header: header,
|
||||||
}
|
}
|
||||||
|
@ -141,6 +166,10 @@ func (v *GobusterVhost) Run(ctx context.Context, word string, resChannel chan<-
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (v *GobusterVhost) AdditionalWords(word string) []string {
|
||||||
|
return []string{}
|
||||||
|
}
|
||||||
|
|
||||||
// GetConfigString returns the string representation of the current config
|
// GetConfigString returns the string representation of the current config
|
||||||
func (v *GobusterVhost) GetConfigString() (string, error) {
|
func (v *GobusterVhost) GetConfigString() (string, error) {
|
||||||
var buffer bytes.Buffer
|
var buffer bytes.Buffer
|
||||||
|
|
|
@ -1,9 +1,19 @@
|
||||||
package gobustervhost
|
package gobustervhost
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/fatih/color"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
white = color.New(color.FgWhite).SprintFunc()
|
||||||
|
yellow = color.New(color.FgYellow).SprintFunc()
|
||||||
|
green = color.New(color.FgGreen).SprintFunc()
|
||||||
|
blue = color.New(color.FgBlue).SprintFunc()
|
||||||
|
red = color.New(color.FgRed).SprintFunc()
|
||||||
|
cyan = color.New(color.FgCyan).SprintFunc()
|
||||||
)
|
)
|
||||||
|
|
||||||
// Result represents a single result
|
// Result represents a single result
|
||||||
|
@ -17,17 +27,29 @@ type Result struct {
|
||||||
|
|
||||||
// ResultToString converts the Result to it's textual representation
|
// ResultToString converts the Result to it's textual representation
|
||||||
func (r Result) ResultToString() (string, error) {
|
func (r Result) ResultToString() (string, error) {
|
||||||
buf := &bytes.Buffer{}
|
statusText := yellow("Missed")
|
||||||
|
|
||||||
statusText := "Missed"
|
|
||||||
if r.Found {
|
if r.Found {
|
||||||
statusText = "Found"
|
statusText = green("Found")
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := fmt.Fprintf(buf, "%s: %s (Status: %d) [Size: %d]\n", statusText, r.Vhost, r.StatusCode, r.Size); err != nil {
|
statusCodeColor := white
|
||||||
return "", err
|
if r.StatusCode == 200 {
|
||||||
|
statusCodeColor = green
|
||||||
|
} else if r.StatusCode >= 300 && r.StatusCode < 400 {
|
||||||
|
statusCodeColor = cyan
|
||||||
|
} else if r.StatusCode >= 400 && r.StatusCode < 500 {
|
||||||
|
statusCodeColor = yellow
|
||||||
|
} else if r.StatusCode >= 500 && r.StatusCode < 600 {
|
||||||
|
statusCodeColor = red
|
||||||
}
|
}
|
||||||
|
|
||||||
s := buf.String()
|
statusCode := statusCodeColor(fmt.Sprintf("Status: %d", r.StatusCode))
|
||||||
return s, nil
|
|
||||||
|
location := r.Header.Get("Location")
|
||||||
|
locationString := ""
|
||||||
|
if location != "" {
|
||||||
|
locationString = blue(fmt.Sprintf(" [--> %s]", location))
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("%s: %s %s [Size: %d]%s\n", statusText, r.Vhost, statusCode, r.Size, locationString), nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,12 +9,13 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// ParseExtensions parses the extensions provided as a comma separated list
|
// ParseExtensions parses the extensions provided as a comma separated list
|
||||||
func ParseExtensions(extensions string) (libgobuster.StringSet, error) {
|
func ParseExtensions(extensions string) (libgobuster.Set[string], error) {
|
||||||
|
ret := libgobuster.NewSet[string]()
|
||||||
|
|
||||||
if extensions == "" {
|
if extensions == "" {
|
||||||
return libgobuster.StringSet{}, nil
|
return ret, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
ret := libgobuster.NewStringSet()
|
|
||||||
for _, e := range strings.Split(extensions, ",") {
|
for _, e := range strings.Split(extensions, ",") {
|
||||||
e = strings.TrimSpace(e)
|
e = strings.TrimSpace(e)
|
||||||
// remove leading . from extensions
|
// remove leading . from extensions
|
||||||
|
@ -24,17 +25,18 @@ func ParseExtensions(extensions string) (libgobuster.StringSet, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseCommaSeparatedInt parses the status codes provided as a comma separated list
|
// ParseCommaSeparatedInt parses the status codes provided as a comma separated list
|
||||||
func ParseCommaSeparatedInt(inputString string) (libgobuster.IntSet, error) {
|
func ParseCommaSeparatedInt(inputString string) (libgobuster.Set[int], error) {
|
||||||
|
ret := libgobuster.NewSet[int]()
|
||||||
|
|
||||||
if inputString == "" {
|
if inputString == "" {
|
||||||
return libgobuster.IntSet{}, nil
|
return ret, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
ret := libgobuster.NewIntSet()
|
|
||||||
for _, c := range strings.Split(inputString, ",") {
|
for _, c := range strings.Split(inputString, ",") {
|
||||||
c = strings.TrimSpace(c)
|
c = strings.TrimSpace(c)
|
||||||
i, err := strconv.Atoi(c)
|
i, err := strconv.Atoi(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return libgobuster.IntSet{}, fmt.Errorf("invalid string given: %s", c)
|
return libgobuster.NewSet[int](), fmt.Errorf("invalid string given: %s", c)
|
||||||
}
|
}
|
||||||
ret.Add(i)
|
ret.Add(i)
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,14 +12,14 @@ func TestParseExtensions(t *testing.T) {
|
||||||
var tt = []struct {
|
var tt = []struct {
|
||||||
testName string
|
testName string
|
||||||
extensions string
|
extensions string
|
||||||
expectedExtensions libgobuster.StringSet
|
expectedExtensions libgobuster.Set[string]
|
||||||
expectedError string
|
expectedError string
|
||||||
}{
|
}{
|
||||||
{"Valid extensions", "php,asp,txt", libgobuster.StringSet{Set: map[string]bool{"php": true, "asp": true, "txt": true}}, ""},
|
{"Valid extensions", "php,asp,txt", libgobuster.Set[string]{Set: map[string]bool{"php": true, "asp": true, "txt": true}}, ""},
|
||||||
{"Spaces", "php, asp , txt", libgobuster.StringSet{Set: map[string]bool{"php": true, "asp": true, "txt": true}}, ""},
|
{"Spaces", "php, asp , txt", libgobuster.Set[string]{Set: map[string]bool{"php": true, "asp": true, "txt": true}}, ""},
|
||||||
{"Double extensions", "php,asp,txt,php,asp,txt", libgobuster.StringSet{Set: map[string]bool{"php": true, "asp": true, "txt": true}}, ""},
|
{"Double extensions", "php,asp,txt,php,asp,txt", libgobuster.Set[string]{Set: map[string]bool{"php": true, "asp": true, "txt": true}}, ""},
|
||||||
{"Leading dot", ".php,asp,.txt", libgobuster.StringSet{Set: map[string]bool{"php": true, "asp": true, "txt": true}}, ""},
|
{"Leading dot", ".php,asp,.txt", libgobuster.Set[string]{Set: map[string]bool{"php": true, "asp": true, "txt": true}}, ""},
|
||||||
{"Empty string", "", libgobuster.NewStringSet(), "invalid extension string provided"},
|
{"Empty string", "", libgobuster.NewSet[string](), "invalid extension string provided"},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, x := range tt {
|
for _, x := range tt {
|
||||||
|
@ -43,15 +43,15 @@ func TestParseCommaSeparatedInt(t *testing.T) {
|
||||||
var tt = []struct {
|
var tt = []struct {
|
||||||
testName string
|
testName string
|
||||||
stringCodes string
|
stringCodes string
|
||||||
expectedCodes libgobuster.IntSet
|
expectedCodes libgobuster.Set[int]
|
||||||
expectedError string
|
expectedError string
|
||||||
}{
|
}{
|
||||||
{"Valid codes", "200,100,202", libgobuster.IntSet{Set: map[int]bool{100: true, 200: true, 202: true}}, ""},
|
{"Valid codes", "200,100,202", libgobuster.Set[int]{Set: map[int]bool{100: true, 200: true, 202: true}}, ""},
|
||||||
{"Spaces", "200, 100 , 202", libgobuster.IntSet{Set: map[int]bool{100: true, 200: true, 202: true}}, ""},
|
{"Spaces", "200, 100 , 202", libgobuster.Set[int]{Set: map[int]bool{100: true, 200: true, 202: true}}, ""},
|
||||||
{"Double codes", "200, 100, 202, 100", libgobuster.IntSet{Set: map[int]bool{100: true, 200: true, 202: true}}, ""},
|
{"Double codes", "200, 100, 202, 100", libgobuster.Set[int]{Set: map[int]bool{100: true, 200: true, 202: true}}, ""},
|
||||||
{"Invalid code", "200,AAA", libgobuster.NewIntSet(), "invalid string given: AAA"},
|
{"Invalid code", "200,AAA", libgobuster.NewSet[int](), "invalid string given: AAA"},
|
||||||
{"Invalid integer", "2000000000000000000000000000000", libgobuster.NewIntSet(), "invalid string given: 2000000000000000000000000000000"},
|
{"Invalid integer", "2000000000000000000000000000000", libgobuster.NewSet[int](), "invalid string given: 2000000000000000000000000000000"},
|
||||||
{"Empty string", "", libgobuster.NewIntSet(), "invalid string provided"},
|
{"Empty string", "", libgobuster.NewSet[int](), "invalid string provided"},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, x := range tt {
|
for _, x := range tt {
|
||||||
|
@ -74,14 +74,14 @@ func BenchmarkParseExtensions(b *testing.B) {
|
||||||
var tt = []struct {
|
var tt = []struct {
|
||||||
testName string
|
testName string
|
||||||
extensions string
|
extensions string
|
||||||
expectedExtensions libgobuster.StringSet
|
expectedExtensions libgobuster.Set[string]
|
||||||
expectedError string
|
expectedError string
|
||||||
}{
|
}{
|
||||||
{"Valid extensions", "php,asp,txt", libgobuster.StringSet{Set: map[string]bool{"php": true, "asp": true, "txt": true}}, ""},
|
{"Valid extensions", "php,asp,txt", libgobuster.Set[string]{Set: map[string]bool{"php": true, "asp": true, "txt": true}}, ""},
|
||||||
{"Spaces", "php, asp , txt", libgobuster.StringSet{Set: map[string]bool{"php": true, "asp": true, "txt": true}}, ""},
|
{"Spaces", "php, asp , txt", libgobuster.Set[string]{Set: map[string]bool{"php": true, "asp": true, "txt": true}}, ""},
|
||||||
{"Double extensions", "php,asp,txt,php,asp,txt", libgobuster.StringSet{Set: map[string]bool{"php": true, "asp": true, "txt": true}}, ""},
|
{"Double extensions", "php,asp,txt,php,asp,txt", libgobuster.Set[string]{Set: map[string]bool{"php": true, "asp": true, "txt": true}}, ""},
|
||||||
{"Leading dot", ".php,asp,.txt", libgobuster.StringSet{Set: map[string]bool{"php": true, "asp": true, "txt": true}}, ""},
|
{"Leading dot", ".php,asp,.txt", libgobuster.Set[string]{Set: map[string]bool{"php": true, "asp": true, "txt": true}}, ""},
|
||||||
{"Empty string", "", libgobuster.NewStringSet(), "invalid extension string provided"},
|
{"Empty string", "", libgobuster.NewSet[string](), "invalid extension string provided"},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, x := range tt {
|
for _, x := range tt {
|
||||||
|
@ -98,15 +98,15 @@ func BenchmarkParseCommaSeparatedInt(b *testing.B) {
|
||||||
var tt = []struct {
|
var tt = []struct {
|
||||||
testName string
|
testName string
|
||||||
stringCodes string
|
stringCodes string
|
||||||
expectedCodes libgobuster.IntSet
|
expectedCodes libgobuster.Set[int]
|
||||||
expectedError string
|
expectedError string
|
||||||
}{
|
}{
|
||||||
{"Valid codes", "200,100,202", libgobuster.IntSet{Set: map[int]bool{100: true, 200: true, 202: true}}, ""},
|
{"Valid codes", "200,100,202", libgobuster.Set[int]{Set: map[int]bool{100: true, 200: true, 202: true}}, ""},
|
||||||
{"Spaces", "200, 100 , 202", libgobuster.IntSet{Set: map[int]bool{100: true, 200: true, 202: true}}, ""},
|
{"Spaces", "200, 100 , 202", libgobuster.Set[int]{Set: map[int]bool{100: true, 200: true, 202: true}}, ""},
|
||||||
{"Double codes", "200, 100, 202, 100", libgobuster.IntSet{Set: map[int]bool{100: true, 200: true, 202: true}}, ""},
|
{"Double codes", "200, 100, 202, 100", libgobuster.Set[int]{Set: map[int]bool{100: true, 200: true, 202: true}}, ""},
|
||||||
{"Invalid code", "200,AAA", libgobuster.NewIntSet(), "invalid string given: AAA"},
|
{"Invalid code", "200,AAA", libgobuster.NewSet[int](), "invalid string given: AAA"},
|
||||||
{"Invalid integer", "2000000000000000000000000000000", libgobuster.NewIntSet(), "invalid string given: 2000000000000000000000000000000"},
|
{"Invalid integer", "2000000000000000000000000000000", libgobuster.NewSet[int](), "invalid string given: 2000000000000000000000000000000"},
|
||||||
{"Empty string", "", libgobuster.NewIntSet(), "invalid string string provided"},
|
{"Empty string", "", libgobuster.NewSet[int](), "invalid string string provided"},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, x := range tt {
|
for _, x := range tt {
|
||||||
|
|
|
@ -5,47 +5,41 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"sort"
|
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// IntSet is a set of Ints
|
// Set is a set of Ts
|
||||||
type IntSet struct {
|
type Set[T comparable] struct {
|
||||||
Set map[int]bool
|
Set map[T]bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// StringSet is a set of Strings
|
// NewSSet creates a new initialized Set
|
||||||
type StringSet struct {
|
func NewSet[T comparable]() Set[T] {
|
||||||
Set map[string]bool
|
return Set[T]{Set: map[T]bool{}}
|
||||||
}
|
|
||||||
|
|
||||||
// NewStringSet creates a new initialized StringSet
|
|
||||||
func NewStringSet() StringSet {
|
|
||||||
return StringSet{Set: map[string]bool{}}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add an element to a set
|
// Add an element to a set
|
||||||
func (set *StringSet) Add(s string) bool {
|
func (set *Set[T]) Add(s T) bool {
|
||||||
_, found := set.Set[s]
|
_, found := set.Set[s]
|
||||||
set.Set[s] = true
|
set.Set[s] = true
|
||||||
return !found
|
return !found
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddRange adds a list of elements to a set
|
// AddRange adds a list of elements to a set
|
||||||
func (set *StringSet) AddRange(ss []string) {
|
func (set *Set[T]) AddRange(ss []T) {
|
||||||
for _, s := range ss {
|
for _, s := range ss {
|
||||||
set.Set[s] = true
|
set.Set[s] = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Contains tests if an element is in a set
|
// Contains tests if an element is in a set
|
||||||
func (set *StringSet) Contains(s string) bool {
|
func (set *Set[T]) Contains(s T) bool {
|
||||||
_, found := set.Set[s]
|
_, found := set.Set[s]
|
||||||
return found
|
return found
|
||||||
}
|
}
|
||||||
|
|
||||||
// ContainsAny checks if any of the elements exist
|
// ContainsAny checks if any of the elements exist
|
||||||
func (set *StringSet) ContainsAny(ss []string) bool {
|
func (set *Set[T]) ContainsAny(ss []T) bool {
|
||||||
for _, s := range ss {
|
for _, s := range ss {
|
||||||
if set.Set[s] {
|
if set.Set[s] {
|
||||||
return true
|
return true
|
||||||
|
@ -55,58 +49,21 @@ func (set *StringSet) ContainsAny(ss []string) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Length returns the length of the Set
|
// Length returns the length of the Set
|
||||||
func (set *StringSet) Length() int {
|
func (set *Set[T]) Length() int {
|
||||||
return len(set.Set)
|
return len(set.Set)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stringify the set
|
// Stringify the set
|
||||||
func (set *StringSet) Stringify() string {
|
func (set *Set[T]) Stringify() string {
|
||||||
values := make([]string, len(set.Set))
|
values := make([]string, len(set.Set))
|
||||||
i := 0
|
i := 0
|
||||||
for s := range set.Set {
|
for s := range set.Set {
|
||||||
values[i] = s
|
values[i] = fmt.Sprint(s)
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
return strings.Join(values, ",")
|
return strings.Join(values, ",")
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewIntSet creates a new initialized IntSet
|
|
||||||
func NewIntSet() IntSet {
|
|
||||||
return IntSet{Set: map[int]bool{}}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add adds an element to a set
|
|
||||||
func (set *IntSet) Add(i int) bool {
|
|
||||||
_, found := set.Set[i]
|
|
||||||
set.Set[i] = true
|
|
||||||
return !found
|
|
||||||
}
|
|
||||||
|
|
||||||
// Contains tests if an element is in a set
|
|
||||||
func (set *IntSet) Contains(i int) bool {
|
|
||||||
_, found := set.Set[i]
|
|
||||||
return found
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stringify the set
|
|
||||||
func (set *IntSet) Stringify() string {
|
|
||||||
values := make([]int, len(set.Set))
|
|
||||||
i := 0
|
|
||||||
for s := range set.Set {
|
|
||||||
values[i] = s
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
sort.Ints(values)
|
|
||||||
|
|
||||||
delim := ","
|
|
||||||
return strings.Trim(strings.Join(strings.Fields(fmt.Sprint(values)), delim), "[]")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Length returns the length of the Set
|
|
||||||
func (set *IntSet) Length() int {
|
|
||||||
return len(set.Set)
|
|
||||||
}
|
|
||||||
|
|
||||||
func lineCounter(r io.Reader) (int, error) {
|
func lineCounter(r io.Reader) (int, error) {
|
||||||
buf := make([]byte, 32*1024)
|
buf := make([]byte, 32*1024)
|
||||||
count := 1
|
count := 1
|
||||||
|
|
|
@ -2,77 +2,109 @@ package libgobuster
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"testing/iotest"
|
"testing/iotest"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNewStringSet(t *testing.T) {
|
func TestNewSet(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
if NewStringSet().Set == nil {
|
if NewSet[string]().Set == nil {
|
||||||
t.Fatal("newStringSet returned nil Set")
|
t.Fatal("NewSet[string] returned nil Set")
|
||||||
|
}
|
||||||
|
|
||||||
|
if NewSet[int]().Set == nil {
|
||||||
|
t.Fatal("NewSet[int] returned nil Set")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewIntSet(t *testing.T) {
|
func TestSetAdd(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
if NewIntSet().Set == nil {
|
x := NewSet[string]()
|
||||||
t.Fatal("newIntSet returned nil Set")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStringSetAdd(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
x := NewStringSet()
|
|
||||||
x.Add("test")
|
x.Add("test")
|
||||||
if len(x.Set) != 1 {
|
if len(x.Set) != 1 {
|
||||||
t.Fatalf("Unexpected size. Should have 1 Got %v", len(x.Set))
|
t.Fatalf("Unexpected string size. Should have 1 Got %v", len(x.Set))
|
||||||
|
}
|
||||||
|
|
||||||
|
y := NewSet[int]()
|
||||||
|
y.Add(1)
|
||||||
|
if len(y.Set) != 1 {
|
||||||
|
t.Fatalf("Unexpected int size. Should have 1 Got %v", len(y.Set))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStringSetAddDouble(t *testing.T) {
|
func TestSetAddDouble(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
x := NewStringSet()
|
x := NewSet[string]()
|
||||||
x.Add("test")
|
x.Add("test")
|
||||||
x.Add("test")
|
x.Add("test")
|
||||||
if len(x.Set) != 1 {
|
if len(x.Set) != 1 {
|
||||||
t.Fatalf("Unexpected size. Should have 1 Got %d", len(x.Set))
|
t.Fatalf("Unexpected string size. Should be 1 (unique) Got %v", len(x.Set))
|
||||||
|
}
|
||||||
|
|
||||||
|
y := NewSet[int]()
|
||||||
|
y.Add(1)
|
||||||
|
y.Add(1)
|
||||||
|
if len(y.Set) != 1 {
|
||||||
|
t.Fatalf("Unexpected int size. Should be 1 (unique) Got %v", len(y.Set))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStringSetAddRange(t *testing.T) {
|
func TestSetAddRange(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
x := NewStringSet()
|
x := NewSet[string]()
|
||||||
x.AddRange([]string{"string1", "string2"})
|
x.AddRange([]string{"string1", "string2"})
|
||||||
if len(x.Set) != 2 {
|
if len(x.Set) != 2 {
|
||||||
t.Fatalf("Unexpected size. Should have 2 Got %d", len(x.Set))
|
t.Fatalf("Unexpected string size. Should have 2 Got %v", len(x.Set))
|
||||||
|
}
|
||||||
|
|
||||||
|
y := NewSet[int]()
|
||||||
|
y.AddRange([]int{1, 2})
|
||||||
|
if len(y.Set) != 2 {
|
||||||
|
t.Fatalf("Unexpected int size. Should have 2 Got %v", len(y.Set))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStringSetAddRangeDouble(t *testing.T) {
|
func TestSetAddRangeDouble(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
x := NewStringSet()
|
x := NewSet[string]()
|
||||||
x.AddRange([]string{"string1", "string2", "string1", "string2"})
|
x.AddRange([]string{"string1", "string2", "string1", "string2"})
|
||||||
if len(x.Set) != 2 {
|
if len(x.Set) != 2 {
|
||||||
t.Fatalf("Unexpected size. Should have 2 Got %d", len(x.Set))
|
t.Fatalf("Unexpected string size. Should be 2 (unique) Got %v", len(x.Set))
|
||||||
|
}
|
||||||
|
|
||||||
|
y := NewSet[int]()
|
||||||
|
y.AddRange([]int{1, 2, 1, 2})
|
||||||
|
if len(y.Set) != 2 {
|
||||||
|
t.Fatalf("Unexpected int size. Should be 2 (unique) Got %v", len(y.Set))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStringSetContains(t *testing.T) {
|
func TestSetContains(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
x := NewStringSet()
|
x := NewSet[string]()
|
||||||
v := []string{"string1", "string2", "1234", "5678"}
|
v := []string{"string1", "string2", "1234", "5678"}
|
||||||
x.AddRange(v)
|
x.AddRange(v)
|
||||||
for _, y := range v {
|
for _, i := range v {
|
||||||
if !x.Contains(y) {
|
if !x.Contains(i) {
|
||||||
t.Fatalf("Did not find value %s in array. %v", y, x.Set)
|
t.Fatalf("Did not find value %s in array. %v", i, x.Set)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
y := NewSet[int]()
|
||||||
|
v2 := []int{1, 2312, 123121, 999, -99}
|
||||||
|
y.AddRange(v2)
|
||||||
|
for _, i := range v2 {
|
||||||
|
if !y.Contains(i) {
|
||||||
|
t.Fatalf("Did not find value %d in array. %v", i, y.Set)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStringSetContainsAny(t *testing.T) {
|
func TestSetContainsAny(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
x := NewStringSet()
|
x := NewSet[string]()
|
||||||
v := []string{"string1", "string2", "1234", "5678"}
|
v := []string{"string1", "string2", "1234", "5678"}
|
||||||
x.AddRange(v)
|
x.AddRange(v)
|
||||||
if !x.ContainsAny(v) {
|
if !x.ContainsAny(v) {
|
||||||
|
@ -83,70 +115,45 @@ func TestStringSetContainsAny(t *testing.T) {
|
||||||
if x.ContainsAny([]string{"mmmm", "nnnnn"}) {
|
if x.ContainsAny([]string{"mmmm", "nnnnn"}) {
|
||||||
t.Fatal("Found unexpected values")
|
t.Fatal("Found unexpected values")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
y := NewSet[int]()
|
||||||
|
v2 := []int{1, 2312, 123121, 999, -99}
|
||||||
|
y.AddRange(v2)
|
||||||
|
if !y.ContainsAny(v2) {
|
||||||
|
t.Fatalf("Did not find any")
|
||||||
|
}
|
||||||
|
|
||||||
|
// test not found
|
||||||
|
if y.ContainsAny([]int{9235, 2398532}) {
|
||||||
|
t.Fatal("Found unexpected values")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStringSetStringify(t *testing.T) {
|
func TestSetStringify(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
x := NewStringSet()
|
x := NewSet[string]()
|
||||||
v := []string{"string1", "string2", "1234", "5678"}
|
v := []string{"string1", "string2", "1234", "5678"}
|
||||||
x.AddRange(v)
|
x.AddRange(v)
|
||||||
z := x.Stringify()
|
z := x.Stringify()
|
||||||
// order is random
|
// order is random
|
||||||
for _, y := range v {
|
for _, i := range v {
|
||||||
if !strings.Contains(z, y) {
|
if !strings.Contains(z, i) {
|
||||||
t.Fatalf("Did not find value %q in %q", y, z)
|
t.Fatalf("Did not find value %q in %q", i, z)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func TestIntSetAdd(t *testing.T) {
|
y := NewSet[int]()
|
||||||
t.Parallel()
|
v2 := []int{1, 2312, 123121, 999, -99}
|
||||||
x := NewIntSet()
|
y.AddRange(v2)
|
||||||
x.Add(1)
|
z = y.Stringify()
|
||||||
if len(x.Set) != 1 {
|
// order is random
|
||||||
t.Fatalf("Unexpected size. Should have 1 Got %d", len(x.Set))
|
for _, i := range v2 {
|
||||||
}
|
if !strings.Contains(z, fmt.Sprint(i)) {
|
||||||
}
|
t.Fatalf("Did not find value %q in %q", i, z)
|
||||||
|
|
||||||
func TestIntSetAddDouble(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
x := NewIntSet()
|
|
||||||
x.Add(1)
|
|
||||||
x.Add(1)
|
|
||||||
if len(x.Set) != 1 {
|
|
||||||
t.Fatalf("Unexpected size. Should have 1 Got %d", len(x.Set))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIntSetContains(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
x := NewIntSet()
|
|
||||||
v := []int{1, 2, 3, 4}
|
|
||||||
for _, y := range v {
|
|
||||||
x.Add(y)
|
|
||||||
}
|
|
||||||
for _, y := range v {
|
|
||||||
if !x.Contains(y) {
|
|
||||||
t.Fatalf("Did not find value %d in array. %v", y, x.Set)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIntSetStringify(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
x := NewIntSet()
|
|
||||||
v := []int{1, 3, 2, 4}
|
|
||||||
expected := "1,2,3,4"
|
|
||||||
for _, y := range v {
|
|
||||||
x.Add(y)
|
|
||||||
}
|
|
||||||
z := x.Stringify()
|
|
||||||
// should be sorted
|
|
||||||
if expected != z {
|
|
||||||
t.Fatalf("Expected %q got %q", expected, z)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLineCounter(t *testing.T) {
|
func TestLineCounter(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
var tt = []struct {
|
var tt = []struct {
|
||||||
|
|
|
@ -97,14 +97,14 @@ func NewHTTPClient(opt *HTTPOptions) (*HTTPClient, error) {
|
||||||
|
|
||||||
// Request makes an http request and returns the status, the content length, the headers, the body and an error
|
// Request makes an http request and returns the status, the content length, the headers, the body and an error
|
||||||
// if you want the body returned set the corresponding property inside RequestOptions
|
// if you want the body returned set the corresponding property inside RequestOptions
|
||||||
func (client *HTTPClient) Request(ctx context.Context, fullURL string, opts RequestOptions) (*int, int64, http.Header, []byte, error) {
|
func (client *HTTPClient) Request(ctx context.Context, fullURL string, opts RequestOptions) (int, int64, http.Header, []byte, error) {
|
||||||
resp, err := client.makeRequest(ctx, fullURL, opts.Host, opts.Body)
|
resp, err := client.makeRequest(ctx, fullURL, opts.Host, opts.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// ignore context canceled errors
|
// ignore context canceled errors
|
||||||
if errors.Is(ctx.Err(), context.Canceled) {
|
if errors.Is(ctx.Err(), context.Canceled) {
|
||||||
return nil, 0, nil, nil, nil
|
return 0, 0, nil, nil, nil
|
||||||
}
|
}
|
||||||
return nil, 0, nil, nil, err
|
return 0, 0, nil, nil, err
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
@ -113,7 +113,7 @@ func (client *HTTPClient) Request(ctx context.Context, fullURL string, opts Requ
|
||||||
if opts.ReturnBody {
|
if opts.ReturnBody {
|
||||||
body, err = io.ReadAll(resp.Body)
|
body, err = io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, nil, nil, fmt.Errorf("could not read body %w", err)
|
return 0, 0, nil, nil, fmt.Errorf("could not read body %w", err)
|
||||||
}
|
}
|
||||||
length = int64(len(body))
|
length = int64(len(body))
|
||||||
} else {
|
} else {
|
||||||
|
@ -121,11 +121,11 @@ func (client *HTTPClient) Request(ctx context.Context, fullURL string, opts Requ
|
||||||
// absolutely needed so golang will reuse connections!
|
// absolutely needed so golang will reuse connections!
|
||||||
length, err = io.Copy(io.Discard, resp.Body)
|
length, err = io.Copy(io.Discard, resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, nil, nil, err
|
return 0, 0, nil, nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &resp.StatusCode, length, resp.Header, body, nil
|
return resp.StatusCode, length, resp.Header, body, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (client *HTTPClient) makeRequest(ctx context.Context, fullURL, host string, data io.Reader) (*http.Response, error) {
|
func (client *HTTPClient) makeRequest(ctx context.Context, fullURL, host string, data io.Reader) (*http.Response, error) {
|
||||||
|
|
|
@ -59,7 +59,7 @@ func TestRequest(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Got Error: %v", err)
|
t.Fatalf("Got Error: %v", err)
|
||||||
}
|
}
|
||||||
if *status != 200 {
|
if status != 200 {
|
||||||
t.Fatalf("Invalid status returned: %d", status)
|
t.Fatalf("Invalid status returned: %d", status)
|
||||||
}
|
}
|
||||||
if length != int64(len(ret)) {
|
if length != int64(len(ret)) {
|
||||||
|
|
|
@ -5,9 +5,9 @@ import "context"
|
||||||
// GobusterPlugin is an interface which plugins must implement
|
// GobusterPlugin is an interface which plugins must implement
|
||||||
type GobusterPlugin interface {
|
type GobusterPlugin interface {
|
||||||
Name() string
|
Name() string
|
||||||
RequestsPerRun() int
|
|
||||||
PreRun(context.Context) error
|
PreRun(context.Context) error
|
||||||
Run(context.Context, string, chan<- Result) error
|
ProcessWord(context.Context, string, *Progress) error
|
||||||
|
AdditionalWords(string) []string
|
||||||
GetConfigString() (string, error)
|
GetConfigString() (string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,8 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/fatih/color"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PATTERN is the pattern for wordlist replacements in pattern file
|
// PATTERN is the pattern for wordlist replacements in pattern file
|
||||||
|
@ -25,15 +27,11 @@ type ResultToStringFunc func(*Gobuster, *Result) (*string, error)
|
||||||
|
|
||||||
// Gobuster is the main object when creating a new run
|
// Gobuster is the main object when creating a new run
|
||||||
type Gobuster struct {
|
type Gobuster struct {
|
||||||
Opts *Options
|
Opts *Options
|
||||||
RequestsExpected int
|
plugin GobusterPlugin
|
||||||
RequestsIssued int
|
LogInfo *log.Logger
|
||||||
RequestsCountMutex *sync.RWMutex
|
LogError *log.Logger
|
||||||
plugin GobusterPlugin
|
Progress *Progress
|
||||||
resultChan chan Result
|
|
||||||
errorChan chan error
|
|
||||||
LogInfo *log.Logger
|
|
||||||
LogError *log.Logger
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewGobuster returns a new Gobuster object
|
// NewGobuster returns a new Gobuster object
|
||||||
|
@ -41,31 +39,13 @@ func NewGobuster(opts *Options, plugin GobusterPlugin) (*Gobuster, error) {
|
||||||
var g Gobuster
|
var g Gobuster
|
||||||
g.Opts = opts
|
g.Opts = opts
|
||||||
g.plugin = plugin
|
g.plugin = plugin
|
||||||
g.RequestsCountMutex = new(sync.RWMutex)
|
|
||||||
g.resultChan = make(chan Result)
|
|
||||||
g.errorChan = make(chan error)
|
|
||||||
g.LogInfo = log.New(os.Stdout, "", log.LstdFlags)
|
g.LogInfo = log.New(os.Stdout, "", log.LstdFlags)
|
||||||
g.LogError = log.New(os.Stderr, "[ERROR] ", log.LstdFlags)
|
g.LogError = log.New(os.Stderr, color.New(color.FgRed).Sprint("[ERROR] "), log.LstdFlags)
|
||||||
|
g.Progress = NewProgress()
|
||||||
|
|
||||||
return &g, nil
|
return &g, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Results returns a channel of Results
|
|
||||||
func (g *Gobuster) Results() <-chan Result {
|
|
||||||
return g.resultChan
|
|
||||||
}
|
|
||||||
|
|
||||||
// Errors returns a channel of errors
|
|
||||||
func (g *Gobuster) Errors() <-chan error {
|
|
||||||
return g.errorChan
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *Gobuster) incrementRequests() {
|
|
||||||
g.RequestsCountMutex.Lock()
|
|
||||||
g.RequestsIssued += g.plugin.RequestsPerRun()
|
|
||||||
g.RequestsCountMutex.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *Gobuster) worker(ctx context.Context, wordChan <-chan string, wg *sync.WaitGroup) {
|
func (g *Gobuster) worker(ctx context.Context, wordChan <-chan string, wg *sync.WaitGroup) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
for {
|
for {
|
||||||
|
@ -77,7 +57,7 @@ func (g *Gobuster) worker(ctx context.Context, wordChan <-chan string, wg *sync.
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
g.incrementRequests()
|
g.Progress.incrementRequests()
|
||||||
|
|
||||||
wordCleaned := strings.TrimSpace(word)
|
wordCleaned := strings.TrimSpace(word)
|
||||||
// Skip "comment" (starts with #), as well as empty lines
|
// Skip "comment" (starts with #), as well as empty lines
|
||||||
|
@ -86,10 +66,10 @@ func (g *Gobuster) worker(ctx context.Context, wordChan <-chan string, wg *sync.
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mode-specific processing
|
// Mode-specific processing
|
||||||
err := g.plugin.Run(ctx, wordCleaned, g.resultChan)
|
err := g.plugin.ProcessWord(ctx, wordCleaned, g.Progress)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// do not exit and continue
|
// do not exit and continue
|
||||||
g.errorChan <- err
|
g.Progress.ErrorChan <- err
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,15 +97,17 @@ func (g *Gobuster) getWordlist() (*bufio.Scanner, error) {
|
||||||
return nil, fmt.Errorf("failed to get number of lines: %w", err)
|
return nil, fmt.Errorf("failed to get number of lines: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
g.RequestsIssued = 0
|
|
||||||
|
|
||||||
// calcutate expected requests
|
// calcutate expected requests
|
||||||
g.RequestsExpected = lines
|
g.Progress.IncrementTotalRequests(lines)
|
||||||
if g.Opts.PatternFile != "" {
|
|
||||||
g.RequestsExpected += lines * len(g.Opts.Patterns)
|
|
||||||
}
|
|
||||||
|
|
||||||
g.RequestsExpected *= g.plugin.RequestsPerRun()
|
// call the function once with a dummy entry to receive the number
|
||||||
|
// of custom words per wordlist word
|
||||||
|
customWordsLen := len(g.plugin.AdditionalWords("dummy"))
|
||||||
|
if customWordsLen > 0 {
|
||||||
|
origExpected := g.Progress.RequestsExpected()
|
||||||
|
inc := origExpected * customWordsLen
|
||||||
|
g.Progress.IncrementTotalRequests(inc)
|
||||||
|
}
|
||||||
|
|
||||||
// rewind wordlist
|
// rewind wordlist
|
||||||
_, err = wordlist.Seek(0, 0)
|
_, err = wordlist.Seek(0, 0)
|
||||||
|
@ -138,8 +120,8 @@ func (g *Gobuster) getWordlist() (*bufio.Scanner, error) {
|
||||||
// Run the busting of the website with the given
|
// Run the busting of the website with the given
|
||||||
// set of settings from the command line.
|
// set of settings from the command line.
|
||||||
func (g *Gobuster) Run(ctx context.Context) error {
|
func (g *Gobuster) Run(ctx context.Context) error {
|
||||||
defer close(g.resultChan)
|
defer close(g.Progress.ResultChan)
|
||||||
defer close(g.errorChan)
|
defer close(g.Progress.ErrorChan)
|
||||||
|
|
||||||
if err := g.plugin.PreRun(ctx); err != nil {
|
if err := g.plugin.PreRun(ctx); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -180,6 +162,15 @@ Scan:
|
||||||
case wordChan <- w:
|
case wordChan <- w:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, w := range g.plugin.AdditionalWords(word) {
|
||||||
|
select {
|
||||||
|
// need to check here too otherwise wordChan will block
|
||||||
|
case <-ctx.Done():
|
||||||
|
break Scan
|
||||||
|
case wordChan <- w:
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
close(wordChan)
|
close(wordChan)
|
||||||
|
|
|
@ -10,6 +10,8 @@ type BasicHTTPOptions struct {
|
||||||
Proxy string
|
Proxy string
|
||||||
NoTLSValidation bool
|
NoTLSValidation bool
|
||||||
Timeout time.Duration
|
Timeout time.Duration
|
||||||
|
RetryOnTimeout bool
|
||||||
|
RetryAttempts int
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTTPOptions is the struct to pass in all http options to Gobuster
|
// HTTPOptions is the struct to pass in all http options to Gobuster
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
package libgobuster
|
||||||
|
|
||||||
|
import "sync"
|
||||||
|
|
||||||
|
type Progress struct {
|
||||||
|
requestsExpectedMutex *sync.RWMutex
|
||||||
|
requestsExpected int
|
||||||
|
requestsCountMutex *sync.RWMutex
|
||||||
|
requestsIssued int
|
||||||
|
ResultChan chan Result
|
||||||
|
ErrorChan chan error
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewProgress() *Progress {
|
||||||
|
var p Progress
|
||||||
|
p.requestsIssued = 0
|
||||||
|
p.requestsExpectedMutex = new(sync.RWMutex)
|
||||||
|
p.requestsCountMutex = new(sync.RWMutex)
|
||||||
|
p.ResultChan = make(chan Result)
|
||||||
|
p.ErrorChan = make(chan error)
|
||||||
|
return &p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Progress) RequestsExpected() int {
|
||||||
|
p.requestsExpectedMutex.RLock()
|
||||||
|
defer p.requestsExpectedMutex.RUnlock()
|
||||||
|
return p.requestsExpected
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Progress) RequestsIssued() int {
|
||||||
|
p.requestsCountMutex.RLock()
|
||||||
|
defer p.requestsCountMutex.RUnlock()
|
||||||
|
return p.requestsIssued
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Progress) incrementRequests() {
|
||||||
|
p.requestsCountMutex.Lock()
|
||||||
|
defer p.requestsCountMutex.Unlock()
|
||||||
|
p.requestsIssued++
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Progress) IncrementTotalRequests(by int) {
|
||||||
|
p.requestsCountMutex.Lock()
|
||||||
|
defer p.requestsCountMutex.Unlock()
|
||||||
|
p.requestsExpected += by
|
||||||
|
}
|
|
@ -2,5 +2,5 @@ package libgobuster
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// VERSION contains the current gobuster version
|
// VERSION contains the current gobuster version
|
||||||
VERSION = "3.1.0"
|
VERSION = "3.2.0-dev"
|
||||||
)
|
)
|
||||||
|
|
150
make.bat
150
make.bat
|
@ -1,150 +0,0 @@
|
||||||
@echo off
|
|
||||||
|
|
||||||
SET ARG=%1
|
|
||||||
SET TARGET=.\build
|
|
||||||
SET BUILDARGS=-ldflags="-s -w" -trimpath
|
|
||||||
|
|
||||||
IF "%ARG%"=="test" (
|
|
||||||
CALL :Test
|
|
||||||
GOTO Done
|
|
||||||
)
|
|
||||||
|
|
||||||
IF "%ARG%"=="clean" (
|
|
||||||
del /F /Q %TARGET%\*.*
|
|
||||||
go clean ./...
|
|
||||||
echo Done.
|
|
||||||
GOTO Done
|
|
||||||
)
|
|
||||||
|
|
||||||
IF "%ARG%"=="windows" (
|
|
||||||
CALL :Windows
|
|
||||||
GOTO Done
|
|
||||||
)
|
|
||||||
|
|
||||||
IF "%ARG%"=="darwin" (
|
|
||||||
CALL :Darwin
|
|
||||||
GOTO Done
|
|
||||||
)
|
|
||||||
|
|
||||||
IF "%ARG%"=="linux" (
|
|
||||||
CALL :Linux
|
|
||||||
GOTO Done
|
|
||||||
)
|
|
||||||
|
|
||||||
IF "%ARG%"=="update" (
|
|
||||||
CALL :Update
|
|
||||||
GOTO Done
|
|
||||||
)
|
|
||||||
|
|
||||||
IF "%ARG%"=="fmt" (
|
|
||||||
CALL :Fmt
|
|
||||||
GOTO Done
|
|
||||||
)
|
|
||||||
|
|
||||||
IF "%ARG%"=="lint" (
|
|
||||||
CALL :Lint
|
|
||||||
GOTO Done
|
|
||||||
)
|
|
||||||
|
|
||||||
IF "%ARG%"=="all" (
|
|
||||||
CALL :Fmt
|
|
||||||
CALL :Update
|
|
||||||
CALL :Lint
|
|
||||||
CALL :Test
|
|
||||||
CALL :Darwin
|
|
||||||
CALL :Linux
|
|
||||||
CALL :Windows
|
|
||||||
GOTO Done
|
|
||||||
)
|
|
||||||
|
|
||||||
IF "%ARG%"=="" (
|
|
||||||
go build -o .\gobuster.exe
|
|
||||||
GOTO Done
|
|
||||||
)
|
|
||||||
|
|
||||||
GOTO Done
|
|
||||||
|
|
||||||
:Test
|
|
||||||
set GO111MODULE=on
|
|
||||||
set CGO_ENABLED=0
|
|
||||||
echo Testing ...
|
|
||||||
go test -v ./...
|
|
||||||
echo Done
|
|
||||||
EXIT /B 0
|
|
||||||
|
|
||||||
:Lint
|
|
||||||
set GO111MODULE=on
|
|
||||||
echo Linting ...
|
|
||||||
go get -u github.com/golangci/golangci-lint@master
|
|
||||||
golangci-lint run ./...
|
|
||||||
rem remove test deps
|
|
||||||
go mod tidy
|
|
||||||
echo Done
|
|
||||||
|
|
||||||
:Fmt
|
|
||||||
set GO111MODULE=on
|
|
||||||
echo Formatting ...
|
|
||||||
go fmt ./...
|
|
||||||
echo Done.
|
|
||||||
EXIT /B 0
|
|
||||||
|
|
||||||
:Update
|
|
||||||
set GO111MODULE=on
|
|
||||||
echo Updating ...
|
|
||||||
go get -u
|
|
||||||
go mod tidy -v
|
|
||||||
echo Done.
|
|
||||||
EXIT /B 0
|
|
||||||
|
|
||||||
:Darwin
|
|
||||||
set GOOS=darwin
|
|
||||||
set GOARCH=amd64
|
|
||||||
set GO111MODULE=on
|
|
||||||
set CGO_ENABLED=0
|
|
||||||
echo Building for %GOOS% %GOARCH% ...
|
|
||||||
set DIR=%TARGET%\gobuster-%GOOS%-%GOARCH%
|
|
||||||
mkdir %DIR% 2> NUL
|
|
||||||
go build %BUILDARGS% -o %DIR%\gobuster
|
|
||||||
set GOARCH=386
|
|
||||||
echo Building for %GOOS% %GOARCH% ...
|
|
||||||
set DIR=%TARGET%\gobuster-%GOOS%-%GOARCH%
|
|
||||||
mkdir %DIR% 2> NUL
|
|
||||||
go build %BUILDARGS% -o %DIR%\gobuster
|
|
||||||
echo Done.
|
|
||||||
EXIT /B 0
|
|
||||||
|
|
||||||
:Linux
|
|
||||||
set GOOS=linux
|
|
||||||
set GOARCH=amd64
|
|
||||||
set GO111MODULE=on
|
|
||||||
set CGO_ENABLED=0
|
|
||||||
echo Building for %GOOS% %GOARCH% ...
|
|
||||||
set DIR=%TARGET%\gobuster-%GOOS%-%GOARCH%
|
|
||||||
mkdir %DIR% 2> NUL
|
|
||||||
go build %BUILDARGS% -o %DIR%\gobuster
|
|
||||||
set GOARCH=386
|
|
||||||
echo Building for %GOOS% %GOARCH% ...
|
|
||||||
set DIR=%TARGET%\gobuster-%GOOS%-%GOARCH%
|
|
||||||
mkdir %DIR% 2> NUL
|
|
||||||
go build %BUILDARGS% -o %DIR%\gobuster
|
|
||||||
echo Done.
|
|
||||||
EXIT /B 0
|
|
||||||
|
|
||||||
:Windows
|
|
||||||
set GOOS=windows
|
|
||||||
set GOARCH=amd64
|
|
||||||
set GO111MODULE=on
|
|
||||||
set CGO_ENABLED=0
|
|
||||||
echo Building for %GOOS% %GOARCH% ...
|
|
||||||
set DIR=%TARGET%\gobuster-%GOOS%-%GOARCH%
|
|
||||||
mkdir %DIR% 2> NUL
|
|
||||||
go build %BUILDARGS% -o %DIR%\gobuster.exe
|
|
||||||
set GOARCH=386
|
|
||||||
echo Building for %GOOS% %GOARCH% ...
|
|
||||||
set DIR=%TARGET%\gobuster-%GOOS%-%GOARCH%
|
|
||||||
mkdir %DIR% 2> NUL
|
|
||||||
go build %BUILDARGS% -o %DIR%\gobuster.exe
|
|
||||||
echo Done.
|
|
||||||
EXIT /B 0
|
|
||||||
|
|
||||||
:Done
|
|
Loading…
Reference in New Issue