├── TODO.md ├── libgobuster ├── output_reset.go ├── output_reset_win.go ├── version.go ├── interfaces.go ├── options_http.go ├── options.go ├── result.go ├── http_test.go ├── helpers.go ├── helpers_test.go ├── libgobuster.go └── http.go ├── .travis.yml ├── go.mod ├── gobustervhost ├── options.go └── gobustervhost.go ├── gobusterdir ├── options_test.go ├── options.go └── gobusterdir.go ├── .gitignore ├── gobusterdns ├── options.go └── gobusterdns.go ├── .golangci.yml ├── Dockerfile ├── main.go ├── THANKS ├── helper ├── helper.go └── helper_test.go ├── cli ├── cmd │ ├── vhost_test.go │ ├── vhost.go │ ├── dir_test.go │ ├── dns.go │ ├── root.go │ ├── http.go │ └── dir.go └── gobuster.go ├── Makefile ├── make.bat ├── go.sum ├── LICENSE └── README.md /TODO.md: -------------------------------------------------------------------------------- 1 | # TODO 2 | 3 | * no log.Printf inside of plugins 4 | -------------------------------------------------------------------------------- /libgobuster/output_reset.go: -------------------------------------------------------------------------------- 1 | // +build !windows 2 | 3 | package libgobuster 4 | 5 | func resetTerminal() string { 6 | return "\r\x1b[2K" 7 | } 8 | -------------------------------------------------------------------------------- /libgobuster/output_reset_win.go: -------------------------------------------------------------------------------- 1 | // +build windows 2 | 3 | package libgobuster 4 | 5 | func resetTerminal() string { 6 | return "\r\r" 7 | } 8 | -------------------------------------------------------------------------------- /libgobuster/version.go: -------------------------------------------------------------------------------- 1 | package libgobuster 2 | 3 | const ( 4 | // VERSION contains the current gobuster version 5 | VERSION = "3.0.1" 6 | ) 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - "1.x" 5 | - "1.8" 6 | - "1.10.x" 7 | - "1.11.x" 8 | - "1.12.x" 9 | - master 10 | 11 | script: make lint test 12 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/OJ/gobuster/v3 2 | 3 | require ( 4 | github.com/google/uuid v1.1.1 5 | github.com/spf13/cobra v0.0.5 6 | golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 7 | golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7 // indirect 8 | ) 9 | -------------------------------------------------------------------------------- /gobustervhost/options.go: -------------------------------------------------------------------------------- 1 | package gobustervhost 2 | 3 | import ( 4 | "github.com/OJ/gobuster/v3/libgobuster" 5 | ) 6 | 7 | // OptionsVhost is the struct to hold all options for this plugin 8 | type OptionsVhost struct { 9 | libgobuster.OptionsHTTP 10 | } 11 | -------------------------------------------------------------------------------- /libgobuster/interfaces.go: -------------------------------------------------------------------------------- 1 | package libgobuster 2 | 3 | // GobusterPlugin is an interface which plugins must implement 4 | type GobusterPlugin interface { 5 | PreRun() error 6 | Run(string) ([]Result, error) 7 | ResultToString(*Result) (*string, error) 8 | GetConfigString() (string, error) 9 | } 10 | -------------------------------------------------------------------------------- /gobusterdir/options_test.go: -------------------------------------------------------------------------------- 1 | package gobusterdir 2 | 3 | import "testing" 4 | 5 | func TestNewOptions(t *testing.T) { 6 | t.Parallel() 7 | 8 | o := NewOptionsDir() 9 | if o.StatusCodesParsed.Set == nil { 10 | t.Fatal("StatusCodesParsed not initialized") 11 | } 12 | 13 | if o.ExtensionsParsed.Set == nil { 14 | t.Fatal("ExtensionsParsed not initialized") 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | *.txt 26 | *.swp 27 | 28 | .vscode/ 29 | gobuster 30 | build 31 | v3 32 | -------------------------------------------------------------------------------- /gobusterdns/options.go: -------------------------------------------------------------------------------- 1 | package gobusterdns 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | // OptionsDNS holds all options for the dns plugin 8 | type OptionsDNS struct { 9 | Domain string 10 | ShowIPs bool 11 | ShowCNAME bool 12 | WildcardForced bool 13 | Resolver string 14 | Timeout time.Duration 15 | } 16 | 17 | // NewOptionsDNS returns a new initialized OptionsDNS 18 | func NewOptionsDNS() *OptionsDNS { 19 | return &OptionsDNS{} 20 | } 21 | -------------------------------------------------------------------------------- /libgobuster/options_http.go: -------------------------------------------------------------------------------- 1 | package libgobuster 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | // OptionsHTTP is the struct to hold all options for common HTTP options 8 | type OptionsHTTP struct { 9 | Password string 10 | URL string 11 | UserAgent string 12 | Username string 13 | Proxy string 14 | Cookies string 15 | Headers []HTTPHeader 16 | Timeout time.Duration 17 | FollowRedirect bool 18 | InsecureSSL bool 19 | } 20 | -------------------------------------------------------------------------------- /libgobuster/options.go: -------------------------------------------------------------------------------- 1 | package libgobuster 2 | 3 | import "time" 4 | 5 | // Options helds all options that can be passed to libgobuster 6 | type Options struct { 7 | Threads int 8 | Wordlist string 9 | OutputFilename string 10 | NoStatus bool 11 | NoProgress bool 12 | Quiet bool 13 | WildcardForced bool 14 | Verbose bool 15 | Delay time.Duration 16 | } 17 | 18 | // NewOptions returns a new initialized Options object 19 | func NewOptions() *Options { 20 | return &Options{} 21 | } 22 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | linters: 2 | enable-all: true 3 | disable: 4 | - lll 5 | - gocyclo 6 | 7 | issues: 8 | exclude-rules: 9 | - text: "TLS InsecureSkipVerify may be true" 10 | linters: 11 | - gosec 12 | 13 | - text: ifElseChain 14 | linters: 15 | - gocritic 16 | 17 | - path: cli\\cmd\\.+\.go 18 | linters: 19 | - gochecknoinits 20 | - gochecknoglobals 21 | 22 | - path: cli/cmd/.+\.go 23 | linters: 24 | - gochecknoinits 25 | - gochecknoglobals 26 | 27 | - path: _test\.go 28 | linters: 29 | - scopelint 30 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:latest AS build-env 2 | WORKDIR /src 3 | ENV GO111MODULE=on 4 | COPY go.mod /src/ 5 | RUN go mod download 6 | COPY . . 7 | RUN CGO_ENABLED=0 GOOS=linux go build -a -o gobuster -ldflags="-s -w" -gcflags="all=-trimpath=/src" -asmflags="all=-trimpath=/src" 8 | 9 | FROM alpine:latest 10 | 11 | RUN apk add --no-cache ca-certificates \ 12 | && rm -rf /var/cache/* 13 | 14 | RUN mkdir -p /app \ 15 | && adduser -D gobuster \ 16 | && chown -R gobuster:gobuster /app 17 | 18 | USER gobuster 19 | WORKDIR /app 20 | 21 | COPY --from=build-env /src/gobuster . 22 | 23 | ENTRYPOINT [ "./gobuster" ] 24 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "github.com/OJ/gobuster/v3/cli/cmd" 4 | 5 | //---------------------------------------------------- 6 | // Gobuster -- by OJ Reeves 7 | // 8 | // A crap attempt at building something that resembles 9 | // dirbuster or dirb using Go. The goal was to build 10 | // a tool that would help learn Go and to actually do 11 | // something useful. The idea of having this compile 12 | // to native code is also appealing. 13 | // 14 | // Run: gobuster -h 15 | // 16 | // Please see THANKS file for contributors. 17 | // Please see LICENSE file for license details. 18 | // 19 | //---------------------------------------------------- 20 | 21 | func main() { 22 | cmd.Execute() 23 | } 24 | -------------------------------------------------------------------------------- /libgobuster/result.go: -------------------------------------------------------------------------------- 1 | package libgobuster 2 | 3 | // ResultStatus is a status enum 4 | type ResultStatus int 5 | 6 | const ( 7 | // StatusFound represents a found item 8 | StatusFound ResultStatus = iota 9 | // StatusMissed represents a missed item 10 | StatusMissed ResultStatus = iota 11 | ) 12 | 13 | // Result represents a single gobuster result 14 | type Result struct { 15 | Entity string 16 | StatusCode int 17 | Status ResultStatus 18 | Extra string 19 | Size *int64 20 | } 21 | 22 | // ToString converts the Result to it's textual representation 23 | func (r *Result) ToString(g *Gobuster) (string, error) { 24 | s, err := g.plugin.ResultToString(r) 25 | if err != nil { 26 | return "", err 27 | } 28 | return *s, nil 29 | } 30 | -------------------------------------------------------------------------------- /THANKS: -------------------------------------------------------------------------------- 1 | @0x42424242 - initial DNS support 2 | @0xdevalias - Refactoring of code, and lots of other stuff 3 | @Ne0nd0g - STDIN support for wordlists 4 | @UID1K - initial DNS wildcard check support 5 | @averagesecurityguy - quiet mode support 6 | @eur0pa - Compiler error fixes for updated dependencies 7 | @g0tmi1k - content length, wordlist and command line parsing fixes 8 | @gehaxelt - DIR mode UUID wildcard detection 9 | @ilyaglow - Refactoring and tidying of code 10 | @justinsteven - HTTP basic auth support 11 | @kevinnz - custom user agent support 12 | @knapsy - saving output to file, and CNAME resolution for DNS mode 13 | @rverton - CLI flag to skip SSL verification 14 | @viaMorgoth - base domain validation for DNS mode 15 | @barrracud4 - Custom HTTP headers 16 | @mattburch - several output improvements 17 | -------------------------------------------------------------------------------- /gobusterdir/options.go: -------------------------------------------------------------------------------- 1 | package gobusterdir 2 | 3 | import ( 4 | "github.com/OJ/gobuster/v3/libgobuster" 5 | ) 6 | 7 | // OptionsDir is the struct to hold all options for this plugin 8 | type OptionsDir struct { 9 | libgobuster.OptionsHTTP 10 | Extensions string 11 | ExtensionsParsed libgobuster.StringSet 12 | StatusCodes string 13 | StatusCodesParsed libgobuster.IntSet 14 | StatusCodesBlacklist string 15 | StatusCodesBlacklistParsed libgobuster.IntSet 16 | UseSlash bool 17 | WildcardForced bool 18 | IncludeLength bool 19 | Expanded bool 20 | NoStatus bool 21 | } 22 | 23 | // NewOptionsDir returns a new initialized OptionsDir 24 | func NewOptionsDir() *OptionsDir { 25 | return &OptionsDir{ 26 | StatusCodesParsed: libgobuster.NewIntSet(), 27 | StatusCodesBlacklistParsed: libgobuster.NewIntSet(), 28 | ExtensionsParsed: libgobuster.NewStringSet(), 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /helper/helper.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "strings" 7 | 8 | "github.com/OJ/gobuster/v3/libgobuster" 9 | ) 10 | 11 | // ParseExtensions parses the extensions provided as a comma separated list 12 | func ParseExtensions(extensions string) (libgobuster.StringSet, error) { 13 | if extensions == "" { 14 | return libgobuster.StringSet{}, fmt.Errorf("invalid extension string provided") 15 | } 16 | 17 | ret := libgobuster.NewStringSet() 18 | exts := strings.Split(extensions, ",") 19 | for _, e := range exts { 20 | e = strings.TrimSpace(e) 21 | // remove leading . from extensions 22 | ret.Add(strings.TrimPrefix(e, ".")) 23 | } 24 | return ret, nil 25 | } 26 | 27 | // ParseStatusCodes parses the status codes provided as a comma separated list 28 | func ParseStatusCodes(statuscodes string) (libgobuster.IntSet, error) { 29 | if statuscodes == "" { 30 | return libgobuster.IntSet{}, fmt.Errorf("invalid status code string provided") 31 | } 32 | 33 | ret := libgobuster.NewIntSet() 34 | for _, c := range strings.Split(statuscodes, ",") { 35 | c = strings.TrimSpace(c) 36 | i, err := strconv.Atoi(c) 37 | if err != nil { 38 | return libgobuster.IntSet{}, fmt.Errorf("invalid status code given: %s", c) 39 | } 40 | ret.Add(i) 41 | } 42 | return ret, nil 43 | } 44 | -------------------------------------------------------------------------------- /cli/cmd/vhost_test.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io/ioutil" 7 | "log" 8 | "os" 9 | "testing" 10 | "time" 11 | 12 | "github.com/OJ/gobuster/v3/cli" 13 | "github.com/OJ/gobuster/v3/gobustervhost" 14 | "github.com/OJ/gobuster/v3/libgobuster" 15 | ) 16 | 17 | func BenchmarkVhostMode(b *testing.B) { 18 | h := httpServer(b, "test") 19 | defer h.Close() 20 | 21 | pluginopts := gobustervhost.OptionsVhost{} 22 | pluginopts.URL = h.URL 23 | pluginopts.Timeout = 10 * time.Second 24 | 25 | wordlist, err := ioutil.TempFile("", "") 26 | if err != nil { 27 | b.Fatalf("could not create tempfile: %v", err) 28 | } 29 | defer os.Remove(wordlist.Name()) 30 | for w := 0; w < 1000; w++ { 31 | _, _ = wordlist.WriteString(fmt.Sprintf("%d\n", w)) 32 | } 33 | wordlist.Close() 34 | 35 | globalopts := libgobuster.Options{ 36 | Threads: 10, 37 | Wordlist: wordlist.Name(), 38 | NoProgress: true, 39 | } 40 | 41 | ctx := context.Background() 42 | oldStdout := os.Stdout 43 | oldStderr := os.Stderr 44 | defer func(out, err *os.File) { os.Stdout = out; os.Stderr = err }(oldStdout, oldStderr) 45 | devnull, err := os.Open(os.DevNull) 46 | if err != nil { 47 | b.Fatalf("could not get devnull %v", err) 48 | } 49 | defer devnull.Close() 50 | log.SetFlags(0) 51 | log.SetOutput(ioutil.Discard) 52 | 53 | // Run the real benchmark 54 | for x := 0; x < b.N; x++ { 55 | os.Stdout = devnull 56 | os.Stderr = devnull 57 | plugin, err := gobustervhost.NewGobusterVhost(ctx, &globalopts, &pluginopts) 58 | if err != nil { 59 | b.Fatalf("error on creating gobusterdir: %v", err) 60 | } 61 | 62 | if err := cli.Gobuster(ctx, &globalopts, plugin); err != nil { 63 | b.Fatalf("error on running gobuster: %v", err) 64 | } 65 | os.Stdout = oldStdout 66 | os.Stderr = oldStderr 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | TARGET=./build 2 | ARCHS=amd64 386 3 | LDFLAGS="-s -w" 4 | GCFLAGS="all=-trimpath=$(shell pwd)" 5 | ASMFLAGS="all=-trimpath=$(shell pwd)" 6 | 7 | current: 8 | @go build -o ./gobuster; \ 9 | echo "Done." 10 | 11 | fmt: 12 | @go fmt ./...; \ 13 | echo "Done." 14 | 15 | update: 16 | @go get -u; \ 17 | go mod tidy -v; \ 18 | echo "Done." 19 | 20 | windows: 21 | @for GOARCH in ${ARCHS}; do \ 22 | echo "Building for windows $${GOARCH} ..." ; \ 23 | mkdir -p ${TARGET}/gobuster-windows-$${GOARCH} ; \ 24 | GOOS=windows GOARCH=$${GOARCH} GO111MODULE=on CGO_ENABLED=0 go build -ldflags=${LDFLAGS} -gcflags=${GCFLAGS} -asmflags=${ASMFLAGS} -o ${TARGET}/gobuster-windows-$${GOARCH}/gobuster.exe ; \ 25 | done; \ 26 | echo "Done." 27 | 28 | linux: 29 | @for GOARCH in ${ARCHS}; do \ 30 | echo "Building for linux $${GOARCH} ..." ; \ 31 | mkdir -p ${TARGET}/gobuster-linux-$${GOARCH} ; \ 32 | GOOS=linux GOARCH=$${GOARCH} GO111MODULE=on CGO_ENABLED=0 go build -ldflags=${LDFLAGS} -gcflags=${GCFLAGS} -asmflags=${ASMFLAGS} -o ${TARGET}/gobuster-linux-$${GOARCH}/gobuster ; \ 33 | done; \ 34 | echo "Done." 35 | 36 | darwin: 37 | @for GOARCH in ${ARCHS}; do \ 38 | echo "Building for darwin $${GOARCH} ..." ; \ 39 | mkdir -p ${TARGET}/gobuster-darwin-$${GOARCH} ; \ 40 | GOOS=darwin GOARCH=$${GOARCH} GO111MODULE=on CGO_ENABLED=0 go build -ldflags=${LDFLAGS} -gcflags=${GCFLAGS} -asmflags=${ASMFLAGS} -o ${TARGET}/gobuster-darwin-$${GOARCH}/gobuster ; \ 41 | done; \ 42 | echo "Done." 43 | 44 | all: clean fmt update lint test darwin linux windows 45 | 46 | test: 47 | @go test -v -race ./... ; \ 48 | echo "Done." 49 | 50 | lint: 51 | @go get -u github.com/golangci/golangci-lint@master ; \ 52 | golangci-lint run ./... ; \ 53 | go mod tidy ; \ 54 | echo Done 55 | 56 | clean: 57 | @rm -rf ${TARGET}/* ; \ 58 | go clean ./... ; \ 59 | echo "Done." 60 | -------------------------------------------------------------------------------- /libgobuster/http_test.go: -------------------------------------------------------------------------------- 1 | package libgobuster 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net/http" 7 | "net/http/httptest" 8 | "testing" 9 | ) 10 | 11 | func httpServerB(b *testing.B, content string) *httptest.Server { 12 | b.Helper() 13 | ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 14 | fmt.Fprint(w, content) 15 | })) 16 | return ts 17 | } 18 | 19 | func httpServerT(t *testing.T, content string) *httptest.Server { 20 | t.Helper() 21 | ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 22 | fmt.Fprint(w, content) 23 | })) 24 | return ts 25 | } 26 | 27 | func TestGet(t *testing.T) { 28 | h := httpServerT(t, "test") 29 | defer h.Close() 30 | var o HTTPOptions 31 | c, err := NewHTTPClient(context.Background(), &o) 32 | if err != nil { 33 | t.Fatalf("Got Error: %v", err) 34 | } 35 | a, b, err := c.Get(h.URL, "", "") 36 | if err != nil { 37 | t.Fatalf("Got Error: %v", err) 38 | } 39 | if *a != 200 { 40 | t.Fatalf("Invalid status returned: %d", a) 41 | } 42 | if b != nil && *b != int64(len("test")) { 43 | t.Fatalf("Invalid length returned: %d", b) 44 | } 45 | } 46 | 47 | func BenchmarkGet(b *testing.B) { 48 | h := httpServerB(b, "test") 49 | defer h.Close() 50 | var o HTTPOptions 51 | c, err := NewHTTPClient(context.Background(), &o) 52 | if err != nil { 53 | b.Fatalf("Got Error: %v", err) 54 | } 55 | for x := 0; x < b.N; x++ { 56 | _, _, err := c.Get(h.URL, "", "") 57 | if err != nil { 58 | b.Fatalf("Got Error: %v", err) 59 | } 60 | } 61 | } 62 | 63 | func BenchmarkNewHTTPClient(b *testing.B) { 64 | h := httpServerB(b, "test") 65 | defer h.Close() 66 | var o HTTPOptions 67 | for x := 0; x < b.N; x++ { 68 | _, err := NewHTTPClient(context.Background(), &o) 69 | if err != nil { 70 | b.Fatalf("Got Error: %v", err) 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /cli/cmd/vhost.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | 7 | "github.com/OJ/gobuster/v3/cli" 8 | "github.com/OJ/gobuster/v3/gobustervhost" 9 | "github.com/OJ/gobuster/v3/libgobuster" 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | var cmdVhost *cobra.Command 14 | 15 | func runVhost(cmd *cobra.Command, args []string) error { 16 | globalopts, pluginopts, err := parseVhostOptions() 17 | if err != nil { 18 | return fmt.Errorf("error on parsing arguments: %v", err) 19 | } 20 | 21 | plugin, err := gobustervhost.NewGobusterVhost(mainContext, globalopts, pluginopts) 22 | if err != nil { 23 | return fmt.Errorf("error on creating gobustervhost: %v", err) 24 | } 25 | 26 | if err := cli.Gobuster(mainContext, globalopts, plugin); err != nil { 27 | return fmt.Errorf("error on running gobuster: %v", err) 28 | } 29 | return nil 30 | } 31 | 32 | func parseVhostOptions() (*libgobuster.Options, *gobustervhost.OptionsVhost, error) { 33 | globalopts, err := parseGlobalOptions() 34 | if err != nil { 35 | return nil, nil, err 36 | } 37 | var plugin gobustervhost.OptionsVhost 38 | 39 | httpOpts, err := parseCommonHTTPOptions(cmdVhost) 40 | if err != nil { 41 | return nil, nil, err 42 | } 43 | plugin.Password = httpOpts.Password 44 | plugin.URL = httpOpts.URL 45 | plugin.UserAgent = httpOpts.UserAgent 46 | plugin.Username = httpOpts.Username 47 | plugin.Proxy = httpOpts.Proxy 48 | plugin.Cookies = httpOpts.Cookies 49 | plugin.Timeout = httpOpts.Timeout 50 | plugin.FollowRedirect = httpOpts.FollowRedirect 51 | plugin.InsecureSSL = httpOpts.InsecureSSL 52 | plugin.Headers = httpOpts.Headers 53 | 54 | return globalopts, &plugin, nil 55 | } 56 | 57 | func init() { 58 | cmdVhost = &cobra.Command{ 59 | Use: "vhost", 60 | Short: "Uses VHOST bruteforcing mode", 61 | RunE: runVhost, 62 | } 63 | if err := addCommonHTTPOptions(cmdVhost); err != nil { 64 | log.Fatalf("%v", err) 65 | } 66 | 67 | cmdVhost.PersistentPreRun = func(cmd *cobra.Command, args []string) { 68 | configureGlobalOptions() 69 | } 70 | 71 | rootCmd.AddCommand(cmdVhost) 72 | } 73 | -------------------------------------------------------------------------------- /cli/cmd/dir_test.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io/ioutil" 7 | "log" 8 | "net/http" 9 | "net/http/httptest" 10 | "os" 11 | "testing" 12 | "time" 13 | 14 | "github.com/OJ/gobuster/v3/cli" 15 | "github.com/OJ/gobuster/v3/gobusterdir" 16 | "github.com/OJ/gobuster/v3/helper" 17 | "github.com/OJ/gobuster/v3/libgobuster" 18 | ) 19 | 20 | func httpServer(b *testing.B, content string) *httptest.Server { 21 | b.Helper() 22 | ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 23 | fmt.Fprint(w, content) 24 | })) 25 | return ts 26 | } 27 | func BenchmarkDirMode(b *testing.B) { 28 | h := httpServer(b, "test") 29 | defer h.Close() 30 | 31 | pluginopts := gobusterdir.NewOptionsDir() 32 | pluginopts.URL = h.URL 33 | pluginopts.Timeout = 10 * time.Second 34 | pluginopts.WildcardForced = true 35 | 36 | pluginopts.Extensions = ".php,.csv" 37 | tmpExt, err := helper.ParseExtensions(pluginopts.Extensions) 38 | if err != nil { 39 | b.Fatalf("could not parse extensions: %v", err) 40 | } 41 | pluginopts.ExtensionsParsed = tmpExt 42 | 43 | pluginopts.StatusCodes = "200,204,301,302,307,401,403" 44 | tmpStat, err := helper.ParseStatusCodes(pluginopts.StatusCodes) 45 | if err != nil { 46 | b.Fatalf("could not parse status codes: %v", err) 47 | } 48 | pluginopts.StatusCodesParsed = tmpStat 49 | 50 | wordlist, err := ioutil.TempFile("", "") 51 | if err != nil { 52 | b.Fatalf("could not create tempfile: %v", err) 53 | } 54 | defer os.Remove(wordlist.Name()) 55 | for w := 0; w < 1000; w++ { 56 | _, _ = wordlist.WriteString(fmt.Sprintf("%d\n", w)) 57 | } 58 | wordlist.Close() 59 | 60 | globalopts := libgobuster.Options{ 61 | Threads: 10, 62 | Wordlist: wordlist.Name(), 63 | NoProgress: true, 64 | } 65 | 66 | ctx := context.Background() 67 | oldStdout := os.Stdout 68 | oldStderr := os.Stderr 69 | defer func(out, err *os.File) { os.Stdout = out; os.Stderr = err }(oldStdout, oldStderr) 70 | devnull, err := os.Open(os.DevNull) 71 | if err != nil { 72 | b.Fatalf("could not get devnull %v", err) 73 | } 74 | defer devnull.Close() 75 | log.SetFlags(0) 76 | log.SetOutput(ioutil.Discard) 77 | 78 | // Run the real benchmark 79 | for x := 0; x < b.N; x++ { 80 | os.Stdout = devnull 81 | os.Stderr = devnull 82 | plugin, err := gobusterdir.NewGobusterDir(ctx, &globalopts, pluginopts) 83 | if err != nil { 84 | b.Fatalf("error on creating gobusterdir: %v", err) 85 | } 86 | 87 | if err := cli.Gobuster(ctx, &globalopts, plugin); err != nil { 88 | b.Fatalf("error on running gobuster: %v", err) 89 | } 90 | os.Stdout = oldStdout 91 | os.Stderr = oldStderr 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /libgobuster/helpers.go: -------------------------------------------------------------------------------- 1 | package libgobuster 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | "sort" 8 | "strings" 9 | ) 10 | 11 | // IntSet is a set of Ints 12 | type IntSet struct { 13 | Set map[int]bool 14 | } 15 | 16 | // StringSet is a set of Strings 17 | type StringSet struct { 18 | Set map[string]bool 19 | } 20 | 21 | // NewStringSet creates a new initialized StringSet 22 | func NewStringSet() StringSet { 23 | return StringSet{Set: map[string]bool{}} 24 | } 25 | 26 | // Add an element to a set 27 | func (set *StringSet) Add(s string) bool { 28 | _, found := set.Set[s] 29 | set.Set[s] = true 30 | return !found 31 | } 32 | 33 | // AddRange adds a list of elements to a set 34 | func (set *StringSet) AddRange(ss []string) { 35 | for _, s := range ss { 36 | set.Set[s] = true 37 | } 38 | } 39 | 40 | // Contains tests if an element is in a set 41 | func (set *StringSet) Contains(s string) bool { 42 | _, found := set.Set[s] 43 | return found 44 | } 45 | 46 | // ContainsAny checks if any of the elements exist 47 | func (set *StringSet) ContainsAny(ss []string) bool { 48 | for _, s := range ss { 49 | if set.Set[s] { 50 | return true 51 | } 52 | } 53 | return false 54 | } 55 | 56 | // Length returns the length of the Set 57 | func (set *StringSet) Length() int { 58 | return len(set.Set) 59 | } 60 | 61 | // Stringify the set 62 | func (set *StringSet) Stringify() string { 63 | values := []string{} 64 | for s := range set.Set { 65 | values = append(values, s) 66 | } 67 | return strings.Join(values, ",") 68 | } 69 | 70 | // NewIntSet creates a new initialized IntSet 71 | func NewIntSet() IntSet { 72 | return IntSet{Set: map[int]bool{}} 73 | } 74 | 75 | // Add adds an element to a set 76 | func (set *IntSet) Add(i int) bool { 77 | _, found := set.Set[i] 78 | set.Set[i] = true 79 | return !found 80 | } 81 | 82 | // Contains tests if an element is in a set 83 | func (set *IntSet) Contains(i int) bool { 84 | _, found := set.Set[i] 85 | return found 86 | } 87 | 88 | // Stringify the set 89 | func (set *IntSet) Stringify() string { 90 | values := []int{} 91 | for s := range set.Set { 92 | values = append(values, s) 93 | } 94 | sort.Ints(values) 95 | 96 | delim := "," 97 | return strings.Trim(strings.Join(strings.Fields(fmt.Sprint(values)), delim), "[]") 98 | } 99 | 100 | // Length returns the length of the Set 101 | func (set *IntSet) Length() int { 102 | return len(set.Set) 103 | } 104 | 105 | func lineCounter(r io.Reader) (int, error) { 106 | buf := make([]byte, 32*1024) 107 | count := 1 108 | lineSep := []byte{'\n'} 109 | 110 | for { 111 | c, err := r.Read(buf) 112 | count += bytes.Count(buf[:c], lineSep) 113 | 114 | switch { 115 | case err == io.EOF: 116 | return count, nil 117 | 118 | case err != nil: 119 | return count, err 120 | } 121 | } 122 | } 123 | 124 | // DefaultUserAgent returns the default user agent to use in HTTP requests 125 | func DefaultUserAgent() string { 126 | return fmt.Sprintf("gobuster/%s", VERSION) 127 | } 128 | -------------------------------------------------------------------------------- /make.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | SET ARG=%1 4 | SET TARGET=.\build 5 | SET BUILDARGS=-ldflags="-s -w" -gcflags="all=-trimpath=%GOPATH%\src" -asmflags="all=-trimpath=%GOPATH%\src" 6 | 7 | IF "%ARG%"=="test" ( 8 | CALL :Test 9 | GOTO Done 10 | ) 11 | 12 | IF "%ARG%"=="clean" ( 13 | del /F /Q %TARGET%\*.* 14 | go clean ./... 15 | echo Done. 16 | GOTO Done 17 | ) 18 | 19 | IF "%ARG%"=="windows" ( 20 | CALL :Windows 21 | GOTO Done 22 | ) 23 | 24 | IF "%ARG%"=="darwin" ( 25 | CALL :Darwin 26 | GOTO Done 27 | ) 28 | 29 | IF "%ARG%"=="linux" ( 30 | CALL :Linux 31 | GOTO Done 32 | ) 33 | 34 | IF "%ARG%"=="update" ( 35 | CALL :Update 36 | GOTO Done 37 | ) 38 | 39 | IF "%ARG%"=="fmt" ( 40 | CALL :Fmt 41 | GOTO Done 42 | ) 43 | 44 | IF "%ARG%"=="all" ( 45 | CALL :Fmt 46 | CALL :Update 47 | CALL :Lint 48 | CALL :Test 49 | CALL :Darwin 50 | CALL :Linux 51 | CALL :Windows 52 | GOTO Done 53 | ) 54 | 55 | IF "%ARG%"=="" ( 56 | go build -o .\gobuster.exe 57 | GOTO Done 58 | ) 59 | 60 | GOTO Done 61 | 62 | :Test 63 | set GO111MODULE=on 64 | set CGO_ENABLED=0 65 | echo Testing ... 66 | go test -v ./... 67 | echo Done 68 | EXIT /B 0 69 | 70 | :Lint 71 | set GO111MODULE=on 72 | echo Linting ... 73 | go get -u github.com/golangci/golangci-lint@master 74 | golangci-lint run ./... 75 | rem remove test deps 76 | go mod tidy 77 | echo Done 78 | 79 | :Fmt 80 | set GO111MODULE=on 81 | echo Formatting ... 82 | go fmt ./... 83 | echo Done. 84 | EXIT /B 0 85 | 86 | :Update 87 | set GO111MODULE=on 88 | echo Updating ... 89 | go get -u 90 | go mod tidy -v 91 | echo Done. 92 | EXIT /B 0 93 | 94 | :Darwin 95 | set GOOS=darwin 96 | set GOARCH=amd64 97 | set GO111MODULE=on 98 | set CGO_ENABLED=0 99 | echo Building for %GOOS% %GOARCH% ... 100 | set DIR=%TARGET%\gobuster-%GOOS%-%GOARCH% 101 | mkdir %DIR% 2> NUL 102 | go build %BUILDARGS% -o %DIR%\gobuster 103 | set GOARCH=386 104 | echo Building for %GOOS% %GOARCH% ... 105 | set DIR=%TARGET%\gobuster-%GOOS%-%GOARCH% 106 | mkdir %DIR% 2> NUL 107 | go build %BUILDARGS% -o %DIR%\gobuster 108 | echo Done. 109 | EXIT /B 0 110 | 111 | :Linux 112 | set GOOS=linux 113 | set GOARCH=amd64 114 | set GO111MODULE=on 115 | set CGO_ENABLED=0 116 | echo Building for %GOOS% %GOARCH% ... 117 | set DIR=%TARGET%\gobuster-%GOOS%-%GOARCH% 118 | mkdir %DIR% 2> NUL 119 | go build %BUILDARGS% -o %DIR%\gobuster 120 | set GOARCH=386 121 | echo Building for %GOOS% %GOARCH% ... 122 | set DIR=%TARGET%\gobuster-%GOOS%-%GOARCH% 123 | mkdir %DIR% 2> NUL 124 | go build %BUILDARGS% -o %DIR%\gobuster 125 | echo Done. 126 | EXIT /B 0 127 | 128 | :Windows 129 | set GOOS=windows 130 | set GOARCH=amd64 131 | set GO111MODULE=on 132 | set CGO_ENABLED=0 133 | echo Building for %GOOS% %GOARCH% ... 134 | set DIR=%TARGET%\gobuster-%GOOS%-%GOARCH% 135 | mkdir %DIR% 2> NUL 136 | go build %BUILDARGS% -o %DIR%\gobuster.exe 137 | set GOARCH=386 138 | echo Building for %GOOS% %GOARCH% ... 139 | set DIR=%TARGET%\gobuster-%GOOS%-%GOARCH% 140 | mkdir %DIR% 2> NUL 141 | go build %BUILDARGS% -o %DIR%\gobuster.exe 142 | echo Done. 143 | EXIT /B 0 144 | 145 | :Done 146 | -------------------------------------------------------------------------------- /cli/cmd/dns.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "runtime" 7 | "time" 8 | 9 | "github.com/OJ/gobuster/v3/cli" 10 | "github.com/OJ/gobuster/v3/gobusterdns" 11 | "github.com/OJ/gobuster/v3/libgobuster" 12 | "github.com/spf13/cobra" 13 | ) 14 | 15 | var cmdDNS *cobra.Command 16 | 17 | func runDNS(cmd *cobra.Command, args []string) error { 18 | globalopts, pluginopts, err := parseDNSOptions() 19 | if err != nil { 20 | return fmt.Errorf("error on parsing arguments: %v", err) 21 | } 22 | 23 | plugin, err := gobusterdns.NewGobusterDNS(globalopts, pluginopts) 24 | if err != nil { 25 | return fmt.Errorf("error on creating gobusterdns: %v", err) 26 | } 27 | 28 | if err := cli.Gobuster(mainContext, globalopts, plugin); err != nil { 29 | if goberr, ok := err.(*gobusterdns.ErrWildcard); ok { 30 | return fmt.Errorf("%s. To force processing of Wildcard DNS, specify the '--wildcard' switch", goberr.Error()) 31 | } 32 | return fmt.Errorf("error on running gobuster: %v", err) 33 | } 34 | return nil 35 | } 36 | 37 | func parseDNSOptions() (*libgobuster.Options, *gobusterdns.OptionsDNS, error) { 38 | globalopts, err := parseGlobalOptions() 39 | if err != nil { 40 | return nil, nil, err 41 | } 42 | plugin := gobusterdns.NewOptionsDNS() 43 | 44 | plugin.Domain, err = cmdDNS.Flags().GetString("domain") 45 | if err != nil { 46 | return nil, nil, fmt.Errorf("invalid value for domain: %v", err) 47 | } 48 | 49 | plugin.ShowIPs, err = cmdDNS.Flags().GetBool("showips") 50 | if err != nil { 51 | return nil, nil, fmt.Errorf("invalid value for showips: %v", err) 52 | } 53 | 54 | plugin.ShowCNAME, err = cmdDNS.Flags().GetBool("showcname") 55 | if err != nil { 56 | return nil, nil, fmt.Errorf("invalid value for showcname: %v", err) 57 | } 58 | 59 | plugin.WildcardForced, err = cmdDNS.Flags().GetBool("wildcard") 60 | if err != nil { 61 | return nil, nil, fmt.Errorf("invalid value for wildcard: %v", err) 62 | } 63 | 64 | plugin.Timeout, err = cmdDNS.Flags().GetDuration("timeout") 65 | if err != nil { 66 | return nil, nil, fmt.Errorf("invalid value for timeout: %v", err) 67 | } 68 | 69 | plugin.Resolver, err = cmdDNS.Flags().GetString("resolver") 70 | if err != nil { 71 | return nil, nil, fmt.Errorf("invalid value for resolver: %v", err) 72 | } 73 | 74 | if plugin.Resolver != "" && runtime.GOOS == "windows" { 75 | return nil, nil, fmt.Errorf("currently can not set custom dns resolver on windows. See https://golang.org/pkg/net/#hdr-Name_Resolution") 76 | } 77 | 78 | return globalopts, plugin, nil 79 | } 80 | 81 | func init() { 82 | cmdDNS = &cobra.Command{ 83 | Use: "dns", 84 | Short: "Uses DNS subdomain bruteforcing mode", 85 | RunE: runDNS, 86 | } 87 | 88 | cmdDNS.Flags().StringP("domain", "d", "", "The target domain") 89 | cmdDNS.Flags().BoolP("showips", "i", false, "Show IP addresses") 90 | cmdDNS.Flags().BoolP("showcname", "c", false, "Show CNAME records (cannot be used with '-i' option)") 91 | cmdDNS.Flags().DurationP("timeout", "", time.Second, "DNS resolver timeout") 92 | cmdDNS.Flags().BoolP("wildcard", "", false, "Force continued operation when wildcard found") 93 | cmdDNS.Flags().StringP("resolver", "r", "", "Use custom DNS server (format server.com or server.com:port)") 94 | if err := cmdDNS.MarkFlagRequired("domain"); err != nil { 95 | log.Fatalf("error on marking flag as required: %v", err) 96 | } 97 | 98 | cmdDNS.PersistentPreRun = func(cmd *cobra.Command, args []string) { 99 | configureGlobalOptions() 100 | } 101 | 102 | rootCmd.AddCommand(cmdDNS) 103 | } 104 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 2 | github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= 3 | github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= 4 | github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= 5 | github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= 6 | github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= 7 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 8 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 9 | github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= 10 | github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 11 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 12 | github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= 13 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 14 | github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= 15 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 16 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 17 | github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= 18 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 19 | github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= 20 | github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= 21 | github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= 22 | github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s= 23 | github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= 24 | github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= 25 | github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= 26 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 27 | github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= 28 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 29 | github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= 30 | github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= 31 | golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 32 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 33 | golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc= 34 | golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 35 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 36 | golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 37 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 38 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 39 | golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7 h1:LepdCS8Gf/MVejFIt8lsiexZATdoGVyp5bcyS+rYoUI= 40 | golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 41 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 42 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 43 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 44 | -------------------------------------------------------------------------------- /cli/cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "os" 8 | "os/signal" 9 | 10 | "github.com/OJ/gobuster/v3/libgobuster" 11 | 12 | "github.com/spf13/cobra" 13 | ) 14 | 15 | var rootCmd = &cobra.Command{ 16 | Use: "gobuster", 17 | SilenceUsage: true, 18 | } 19 | 20 | var mainContext context.Context 21 | 22 | // Execute is the main cobra method 23 | func Execute() { 24 | var cancel context.CancelFunc 25 | mainContext, cancel = context.WithCancel(context.Background()) 26 | defer cancel() 27 | 28 | signalChan := make(chan os.Signal, 1) 29 | signal.Notify(signalChan, os.Interrupt) 30 | defer func() { 31 | signal.Stop(signalChan) 32 | cancel() 33 | }() 34 | go func() { 35 | select { 36 | case <-signalChan: 37 | // caught CTRL+C 38 | fmt.Println("\n[!] Keyboard interrupt detected, terminating.") 39 | cancel() 40 | case <-mainContext.Done(): 41 | } 42 | }() 43 | 44 | if err := rootCmd.Execute(); err != nil { 45 | // Leaving this in results in the same error appearing twice 46 | // Once before and once after the help output. Not sure if 47 | // this is going to be needed to output other errors that 48 | // aren't automatically outputted. 49 | //fmt.Println(err) 50 | os.Exit(1) 51 | } 52 | } 53 | 54 | func parseGlobalOptions() (*libgobuster.Options, error) { 55 | globalopts := libgobuster.NewOptions() 56 | 57 | threads, err := rootCmd.Flags().GetInt("threads") 58 | if err != nil { 59 | return nil, fmt.Errorf("invalid value for threads: %v", err) 60 | } 61 | 62 | if threads <= 0 { 63 | return nil, fmt.Errorf("threads must be bigger than 0") 64 | } 65 | globalopts.Threads = threads 66 | 67 | delay, err := rootCmd.Flags().GetDuration("delay") 68 | if err != nil { 69 | return nil, fmt.Errorf("invalid value for delay: %v", err) 70 | } 71 | 72 | if delay < 0 { 73 | return nil, fmt.Errorf("delay must be positive") 74 | } 75 | globalopts.Delay = delay 76 | 77 | globalopts.Wordlist, err = rootCmd.Flags().GetString("wordlist") 78 | if err != nil { 79 | return nil, fmt.Errorf("invalid value for wordlist: %v", err) 80 | } 81 | 82 | if globalopts.Wordlist == "-" { 83 | // STDIN 84 | } else if _, err2 := os.Stat(globalopts.Wordlist); os.IsNotExist(err2) { 85 | return nil, fmt.Errorf("wordlist file %q does not exist: %v", globalopts.Wordlist, err2) 86 | } 87 | 88 | globalopts.OutputFilename, err = rootCmd.Flags().GetString("output") 89 | if err != nil { 90 | return nil, fmt.Errorf("invalid value for output filename: %v", err) 91 | } 92 | 93 | globalopts.Verbose, err = rootCmd.Flags().GetBool("verbose") 94 | if err != nil { 95 | return nil, fmt.Errorf("invalid value for verbose: %v", err) 96 | } 97 | 98 | globalopts.Quiet, err = rootCmd.Flags().GetBool("quiet") 99 | if err != nil { 100 | return nil, fmt.Errorf("invalid value for quiet: %v", err) 101 | } 102 | 103 | globalopts.NoProgress, err = rootCmd.Flags().GetBool("noprogress") 104 | if err != nil { 105 | return nil, fmt.Errorf("invalid value for noprogress: %v", err) 106 | } 107 | 108 | return globalopts, nil 109 | } 110 | 111 | // This has to be called as part of the pre-run for sub commands. Including 112 | // this in the init() function results in the built-in `help` command not 113 | // working as intended. The required flags should only be marked as required 114 | // on the global flags when one of the non-help commands is utilised. 115 | func configureGlobalOptions() { 116 | if err := rootCmd.MarkPersistentFlagRequired("wordlist"); err != nil { 117 | log.Fatalf("error on marking flag as required: %v", err) 118 | } 119 | } 120 | 121 | func init() { 122 | rootCmd.PersistentFlags().DurationP("delay", "", 0, "Time each thread waits between requests (e.g. 1500ms)") 123 | rootCmd.PersistentFlags().IntP("threads", "t", 10, "Number of concurrent threads") 124 | rootCmd.PersistentFlags().StringP("wordlist", "w", "", "Path to the wordlist") 125 | rootCmd.PersistentFlags().StringP("output", "o", "", "Output file to write results to (defaults to stdout)") 126 | rootCmd.PersistentFlags().BoolP("verbose", "v", false, "Verbose output (errors)") 127 | rootCmd.PersistentFlags().BoolP("quiet", "q", false, "Don't print the banner and other noise") 128 | rootCmd.PersistentFlags().BoolP("noprogress", "z", false, "Don't display progress") 129 | } 130 | -------------------------------------------------------------------------------- /libgobuster/helpers_test.go: -------------------------------------------------------------------------------- 1 | package libgobuster 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | "testing/iotest" 7 | ) 8 | 9 | func TestNewStringSet(t *testing.T) { 10 | if NewStringSet().Set == nil { 11 | t.Fatal("newStringSet returned nil Set") 12 | } 13 | } 14 | 15 | func TestNewIntSet(t *testing.T) { 16 | if NewIntSet().Set == nil { 17 | t.Fatal("newIntSet returned nil Set") 18 | } 19 | } 20 | 21 | func TestStringSetAdd(t *testing.T) { 22 | x := NewStringSet() 23 | x.Add("test") 24 | if len(x.Set) != 1 { 25 | t.Fatalf("Unexptected size. Should have 1 Got %v", len(x.Set)) 26 | } 27 | } 28 | 29 | func TestStringSetAddDouble(t *testing.T) { 30 | x := NewStringSet() 31 | x.Add("test") 32 | x.Add("test") 33 | if len(x.Set) != 1 { 34 | t.Fatalf("Unexptected size. Should have 1 Got %d", len(x.Set)) 35 | } 36 | } 37 | 38 | func TestStringSetAddRange(t *testing.T) { 39 | x := NewStringSet() 40 | x.AddRange([]string{"asdf", "ghjk"}) 41 | if len(x.Set) != 2 { 42 | t.Fatalf("Unexptected size. Should have 2 Got %d", len(x.Set)) 43 | } 44 | } 45 | 46 | func TestStringSetAddRangeDouble(t *testing.T) { 47 | x := NewStringSet() 48 | x.AddRange([]string{"asdf", "ghjk", "asdf", "ghjk"}) 49 | if len(x.Set) != 2 { 50 | t.Fatalf("Unexptected size. Should have 2 Got %d", len(x.Set)) 51 | } 52 | } 53 | 54 | func TestStringSetContains(t *testing.T) { 55 | x := NewStringSet() 56 | v := []string{"asdf", "ghjk", "1234", "5678"} 57 | x.AddRange(v) 58 | for _, y := range v { 59 | if !x.Contains(y) { 60 | t.Fatalf("Did not find value %s in array. %v", y, x.Set) 61 | } 62 | } 63 | } 64 | 65 | func TestStringSetContainsAny(t *testing.T) { 66 | x := NewStringSet() 67 | v := []string{"asdf", "ghjk", "1234", "5678"} 68 | x.AddRange(v) 69 | if !x.ContainsAny(v) { 70 | t.Fatalf("Did not find any") 71 | } 72 | 73 | // test not found 74 | if x.ContainsAny([]string{"mmmm", "nnnnn"}) { 75 | t.Fatal("Found unexpected values") 76 | } 77 | } 78 | 79 | func TestStringSetStringify(t *testing.T) { 80 | x := NewStringSet() 81 | v := []string{"asdf", "ghjk", "1234", "5678"} 82 | x.AddRange(v) 83 | z := x.Stringify() 84 | // order is random 85 | for _, y := range v { 86 | if !strings.Contains(z, y) { 87 | t.Fatalf("Did not find value %q in %q", y, z) 88 | } 89 | } 90 | } 91 | 92 | func TestIntSetAdd(t *testing.T) { 93 | x := NewIntSet() 94 | x.Add(1) 95 | if len(x.Set) != 1 { 96 | t.Fatalf("Unexptected size. Should have 1 Got %d", len(x.Set)) 97 | } 98 | } 99 | 100 | func TestIntSetAddDouble(t *testing.T) { 101 | x := NewIntSet() 102 | x.Add(1) 103 | x.Add(1) 104 | if len(x.Set) != 1 { 105 | t.Fatalf("Unexptected size. Should have 1 Got %d", len(x.Set)) 106 | } 107 | } 108 | 109 | func TestIntSetContains(t *testing.T) { 110 | x := NewIntSet() 111 | v := []int{1, 2, 3, 4} 112 | for _, y := range v { 113 | x.Add(y) 114 | } 115 | for _, y := range v { 116 | if !x.Contains(y) { 117 | t.Fatalf("Did not find value %d in array. %v", y, x.Set) 118 | } 119 | } 120 | } 121 | 122 | func TestIntSetStringify(t *testing.T) { 123 | x := NewIntSet() 124 | v := []int{1, 3, 2, 4} 125 | expected := "1,2,3,4" 126 | for _, y := range v { 127 | x.Add(y) 128 | } 129 | z := x.Stringify() 130 | // should be sorted 131 | if expected != z { 132 | t.Fatalf("Expected %q got %q", expected, z) 133 | } 134 | } 135 | 136 | func TestLineCounter(t *testing.T) { 137 | var tt = []struct { 138 | testName string 139 | s string 140 | expected int 141 | }{ 142 | {"One Line", "test", 1}, 143 | {"3 Lines", "TestString\nTest\n1234", 3}, 144 | {"Trailing newline", "TestString\nTest\n1234\n", 4}, 145 | {"3 Lines cr lf", "TestString\r\nTest\r\n1234", 3}, 146 | {"Empty", "", 1}, 147 | } 148 | for _, x := range tt { 149 | t.Run(x.testName, func(t *testing.T) { 150 | r := strings.NewReader(x.s) 151 | l, err := lineCounter(r) 152 | if err != nil { 153 | t.Fatalf("Got error: %v", err) 154 | } 155 | if l != x.expected { 156 | t.Fatalf("wrong line count! Got %d expected %d", l, x.expected) 157 | } 158 | }) 159 | } 160 | } 161 | 162 | func TestLineCounterError(t *testing.T) { 163 | r := iotest.TimeoutReader(strings.NewReader("test")) 164 | _, err := lineCounter(r) 165 | if err != iotest.ErrTimeout { 166 | t.Fatalf("Got wrong error! %v", err) 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /cli/gobuster.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | "strings" 8 | "sync" 9 | "time" 10 | 11 | "github.com/OJ/gobuster/v3/libgobuster" 12 | ) 13 | 14 | func ruler() { 15 | fmt.Println("===============================================================") 16 | } 17 | 18 | func banner() { 19 | fmt.Printf("Gobuster v%s\n", libgobuster.VERSION) 20 | fmt.Println("by OJ Reeves (@TheColonial) & Christian Mehlmauer (@_FireFart_)") 21 | } 22 | 23 | // resultWorker outputs the results as they come in. This needs to be a range and should not handle 24 | // the context so the channel always has a receiver and libgobuster will not block. 25 | func resultWorker(g *libgobuster.Gobuster, filename string, wg *sync.WaitGroup) { 26 | defer wg.Done() 27 | 28 | var f *os.File 29 | var err error 30 | if filename != "" { 31 | f, err = os.Create(filename) 32 | if err != nil { 33 | g.LogError.Fatalf("error on creating output file: %v", err) 34 | } 35 | defer f.Close() 36 | } 37 | 38 | for r := range g.Results() { 39 | s, err := r.ToString(g) 40 | if err != nil { 41 | g.LogError.Fatal(err) 42 | } 43 | if s != "" { 44 | g.ClearProgress() 45 | s = strings.TrimSpace(s) 46 | fmt.Println(s) 47 | if f != nil { 48 | err = writeToFile(f, s) 49 | if err != nil { 50 | g.LogError.Fatalf("error on writing output file: %v", err) 51 | } 52 | } 53 | } 54 | } 55 | } 56 | 57 | // errorWorker outputs the errors as they come in. This needs to be a range and should not handle 58 | // the context so the channel always has a receiver and libgobuster will not block. 59 | func errorWorker(g *libgobuster.Gobuster, wg *sync.WaitGroup) { 60 | defer wg.Done() 61 | 62 | for e := range g.Errors() { 63 | if !g.Opts.Quiet { 64 | g.ClearProgress() 65 | g.LogError.Printf("[!] %v", e) 66 | } 67 | } 68 | } 69 | 70 | // progressWorker outputs the progress every tick. It will stop once cancel() is called 71 | // on the context 72 | func progressWorker(c context.Context, g *libgobuster.Gobuster, wg *sync.WaitGroup) { 73 | defer wg.Done() 74 | 75 | tick := time.NewTicker(1 * time.Second) 76 | 77 | for { 78 | select { 79 | case <-tick.C: 80 | g.PrintProgress() 81 | case <-c.Done(): 82 | return 83 | } 84 | } 85 | } 86 | 87 | func writeToFile(f *os.File, output string) error { 88 | _, err := f.WriteString(fmt.Sprintf("%s\n", output)) 89 | if err != nil { 90 | return fmt.Errorf("[!] Unable to write to file %v", err) 91 | } 92 | return nil 93 | } 94 | 95 | // Gobuster is the main entry point for the CLI 96 | func Gobuster(prevCtx context.Context, opts *libgobuster.Options, plugin libgobuster.GobusterPlugin) error { 97 | // Sanity checks 98 | if opts == nil { 99 | return fmt.Errorf("please provide valid options") 100 | } 101 | 102 | if plugin == nil { 103 | return fmt.Errorf("please provide a valid plugin") 104 | } 105 | 106 | ctx, cancel := context.WithCancel(prevCtx) 107 | defer cancel() 108 | 109 | gobuster, err := libgobuster.NewGobuster(ctx, opts, plugin) 110 | if err != nil { 111 | return err 112 | } 113 | 114 | if !opts.Quiet { 115 | ruler() 116 | banner() 117 | ruler() 118 | c, err := gobuster.GetConfigString() 119 | if err != nil { 120 | return fmt.Errorf("error on creating config string: %v", err) 121 | } 122 | fmt.Println(c) 123 | ruler() 124 | gobuster.LogInfo.Println("Starting gobuster") 125 | ruler() 126 | } 127 | 128 | // our waitgroup for all goroutines 129 | // this ensures all goroutines are finished 130 | // when we call wg.Wait() 131 | var wg sync.WaitGroup 132 | // 2 is the number of goroutines we spin up 133 | wg.Add(2) 134 | go errorWorker(gobuster, &wg) 135 | go resultWorker(gobuster, opts.OutputFilename, &wg) 136 | 137 | if !opts.Quiet && !opts.NoProgress { 138 | // if not quiet add a new workgroup entry and start the goroutine 139 | wg.Add(1) 140 | go progressWorker(ctx, gobuster, &wg) 141 | } 142 | 143 | err = gobuster.Start() 144 | 145 | // call cancel func so progressWorker will exit (the only goroutine in this 146 | // file using the context) and to free ressources 147 | cancel() 148 | // wait for all spun up goroutines to finish (all have to call wg.Done()) 149 | wg.Wait() 150 | 151 | // Late error checking to finish all threads 152 | if err != nil { 153 | return err 154 | } 155 | 156 | if !opts.Quiet { 157 | gobuster.ClearProgress() 158 | ruler() 159 | gobuster.LogInfo.Println("Finished") 160 | ruler() 161 | } 162 | return nil 163 | } 164 | -------------------------------------------------------------------------------- /helper/helper_test.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/OJ/gobuster/v3/libgobuster" 8 | ) 9 | 10 | func TestParseExtensions(t *testing.T) { 11 | t.Parallel() 12 | 13 | var tt = []struct { 14 | testName string 15 | extensions string 16 | expectedExtensions libgobuster.StringSet 17 | expectedError string 18 | }{ 19 | {"Valid extensions", "php,asp,txt", libgobuster.StringSet{Set: map[string]bool{"php": true, "asp": true, "txt": true}}, ""}, 20 | {"Spaces", "php, asp , txt", libgobuster.StringSet{Set: map[string]bool{"php": true, "asp": true, "txt": true}}, ""}, 21 | {"Double extensions", "php,asp,txt,php,asp,txt", libgobuster.StringSet{Set: map[string]bool{"php": true, "asp": true, "txt": true}}, ""}, 22 | {"Leading dot", ".php,asp,.txt", libgobuster.StringSet{Set: map[string]bool{"php": true, "asp": true, "txt": true}}, ""}, 23 | {"Empty string", "", libgobuster.NewStringSet(), "invalid extension string provided"}, 24 | } 25 | 26 | for _, x := range tt { 27 | t.Run(x.testName, func(t *testing.T) { 28 | ret, err := ParseExtensions(x.extensions) 29 | if x.expectedError != "" { 30 | if err.Error() != x.expectedError { 31 | t.Fatalf("Expected error %q but got %q", x.expectedError, err.Error()) 32 | } 33 | } else if !reflect.DeepEqual(x.expectedExtensions, ret) { 34 | t.Fatalf("Expected %v but got %v", x.expectedExtensions, ret) 35 | } 36 | }) 37 | } 38 | } 39 | 40 | func TestParseStatusCodes(t *testing.T) { 41 | t.Parallel() 42 | 43 | var tt = []struct { 44 | testName string 45 | stringCodes string 46 | expectedCodes libgobuster.IntSet 47 | expectedError string 48 | }{ 49 | {"Valid codes", "200,100,202", libgobuster.IntSet{Set: map[int]bool{100: true, 200: true, 202: true}}, ""}, 50 | {"Spaces", "200, 100 , 202", libgobuster.IntSet{Set: map[int]bool{100: true, 200: true, 202: true}}, ""}, 51 | {"Double codes", "200, 100, 202, 100", libgobuster.IntSet{Set: map[int]bool{100: true, 200: true, 202: true}}, ""}, 52 | {"Invalid code", "200,AAA", libgobuster.NewIntSet(), "invalid status code given: AAA"}, 53 | {"Invalid integer", "2000000000000000000000000000000", libgobuster.NewIntSet(), "invalid status code given: 2000000000000000000000000000000"}, 54 | {"Empty string", "", libgobuster.NewIntSet(), "invalid status code string provided"}, 55 | } 56 | 57 | for _, x := range tt { 58 | t.Run(x.testName, func(t *testing.T) { 59 | ret, err := ParseStatusCodes(x.stringCodes) 60 | if x.expectedError != "" { 61 | if err.Error() != x.expectedError { 62 | t.Fatalf("Expected error %q but got %q", x.expectedError, err.Error()) 63 | } 64 | } else if !reflect.DeepEqual(x.expectedCodes, ret) { 65 | t.Fatalf("Expected %v but got %v", x.expectedCodes, ret) 66 | } 67 | }) 68 | } 69 | } 70 | 71 | func BenchmarkParseExtensions(b *testing.B) { 72 | var tt = []struct { 73 | testName string 74 | extensions string 75 | expectedExtensions libgobuster.StringSet 76 | expectedError string 77 | }{ 78 | {"Valid extensions", "php,asp,txt", libgobuster.StringSet{Set: map[string]bool{"php": true, "asp": true, "txt": true}}, ""}, 79 | {"Spaces", "php, asp , txt", libgobuster.StringSet{Set: map[string]bool{"php": true, "asp": true, "txt": true}}, ""}, 80 | {"Double extensions", "php,asp,txt,php,asp,txt", libgobuster.StringSet{Set: map[string]bool{"php": true, "asp": true, "txt": true}}, ""}, 81 | {"Leading dot", ".php,asp,.txt", libgobuster.StringSet{Set: map[string]bool{"php": true, "asp": true, "txt": true}}, ""}, 82 | {"Empty string", "", libgobuster.NewStringSet(), "invalid extension string provided"}, 83 | } 84 | 85 | for _, x := range tt { 86 | b.Run(x.testName, func(b2 *testing.B) { 87 | for y := 0; y < b2.N; y++ { 88 | _, _ = ParseExtensions(x.extensions) 89 | } 90 | }) 91 | } 92 | } 93 | 94 | func BenchmarkParseStatusCodes(b *testing.B) { 95 | var tt = []struct { 96 | testName string 97 | stringCodes string 98 | expectedCodes libgobuster.IntSet 99 | expectedError string 100 | }{ 101 | {"Valid codes", "200,100,202", libgobuster.IntSet{Set: map[int]bool{100: true, 200: true, 202: true}}, ""}, 102 | {"Spaces", "200, 100 , 202", libgobuster.IntSet{Set: map[int]bool{100: true, 200: true, 202: true}}, ""}, 103 | {"Double codes", "200, 100, 202, 100", libgobuster.IntSet{Set: map[int]bool{100: true, 200: true, 202: true}}, ""}, 104 | {"Invalid code", "200,AAA", libgobuster.NewIntSet(), "invalid status code given: AAA"}, 105 | {"Invalid integer", "2000000000000000000000000000000", libgobuster.NewIntSet(), "invalid status code given: 2000000000000000000000000000000"}, 106 | {"Empty string", "", libgobuster.NewIntSet(), "invalid status code string provided"}, 107 | } 108 | 109 | for _, x := range tt { 110 | b.Run(x.testName, func(b2 *testing.B) { 111 | for y := 0; y < b2.N; y++ { 112 | _, _ = ParseStatusCodes(x.stringCodes) 113 | } 114 | }) 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /cli/cmd/http.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "regexp" 6 | "strconv" 7 | "strings" 8 | "syscall" 9 | "time" 10 | 11 | "github.com/OJ/gobuster/v3/libgobuster" 12 | "github.com/spf13/cobra" 13 | "golang.org/x/crypto/ssh/terminal" 14 | ) 15 | 16 | func addCommonHTTPOptions(cmd *cobra.Command) error { 17 | cmd.Flags().StringP("url", "u", "", "The target URL") 18 | cmd.Flags().StringP("cookies", "c", "", "Cookies to use for the requests") 19 | cmd.Flags().StringP("username", "U", "", "Username for Basic Auth") 20 | cmd.Flags().StringP("password", "P", "", "Password for Basic Auth") 21 | cmd.Flags().StringP("useragent", "a", libgobuster.DefaultUserAgent(), "Set the User-Agent string") 22 | cmd.Flags().StringP("proxy", "p", "", "Proxy to use for requests [http(s)://host:port]") 23 | cmd.Flags().DurationP("timeout", "", 10*time.Second, "HTTP Timeout") 24 | cmd.Flags().BoolP("followredirect", "r", false, "Follow redirects") 25 | cmd.Flags().BoolP("insecuressl", "k", false, "Skip SSL certificate verification") 26 | cmd.Flags().StringArrayP("headers", "H", []string{""}, "Specify HTTP headers, -H 'Header1: val1' -H 'Header2: val2'") 27 | 28 | if err := cmdDir.MarkFlagRequired("url"); err != nil { 29 | return fmt.Errorf("error on marking flag as required: %v", err) 30 | } 31 | 32 | return nil 33 | } 34 | 35 | func parseCommonHTTPOptions(cmd *cobra.Command) (libgobuster.OptionsHTTP, error) { 36 | options := libgobuster.OptionsHTTP{} 37 | var err error 38 | 39 | options.URL, err = cmd.Flags().GetString("url") 40 | if err != nil { 41 | return options, fmt.Errorf("invalid value for url: %v", err) 42 | } 43 | 44 | if !strings.HasPrefix(options.URL, "http") { 45 | // check to see if a port was specified 46 | re := regexp.MustCompile(`^[^/]+:(\d+)`) 47 | match := re.FindStringSubmatch(options.URL) 48 | 49 | if len(match) < 2 { 50 | // no port, default to http on 80 51 | options.URL = fmt.Sprintf("http://%s", options.URL) 52 | } else { 53 | port, err2 := strconv.Atoi(match[1]) 54 | if err2 != nil || (port != 80 && port != 443) { 55 | return options, fmt.Errorf("url scheme not specified") 56 | } else if port == 80 { 57 | options.URL = fmt.Sprintf("http://%s", options.URL) 58 | } else { 59 | options.URL = fmt.Sprintf("https://%s", options.URL) 60 | } 61 | } 62 | } 63 | 64 | options.Cookies, err = cmd.Flags().GetString("cookies") 65 | if err != nil { 66 | return options, fmt.Errorf("invalid value for cookies: %v", err) 67 | } 68 | 69 | options.Username, err = cmd.Flags().GetString("username") 70 | if err != nil { 71 | return options, fmt.Errorf("invalid value for username: %v", err) 72 | } 73 | 74 | options.Password, err = cmd.Flags().GetString("password") 75 | if err != nil { 76 | return options, fmt.Errorf("invalid value for password: %v", err) 77 | } 78 | 79 | options.UserAgent, err = cmd.Flags().GetString("useragent") 80 | if err != nil { 81 | return options, fmt.Errorf("invalid value for useragent: %v", err) 82 | } 83 | 84 | options.Proxy, err = cmd.Flags().GetString("proxy") 85 | if err != nil { 86 | return options, fmt.Errorf("invalid value for proxy: %v", err) 87 | } 88 | 89 | options.Timeout, err = cmd.Flags().GetDuration("timeout") 90 | if err != nil { 91 | return options, fmt.Errorf("invalid value for timeout: %v", err) 92 | } 93 | 94 | options.FollowRedirect, err = cmd.Flags().GetBool("followredirect") 95 | if err != nil { 96 | return options, fmt.Errorf("invalid value for followredirect: %v", err) 97 | } 98 | 99 | options.InsecureSSL, err = cmd.Flags().GetBool("insecuressl") 100 | if err != nil { 101 | return options, fmt.Errorf("invalid value for insecuressl: %v", err) 102 | } 103 | 104 | headers, err := cmd.Flags().GetStringArray("headers") 105 | if err != nil { 106 | return options, fmt.Errorf("invalid value for headers: %v", err) 107 | } 108 | 109 | for _, h := range headers { 110 | keyAndValue := strings.SplitN(h, ":", 2) 111 | if len(keyAndValue) != 2 { 112 | return options, fmt.Errorf("invalid header format for header %q", h) 113 | } 114 | key := strings.TrimSpace(keyAndValue[0]) 115 | value := strings.TrimSpace(keyAndValue[1]) 116 | if len(key) == 0 { 117 | return options, fmt.Errorf("invalid header format for header %q - name is empty", h) 118 | } 119 | header := libgobuster.HTTPHeader{Name: key, Value: value} 120 | options.Headers = append(options.Headers, header) 121 | } 122 | 123 | // Prompt for PW if not provided 124 | if options.Username != "" && options.Password == "" { 125 | fmt.Printf("[?] Auth Password: ") 126 | // please don't remove the int cast here as it is sadly needed on windows :/ 127 | passBytes, err := terminal.ReadPassword(int(syscall.Stdin)) 128 | // print a newline to simulate the newline that was entered 129 | // this means that formatting/printing after doesn't look bad. 130 | fmt.Println("") 131 | if err != nil { 132 | return options, fmt.Errorf("username given but reading of password failed") 133 | } 134 | options.Password = string(passBytes) 135 | } 136 | // if it's still empty bail out 137 | if options.Username != "" && options.Password == "" { 138 | return options, fmt.Errorf("username was provided but password is missing") 139 | } 140 | 141 | return options, nil 142 | } 143 | -------------------------------------------------------------------------------- /cli/cmd/dir.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | 7 | "github.com/OJ/gobuster/v3/cli" 8 | "github.com/OJ/gobuster/v3/gobusterdir" 9 | "github.com/OJ/gobuster/v3/helper" 10 | "github.com/OJ/gobuster/v3/libgobuster" 11 | "github.com/spf13/cobra" 12 | ) 13 | 14 | var cmdDir *cobra.Command 15 | 16 | func runDir(cmd *cobra.Command, args []string) error { 17 | globalopts, pluginopts, err := parseDirOptions() 18 | if err != nil { 19 | return fmt.Errorf("error on parsing arguments: %v", err) 20 | } 21 | 22 | plugin, err := gobusterdir.NewGobusterDir(mainContext, globalopts, pluginopts) 23 | if err != nil { 24 | return fmt.Errorf("error on creating gobusterdir: %v", err) 25 | } 26 | 27 | if err := cli.Gobuster(mainContext, globalopts, plugin); err != nil { 28 | if goberr, ok := err.(*gobusterdir.ErrWildcard); ok { 29 | return fmt.Errorf("%s. To force processing of Wildcard responses, specify the '--wildcard' switch", goberr.Error()) 30 | } 31 | return fmt.Errorf("error on running gobuster: %v", err) 32 | } 33 | return nil 34 | } 35 | 36 | func parseDirOptions() (*libgobuster.Options, *gobusterdir.OptionsDir, error) { 37 | globalopts, err := parseGlobalOptions() 38 | if err != nil { 39 | return nil, nil, err 40 | } 41 | 42 | plugin := gobusterdir.NewOptionsDir() 43 | 44 | httpOpts, err := parseCommonHTTPOptions(cmdDir) 45 | if err != nil { 46 | return nil, nil, err 47 | } 48 | plugin.Password = httpOpts.Password 49 | plugin.URL = httpOpts.URL 50 | plugin.UserAgent = httpOpts.UserAgent 51 | plugin.Username = httpOpts.Username 52 | plugin.Proxy = httpOpts.Proxy 53 | plugin.Cookies = httpOpts.Cookies 54 | plugin.Timeout = httpOpts.Timeout 55 | plugin.FollowRedirect = httpOpts.FollowRedirect 56 | plugin.InsecureSSL = httpOpts.InsecureSSL 57 | plugin.Headers = httpOpts.Headers 58 | 59 | plugin.Extensions, err = cmdDir.Flags().GetString("extensions") 60 | if err != nil { 61 | return nil, nil, fmt.Errorf("invalid value for extensions: %v", err) 62 | } 63 | 64 | if plugin.Extensions != "" { 65 | ret, err := helper.ParseExtensions(plugin.Extensions) 66 | if err != nil { 67 | return nil, nil, fmt.Errorf("invalid value for extensions: %v", err) 68 | } 69 | plugin.ExtensionsParsed = ret 70 | } 71 | 72 | plugin.StatusCodesBlacklist, err = cmdDir.Flags().GetString("statuscodesblacklist") 73 | if err != nil { 74 | return nil, nil, fmt.Errorf("invalid value for statuscodesblacklist: %v", err) 75 | } 76 | 77 | // blacklist will override the normal status codes 78 | if plugin.StatusCodesBlacklist != "" { 79 | ret, err := helper.ParseStatusCodes(plugin.StatusCodesBlacklist) 80 | if err != nil { 81 | return nil, nil, fmt.Errorf("invalid value for statuscodesblacklist: %v", err) 82 | } 83 | plugin.StatusCodesBlacklistParsed = ret 84 | } else { 85 | // parse normal status codes 86 | plugin.StatusCodes, err = cmdDir.Flags().GetString("statuscodes") 87 | if err != nil { 88 | return nil, nil, fmt.Errorf("invalid value for statuscodes: %v", err) 89 | } 90 | ret, err := helper.ParseStatusCodes(plugin.StatusCodes) 91 | if err != nil { 92 | return nil, nil, fmt.Errorf("invalid value for statuscodes: %v", err) 93 | } 94 | plugin.StatusCodesParsed = ret 95 | } 96 | 97 | plugin.UseSlash, err = cmdDir.Flags().GetBool("addslash") 98 | if err != nil { 99 | return nil, nil, fmt.Errorf("invalid value for addslash: %v", err) 100 | } 101 | 102 | plugin.Expanded, err = cmdDir.Flags().GetBool("expanded") 103 | if err != nil { 104 | return nil, nil, fmt.Errorf("invalid value for expanded: %v", err) 105 | } 106 | 107 | plugin.NoStatus, err = cmdDir.Flags().GetBool("nostatus") 108 | if err != nil { 109 | return nil, nil, fmt.Errorf("invalid value for nostatus: %v", err) 110 | } 111 | 112 | plugin.IncludeLength, err = cmdDir.Flags().GetBool("includelength") 113 | if err != nil { 114 | return nil, nil, fmt.Errorf("invalid value for includelength: %v", err) 115 | } 116 | 117 | plugin.WildcardForced, err = cmdDir.Flags().GetBool("wildcard") 118 | if err != nil { 119 | return nil, nil, fmt.Errorf("invalid value for wildcard: %v", err) 120 | } 121 | 122 | return globalopts, plugin, nil 123 | } 124 | 125 | func init() { 126 | cmdDir = &cobra.Command{ 127 | Use: "dir", 128 | Short: "Uses directory/file brutceforcing mode", 129 | RunE: runDir, 130 | } 131 | 132 | if err := addCommonHTTPOptions(cmdDir); err != nil { 133 | log.Fatalf("%v", err) 134 | } 135 | cmdDir.Flags().StringP("statuscodes", "s", "200,204,301,302,307,401,403", "Positive status codes (will be overwritten with statuscodesblacklist if set)") 136 | cmdDir.Flags().StringP("statuscodesblacklist", "b", "", "Negative status codes (will override statuscodes if set)") 137 | cmdDir.Flags().StringP("extensions", "x", "", "File extension(s) to search for") 138 | cmdDir.Flags().BoolP("expanded", "e", false, "Expanded mode, print full URLs") 139 | cmdDir.Flags().BoolP("nostatus", "n", false, "Don't print status codes") 140 | cmdDir.Flags().BoolP("includelength", "l", false, "Include the length of the body in the output") 141 | cmdDir.Flags().BoolP("addslash", "f", false, "Append / to each request") 142 | cmdDir.Flags().BoolP("wildcard", "", false, "Force continued operation when wildcard found") 143 | 144 | cmdDir.PersistentPreRun = func(cmd *cobra.Command, args []string) { 145 | configureGlobalOptions() 146 | } 147 | 148 | rootCmd.AddCommand(cmdDir) 149 | } 150 | -------------------------------------------------------------------------------- /libgobuster/libgobuster.go: -------------------------------------------------------------------------------- 1 | package libgobuster 2 | 3 | import ( 4 | "bufio" 5 | "context" 6 | "fmt" 7 | "log" 8 | "os" 9 | "strings" 10 | "sync" 11 | "time" 12 | ) 13 | 14 | // SetupFunc is the "setup" function prototype for implementations 15 | type SetupFunc func(*Gobuster) error 16 | 17 | // ProcessFunc is the "process" function prototype for implementations 18 | type ProcessFunc func(*Gobuster, string) ([]Result, error) 19 | 20 | // ResultToStringFunc is the "to string" function prototype for implementations 21 | type ResultToStringFunc func(*Gobuster, *Result) (*string, error) 22 | 23 | // Gobuster is the main object when creating a new run 24 | type Gobuster struct { 25 | Opts *Options 26 | context context.Context 27 | requestsExpected int 28 | requestsIssued int 29 | mu *sync.RWMutex 30 | plugin GobusterPlugin 31 | resultChan chan Result 32 | errorChan chan error 33 | LogInfo *log.Logger 34 | LogError *log.Logger 35 | } 36 | 37 | // NewGobuster returns a new Gobuster object 38 | func NewGobuster(c context.Context, opts *Options, plugin GobusterPlugin) (*Gobuster, error) { 39 | var g Gobuster 40 | g.Opts = opts 41 | g.plugin = plugin 42 | g.mu = new(sync.RWMutex) 43 | g.context = c 44 | g.resultChan = make(chan Result) 45 | g.errorChan = make(chan error) 46 | g.LogInfo = log.New(os.Stdout, "", log.LstdFlags) 47 | g.LogError = log.New(os.Stderr, "[ERROR] ", log.LstdFlags) 48 | 49 | return &g, nil 50 | } 51 | 52 | // Results returns a channel of Results 53 | func (g *Gobuster) Results() <-chan Result { 54 | return g.resultChan 55 | } 56 | 57 | // Errors returns a channel of errors 58 | func (g *Gobuster) Errors() <-chan error { 59 | return g.errorChan 60 | } 61 | 62 | func (g *Gobuster) incrementRequests() { 63 | g.mu.Lock() 64 | g.requestsIssued++ 65 | g.mu.Unlock() 66 | } 67 | 68 | // PrintProgress outputs the current wordlist progress to stderr 69 | func (g *Gobuster) PrintProgress() { 70 | if !g.Opts.Quiet && !g.Opts.NoProgress { 71 | g.mu.RLock() 72 | if g.Opts.Wordlist == "-" { 73 | fmt.Fprintf(os.Stderr, "\rProgress: %d", g.requestsIssued) 74 | // only print status if we already read in the wordlist 75 | } else if g.requestsExpected > 0 { 76 | fmt.Fprintf(os.Stderr, "\rProgress: %d / %d (%3.2f%%)", g.requestsIssued, g.requestsExpected, float32(g.requestsIssued)*100.0/float32(g.requestsExpected)) 77 | } 78 | g.mu.RUnlock() 79 | } 80 | } 81 | 82 | // ClearProgress removes the last status line from stderr 83 | func (g *Gobuster) ClearProgress() { 84 | fmt.Fprint(os.Stderr, resetTerminal()) 85 | } 86 | 87 | func (g *Gobuster) worker(wordChan <-chan string, wg *sync.WaitGroup) { 88 | defer wg.Done() 89 | for { 90 | select { 91 | case <-g.context.Done(): 92 | return 93 | case word, ok := <-wordChan: 94 | // worker finished 95 | if !ok { 96 | return 97 | } 98 | g.incrementRequests() 99 | 100 | wordCleaned := strings.TrimSpace(word) 101 | // Skip "comment" (starts with #), as well as empty lines 102 | if strings.HasPrefix(wordCleaned, "#") || len(wordCleaned) == 0 { 103 | break 104 | } 105 | 106 | // Mode-specific processing 107 | res, err := g.plugin.Run(wordCleaned) 108 | if err != nil { 109 | // do not exit and continue 110 | g.errorChan <- err 111 | continue 112 | } else { 113 | for _, r := range res { 114 | g.resultChan <- r 115 | } 116 | } 117 | 118 | select { 119 | case <-g.context.Done(): 120 | case <-time.After(g.Opts.Delay): 121 | } 122 | } 123 | } 124 | } 125 | 126 | func (g *Gobuster) getWordlist() (*bufio.Scanner, error) { 127 | if g.Opts.Wordlist == "-" { 128 | // Read directly from stdin 129 | return bufio.NewScanner(os.Stdin), nil 130 | } 131 | // Pull content from the wordlist 132 | wordlist, err := os.Open(g.Opts.Wordlist) 133 | if err != nil { 134 | return nil, fmt.Errorf("failed to open wordlist: %v", err) 135 | } 136 | 137 | lines, err := lineCounter(wordlist) 138 | if err != nil { 139 | return nil, fmt.Errorf("failed to get number of lines: %v", err) 140 | } 141 | 142 | g.requestsExpected = lines 143 | g.requestsIssued = 0 144 | 145 | // rewind wordlist 146 | _, err = wordlist.Seek(0, 0) 147 | if err != nil { 148 | return nil, fmt.Errorf("failed to rewind wordlist: %v", err) 149 | } 150 | return bufio.NewScanner(wordlist), nil 151 | } 152 | 153 | // Start the busting of the website with the given 154 | // set of settings from the command line. 155 | func (g *Gobuster) Start() error { 156 | defer close(g.resultChan) 157 | defer close(g.errorChan) 158 | 159 | if err := g.plugin.PreRun(); err != nil { 160 | return err 161 | } 162 | 163 | var workerGroup sync.WaitGroup 164 | workerGroup.Add(g.Opts.Threads) 165 | 166 | wordChan := make(chan string, g.Opts.Threads) 167 | 168 | // Create goroutines for each of the number of threads 169 | // specified. 170 | for i := 0; i < g.Opts.Threads; i++ { 171 | go g.worker(wordChan, &workerGroup) 172 | } 173 | 174 | scanner, err := g.getWordlist() 175 | if err != nil { 176 | return err 177 | } 178 | 179 | Scan: 180 | for scanner.Scan() { 181 | select { 182 | case <-g.context.Done(): 183 | break Scan 184 | case wordChan <- scanner.Text(): 185 | } 186 | } 187 | close(wordChan) 188 | workerGroup.Wait() 189 | return nil 190 | } 191 | 192 | // GetConfigString returns the current config as a printable string 193 | func (g *Gobuster) GetConfigString() (string, error) { 194 | return g.plugin.GetConfigString() 195 | } 196 | -------------------------------------------------------------------------------- /gobustervhost/gobustervhost.go: -------------------------------------------------------------------------------- 1 | package gobustervhost 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "context" 7 | "fmt" 8 | "net/url" 9 | "strings" 10 | "text/tabwriter" 11 | 12 | "github.com/OJ/gobuster/v3/libgobuster" 13 | "github.com/google/uuid" 14 | ) 15 | 16 | // GobusterVhost is the main type to implement the interface 17 | type GobusterVhost struct { 18 | options *OptionsVhost 19 | globalopts *libgobuster.Options 20 | http *libgobuster.HTTPClient 21 | domain string 22 | baseline1 []byte 23 | baseline2 []byte 24 | } 25 | 26 | // NewGobusterVhost creates a new initialized GobusterDir 27 | func NewGobusterVhost(cont context.Context, globalopts *libgobuster.Options, opts *OptionsVhost) (*GobusterVhost, error) { 28 | if globalopts == nil { 29 | return nil, fmt.Errorf("please provide valid global options") 30 | } 31 | 32 | if opts == nil { 33 | return nil, fmt.Errorf("please provide valid plugin options") 34 | } 35 | 36 | g := GobusterVhost{ 37 | options: opts, 38 | globalopts: globalopts, 39 | } 40 | 41 | httpOpts := libgobuster.HTTPOptions{ 42 | Proxy: opts.Proxy, 43 | FollowRedirect: opts.FollowRedirect, 44 | InsecureSSL: opts.InsecureSSL, 45 | Timeout: opts.Timeout, 46 | Username: opts.Username, 47 | Password: opts.Password, 48 | UserAgent: opts.UserAgent, 49 | Headers: opts.Headers, 50 | } 51 | 52 | h, err := libgobuster.NewHTTPClient(cont, &httpOpts) 53 | if err != nil { 54 | return nil, err 55 | } 56 | g.http = h 57 | return &g, nil 58 | } 59 | 60 | // PreRun is the pre run implementation of gobusterdir 61 | func (v *GobusterVhost) PreRun() error { 62 | 63 | // add trailing slash 64 | if !strings.HasSuffix(v.options.URL, "/") { 65 | v.options.URL = fmt.Sprintf("%s/", v.options.URL) 66 | } 67 | 68 | url, err := url.Parse(v.options.URL) 69 | if err != nil { 70 | return fmt.Errorf("invalid url %s: %v", v.options.URL, err) 71 | } 72 | v.domain = url.Host 73 | 74 | // request default vhost for baseline1 75 | _, tmp, err := v.http.GetWithBody(v.options.URL, "", v.options.Cookies) 76 | if err != nil { 77 | return fmt.Errorf("unable to connect to %s: %v", v.options.URL, err) 78 | } 79 | v.baseline1 = *tmp 80 | 81 | // request non existent vhost for baseline2 82 | subdomain := fmt.Sprintf("%s.%s", uuid.New(), v.domain) 83 | _, tmp, err = v.http.GetWithBody(v.options.URL, subdomain, v.options.Cookies) 84 | if err != nil { 85 | return fmt.Errorf("unable to connect to %s: %v", v.options.URL, err) 86 | } 87 | v.baseline2 = *tmp 88 | return nil 89 | } 90 | 91 | // Run is the process implementation of gobusterdir 92 | func (v *GobusterVhost) Run(word string) ([]libgobuster.Result, error) { 93 | subdomain := fmt.Sprintf("%s.%s", word, v.domain) 94 | status, body, err := v.http.GetWithBody(v.options.URL, subdomain, v.options.Cookies) 95 | var ret []libgobuster.Result 96 | if err != nil { 97 | return ret, err 98 | } 99 | 100 | // subdomain must not match default vhost and non existent vhost 101 | // or verbose mode is enabled 102 | found := !bytes.Equal(*body, v.baseline1) && !bytes.Equal(*body, v.baseline2) 103 | if found || v.globalopts.Verbose { 104 | size := int64(len(*body)) 105 | resultStatus := libgobuster.StatusMissed 106 | if found { 107 | resultStatus = libgobuster.StatusFound 108 | } 109 | result := libgobuster.Result{ 110 | Entity: subdomain, 111 | StatusCode: *status, 112 | Size: &size, 113 | Status: resultStatus, 114 | } 115 | ret = append(ret, result) 116 | } 117 | return ret, nil 118 | } 119 | 120 | // ResultToString is the to string implementation of gobusterdir 121 | func (v *GobusterVhost) ResultToString(r *libgobuster.Result) (*string, error) { 122 | buf := &bytes.Buffer{} 123 | 124 | statusText := "Found" 125 | if r.Status == libgobuster.StatusMissed { 126 | statusText = "Missed" 127 | } 128 | 129 | if _, err := fmt.Fprintf(buf, "%s: %s (Status: %d) [Size: %d]\n", statusText, r.Entity, r.StatusCode, *r.Size); err != nil { 130 | return nil, err 131 | } 132 | 133 | s := buf.String() 134 | return &s, nil 135 | } 136 | 137 | // GetConfigString returns the string representation of the current config 138 | func (v *GobusterVhost) GetConfigString() (string, error) { 139 | var buffer bytes.Buffer 140 | bw := bufio.NewWriter(&buffer) 141 | tw := tabwriter.NewWriter(bw, 0, 5, 3, ' ', 0) 142 | o := v.options 143 | if _, err := fmt.Fprintf(tw, "[+] Url:\t%s\n", o.URL); err != nil { 144 | return "", err 145 | } 146 | 147 | if _, err := fmt.Fprintf(tw, "[+] Threads:\t%d\n", v.globalopts.Threads); err != nil { 148 | return "", err 149 | } 150 | 151 | if v.globalopts.Delay > 0 { 152 | if _, err := fmt.Fprintf(tw, "[+] Delay:\t%s\n", v.globalopts.Delay); err != nil { 153 | return "", err 154 | } 155 | } 156 | 157 | wordlist := "stdin (pipe)" 158 | if v.globalopts.Wordlist != "-" { 159 | wordlist = v.globalopts.Wordlist 160 | } 161 | if _, err := fmt.Fprintf(tw, "[+] Wordlist:\t%s\n", wordlist); err != nil { 162 | return "", err 163 | } 164 | 165 | if o.Proxy != "" { 166 | if _, err := fmt.Fprintf(tw, "[+] Proxy:\t%s\n", o.Proxy); err != nil { 167 | return "", err 168 | } 169 | } 170 | 171 | if o.Cookies != "" { 172 | if _, err := fmt.Fprintf(tw, "[+] Cookies:\t%s\n", o.Cookies); err != nil { 173 | return "", err 174 | } 175 | } 176 | 177 | if o.UserAgent != "" { 178 | if _, err := fmt.Fprintf(tw, "[+] User Agent:\t%s\n", o.UserAgent); err != nil { 179 | return "", err 180 | } 181 | } 182 | 183 | if o.Username != "" { 184 | if _, err := fmt.Fprintf(tw, "[+] Auth User:\t%s\n", o.Username); err != nil { 185 | return "", err 186 | } 187 | } 188 | 189 | if v.globalopts.Verbose { 190 | if _, err := fmt.Fprintf(tw, "[+] Verbose:\ttrue\n"); err != nil { 191 | return "", err 192 | } 193 | } 194 | 195 | if _, err := fmt.Fprintf(tw, "[+] Timeout:\t%s\n", o.Timeout.String()); err != nil { 196 | return "", err 197 | } 198 | 199 | if err := tw.Flush(); err != nil { 200 | return "", fmt.Errorf("error on tostring: %v", err) 201 | } 202 | 203 | if err := bw.Flush(); err != nil { 204 | return "", fmt.Errorf("error on tostring: %v", err) 205 | } 206 | 207 | return strings.TrimSpace(buffer.String()), nil 208 | } 209 | -------------------------------------------------------------------------------- /libgobuster/http.go: -------------------------------------------------------------------------------- 1 | package libgobuster 2 | 3 | import ( 4 | "context" 5 | "crypto/tls" 6 | "fmt" 7 | "io" 8 | "io/ioutil" 9 | "net/http" 10 | "net/url" 11 | "strings" 12 | "time" 13 | "unicode/utf8" 14 | ) 15 | 16 | // HTTPHeader holds a single key value pair of a HTTP header 17 | type HTTPHeader struct { 18 | Name string 19 | Value string 20 | } 21 | 22 | // HTTPClient represents a http object 23 | type HTTPClient struct { 24 | client *http.Client 25 | context context.Context 26 | userAgent string 27 | defaultUserAgent string 28 | username string 29 | password string 30 | headers []HTTPHeader 31 | includeLength bool 32 | } 33 | 34 | // HTTPOptions provides options to the http client 35 | type HTTPOptions struct { 36 | Proxy string 37 | Username string 38 | Password string 39 | UserAgent string 40 | Headers []HTTPHeader 41 | Timeout time.Duration 42 | FollowRedirect bool 43 | InsecureSSL bool 44 | IncludeLength bool 45 | } 46 | 47 | // NewHTTPClient returns a new HTTPClient 48 | func NewHTTPClient(c context.Context, opt *HTTPOptions) (*HTTPClient, error) { 49 | var proxyURLFunc func(*http.Request) (*url.URL, error) 50 | var client HTTPClient 51 | proxyURLFunc = http.ProxyFromEnvironment 52 | 53 | if opt == nil { 54 | return nil, fmt.Errorf("options is nil") 55 | } 56 | 57 | if opt.Proxy != "" { 58 | proxyURL, err := url.Parse(opt.Proxy) 59 | if err != nil { 60 | return nil, fmt.Errorf("proxy URL is invalid (%v)", err) 61 | } 62 | proxyURLFunc = http.ProxyURL(proxyURL) 63 | } 64 | 65 | var redirectFunc func(req *http.Request, via []*http.Request) error 66 | if !opt.FollowRedirect { 67 | redirectFunc = func(req *http.Request, via []*http.Request) error { 68 | return http.ErrUseLastResponse 69 | } 70 | } else { 71 | redirectFunc = nil 72 | } 73 | 74 | client.client = &http.Client{ 75 | Timeout: opt.Timeout, 76 | CheckRedirect: redirectFunc, 77 | Transport: &http.Transport{ 78 | Proxy: proxyURLFunc, 79 | MaxIdleConns: 100, 80 | MaxIdleConnsPerHost: 100, 81 | TLSClientConfig: &tls.Config{ 82 | InsecureSkipVerify: opt.InsecureSSL, 83 | }, 84 | }} 85 | client.context = c 86 | client.username = opt.Username 87 | client.password = opt.Password 88 | client.includeLength = opt.IncludeLength 89 | client.userAgent = opt.UserAgent 90 | client.defaultUserAgent = DefaultUserAgent() 91 | client.headers = opt.Headers 92 | return &client, nil 93 | } 94 | 95 | // Get gets an URL and returns the status, the length and an error 96 | func (client *HTTPClient) Get(fullURL, host, cookie string) (*int, *int64, error) { 97 | return client.requestWithoutBody(http.MethodGet, fullURL, host, cookie, nil) 98 | } 99 | 100 | // Post posts to an URL and returns the status, the length and an error 101 | func (client *HTTPClient) Post(fullURL, host, cookie string, data io.Reader) (*int, *int64, error) { 102 | return client.requestWithoutBody(http.MethodPost, fullURL, host, cookie, data) 103 | } 104 | 105 | // GetWithBody gets an URL and returns the status and the body 106 | func (client *HTTPClient) GetWithBody(fullURL, host, cookie string) (*int, *[]byte, error) { 107 | return client.requestWithBody(http.MethodGet, fullURL, host, cookie, nil) 108 | } 109 | 110 | // PostWithBody gets an URL and returns the status and the body 111 | func (client *HTTPClient) PostWithBody(fullURL, host, cookie string, data io.Reader) (*int, *[]byte, error) { 112 | return client.requestWithBody(http.MethodPost, fullURL, host, cookie, data) 113 | } 114 | 115 | // requestWithoutBody makes an http request and returns the status, the length and an error 116 | func (client *HTTPClient) requestWithoutBody(method, fullURL, host, cookie string, data io.Reader) (*int, *int64, error) { 117 | resp, err := client.makeRequest(method, fullURL, host, cookie, data) 118 | if err != nil { 119 | // ignore context canceled errors 120 | if client.context.Err() == context.Canceled { 121 | return nil, nil, nil 122 | } 123 | return nil, nil, err 124 | } 125 | defer resp.Body.Close() 126 | 127 | var length *int64 128 | 129 | if client.includeLength { 130 | length = new(int64) 131 | if resp.ContentLength <= 0 { 132 | body, err2 := ioutil.ReadAll(resp.Body) 133 | if err2 == nil { 134 | *length = int64(utf8.RuneCountInString(string(body))) 135 | } 136 | } else { 137 | *length = resp.ContentLength 138 | } 139 | } else { 140 | // DO NOT REMOVE! 141 | // absolutely needed so golang will reuse connections! 142 | _, err := io.Copy(ioutil.Discard, resp.Body) 143 | if err != nil { 144 | return nil, nil, err 145 | } 146 | } 147 | 148 | return &resp.StatusCode, length, nil 149 | } 150 | 151 | // requestWithBody makes an http request and returns the status and the body 152 | func (client *HTTPClient) requestWithBody(method, fullURL, host, cookie string, data io.Reader) (*int, *[]byte, error) { 153 | resp, err := client.makeRequest(method, fullURL, host, cookie, data) 154 | if err != nil { 155 | // ignore context canceled errors 156 | if client.context.Err() == context.Canceled { 157 | return nil, nil, nil 158 | } 159 | return nil, nil, err 160 | } 161 | defer resp.Body.Close() 162 | 163 | body, err := ioutil.ReadAll(resp.Body) 164 | if err != nil { 165 | return nil, nil, fmt.Errorf("could not read body: %v", err) 166 | } 167 | 168 | return &resp.StatusCode, &body, nil 169 | } 170 | 171 | func (client *HTTPClient) makeRequest(method, fullURL, host, cookie string, data io.Reader) (*http.Response, error) { 172 | var req *http.Request 173 | var err error 174 | 175 | switch method { 176 | case http.MethodGet: 177 | req, err = http.NewRequest(http.MethodGet, fullURL, nil) 178 | if err != nil { 179 | return nil, err 180 | } 181 | case http.MethodPost: 182 | req, err = http.NewRequest(http.MethodPost, fullURL, data) 183 | if err != nil { 184 | return nil, err 185 | } 186 | default: 187 | return nil, fmt.Errorf("invalid method %s", method) 188 | } 189 | 190 | // add the context so we can easily cancel out 191 | req = req.WithContext(client.context) 192 | 193 | if cookie != "" { 194 | req.Header.Set("Cookie", cookie) 195 | } 196 | 197 | if host != "" { 198 | req.Host = host 199 | } 200 | 201 | if client.userAgent != "" { 202 | req.Header.Set("User-Agent", client.userAgent) 203 | } else { 204 | req.Header.Set("User-Agent", client.defaultUserAgent) 205 | } 206 | 207 | // add custom headers 208 | for _, h := range client.headers { 209 | req.Header.Set(h.Name, h.Value) 210 | } 211 | 212 | if client.username != "" { 213 | req.SetBasicAuth(client.username, client.password) 214 | } 215 | 216 | resp, err := client.client.Do(req) 217 | if err != nil { 218 | if ue, ok := err.(*url.Error); ok { 219 | if strings.HasPrefix(ue.Err.Error(), "x509") { 220 | return nil, fmt.Errorf("invalid certificate: %v", ue.Err) 221 | } 222 | } 223 | return nil, err 224 | } 225 | 226 | return resp, nil 227 | } 228 | -------------------------------------------------------------------------------- /gobusterdns/gobusterdns.go: -------------------------------------------------------------------------------- 1 | package gobusterdns 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "context" 7 | "fmt" 8 | "log" 9 | "net" 10 | "strings" 11 | "text/tabwriter" 12 | "time" 13 | 14 | "github.com/OJ/gobuster/v3/libgobuster" 15 | "github.com/google/uuid" 16 | ) 17 | 18 | // ErrWildcard is returned if a wildcard response is found 19 | type ErrWildcard struct { 20 | wildcardIps libgobuster.StringSet 21 | } 22 | 23 | // Error is the implementation of the error interface 24 | func (e *ErrWildcard) Error() string { 25 | return fmt.Sprintf("the DNS Server returned the same IP for every domain. IP address(es) returned: %s", e.wildcardIps.Stringify()) 26 | } 27 | 28 | // GobusterDNS is the main type to implement the interface 29 | type GobusterDNS struct { 30 | resolver *net.Resolver 31 | globalopts *libgobuster.Options 32 | options *OptionsDNS 33 | isWildcard bool 34 | wildcardIps libgobuster.StringSet 35 | } 36 | 37 | func newCustomDialer(server string) func(ctx context.Context, network, address string) (net.Conn, error) { 38 | return func(ctx context.Context, network, address string) (net.Conn, error) { 39 | d := net.Dialer{} 40 | if !strings.Contains(server, ":") { 41 | server = fmt.Sprintf("%s:53", server) 42 | } 43 | return d.DialContext(ctx, "udp", server) 44 | } 45 | } 46 | 47 | // NewGobusterDNS creates a new initialized GobusterDNS 48 | func NewGobusterDNS(globalopts *libgobuster.Options, opts *OptionsDNS) (*GobusterDNS, error) { 49 | if globalopts == nil { 50 | return nil, fmt.Errorf("please provide valid global options") 51 | } 52 | 53 | if opts == nil { 54 | return nil, fmt.Errorf("please provide valid plugin options") 55 | } 56 | 57 | resolver := net.DefaultResolver 58 | if opts.Resolver != "" { 59 | resolver = &net.Resolver{ 60 | PreferGo: true, 61 | Dial: newCustomDialer(opts.Resolver), 62 | } 63 | } 64 | 65 | g := GobusterDNS{ 66 | options: opts, 67 | globalopts: globalopts, 68 | wildcardIps: libgobuster.NewStringSet(), 69 | resolver: resolver, 70 | } 71 | return &g, nil 72 | } 73 | 74 | // PreRun is the pre run implementation of gobusterdns 75 | func (d *GobusterDNS) PreRun() error { 76 | // Resolve a subdomain sthat probably shouldn't exist 77 | guid := uuid.New() 78 | wildcardIps, err := d.dnsLookup(fmt.Sprintf("%s.%s", guid, d.options.Domain)) 79 | if err == nil { 80 | d.isWildcard = true 81 | d.wildcardIps.AddRange(wildcardIps) 82 | if !d.options.WildcardForced { 83 | return &ErrWildcard{wildcardIps: d.wildcardIps} 84 | } 85 | } 86 | 87 | if !d.globalopts.Quiet { 88 | // Provide a warning if the base domain doesn't resolve (in case of typo) 89 | _, err = d.dnsLookup(d.options.Domain) 90 | if err != nil { 91 | // Not an error, just a warning. Eg. `yp.to` doesn't resolve, but `cr.yp.to` does! 92 | log.Printf("[-] Unable to validate base domain: %s (%v)", d.options.Domain, err) 93 | } 94 | } 95 | 96 | return nil 97 | } 98 | 99 | // Run is the process implementation of gobusterdns 100 | func (d *GobusterDNS) Run(word string) ([]libgobuster.Result, error) { 101 | subdomain := fmt.Sprintf("%s.%s", word, d.options.Domain) 102 | ips, err := d.dnsLookup(subdomain) 103 | var ret []libgobuster.Result 104 | if err == nil { 105 | if !d.isWildcard || !d.wildcardIps.ContainsAny(ips) { 106 | result := libgobuster.Result{ 107 | Entity: subdomain, 108 | Status: libgobuster.StatusFound, 109 | } 110 | if d.options.ShowIPs { 111 | result.Extra = strings.Join(ips, ", ") 112 | } else if d.options.ShowCNAME { 113 | cname, err := d.dnsLookupCname(subdomain) 114 | if err == nil { 115 | result.Extra = cname 116 | } 117 | } 118 | ret = append(ret, result) 119 | } 120 | } else if d.globalopts.Verbose { 121 | ret = append(ret, libgobuster.Result{ 122 | Entity: subdomain, 123 | Status: libgobuster.StatusMissed, 124 | }) 125 | } 126 | return ret, nil 127 | } 128 | 129 | // ResultToString is the to string implementation of gobusterdns 130 | func (d *GobusterDNS) ResultToString(r *libgobuster.Result) (*string, error) { 131 | buf := &bytes.Buffer{} 132 | 133 | if r.Status == libgobuster.StatusFound { 134 | if _, err := fmt.Fprintf(buf, "Found: "); err != nil { 135 | return nil, err 136 | } 137 | } else if r.Status == libgobuster.StatusMissed { 138 | if _, err := fmt.Fprintf(buf, "Missed: "); err != nil { 139 | return nil, err 140 | } 141 | } 142 | 143 | if d.options.ShowIPs { 144 | if _, err := fmt.Fprintf(buf, "%s [%s]\n", r.Entity, r.Extra); err != nil { 145 | return nil, err 146 | } 147 | } else if d.options.ShowCNAME { 148 | if _, err := fmt.Fprintf(buf, "%s [%s]\n", r.Entity, r.Extra); err != nil { 149 | return nil, err 150 | } 151 | } else { 152 | if _, err := fmt.Fprintf(buf, "%s\n", r.Entity); err != nil { 153 | return nil, err 154 | } 155 | } 156 | 157 | s := buf.String() 158 | return &s, nil 159 | } 160 | 161 | // GetConfigString returns the string representation of the current config 162 | func (d *GobusterDNS) GetConfigString() (string, error) { 163 | var buffer bytes.Buffer 164 | bw := bufio.NewWriter(&buffer) 165 | tw := tabwriter.NewWriter(bw, 0, 5, 3, ' ', 0) 166 | o := d.options 167 | 168 | if _, err := fmt.Fprintf(tw, "[+] Domain:\t%s\n", o.Domain); err != nil { 169 | return "", err 170 | } 171 | 172 | if _, err := fmt.Fprintf(tw, "[+] Threads:\t%d\n", d.globalopts.Threads); err != nil { 173 | return "", err 174 | } 175 | 176 | if d.globalopts.Delay > 0 { 177 | if _, err := fmt.Fprintf(tw, "[+] Delay:\t%s\n", d.globalopts.Delay); err != nil { 178 | return "", err 179 | } 180 | } 181 | 182 | if o.Resolver != "" { 183 | if _, err := fmt.Fprintf(tw, "[+] Resolver:\t%s\n", o.Resolver); err != nil { 184 | return "", err 185 | } 186 | } 187 | 188 | if o.ShowCNAME { 189 | if _, err := fmt.Fprintf(tw, "[+] Show CNAME:\ttrue\n"); err != nil { 190 | return "", err 191 | } 192 | } 193 | 194 | if o.ShowIPs { 195 | if _, err := fmt.Fprintf(tw, "[+] Show IPs:\ttrue\n"); err != nil { 196 | return "", err 197 | } 198 | } 199 | 200 | if o.WildcardForced { 201 | if _, err := fmt.Fprintf(tw, "[+] Wildcard forced:\ttrue\n"); err != nil { 202 | return "", err 203 | } 204 | } 205 | 206 | if _, err := fmt.Fprintf(tw, "[+] Timeout:\t%s\n", o.Timeout.String()); err != nil { 207 | return "", err 208 | } 209 | 210 | wordlist := "stdin (pipe)" 211 | if d.globalopts.Wordlist != "-" { 212 | wordlist = d.globalopts.Wordlist 213 | } 214 | if _, err := fmt.Fprintf(tw, "[+] Wordlist:\t%s\n", wordlist); err != nil { 215 | return "", err 216 | } 217 | 218 | if d.globalopts.Verbose { 219 | if _, err := fmt.Fprintf(tw, "[+] Verbose:\ttrue\n"); err != nil { 220 | return "", err 221 | } 222 | } 223 | 224 | if err := tw.Flush(); err != nil { 225 | return "", fmt.Errorf("error on tostring: %v", err) 226 | } 227 | 228 | if err := bw.Flush(); err != nil { 229 | return "", fmt.Errorf("error on tostring: %v", err) 230 | } 231 | 232 | return strings.TrimSpace(buffer.String()), nil 233 | } 234 | 235 | func (d *GobusterDNS) dnsLookup(domain string) ([]string, error) { 236 | ctx, cancel := context.WithTimeout(context.Background(), d.options.Timeout) 237 | defer cancel() 238 | return d.resolver.LookupHost(ctx, domain) 239 | } 240 | 241 | func (d *GobusterDNS) dnsLookupCname(domain string) (string, error) { 242 | ctx, cancel := context.WithTimeout(context.Background(), d.options.Timeout) 243 | defer cancel() 244 | time.Sleep(time.Second) 245 | return d.resolver.LookupCNAME(ctx, domain) 246 | } 247 | -------------------------------------------------------------------------------- /gobusterdir/gobusterdir.go: -------------------------------------------------------------------------------- 1 | package gobusterdir 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "context" 7 | "fmt" 8 | "strings" 9 | "text/tabwriter" 10 | 11 | "github.com/OJ/gobuster/v3/libgobuster" 12 | "github.com/google/uuid" 13 | ) 14 | 15 | // ErrWildcard is returned if a wildcard response is found 16 | type ErrWildcard struct { 17 | url string 18 | statusCode int 19 | } 20 | 21 | // Error is the implementation of the error interface 22 | func (e *ErrWildcard) Error() string { 23 | return fmt.Sprintf("the server returns a status code that matches the provided options for non existing urls. %s => %d", e.url, e.statusCode) 24 | } 25 | 26 | // GobusterDir is the main type to implement the interface 27 | type GobusterDir struct { 28 | options *OptionsDir 29 | globalopts *libgobuster.Options 30 | http *libgobuster.HTTPClient 31 | } 32 | 33 | // GetRequest issues a GET request to the target and returns 34 | // the status code, length and an error 35 | func (d *GobusterDir) get(url string) (*int, *int64, error) { 36 | return d.http.Get(url, "", d.options.Cookies) 37 | } 38 | 39 | // NewGobusterDir creates a new initialized GobusterDir 40 | func NewGobusterDir(cont context.Context, globalopts *libgobuster.Options, opts *OptionsDir) (*GobusterDir, error) { 41 | if globalopts == nil { 42 | return nil, fmt.Errorf("please provide valid global options") 43 | } 44 | 45 | if opts == nil { 46 | return nil, fmt.Errorf("please provide valid plugin options") 47 | } 48 | 49 | g := GobusterDir{ 50 | options: opts, 51 | globalopts: globalopts, 52 | } 53 | 54 | httpOpts := libgobuster.HTTPOptions{ 55 | Proxy: opts.Proxy, 56 | FollowRedirect: opts.FollowRedirect, 57 | InsecureSSL: opts.InsecureSSL, 58 | IncludeLength: opts.IncludeLength, 59 | Timeout: opts.Timeout, 60 | Username: opts.Username, 61 | Password: opts.Password, 62 | UserAgent: opts.UserAgent, 63 | Headers: opts.Headers, 64 | } 65 | 66 | h, err := libgobuster.NewHTTPClient(cont, &httpOpts) 67 | if err != nil { 68 | return nil, err 69 | } 70 | g.http = h 71 | return &g, nil 72 | } 73 | 74 | // PreRun is the pre run implementation of gobusterdir 75 | func (d *GobusterDir) PreRun() error { 76 | // add trailing slash 77 | if !strings.HasSuffix(d.options.URL, "/") { 78 | d.options.URL = fmt.Sprintf("%s/", d.options.URL) 79 | } 80 | 81 | _, _, err := d.get(d.options.URL) 82 | if err != nil { 83 | return fmt.Errorf("unable to connect to %s: %v", d.options.URL, err) 84 | } 85 | 86 | guid := uuid.New() 87 | url := fmt.Sprintf("%s%s", d.options.URL, guid) 88 | wildcardResp, _, err := d.get(url) 89 | if err != nil { 90 | return err 91 | } 92 | 93 | if d.options.StatusCodesBlacklistParsed.Length() > 0 { 94 | if !d.options.StatusCodesBlacklistParsed.Contains(*wildcardResp) && !d.options.WildcardForced { 95 | return &ErrWildcard{url: url, statusCode: *wildcardResp} 96 | } 97 | } else if d.options.StatusCodesParsed.Length() > 0 { 98 | if d.options.StatusCodesParsed.Contains(*wildcardResp) && !d.options.WildcardForced { 99 | return &ErrWildcard{url: url, statusCode: *wildcardResp} 100 | } 101 | } else { 102 | return fmt.Errorf("StatusCodes and StatusCodesBlacklist are both not set which should not happen") 103 | } 104 | 105 | return nil 106 | } 107 | 108 | // Run is the process implementation of gobusterdir 109 | func (d *GobusterDir) Run(word string) ([]libgobuster.Result, error) { 110 | suffix := "" 111 | if d.options.UseSlash { 112 | suffix = "/" 113 | } 114 | 115 | // Try the DIR first 116 | url := fmt.Sprintf("%s%s%s", d.options.URL, word, suffix) 117 | dirResp, dirSize, err := d.get(url) 118 | if err != nil { 119 | return nil, err 120 | } 121 | var ret []libgobuster.Result 122 | if dirResp != nil { 123 | resultStatus := libgobuster.StatusMissed 124 | 125 | if d.options.StatusCodesBlacklistParsed.Length() > 0 { 126 | if !d.options.StatusCodesBlacklistParsed.Contains(*dirResp) { 127 | resultStatus = libgobuster.StatusFound 128 | } 129 | } else if d.options.StatusCodesParsed.Length() > 0 { 130 | if d.options.StatusCodesParsed.Contains(*dirResp) { 131 | resultStatus = libgobuster.StatusFound 132 | } 133 | } else { 134 | return nil, fmt.Errorf("StatusCodes and StatusCodesBlacklist are both not set which should not happen") 135 | } 136 | 137 | if resultStatus == libgobuster.StatusFound || d.globalopts.Verbose { 138 | ret = append(ret, libgobuster.Result{ 139 | Entity: fmt.Sprintf("%s%s", word, suffix), 140 | StatusCode: *dirResp, 141 | Size: dirSize, 142 | Status: resultStatus, 143 | }) 144 | } 145 | } 146 | 147 | // Follow up with files using each ext. 148 | for ext := range d.options.ExtensionsParsed.Set { 149 | file := fmt.Sprintf("%s.%s", word, ext) 150 | url = fmt.Sprintf("%s%s", d.options.URL, file) 151 | fileResp, fileSize, err := d.get(url) 152 | if err != nil { 153 | return nil, err 154 | } 155 | 156 | if fileResp != nil { 157 | resultStatus := libgobuster.StatusMissed 158 | 159 | if d.options.StatusCodesBlacklistParsed.Length() > 0 { 160 | if !d.options.StatusCodesBlacklistParsed.Contains(*fileResp) { 161 | resultStatus = libgobuster.StatusFound 162 | } 163 | } else if d.options.StatusCodesParsed.Length() > 0 { 164 | if d.options.StatusCodesParsed.Contains(*fileResp) { 165 | resultStatus = libgobuster.StatusFound 166 | } 167 | } else { 168 | return nil, fmt.Errorf("StatusCodes and StatusCodesBlacklist are both not set which should not happen") 169 | } 170 | 171 | if resultStatus == libgobuster.StatusFound || d.globalopts.Verbose { 172 | ret = append(ret, libgobuster.Result{ 173 | Entity: file, 174 | StatusCode: *fileResp, 175 | Size: fileSize, 176 | Status: resultStatus, 177 | }) 178 | } 179 | } 180 | } 181 | return ret, nil 182 | } 183 | 184 | // ResultToString is the to string implementation of gobusterdir 185 | func (d *GobusterDir) ResultToString(r *libgobuster.Result) (*string, error) { 186 | buf := &bytes.Buffer{} 187 | 188 | // Prefix if we're in verbose mode 189 | if d.globalopts.Verbose { 190 | switch r.Status { 191 | case libgobuster.StatusFound: 192 | if _, err := fmt.Fprintf(buf, "Found: "); err != nil { 193 | return nil, err 194 | } 195 | case libgobuster.StatusMissed: 196 | if _, err := fmt.Fprintf(buf, "Missed: "); err != nil { 197 | return nil, err 198 | } 199 | default: 200 | return nil, fmt.Errorf("unknown status %d", r.Status) 201 | } 202 | } 203 | 204 | if d.options.Expanded { 205 | if _, err := fmt.Fprintf(buf, "%s", d.options.URL); err != nil { 206 | return nil, err 207 | } 208 | } else { 209 | if _, err := fmt.Fprintf(buf, "/"); err != nil { 210 | return nil, err 211 | } 212 | } 213 | if _, err := fmt.Fprintf(buf, "%s", r.Entity); err != nil { 214 | return nil, err 215 | } 216 | 217 | if !d.options.NoStatus { 218 | if _, err := fmt.Fprintf(buf, " (Status: %d)", r.StatusCode); err != nil { 219 | return nil, err 220 | } 221 | } 222 | 223 | if r.Size != nil { 224 | if _, err := fmt.Fprintf(buf, " [Size: %d]", *r.Size); err != nil { 225 | return nil, err 226 | } 227 | } 228 | if _, err := fmt.Fprintf(buf, "\n"); err != nil { 229 | return nil, err 230 | } 231 | 232 | s := buf.String() 233 | return &s, nil 234 | } 235 | 236 | // GetConfigString returns the string representation of the current config 237 | func (d *GobusterDir) GetConfigString() (string, error) { 238 | var buffer bytes.Buffer 239 | bw := bufio.NewWriter(&buffer) 240 | tw := tabwriter.NewWriter(bw, 0, 5, 3, ' ', 0) 241 | o := d.options 242 | if _, err := fmt.Fprintf(tw, "[+] Url:\t%s\n", o.URL); err != nil { 243 | return "", err 244 | } 245 | 246 | if _, err := fmt.Fprintf(tw, "[+] Threads:\t%d\n", d.globalopts.Threads); err != nil { 247 | return "", err 248 | } 249 | 250 | if d.globalopts.Delay > 0 { 251 | if _, err := fmt.Fprintf(tw, "[+] Delay:\t%s\n", d.globalopts.Delay); err != nil { 252 | return "", err 253 | } 254 | } 255 | 256 | wordlist := "stdin (pipe)" 257 | if d.globalopts.Wordlist != "-" { 258 | wordlist = d.globalopts.Wordlist 259 | } 260 | if _, err := fmt.Fprintf(tw, "[+] Wordlist:\t%s\n", wordlist); err != nil { 261 | return "", err 262 | } 263 | 264 | if o.StatusCodesBlacklistParsed.Length() > 0 { 265 | if _, err := fmt.Fprintf(tw, "[+] Negative Status codes:\t%s\n", o.StatusCodesBlacklistParsed.Stringify()); err != nil { 266 | return "", err 267 | } 268 | } else if o.StatusCodesParsed.Length() > 0 { 269 | if _, err := fmt.Fprintf(tw, "[+] Status codes:\t%s\n", o.StatusCodesParsed.Stringify()); err != nil { 270 | return "", err 271 | } 272 | } 273 | 274 | if o.Proxy != "" { 275 | if _, err := fmt.Fprintf(tw, "[+] Proxy:\t%s\n", o.Proxy); err != nil { 276 | return "", err 277 | } 278 | } 279 | 280 | if o.Cookies != "" { 281 | if _, err := fmt.Fprintf(tw, "[+] Cookies:\t%s\n", o.Cookies); err != nil { 282 | return "", err 283 | } 284 | } 285 | 286 | if o.UserAgent != "" { 287 | if _, err := fmt.Fprintf(tw, "[+] User Agent:\t%s\n", o.UserAgent); err != nil { 288 | return "", err 289 | } 290 | } 291 | 292 | if o.IncludeLength { 293 | if _, err := fmt.Fprintf(tw, "[+] Show length:\ttrue\n"); err != nil { 294 | return "", err 295 | } 296 | } 297 | 298 | if o.Username != "" { 299 | if _, err := fmt.Fprintf(tw, "[+] Auth User:\t%s\n", o.Username); err != nil { 300 | return "", err 301 | } 302 | } 303 | 304 | if o.Extensions != "" { 305 | if _, err := fmt.Fprintf(tw, "[+] Extensions:\t%s\n", o.ExtensionsParsed.Stringify()); err != nil { 306 | return "", err 307 | } 308 | } 309 | 310 | if o.UseSlash { 311 | if _, err := fmt.Fprintf(tw, "[+] Add Slash:\ttrue\n"); err != nil { 312 | return "", err 313 | } 314 | } 315 | 316 | if o.FollowRedirect { 317 | if _, err := fmt.Fprintf(tw, "[+] Follow Redir:\ttrue\n"); err != nil { 318 | return "", err 319 | } 320 | } 321 | 322 | if o.Expanded { 323 | if _, err := fmt.Fprintf(tw, "[+] Expanded:\ttrue\n"); err != nil { 324 | return "", err 325 | } 326 | } 327 | 328 | if o.NoStatus { 329 | if _, err := fmt.Fprintf(tw, "[+] No status:\ttrue\n"); err != nil { 330 | return "", err 331 | } 332 | } 333 | 334 | if d.globalopts.Verbose { 335 | if _, err := fmt.Fprintf(tw, "[+] Verbose:\ttrue\n"); err != nil { 336 | return "", err 337 | } 338 | } 339 | 340 | if _, err := fmt.Fprintf(tw, "[+] Timeout:\t%s\n", o.Timeout.String()); err != nil { 341 | return "", err 342 | } 343 | 344 | if err := tw.Flush(); err != nil { 345 | return "", fmt.Errorf("error on tostring: %v", err) 346 | } 347 | 348 | if err := bw.Flush(); err != nil { 349 | return "", fmt.Errorf("error on tostring: %v", err) 350 | } 351 | 352 | return strings.TrimSpace(buffer.String()), nil 353 | } 354 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Gobuster v3.0.1 (OJ Reeves @TheColonial) 2 | 3 | Gobuster is a tool used to brute-force: 4 | 5 | * URIs (directories and files) in web sites. 6 | * DNS subdomains (with wildcard support). 7 | * Virtual Host names on target web servers. 8 | 9 | ## Tags, Statuses, etc 10 | 11 | [![Build Status](https://travis-ci.com/OJ/gobuster.svg?branch=master)](https://travis-ci.com/OJ/gobuster) [![Backers on Open Collective](https://opencollective.com/gobuster/backers/badge.svg)](#backers) [![Sponsors on Open Collective](https://opencollective.com/gobuster/sponsors/badge.svg)](#sponsors) 12 | 13 | ## Oh dear God.. WHY!? 14 | 15 | Because I wanted: 16 | 17 | 1. ... something that didn't have a fat Java GUI (console FTW). 18 | 1. ... to build something that just worked on the command line. 19 | 1. ... something that did not do recursive brute force. 20 | 1. ... something that allowed me to brute force folders and multiple extensions at once. 21 | 1. ... something that compiled to native on multiple platforms. 22 | 1. ... something that was faster than an interpreted script (such as Python). 23 | 1. ... something that didn't require a runtime. 24 | 1. ... use something that was good with concurrency (hence Go). 25 | 1. ... to build something in Go that wasn't totally useless. 26 | 27 | ## But it's shit! And your implementation sucks! 28 | 29 | Yes, you're probably correct. Feel free to: 30 | 31 | * Not use it. 32 | * Show me how to do it better. 33 | 34 | ## Love this tool? Back it! 35 | 36 | If you're backing us already, you rock. If you're not, that's cool too! Want to back us? [Become a backer](https://opencollective.com/gobuster#backer)! 37 | 38 | [![Backers](https://opencollective.com/gobuster/backers.svg?width=890)](https://opencollective.com/gobuster#backers) 39 | 40 | All funds that are donated to this project will be donated to charity. A full log of charity donations will be available in this repository as they are processed. 41 | 42 | ## Changes in 3.0 43 | 44 | * New CLI options so modes are strictly seperated (`-m` is now gone!) 45 | * Performance Optimizations and better connection handling 46 | * Ability to bruteforce vhost names 47 | * Option to supply custom HTTP headers 48 | 49 | ## Available Modes 50 | 51 | * dir - the classic directory brute-forcing mode 52 | * dns - DNS subdomain brute-forcing mode 53 | * vhost - virtual host brute-forcing mode (not the same as DNS!) 54 | 55 | ## Built-in Help 56 | 57 | Help is built-in! 58 | 59 | * `gobuster help` - outputs the top-level help. 60 | * `gobuster help ` - outputs the help specific to that mode. 61 | 62 | ## `dns` Mode Help 63 | 64 | ```text 65 | Usage: 66 | gobuster dns [flags] 67 | 68 | Flags: 69 | -d, --domain string The target domain 70 | -h, --help help for dns 71 | -r, --resolver string Use custom DNS server (format server.com or server.com:port) 72 | -c, --showcname Show CNAME records (cannot be used with '-i' option) 73 | -i, --showips Show IP addresses 74 | --timeout duration DNS resolver timeout (default 1s) 75 | --wildcard Force continued operation when wildcard found 76 | 77 | Global Flags: 78 | -z, --noprogress Don't display progress 79 | -o, --output string Output file to write results to (defaults to stdout) 80 | -q, --quiet Don't print the banner and other noise 81 | -t, --threads int Number of concurrent threads (default 10) 82 | --delay duration Time each thread waits between requests (e.g. 1500ms) 83 | -v, --verbose Verbose output (errors) 84 | -w, --wordlist string Path to the wordlist 85 | ``` 86 | 87 | ## `dir` Mode Options 88 | 89 | ```text 90 | Usage: 91 | gobuster dir [flags] 92 | 93 | Flags: 94 | -f, --addslash Append / to each request 95 | -c, --cookies string Cookies to use for the requests 96 | -e, --expanded Expanded mode, print full URLs 97 | -x, --extensions string File extension(s) to search for 98 | -r, --followredirect Follow redirects 99 | -H, --headers stringArray Specify HTTP headers, -H 'Header1: val1' -H 'Header2: val2' 100 | -h, --help help for dir 101 | -l, --includelength Include the length of the body in the output 102 | -k, --insecuressl Skip SSL certificate verification 103 | -n, --nostatus Don't print status codes 104 | -P, --password string Password for Basic Auth 105 | -p, --proxy string Proxy to use for requests [http(s)://host:port] 106 | -s, --statuscodes string Positive status codes (will be overwritten with statuscodesblacklist if set) (default "200,204,301,302,307,401,403") 107 | -b, --statuscodesblacklist string Negative status codes (will override statuscodes if set) 108 | --timeout duration HTTP Timeout (default 10s) 109 | -u, --url string The target URL 110 | -a, --useragent string Set the User-Agent string (default "gobuster/3.0.1") 111 | -U, --username string Username for Basic Auth 112 | --wildcard Force continued operation when wildcard found 113 | 114 | Global Flags: 115 | -z, --noprogress Don't display progress 116 | -o, --output string Output file to write results to (defaults to stdout) 117 | -q, --quiet Don't print the banner and other noise 118 | -t, --threads int Number of concurrent threads (default 10) 119 | --delay duration Time each thread waits between requests (e.g. 1500ms) 120 | -v, --verbose Verbose output (errors) 121 | -w, --wordlist string Path to the wordlist 122 | ``` 123 | 124 | ## `vhost` Mode Options 125 | 126 | ```text 127 | Usage: 128 | gobuster vhost [flags] 129 | 130 | Flags: 131 | -c, --cookies string Cookies to use for the requests 132 | -r, --followredirect Follow redirects 133 | -H, --headers stringArray Specify HTTP headers, -H 'Header1: val1' -H 'Header2: val2' 134 | -h, --help help for vhost 135 | -k, --insecuressl Skip SSL certificate verification 136 | -P, --password string Password for Basic Auth 137 | -p, --proxy string Proxy to use for requests [http(s)://host:port] 138 | --timeout duration HTTP Timeout (default 10s) 139 | -u, --url string The target URL 140 | -a, --useragent string Set the User-Agent string (default "gobuster/3.0.1") 141 | -U, --username string Username for Basic Auth 142 | 143 | Global Flags: 144 | -z, --noprogress Don't display progress 145 | -o, --output string Output file to write results to (defaults to stdout) 146 | -q, --quiet Don't print the banner and other noise 147 | -t, --threads int Number of concurrent threads (default 10) 148 | --delay duration Time each thread waits between requests (e.g. 1500ms) 149 | -v, --verbose Verbose output (errors) 150 | -w, --wordlist string Path to the wordlist 151 | ``` 152 | 153 | ## Easy Installation 154 | 155 | ### Binary Releases 156 | 157 | We are now shipping binaries for each of the releases so that you don't even have to build them yourself! How wonderful is that! 158 | 159 | If you're stupid enough to trust binaries that I've put together, you can download them from the [releases](https://github.com/OJ/gobuster/releases) page. 160 | 161 | ### Using `go get` 162 | 163 | If you have a [Go](https://golang.org/) environment ready to go, it's as easy as: 164 | 165 | ```bash 166 | go get github.com/OJ/gobuster 167 | ``` 168 | 169 | ## Building From Source 170 | 171 | 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. 172 | 173 | ### Compiling 174 | 175 | `gobuster` now has external dependencies, and so they need to be pulled in first: 176 | 177 | ```bash 178 | go get && go build 179 | ``` 180 | 181 | This will create a `gobuster` binary for you. If you want to install it in the `$GOPATH/bin` folder you can run: 182 | 183 | ```bash 184 | go install 185 | ``` 186 | 187 | If you have all the dependencies already, you can make use of the build scripts: 188 | 189 | * `make` - builds for the current Go configuration (ie. runs `go build`). 190 | * `make windows` - builds 32 and 64 bit binaries for windows, and writes them to the `build` subfolder. 191 | * `make linux` - builds 32 and 64 bit binaries for linux, and writes them to the `build` subfolder. 192 | * `make darwin` - builds 32 and 64 bit binaries for darwin, and writes them to the `build` subfolder. 193 | * `make all` - builds for all platforms and architectures, and writes the resulting binaries to the `build` subfolder. 194 | * `make clean` - clears out the `build` subfolder. 195 | * `make test` - runs the tests. 196 | 197 | ## Wordlists via STDIN 198 | 199 | Wordlists can be piped into `gobuster` via stdin by providing a `-` to the `-w` option: 200 | 201 | ```bash 202 | hashcat -a 3 --stdout ?l | gobuster dir -u https://mysite.com -w - 203 | ``` 204 | 205 | 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. 206 | 207 | ## Examples 208 | 209 | ### `dir` Mode 210 | 211 | Command line might look like this: 212 | 213 | ```bash 214 | gobuster dir -u https://mysite.com/path/to/folder -c 'session=123456' -t 50 -w common-files.txt -x .php,.html 215 | ``` 216 | 217 | Default options looks like this: 218 | 219 | ```bash 220 | gobuster dir -u https://buffered.io -w ~/wordlists/shortlist.txt 221 | 222 | =============================================================== 223 | Gobuster v3.0.1 224 | by OJ Reeves (@TheColonial) & Christian Mehlmauer (@_FireFart_) 225 | =============================================================== 226 | [+] Mode : dir 227 | [+] Url/Domain : https://buffered.io/ 228 | [+] Threads : 10 229 | [+] Wordlist : /home/oj/wordlists/shortlist.txt 230 | [+] Status codes : 200,204,301,302,307,401,403 231 | [+] User Agent : gobuster/3.0.1 232 | [+] Timeout : 10s 233 | =============================================================== 234 | 2019/06/21 11:49:43 Starting gobuster 235 | =============================================================== 236 | /categories (Status: 301) 237 | /contact (Status: 301) 238 | /posts (Status: 301) 239 | /index (Status: 200) 240 | =============================================================== 241 | 2019/06/21 11:49:44 Finished 242 | =============================================================== 243 | ``` 244 | 245 | Default options with status codes disabled looks like this: 246 | 247 | ```bash 248 | gobuster dir -u https://buffered.io -w ~/wordlists/shortlist.txt -n 249 | 250 | =============================================================== 251 | Gobuster v3.0.1 252 | by OJ Reeves (@TheColonial) & Christian Mehlmauer (@_FireFart_) 253 | =============================================================== 254 | [+] Mode : dir 255 | [+] Url/Domain : https://buffered.io/ 256 | [+] Threads : 10 257 | [+] Wordlist : /home/oj/wordlists/shortlist.txt 258 | [+] Status codes : 200,204,301,302,307,401,403 259 | [+] User Agent : gobuster/3.0.1 260 | [+] No status : true 261 | [+] Timeout : 10s 262 | =============================================================== 263 | 2019/06/21 11:50:18 Starting gobuster 264 | =============================================================== 265 | /categories 266 | /contact 267 | /index 268 | /posts 269 | =============================================================== 270 | 2019/06/21 11:50:18 Finished 271 | =============================================================== 272 | ``` 273 | 274 | Verbose output looks like this: 275 | 276 | ```bash 277 | gobuster dir -u https://buffered.io -w ~/wordlists/shortlist.txt -v 278 | 279 | =============================================================== 280 | Gobuster v3.0.1 281 | by OJ Reeves (@TheColonial) & Christian Mehlmauer (@_FireFart_) 282 | =============================================================== 283 | [+] Mode : dir 284 | [+] Url/Domain : https://buffered.io/ 285 | [+] Threads : 10 286 | [+] Wordlist : /home/oj/wordlists/shortlist.txt 287 | [+] Status codes : 200,204,301,302,307,401,403 288 | [+] User Agent : gobuster/3.0.1 289 | [+] Verbose : true 290 | [+] Timeout : 10s 291 | =============================================================== 292 | 2019/06/21 11:50:51 Starting gobuster 293 | =============================================================== 294 | Missed: /alsodoesnotexist (Status: 404) 295 | Found: /index (Status: 200) 296 | Missed: /doesnotexist (Status: 404) 297 | Found: /categories (Status: 301) 298 | Found: /posts (Status: 301) 299 | Found: /contact (Status: 301) 300 | =============================================================== 301 | 2019/06/21 11:50:51 Finished 302 | =============================================================== 303 | ``` 304 | 305 | Example showing content length: 306 | 307 | ```bash 308 | gobuster dir -u https://buffered.io -w ~/wordlists/shortlist.txt -l 309 | 310 | =============================================================== 311 | Gobuster v3.0.1 312 | by OJ Reeves (@TheColonial) & Christian Mehlmauer (@_FireFart_) 313 | =============================================================== 314 | [+] Mode : dir 315 | [+] Url/Domain : https://buffered.io/ 316 | [+] Threads : 10 317 | [+] Wordlist : /home/oj/wordlists/shortlist.txt 318 | [+] Status codes : 200,204,301,302,307,401,403 319 | [+] User Agent : gobuster/3.0.1 320 | [+] Show length : true 321 | [+] Timeout : 10s 322 | =============================================================== 323 | 2019/06/21 11:51:16 Starting gobuster 324 | =============================================================== 325 | /categories (Status: 301) [Size: 178] 326 | /posts (Status: 301) [Size: 178] 327 | /contact (Status: 301) [Size: 178] 328 | /index (Status: 200) [Size: 51759] 329 | =============================================================== 330 | 2019/06/21 11:51:17 Finished 331 | =============================================================== 332 | ``` 333 | 334 | Quiet output, with status disabled and expanded mode looks like this ("grep mode"): 335 | 336 | ```bash 337 | gobuster dir -u https://buffered.io -w ~/wordlists/shortlist.txt -q -n -e 338 | https://buffered.io/index 339 | https://buffered.io/contact 340 | https://buffered.io/posts 341 | https://buffered.io/categories 342 | ``` 343 | 344 | ### `dns` Mode 345 | 346 | Command line might look like this: 347 | 348 | ```bash 349 | gobuster dns -d mysite.com -t 50 -w common-names.txt 350 | ``` 351 | 352 | Normal sample run goes like this: 353 | 354 | ```bash 355 | gobuster dns -d google.com -w ~/wordlists/subdomains.txt 356 | 357 | =============================================================== 358 | Gobuster v3.0.1 359 | by OJ Reeves (@TheColonial) & Christian Mehlmauer (@_FireFart_) 360 | =============================================================== 361 | [+] Mode : dns 362 | [+] Url/Domain : google.com 363 | [+] Threads : 10 364 | [+] Wordlist : /home/oj/wordlists/subdomains.txt 365 | =============================================================== 366 | 2019/06/21 11:54:20 Starting gobuster 367 | =============================================================== 368 | Found: chrome.google.com 369 | Found: ns1.google.com 370 | Found: admin.google.com 371 | Found: www.google.com 372 | Found: m.google.com 373 | Found: support.google.com 374 | Found: translate.google.com 375 | Found: cse.google.com 376 | Found: news.google.com 377 | Found: music.google.com 378 | Found: mail.google.com 379 | Found: store.google.com 380 | Found: mobile.google.com 381 | Found: search.google.com 382 | Found: wap.google.com 383 | Found: directory.google.com 384 | Found: local.google.com 385 | Found: blog.google.com 386 | =============================================================== 387 | 2019/06/21 11:54:20 Finished 388 | =============================================================== 389 | ``` 390 | 391 | Show IP sample run goes like this: 392 | 393 | ```bash 394 | gobuster dns -d google.com -w ~/wordlists/subdomains.txt -i 395 | 396 | =============================================================== 397 | Gobuster v3.0.1 398 | by OJ Reeves (@TheColonial) & Christian Mehlmauer (@_FireFart_) 399 | =============================================================== 400 | [+] Mode : dns 401 | [+] Url/Domain : google.com 402 | [+] Threads : 10 403 | [+] Wordlist : /home/oj/wordlists/subdomains.txt 404 | =============================================================== 405 | 2019/06/21 11:54:54 Starting gobuster 406 | =============================================================== 407 | Found: www.google.com [172.217.25.36, 2404:6800:4006:802::2004] 408 | Found: admin.google.com [172.217.25.46, 2404:6800:4006:806::200e] 409 | Found: store.google.com [172.217.167.78, 2404:6800:4006:802::200e] 410 | Found: mobile.google.com [172.217.25.43, 2404:6800:4006:802::200b] 411 | Found: ns1.google.com [216.239.32.10, 2001:4860:4802:32::a] 412 | Found: m.google.com [172.217.25.43, 2404:6800:4006:802::200b] 413 | Found: cse.google.com [172.217.25.46, 2404:6800:4006:80a::200e] 414 | Found: chrome.google.com [172.217.25.46, 2404:6800:4006:802::200e] 415 | Found: search.google.com [172.217.25.46, 2404:6800:4006:802::200e] 416 | Found: local.google.com [172.217.25.46, 2404:6800:4006:80a::200e] 417 | Found: news.google.com [172.217.25.46, 2404:6800:4006:802::200e] 418 | Found: blog.google.com [216.58.199.73, 2404:6800:4006:806::2009] 419 | Found: support.google.com [172.217.25.46, 2404:6800:4006:802::200e] 420 | Found: wap.google.com [172.217.25.46, 2404:6800:4006:802::200e] 421 | Found: directory.google.com [172.217.25.46, 2404:6800:4006:802::200e] 422 | Found: translate.google.com [172.217.25.46, 2404:6800:4006:802::200e] 423 | Found: music.google.com [172.217.25.46, 2404:6800:4006:802::200e] 424 | Found: mail.google.com [172.217.25.37, 2404:6800:4006:802::2005] 425 | =============================================================== 426 | 2019/06/21 11:54:55 Finished 427 | =============================================================== 428 | ``` 429 | 430 | 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. 431 | 432 | ```bash 433 | gobuster dns -d yp.to -w ~/wordlists/subdomains.txt -i 434 | 435 | =============================================================== 436 | Gobuster v3.0.1 437 | by OJ Reeves (@TheColonial) & Christian Mehlmauer (@_FireFart_) 438 | =============================================================== 439 | [+] Mode : dns 440 | [+] Url/Domain : yp.to 441 | [+] Threads : 10 442 | [+] Wordlist : /home/oj/wordlists/subdomains.txt 443 | =============================================================== 444 | 2019/06/21 11:56:43 Starting gobuster 445 | =============================================================== 446 | 2019/06/21 11:56:53 [-] Unable to validate base domain: yp.to 447 | Found: cr.yp.to [131.193.32.108, 131.193.32.109] 448 | =============================================================== 449 | 2019/06/21 11:56:53 Finished 450 | =============================================================== 451 | ``` 452 | 453 | Wildcard DNS is also detected properly: 454 | 455 | ```bash 456 | gobuster dns -d 0.0.1.xip.io -w ~/wordlists/subdomains.txt 457 | 458 | =============================================================== 459 | Gobuster v3.0.1 460 | by OJ Reeves (@TheColonial) & Christian Mehlmauer (@_FireFart_) 461 | =============================================================== 462 | [+] Mode : dns 463 | [+] Url/Domain : 0.0.1.xip.io 464 | [+] Threads : 10 465 | [+] Wordlist : /home/oj/wordlists/subdomains.txt 466 | =============================================================== 467 | 2019/06/21 12:13:48 Starting gobuster 468 | =============================================================== 469 | 2019/06/21 12:13:48 [-] Wildcard DNS found. IP address(es): 1.0.0.0 470 | 2019/06/21 12:13:48 [!] To force processing of Wildcard DNS, specify the '--wildcard' switch. 471 | =============================================================== 472 | 2019/06/21 12:13:48 Finished 473 | =============================================================== 474 | ``` 475 | 476 | If the user wants to force processing of a domain that has wildcard entries, use `--wildcard`: 477 | 478 | ```bash 479 | gobuster dns -d 0.0.1.xip.io -w ~/wordlists/subdomains.txt --wildcard 480 | 481 | =============================================================== 482 | Gobuster v3.0.1 483 | by OJ Reeves (@TheColonial) & Christian Mehlmauer (@_FireFart_) 484 | =============================================================== 485 | [+] Mode : dns 486 | [+] Url/Domain : 0.0.1.xip.io 487 | [+] Threads : 10 488 | [+] Wordlist : /home/oj/wordlists/subdomains.txt 489 | =============================================================== 490 | 2019/06/21 12:13:51 Starting gobuster 491 | =============================================================== 492 | 2019/06/21 12:13:51 [-] Wildcard DNS found. IP address(es): 1.0.0.0 493 | Found: 127.0.0.1.xip.io 494 | Found: test.127.0.0.1.xip.io 495 | =============================================================== 496 | 2019/06/21 12:13:53 Finished 497 | =============================================================== 498 | ``` 499 | 500 | ### `vhost` Mode 501 | 502 | Command line might look like this: 503 | 504 | ```bash 505 | gobuster vhost -u https://mysite.com -w common-vhosts.txt 506 | ``` 507 | 508 | Normal sample run goes like this: 509 | 510 | ```bash 511 | gobuster vhost -u https://mysite.com -w common-vhosts.txt 512 | 513 | =============================================================== 514 | Gobuster v3.0.1 515 | by OJ Reeves (@TheColonial) & Christian Mehlmauer (@_FireFart_) 516 | =============================================================== 517 | [+] Url: https://mysite.com 518 | [+] Threads: 10 519 | [+] Wordlist: common-vhosts.txt 520 | [+] User Agent: gobuster/3.0.1 521 | [+] Timeout: 10s 522 | =============================================================== 523 | 2019/06/21 08:36:00 Starting gobuster 524 | =============================================================== 525 | Found: www.mysite.com 526 | Found: piwik.mysite.com 527 | Found: mail.mysite.com 528 | =============================================================== 529 | 2019/06/21 08:36:05 Finished 530 | =============================================================== 531 | ``` 532 | 533 | ## License 534 | 535 | See the LICENSE file. 536 | 537 | ## Thanks 538 | 539 | See the THANKS file for people who helped out. 540 | --------------------------------------------------------------------------------