├── libgobuster ├── output.go ├── dns.go ├── helpers.go ├── state.go ├── dir.go └── statehelpers.go ├── .gitignore ├── THANKS ├── main.go ├── LICENSE └── README.md /libgobuster/output.go: -------------------------------------------------------------------------------- 1 | package libgobuster 2 | 3 | // WriteToFile ... Output a string to a file in the global State 4 | func WriteToFile(output string, s *State) { 5 | _, err := s.OutputFile.WriteString(output) 6 | if err != nil { 7 | panic("[!] Unable to write to file " + s.OutputFileName) 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.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 | # The executable, if it has been built 29 | gobuster 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 | @mzpqnxow - extensive linting, arbitrary headers, arbitrary verbs, HTTP body, ... 16 | -------------------------------------------------------------------------------- /libgobuster/dns.go: -------------------------------------------------------------------------------- 1 | package libgobuster 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "strings" 7 | 8 | uuid "github.com/satori/go.uuid" 9 | ) 10 | 11 | // SetupDNS ... Resolve a subdomain that probably shouldn't exist 12 | func SetupDNS(s *State) bool { 13 | guid := uuid.Must(uuid.NewV4()) 14 | wildcardIps, err := net.LookupHost(fmt.Sprintf("%s.%s", guid, s.URL)) 15 | if err == nil { 16 | s.IsWildcard = true 17 | s.WildcardIps.AddRange(wildcardIps) 18 | fmt.Println("[-] Wildcard DNS found. IP address(es): ", s.WildcardIps.Stringify()) 19 | if !s.WildcardForced { 20 | fmt.Println("[-] To force processing of Wildcard DNS, specify the '-fw' switch.") 21 | } 22 | return s.WildcardForced 23 | } 24 | 25 | if !s.Quiet { 26 | // Provide a warning if the base domain doesn't resolve (in case of typo) 27 | _, err = net.LookupHost(s.URL) 28 | if err != nil { 29 | // Not an error, just a warning. Eg. `yp.to` doesn't resolve, but `cr.py.to` does! 30 | fmt.Println("[-] Unable to validate base domain:", s.URL) 31 | } 32 | } 33 | 34 | return true 35 | } 36 | 37 | // ProcessDNSEntry ... Make a DNS request to a domain from the wordlist 38 | func ProcessDNSEntry(s *State, word string, resultChan chan<- Result) { 39 | subdomain := word + "." + s.URL 40 | ips, err := net.LookupHost(subdomain) 41 | 42 | if err == nil { 43 | if !s.IsWildcard || !s.WildcardIps.ContainsAny(ips) { 44 | result := Result{ 45 | Entity: subdomain, 46 | } 47 | if s.ShowIPs { 48 | result.Extra = strings.Join(ips, ", ") 49 | } else if s.ShowCNAME { 50 | cname, err := net.LookupCNAME(subdomain) 51 | if err == nil { 52 | result.Extra = cname 53 | } 54 | } 55 | resultChan <- result 56 | } 57 | } else if s.Verbose { 58 | result := Result{ 59 | Entity: subdomain, 60 | Status: 404, 61 | } 62 | resultChan <- result 63 | } 64 | } 65 | 66 | // PrintDNSResult ... Print out various metadata about the DNS response 67 | func PrintDNSResult(s *State, r *Result) { 68 | output := "" 69 | if r.Status == 404 { 70 | output = fmt.Sprintf("Missing: %s\n", r.Entity) 71 | } else if s.ShowIPs { 72 | output = fmt.Sprintf("Found: %s [%s]\n", r.Entity, r.Extra) 73 | } else if s.ShowCNAME { 74 | output = fmt.Sprintf("Found: %s [%s]\n", r.Entity, r.Extra) 75 | } else { 76 | output = fmt.Sprintf("Found: %s\n", r.Entity) 77 | } 78 | fmt.Printf("%s", output) 79 | 80 | if s.OutputFile != nil { 81 | WriteToFile(output, s) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | //---------------------------------------------------- 4 | // Gobuster -- by OJ Reeves 5 | // 6 | // A crap attempt at building something that resembles 7 | // dirbuster or dirb using Go. The goal was to build 8 | // a tool that would help learn Go and to actually do 9 | // something useful. The idea of having this compile 10 | // to native code is also appealing. 11 | // 12 | // Run: gobuster -h 13 | // 14 | // Please see THANKS file for contributors. 15 | // Please see LICENSE file for license details. 16 | // 17 | //---------------------------------------------------- 18 | 19 | import ( 20 | "flag" 21 | "fmt" 22 | "./libgobuster" 23 | //"github.com/mzpqnxow/gobuster/libgobuster" 24 | ) 25 | 26 | // ParseCmdLine ... Parse all the command line options into a settings 27 | // instance for future use. 28 | func ParseCmdLine() *libgobuster.State { 29 | var extensions string 30 | var codes string 31 | var proxy string 32 | 33 | s := libgobuster.InitState() 34 | 35 | // Set up the variables we're interested in parsing. 36 | flag.IntVar(&s.Threads, "t", 10, "Number of concurrent threads") 37 | flag.StringVar(&s.Mode, "m", "dir", "Directory/File mode (dir) or DNS mode (dns)") 38 | flag.StringVar(&s.Wordlist, "w", "", "Path to the wordlist") 39 | flag.StringVar(&codes, "s", "200,204,301,302,307", "Positive status codes (dir mode only)") 40 | flag.StringVar(&s.OutputFileName, "o", "", "Output file to write results to (defaults to stdout)") 41 | flag.StringVar(&s.URL, "u", "", "The target URL or Domain") 42 | flag.StringVar(&s.Cookies, "c", "", "Cookies to use for the requests (dir mode only)") 43 | flag.StringVar(&s.Username, "U", "", "Username for Basic Auth (dir mode only)") 44 | flag.StringVar(&s.Password, "P", "", "Password for Basic Auth (dir mode only)") 45 | flag.StringVar(&extensions, "x", "", "File extension(s) to search for (dir mode only)") 46 | flag.StringVar(&s.UserAgent, "a", "", "Set the User-Agent string (dir mode only)") 47 | flag.StringVar(&proxy, "p", "", "Proxy to use for requests [http(s)://host:port] (dir mode only)") 48 | flag.StringVar(&s.ContentType, "ct", "", "Default Content-Type for POST requests") 49 | flag.StringVar(&s.Verb, "X", "GET", "Verb to use instead of GET (GET, POST, PUT) are valid)") 50 | flag.StringVar(&s.Body, "b", "", "Content of POST body, i.e. '{}' for Application/JSON") 51 | flag.StringVar(&s.Headers, "H", "", "List of arbitrary headers to supply, separated by '|' characters") 52 | flag.BoolVar(&s.Verbose, "v", false, "Verbose output (errors)") 53 | flag.BoolVar(&s.ShowIPs, "i", false, "Show IP addresses (dns mode only)") 54 | flag.BoolVar(&s.ShowCNAME, "cn", false, "Show CNAME records (dns mode only, cannot be used with '-i' option)") 55 | flag.BoolVar(&s.FollowRedirect, "r", false, "Follow redirects") 56 | flag.BoolVar(&s.Quiet, "q", false, "Don't print the banner and other noise") 57 | flag.BoolVar(&s.Expanded, "e", false, "Expanded mode, print full URLs") 58 | flag.BoolVar(&s.NoStatus, "n", false, "Don't print status codes") 59 | flag.BoolVar(&s.IncludeLength, "l", false, "Include the length of the body in the output (dir mode only)") 60 | flag.BoolVar(&s.UseSlash, "f", false, "Append a forward-slash to each directory request (dir mode only)") 61 | flag.BoolVar(&s.WildcardForced, "fw", false, "Force continued operation when wildcard found") 62 | flag.BoolVar(&s.InsecureSSL, "k", false, "Skip SSL certificate verification") 63 | 64 | flag.Parse() 65 | 66 | libgobuster.Banner(&s) 67 | 68 | if err := libgobuster.ValidateState(&s, extensions, codes, proxy); err.ErrorOrNil() != nil { 69 | fmt.Printf("%s\n", err.Error()) 70 | return nil 71 | } 72 | 73 | libgobuster.Ruler(&s) 74 | return &s 75 | } 76 | 77 | func main() { 78 | state := ParseCmdLine() 79 | if state != nil { 80 | libgobuster.Process(state) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /libgobuster/helpers.go: -------------------------------------------------------------------------------- 1 | package libgobuster 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "os/signal" 7 | "strconv" 8 | "strings" 9 | ) 10 | 11 | // PrepareSignalHandler ... Set up a SIGINT handler 12 | func PrepareSignalHandler(s *State) { 13 | s.SignalChan = make(chan os.Signal, 1) 14 | signal.Notify(s.SignalChan, os.Interrupt) 15 | go func() { 16 | for range s.SignalChan { 17 | // caught CTRL+C 18 | if !s.Quiet { 19 | fmt.Println("[!] Keyboard interrupt detected, terminating.") 20 | s.Terminate = true 21 | } 22 | } 23 | }() 24 | } 25 | 26 | // Ruler ... Perform advanced screen I/O :> 27 | func Ruler(s *State) { 28 | if !s.Quiet { 29 | fmt.Println("=====================================================") 30 | } 31 | } 32 | 33 | // Banner ... Print the Gobuster banner to the screen 34 | func Banner(s *State) { 35 | if s.Quiet { 36 | return 37 | } 38 | 39 | fmt.Println("") 40 | fmt.Println("Gobuster v1.4.1 OJ Reeves (@TheColonial)") 41 | Ruler(s) 42 | } 43 | 44 | // ShowConfig ... Print the state to the screen 45 | func ShowConfig(s *State) { 46 | if s.Quiet { 47 | return 48 | } 49 | 50 | if s != nil { 51 | fmt.Printf("[+] Mode : %s\n", s.Mode) 52 | fmt.Printf("[+] URL/Domain : %s\n", s.URL) 53 | fmt.Printf("[+] Threads : %d\n", s.Threads) 54 | 55 | wordlist := "stdin (pipe)" 56 | if !s.StdIn { 57 | wordlist = s.Wordlist 58 | } 59 | fmt.Printf("[+] Wordlist : %s\n", wordlist) 60 | 61 | if s.OutputFileName != "" { 62 | fmt.Printf("[+] Output file : %s\n", s.OutputFileName) 63 | } 64 | 65 | if s.Mode == "dir" { 66 | fmt.Printf("[+] Status codes : %s\n", s.StatusCodes.Stringify()) 67 | 68 | if s.ProxyURL != nil { 69 | fmt.Printf("[+] Proxy : %s\n", s.ProxyURL) 70 | } 71 | 72 | if s.Cookies != "" { 73 | fmt.Printf("[+] Cookies : %s\n", s.Cookies) 74 | } 75 | 76 | if s.UserAgent != "" { 77 | fmt.Printf("[+] User Agent : %s\n", s.UserAgent) 78 | } 79 | 80 | if s.IncludeLength { 81 | fmt.Printf("[+] Show length : true\n") 82 | } 83 | 84 | if s.Username != "" { 85 | fmt.Printf("[+] Auth User : %s\n", s.Username) 86 | } 87 | 88 | if len(s.Extensions) > 0 { 89 | fmt.Printf("[+] Extensions : %s\n", strings.Join(s.Extensions, ",")) 90 | } 91 | 92 | if s.UseSlash { 93 | fmt.Printf("[+] Add Slash : true\n") 94 | } 95 | 96 | if s.FollowRedirect { 97 | fmt.Printf("[+] Follow Redir : true\n") 98 | } 99 | 100 | if s.Expanded { 101 | fmt.Printf("[+] Expanded : true\n") 102 | } 103 | 104 | if s.NoStatus { 105 | fmt.Printf("[+] No status : true\n") 106 | } 107 | 108 | if s.Verbose { 109 | fmt.Printf("[+] Verbose : true\n") 110 | } 111 | } 112 | 113 | Ruler(s) 114 | } 115 | } 116 | 117 | // Add ... Add an element to a set 118 | func (set *StringSet) Add(s string) bool { 119 | _, found := set.Set[s] 120 | set.Set[s] = true 121 | return !found 122 | } 123 | 124 | // AddRange ... Add a list of elements to a set 125 | func (set *StringSet) AddRange(ss []string) { 126 | for _, s := range ss { 127 | set.Set[s] = true 128 | } 129 | } 130 | 131 | // Contains ... Test if an element is in a set 132 | func (set *StringSet) Contains(s string) bool { 133 | _, found := set.Set[s] 134 | return found 135 | } 136 | 137 | // ContainsAny ... Check if any of the elements exist 138 | func (set *StringSet) ContainsAny(ss []string) bool { 139 | for _, s := range ss { 140 | if set.Set[s] { 141 | return true 142 | } 143 | } 144 | return false 145 | } 146 | 147 | // Stringify ... Stringify the set 148 | func (set *StringSet) Stringify() string { 149 | values := []string{} 150 | for s := range set.Set { 151 | values = append(values, s) 152 | } 153 | return strings.Join(values, ",") 154 | } 155 | 156 | // Add ... Add an element to a set 157 | func (set *IntSet) Add(i int) bool { 158 | _, found := set.Set[i] 159 | set.Set[i] = true 160 | return !found 161 | } 162 | 163 | // Contains ... Test if an element is in a set 164 | func (set *IntSet) Contains(i int) bool { 165 | _, found := set.Set[i] 166 | return found 167 | } 168 | 169 | // Stringify ... Stringify the set 170 | func (set *IntSet) Stringify() string { 171 | values := []string{} 172 | for s := range set.Set { 173 | values = append(values, strconv.Itoa(s)) 174 | } 175 | return strings.Join(values, ",") 176 | } 177 | -------------------------------------------------------------------------------- /libgobuster/state.go: -------------------------------------------------------------------------------- 1 | package libgobuster 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "net/http" 7 | "net/url" 8 | "os" 9 | "strings" 10 | "sync" 11 | ) 12 | 13 | // Result ...A single result which comes from an individual web 14 | // request. 15 | type Result struct { 16 | Entity string 17 | Status int 18 | Extra string 19 | Size *int64 20 | } 21 | 22 | // PrintResultFunc ... 23 | type PrintResultFunc func(s *State, r *Result) 24 | // ProcessorFunc ... 25 | type ProcessorFunc func(s *State, entity string, resultChan chan<- Result) 26 | // SetupFunc ... 27 | type SetupFunc func(s *State) bool 28 | 29 | // IntSet ... Shim type for "set" containing ints 30 | type IntSet struct { 31 | Set map[int]bool 32 | } 33 | 34 | // StringSet ... Shim type for "set" containing strings 35 | type StringSet struct { 36 | Set map[string]bool 37 | } 38 | 39 | // State ... Contains State that are read in from the command 40 | // line when the program is invoked. 41 | type State struct { 42 | Body string 43 | Client *http.Client 44 | Cookies string 45 | ContentType string 46 | Expanded bool 47 | Extensions []string 48 | Headers string 49 | FollowRedirect bool 50 | IncludeLength bool 51 | Mode string 52 | NoStatus bool 53 | Password string 54 | Printer PrintResultFunc 55 | Processor ProcessorFunc 56 | ProxyURL *url.URL 57 | Quiet bool 58 | Request *http.Request 59 | Setup SetupFunc 60 | ShowIPs bool 61 | ShowCNAME bool 62 | StatusCodes IntSet 63 | Threads int 64 | URL string 65 | UseSlash bool 66 | UserAgent string 67 | Username string 68 | Verbose bool 69 | Wordlist string 70 | OutputFileName string 71 | OutputFile *os.File 72 | IsWildcard bool 73 | WildcardForced bool 74 | WildcardIps StringSet 75 | SignalChan chan os.Signal 76 | Terminate bool 77 | StdIn bool 78 | InsecureSSL bool 79 | Verb string 80 | } 81 | 82 | // Process the busting of the website with the given 83 | // set of settings from the command line. 84 | func Process(s *State) { 85 | 86 | ShowConfig(s) 87 | 88 | if !s.Setup(s) { 89 | Ruler(s) 90 | return 91 | } 92 | 93 | PrepareSignalHandler(s) 94 | 95 | // channels used for comms 96 | wordChan := make(chan string, s.Threads) 97 | resultChan := make(chan Result) 98 | 99 | // Use a wait group for waiting for all threads 100 | // to finish 101 | processorGroup := new(sync.WaitGroup) 102 | processorGroup.Add(s.Threads) 103 | printerGroup := new(sync.WaitGroup) 104 | printerGroup.Add(1) 105 | 106 | // Create goroutines for each of the number of threads 107 | // specified. 108 | for i := 0; i < s.Threads; i++ { 109 | go func() { 110 | for { 111 | word := <-wordChan 112 | 113 | // Did we reach the end? If so break. 114 | if word == "" { 115 | break 116 | } 117 | 118 | // Mode-specific processing 119 | s.Processor(s, word, resultChan) 120 | } 121 | 122 | // Indicate to the wait group that the thread 123 | // has finished. 124 | processorGroup.Done() 125 | }() 126 | } 127 | 128 | // Single goroutine which handles the results as they 129 | // appear from the worker threads. 130 | go func() { 131 | for r := range resultChan { 132 | s.Printer(s, &r) 133 | } 134 | printerGroup.Done() 135 | }() 136 | 137 | var scanner *bufio.Scanner 138 | 139 | if s.StdIn { 140 | // Read directly from stdin 141 | scanner = bufio.NewScanner(os.Stdin) 142 | } else { 143 | // Pull content from the wordlist 144 | wordlist, err := os.Open(s.Wordlist) 145 | if err != nil { 146 | panic("Failed to open wordlist") 147 | } 148 | defer wordlist.Close() 149 | 150 | // Lazy reading of the wordlist line by line 151 | scanner = bufio.NewScanner(wordlist) 152 | } 153 | 154 | var outputFile *os.File 155 | if s.OutputFileName != "" { 156 | outputFile, err := os.Create(s.OutputFileName) 157 | if err != nil { 158 | fmt.Printf("[!] Unable to write to %s, falling back to stdout.\n", s.OutputFileName) 159 | s.OutputFileName = "" 160 | s.OutputFile = nil 161 | } else { 162 | s.OutputFile = outputFile 163 | } 164 | } 165 | 166 | for scanner.Scan() { 167 | if s.Terminate { 168 | break 169 | } 170 | word := strings.TrimSpace(scanner.Text()) 171 | 172 | // Skip "comment" (starts with #), as well as empty lines 173 | if !strings.HasPrefix(word, "#") && len(word) > 0 { 174 | wordChan <- word 175 | } 176 | } 177 | 178 | close(wordChan) 179 | processorGroup.Wait() 180 | close(resultChan) 181 | printerGroup.Wait() 182 | if s.OutputFile != nil { 183 | outputFile.Close() 184 | } 185 | Ruler(s) 186 | } 187 | -------------------------------------------------------------------------------- /libgobuster/dir.go: -------------------------------------------------------------------------------- 1 | package libgobuster 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "io/ioutil" 7 | "net/http" 8 | "net/url" 9 | "strings" 10 | "unicode/utf8" 11 | uuid "github.com/satori/go.uuid" 12 | ) 13 | 14 | // RedirectHandler ... A handler structure for HTTP 3xx responses 15 | type RedirectHandler struct { 16 | Transport http.RoundTripper 17 | State *State 18 | } 19 | 20 | // RedirectError ... A simple structure for an HTTP response status code 21 | type RedirectError struct { 22 | StatusCode int 23 | } 24 | 25 | func (e *RedirectError) Error() string { 26 | return fmt.Sprintf("Redirect code: %d", e.StatusCode) 27 | } 28 | 29 | 30 | // RoundTrip ... handle an HTTP 3xx (Redirect) 31 | func (rh *RedirectHandler) RoundTrip(req *http.Request) (resp *http.Response, err error) { 32 | if rh.State.FollowRedirect { 33 | return rh.Transport.RoundTrip(req) 34 | } 35 | 36 | resp, err = rh.Transport.RoundTrip(req) 37 | if err != nil { 38 | return resp, err 39 | } 40 | 41 | switch resp.StatusCode { 42 | case http.StatusMovedPermanently, http.StatusFound, http.StatusSeeOther, 43 | http.StatusNotModified, http.StatusUseProxy, http.StatusTemporaryRedirect: 44 | return nil, &RedirectError{StatusCode: resp.StatusCode} 45 | } 46 | 47 | return resp, err 48 | } 49 | 50 | // MakeRequest ... Make a request to the given URL 51 | func MakeRequest(s *State, fullURL, cookie string) (*int, *int64) { 52 | 53 | request, err := http.NewRequest(s.Verb, fullURL, strings.NewReader(s.Body)) 54 | 55 | if err != nil { 56 | os.Exit(1) 57 | } 58 | 59 | if cookie != "" { 60 | request.Header.Set("Cookie", cookie) 61 | } 62 | 63 | if s.ContentType != "" { 64 | request.Header.Set("Content-Type", s.ContentType) 65 | } 66 | 67 | if s.UserAgent != "" { 68 | request.Header.Set("User-Agent", s.UserAgent) 69 | } 70 | 71 | if s.Username != "" { 72 | request.SetBasicAuth(s.Username, s.Password) 73 | } 74 | 75 | if s.Headers != "" { 76 | headers := strings.Split(s.Headers, "|") 77 | for i := range headers { 78 | headerPair := strings.Split(headers[i], ": ") 79 | request.Header.Set(headerPair[0], headerPair[1]) 80 | } 81 | } 82 | 83 | resp, err := s.Client.Do(request) 84 | 85 | if err != nil { 86 | if ue, ok := err.(*url.Error); ok { 87 | 88 | if strings.HasPrefix(ue.Err.Error(), "x509") { 89 | fmt.Println("[-] Invalid certificate") 90 | } 91 | 92 | if re, ok := ue.Err.(*RedirectError); ok { 93 | return &re.StatusCode, nil 94 | } 95 | } 96 | return nil, nil 97 | } 98 | 99 | defer resp.Body.Close() 100 | 101 | var length *int64 102 | 103 | if s.IncludeLength { 104 | length = new(int64) 105 | if resp.ContentLength <= 0 { 106 | body, err := ioutil.ReadAll(resp.Body) 107 | if err == nil { 108 | *length = int64(utf8.RuneCountInString(string(body))) 109 | } 110 | } else { 111 | *length = resp.ContentLength 112 | } 113 | } 114 | 115 | return &resp.StatusCode, length 116 | } 117 | 118 | // GoGet ... Small helper to combine URL with URI then make a 119 | // request to the generated location. 120 | func GoGet(s *State, url, uri, cookie string) (*int, *int64) { 121 | return MakeRequest(s, url+uri, cookie) 122 | } 123 | 124 | // SetupDir ... Make an initial request with a random GUID to identify wildcard 125 | // responses 126 | func SetupDir(s *State) bool { 127 | guid := uuid.Must(uuid.NewV4()) 128 | wildcardResp, _ := GoGet(s, s.URL, guid.String(), s.Cookies) 129 | 130 | if s.StatusCodes.Contains(*wildcardResp) { 131 | s.IsWildcard = true 132 | fmt.Println("[-] Wildcard response found:", fmt.Sprintf("%s%s", s.URL, guid), "=>", *wildcardResp) 133 | if !s.WildcardForced { 134 | fmt.Println("[-] To force processing of Wildcard responses, specify the '-fw' switch.") 135 | } 136 | return s.WildcardForced 137 | } 138 | 139 | return true 140 | } 141 | 142 | // ProcessDirEntry ... Make a request to see if a URL is present 143 | func ProcessDirEntry(s *State, word string, resultChan chan<- Result) { 144 | suffix := "" 145 | if s.UseSlash { 146 | suffix = "/" 147 | } 148 | 149 | // Try the DIR first 150 | dirResp, dirSize := GoGet(s, s.URL, word+suffix, s.Cookies) 151 | if dirResp != nil { 152 | resultChan <- Result{ 153 | Entity: word + suffix, 154 | Status: *dirResp, 155 | Size: dirSize, 156 | } 157 | } 158 | 159 | // Follow up with files using each ext. 160 | for ext := range s.Extensions { 161 | file := word + s.Extensions[ext] 162 | fileResp, fileSize := GoGet(s, s.URL, file, s.Cookies) 163 | 164 | if fileResp != nil { 165 | resultChan <- Result{ 166 | Entity: file, 167 | Status: *fileResp, 168 | Size: fileSize, 169 | } 170 | } 171 | } 172 | } 173 | 174 | // PrintDirResult ... Print various metadata about an HTTP response 175 | func PrintDirResult(s *State, r *Result) { 176 | output := "" 177 | 178 | // Prefix if we're in verbose mode 179 | if s.Verbose { 180 | if s.StatusCodes.Contains(r.Status) { 181 | output = "Found : " 182 | } else { 183 | output = "Missed: " 184 | } 185 | } 186 | 187 | if s.StatusCodes.Contains(r.Status) || s.Verbose { 188 | if s.Expanded { 189 | output += s.URL 190 | } else { 191 | output += "/" 192 | } 193 | output += r.Entity 194 | 195 | if !s.NoStatus { 196 | output += fmt.Sprintf(" (Status: %d)", r.Status) 197 | } 198 | 199 | if r.Size != nil { 200 | output += fmt.Sprintf(" [Size: %d]", *r.Size) 201 | } 202 | output += "\n" 203 | 204 | fmt.Printf("%s", output) 205 | 206 | if s.OutputFile != nil { 207 | WriteToFile(output, s) 208 | } 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /libgobuster/statehelpers.go: -------------------------------------------------------------------------------- 1 | package libgobuster 2 | 3 | //---------------------------------------------------- 4 | // Gobuster -- by OJ Reeves 5 | // 6 | // A crap attempt at building something that resembles 7 | // dirbuster or dirb using Go. The goal was to build 8 | // a tool that would help learn Go and to actually do 9 | // something useful. The idea of having this compile 10 | // to native code is also appealing. 11 | // 12 | // Run: gobuster -h 13 | // 14 | // Please see THANKS file for contributors. 15 | // Please see LICENSE file for license details. 16 | // 17 | //---------------------------------------------------- 18 | 19 | import ( 20 | "crypto/tls" 21 | "fmt" 22 | "go-multierror" 23 | "golang.org/x/crypto/ssh/terminal" 24 | "net/http" 25 | "net/url" 26 | "os" 27 | "regexp" 28 | "strconv" 29 | "strings" 30 | "syscall" 31 | ) 32 | 33 | // InitState ... 34 | func InitState() State { 35 | return State{ 36 | StatusCodes: IntSet{Set: map[int]bool{}}, 37 | WildcardIps: StringSet{Set: map[string]bool{}}, 38 | IsWildcard: false, 39 | StdIn: false, 40 | } 41 | } 42 | 43 | // ValidVerb ... 44 | func ValidVerb(verb string) bool { 45 | switch verb { 46 | case 47 | "OPTIONS", 48 | "GET", 49 | "PUT", 50 | "POST": 51 | return true 52 | default: 53 | return false 54 | } 55 | } 56 | 57 | // ValidHeaders ... 58 | func ValidHeaders(headers string) bool { 59 | if headers == "" { 60 | return true 61 | } 62 | headerSet := strings.Split(headers, "|") 63 | for i := range headerSet { 64 | headerPair := strings.Split(headerSet[i], ": ") 65 | if len(headerPair) != 2 { 66 | return false 67 | } 68 | } 69 | return true 70 | } 71 | 72 | // ValidateState ... 73 | func ValidateState( 74 | s *State, 75 | extensions string, 76 | codes string, 77 | proxy string) *multierror.Error { 78 | 79 | var errorList *multierror.Error 80 | 81 | switch strings.ToLower(s.Mode) { 82 | case "dir": 83 | s.Printer = PrintDirResult 84 | s.Processor = ProcessDirEntry 85 | s.Setup = SetupDir 86 | case "dns": 87 | s.Printer = PrintDNSResult 88 | s.Processor = ProcessDNSEntry 89 | s.Setup = SetupDNS 90 | default: 91 | errorList = multierror.Append(errorList, fmt.Errorf("[!] Mode (-m): Invalid value: %s", s.Mode)) 92 | } 93 | 94 | if s.Threads < 0 { 95 | errorList = multierror.Append(errorList, fmt.Errorf("[!] Threads (-t): Invalid value: %s", s.Threads)) 96 | } 97 | 98 | stdin, err := os.Stdin.Stat() 99 | if err != nil { 100 | fmt.Println("[!] Unable to stat stdin, falling back to wordlist file.") 101 | } else if (stdin.Mode()&os.ModeCharDevice) == 0 && stdin.Size() > 0 { 102 | s.StdIn = true 103 | } 104 | 105 | if !s.StdIn { 106 | if s.Wordlist == "" { 107 | errorList = multierror.Append(errorList, fmt.Errorf("[!] WordList (-w): Must be specified")) 108 | } else if _, err := os.Stat(s.Wordlist); os.IsNotExist(err) { 109 | errorList = multierror.Append(errorList, fmt.Errorf("[!] Wordlist (-w): File does not exist: %s", s.Wordlist)) 110 | } 111 | } else if s.Wordlist != "" { 112 | errorList = multierror.Append(errorList, fmt.Errorf("[!] Wordlist (-w) specified with pipe from stdin. Can't have both")) 113 | } 114 | 115 | if s.URL == "" { 116 | errorList = multierror.Append(errorList, fmt.Errorf("[!] URL/Domain (-u): Must be specified")) 117 | } 118 | 119 | if s.Mode == "dir" { 120 | if err := validateDirModeState(s, extensions, codes, proxy, errorList); err.ErrorOrNil() != nil { 121 | errorList = err 122 | } 123 | } 124 | 125 | return errorList 126 | } 127 | 128 | func validateDirModeState( 129 | s *State, 130 | extensions string, 131 | codes string, 132 | proxy string, 133 | previousErrors *multierror.Error) *multierror.Error { 134 | 135 | // If we had previous errors, copy them into the current errorList. 136 | // This is an easier to understand solution compared to double pointer black magick 137 | var errorList *multierror.Error 138 | if previousErrors.ErrorOrNil() != nil { 139 | errorList = multierror.Append(errorList, previousErrors) 140 | } 141 | 142 | if !strings.HasSuffix(s.URL, "/") { 143 | s.URL = s.URL + "/" 144 | } 145 | 146 | if !strings.HasPrefix(s.URL, "http") { 147 | // check to see if a port was specified 148 | re := regexp.MustCompile(`^[^/]+:(\d+)`) 149 | match := re.FindStringSubmatch(s.URL) 150 | 151 | if len(match) < 2 { 152 | // no port, default to http on 80 153 | s.URL = "http://" + s.URL 154 | } else { 155 | port, err := strconv.Atoi(match[1]) 156 | if err != nil || (port != 80 && port != 443) { 157 | errorList = multierror.Append(errorList, fmt.Errorf("[!] URL/Domain (-u): Scheme not specified")) 158 | } else if port == 80 { 159 | s.URL = "http://" + s.URL 160 | } else { 161 | s.URL = "https://" + s.URL 162 | } 163 | } 164 | } 165 | 166 | 167 | if !ValidVerb(s.Verb) { 168 | errorList = multierror.Append(errorList, fmt.Errorf("[!] Verb (-X): Invalid value: %s", s.Verb)) 169 | } 170 | 171 | if !ValidHeaders(s.Headers) { 172 | errorList = multierror.Append(errorList, fmt.Errorf("[!] Headers (-H): Invalid value: %s", s.Headers)) 173 | } 174 | 175 | if (s.Verb == "GET" || s.Verb == "OPTIONS") && s.Body != "" { 176 | errorList = multierror.Append(errorList, fmt.Errorf("[!] Body (-b): GET and OPTIONS requests have no body")) 177 | } 178 | 179 | if (s.Verb == "GET" || s.Verb == "OPTIONS") && s.ContentType != "" { 180 | errorList = multierror.Append(errorList, fmt.Errorf("[!] Content-Type (-ct): GET and OPTIONS requests have no Content-Type")) 181 | } 182 | 183 | // extensions are comma separated 184 | if extensions != "" { 185 | s.Extensions = strings.Split(extensions, ",") 186 | for i := range s.Extensions { 187 | if s.Extensions[i][0] != '.' { 188 | s.Extensions[i] = "." + s.Extensions[i] 189 | } 190 | } 191 | } 192 | 193 | // status codes are comma separated 194 | if codes != "" { 195 | for _, c := range strings.Split(codes, ",") { 196 | i, err := strconv.Atoi(c) 197 | if err != nil { 198 | errorList = multierror.Append(errorList, fmt.Errorf("[!] Invalid status code given: %s", c)) 199 | } else { 200 | s.StatusCodes.Add(i) 201 | } 202 | } 203 | } 204 | 205 | // prompt for password if needed 206 | if errorList.ErrorOrNil() == nil && s.Username != "" && s.Password == "" { 207 | fmt.Printf("[?] Auth Password: ") 208 | passBytes, err := terminal.ReadPassword(syscall.Stdin) 209 | 210 | // print a newline to simulate the newline that was entered 211 | // this means that formatting/printing after doesn't look bad. 212 | fmt.Println("") 213 | 214 | if err == nil { 215 | s.Password = string(passBytes) 216 | } else { 217 | errorList = multierror.Append(errorList, fmt.Errorf("[!] Auth username given but reading of password failed")) 218 | } 219 | } 220 | 221 | if errorList.ErrorOrNil() == nil { 222 | var proxyURLFunc = http.ProxyFromEnvironment 223 | 224 | if proxy != "" { 225 | proxyURL, err := url.Parse(proxy) 226 | if err != nil { 227 | errorList = multierror.Append(errorList, fmt.Errorf("[!] Proxy URL is invalid")) 228 | panic("[!] Proxy URL is invalid") // TODO: Does this need to be a panic? Could be a standard error? 229 | } 230 | s.ProxyURL = proxyURL 231 | proxyURLFunc = http.ProxyURL(s.ProxyURL) 232 | } 233 | 234 | s.Client = &http.Client{ 235 | Transport: &RedirectHandler{ 236 | State: s, 237 | Transport: &http.Transport{ 238 | Proxy: proxyURLFunc, 239 | TLSClientConfig: &tls.Config{ 240 | InsecureSkipVerify: s.InsecureSSL, 241 | }, 242 | }, 243 | }} 244 | 245 | code, _ := GoGet(s, s.URL, "", s.Cookies) 246 | if code == nil { 247 | errorList = multierror.Append(errorList, fmt.Errorf("[-] Unable to connect: %s", s.URL)) 248 | } 249 | } 250 | 251 | return errorList 252 | } 253 | -------------------------------------------------------------------------------- /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 v1.4.1 (OJ Reeves @TheColonial) 2 | ======================================== 3 | 4 | Gobuster is a tool used to brute-force: 5 | 6 | * URIs (directories and files) in web sites. 7 | * DNS subdomains (with wildcard support). 8 | 9 | ### Oh dear God.. WHY!? 10 | 11 | Because I wanted: 12 | 13 | 1. ... something that didn't have a fat Java GUI (console FTW). 14 | 1. ... to build something that just worked on the command line. 15 | 1. ... something that did not do recursive brute force. 16 | 1. ... something that allowed me to brute force folders and multiple extensions at once. 17 | 1. ... something that compiled to native on multiple platforms. 18 | 1. ... something that was faster than an interpreted script (such as Python). 19 | 1. ... something that didn't require a runtime. 20 | 1. ... use something that was good with concurrency (hence Go). 21 | 1. ... to build something in Go that wasn't totally useless. 22 | 23 | ### But it's shit! And your implementation sucks! 24 | 25 | Yes, you're probably correct. Feel free to: 26 | 27 | * Not use it. 28 | * Show me how to do it better. 29 | * The code has been gometalint'd as of 3/4/2018 so calm down :> 30 | 31 | ### Common Command line options 32 | 33 | * `-fw` - Force processing of a domain with wildcard results. 34 | * `-m ` - which mode to use, either `dir` or `dns` (default: `dir`) 35 | * `-q` - disables banner/underline output. 36 | * `-t ` - number of threads to run (default: `10`). 37 | * `-u ` - full URL (including scheme), or base domain name. 38 | * `-v` - verbose output (show all results). 39 | * `-w ` - path to the wordlist used for brute forcing. 40 | 41 | ### Command line options for `dns` mode 42 | 43 | * `-cn` - show CNAME records (cannot be used with '-i' option). 44 | * `-i` - show all IP addresses for the result. 45 | 46 | ### Command line options for `dir` mode 47 | 48 | * `-a ` - specify a user agent string to send in the request header. 49 | * `-c ` - use this to specify any cookies that you might need (simulating auth). 50 | * `-e` - specify extended mode that renders the full URL. 51 | * `-f` - append `/` for directory brute forces. 52 | * `-k` - Skip verification of SSL certificates. 53 | * `-l` - show the length of the response. 54 | * `-n` - "no status" mode, disables the output of the result's status code. 55 | * `-o ` - specify a file name to write the output to. 56 | * `-p ` - specify a proxy to use for all requests (scheme much match the URL scheme). 57 | * `-r` - follow redirects. 58 | * `-s ` - comma-separated set of the list of status codes to be deemed a "positive" (default: `200,204,301,302,307`). 59 | * `-x ` - list of extensions to check for, if any. 60 | * `-P ` - HTTP Authorization password (Basic Auth only, prompted if missing). 61 | * `-U ` - HTTP Authorization username (Basic Auth only). 62 | * `-H ` - Verb to use instead of GET (GET, POST, OPTIONS, PUT are supported, default is GET) 64 | * `-b ` - String to use as the POST, PUT body. Use $'xxx' in the shell for best results 65 | * `-ct ` - Content-Type header value for POST, PUT requests, i.e. application/json 66 | 67 | ### Building 68 | 69 | Since this tool is written in [Go](https://golang.org/) you need 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. 70 | 71 | #### Compiling 72 | `gobuster` now has external dependencies, and so they need to be pulled in first: 73 | ``` 74 | gobuster $ go get && go build 75 | ``` 76 | This will create a `gobuster` binary for you. If you want to install it in the `$GOPATH/bin` folder you can run: 77 | ``` 78 | gobuster $ go install 79 | ``` 80 | 81 | #### Running as a script 82 | ``` 83 | gobuster$ go run main.go 84 | ``` 85 | 86 | ### Wordlists via STDIN 87 | Wordlists can be piped into `gobuster` via stdin: 88 | ``` 89 | hashcat -a 3 --stdout ?l | gobuster -u https://mysite.com 90 | ``` 91 | 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. 92 | 93 | ### Examples 94 | 95 | #### `dir` mode 96 | 97 | Command line might look like this: 98 | ``` 99 | $ gobuster -u https://mysite.com/path/to/folder -c 'session=123456' -t 50 -w common-files.txt -x .php,.html 100 | ``` 101 | Default options looks like this: 102 | ``` 103 | $ gobuster -u http://buffered.io/ -w words.txt 104 | 105 | Gobuster v1.4.1 OJ Reeves (@TheColonial) 106 | ===================================================== 107 | [+] Mode : dir 108 | [+] Url/Domain : http://buffered.io/ 109 | [+] Threads : 10 110 | [+] Wordlist : words.txt 111 | [+] Status codes : 200,204,301,302,307 112 | ===================================================== 113 | /index (Status: 200) 114 | /posts (Status: 301) 115 | /contact (Status: 301) 116 | ===================================================== 117 | ``` 118 | Default options with status codes disabled looks like this: 119 | ``` 120 | $ gobuster -u http://buffered.io/ -w words.txt -n 121 | 122 | Gobuster v1.4.1 OJ Reeves (@TheColonial) 123 | ===================================================== 124 | [+] Mode : dir 125 | [+] Url/Domain : http://buffered.io/ 126 | [+] Threads : 10 127 | [+] Wordlist : words.txt 128 | [+] Status codes : 200,204,301,302,307 129 | [+] No status : true 130 | ===================================================== 131 | /index 132 | /posts 133 | /contact 134 | ===================================================== 135 | ``` 136 | 137 | Calling a JSON API would look like this: 138 | ``` 139 | $ ./gobuster.git -u http://localhost/api/v1/ -X POST -b '{"test": "test"}' -w api-method-wordlist.txt -ct application/json 140 | 141 | Gobuster v1.4.1 OJ Reeves (@TheColonial) 142 | ===================================================== 143 | ===================================================== 144 | [+] Mode : dir 145 | [+] URL/Domain : http://localhost/api/vi/ 146 | [+] Threads : 10 147 | [+] Wordlist : ../raft-medium-directories.txt 148 | [+] Status codes : 302,307,200,204,301 149 | ===================================================== 150 | ===================================================== 151 | /api/v1/login 152 | ... 153 | $ 154 | ``` 155 | 156 | Verbose output looks like this: 157 | ``` 158 | $ gobuster -u http://buffered.io/ -w words.txt -v 159 | 160 | Gobuster v1.4.1 OJ Reeves (@TheColonial) 161 | ===================================================== 162 | [+] Mode : dir 163 | [+] Url/Domain : http://buffered.io/ 164 | [+] Threads : 10 165 | [+] Wordlist : words.txt 166 | [+] Status codes : 200,204,301,302,307 167 | [+] Verbose : true 168 | ===================================================== 169 | Found : /index (Status: 200) 170 | Missed: /derp (Status: 404) 171 | Found : /posts (Status: 301) 172 | Found : /contact (Status: 301) 173 | ===================================================== 174 | ``` 175 | Example showing content length: 176 | ``` 177 | $ gobuster -u http://buffered.io/ -w words.txt -l 178 | 179 | Gobuster v1.4.1 OJ Reeves (@TheColonial) 180 | ===================================================== 181 | [+] Mode : dir 182 | [+] Url/Domain : http://buffered.io/ 183 | [+] Threads : 10 184 | [+] Wordlist : /tmp/words 185 | [+] Status codes : 301,302,307,200,204 186 | [+] Show length : true 187 | ===================================================== 188 | /contact (Status: 301) 189 | /posts (Status: 301) 190 | /index (Status: 200) [Size: 61481] 191 | ===================================================== 192 | ``` 193 | Quiet output, with status disabled and expanded mode looks like this ("grep mode"): 194 | ``` 195 | $ gobuster -u http://buffered.io/ -w words.txt -q -n -e 196 | http://buffered.io/posts 197 | http://buffered.io/contact 198 | http://buffered.io/index 199 | ``` 200 | 201 | #### `dns` mode 202 | 203 | Command line might look like this: 204 | ``` 205 | $ gobuster -m dns -u mysite.com -t 50 -w common-names.txt 206 | ``` 207 | Normal sample run goes like this: 208 | ``` 209 | $ gobuster -m dns -w subdomains.txt -u google.com 210 | 211 | Gobuster v1.4.1 OJ Reeves (@TheColonial) 212 | ===================================================== 213 | [+] Mode : dns 214 | [+] Url/Domain : google.com 215 | [+] Threads : 10 216 | [+] Wordlist : subdomains.txt 217 | ===================================================== 218 | Found: m.google.com 219 | Found: admin.google.com 220 | Found: mobile.google.com 221 | Found: www.google.com 222 | Found: search.google.com 223 | Found: chrome.google.com 224 | Found: ns1.google.com 225 | Found: store.google.com 226 | Found: wap.google.com 227 | Found: support.google.com 228 | Found: directory.google.com 229 | Found: translate.google.com 230 | Found: news.google.com 231 | Found: music.google.com 232 | Found: mail.google.com 233 | Found: blog.google.com 234 | Found: cse.google.com 235 | Found: local.google.com 236 | ===================================================== 237 | ``` 238 | Show IP sample run goes like this: 239 | ``` 240 | $ gobuster -m dns -w subdomains.txt -u google.com -i 241 | 242 | Gobuster v1.4.1 OJ Reeves (@TheColonial) 243 | ===================================================== 244 | [+] Mode : dns 245 | [+] Url/Domain : google.com 246 | [+] Threads : 10 247 | [+] Wordlist : subdomains.txt 248 | [+] Verbose : true 249 | ===================================================== 250 | Found: chrome.google.com [2404:6800:4006:801::200e, 216.58.220.110] 251 | Found: m.google.com [216.58.220.107, 2404:6800:4006:801::200b] 252 | Found: www.google.com [74.125.237.179, 74.125.237.177, 74.125.237.178, 74.125.237.180, 74.125.237.176, 2404:6800:4006:801::2004] 253 | Found: search.google.com [2404:6800:4006:801::200e, 216.58.220.110] 254 | Found: admin.google.com [216.58.220.110, 2404:6800:4006:801::200e] 255 | Found: store.google.com [216.58.220.110, 2404:6800:4006:801::200e] 256 | Found: mobile.google.com [216.58.220.107, 2404:6800:4006:801::200b] 257 | Found: ns1.google.com [216.239.32.10] 258 | Found: directory.google.com [216.58.220.110, 2404:6800:4006:801::200e] 259 | Found: translate.google.com [216.58.220.110, 2404:6800:4006:801::200e] 260 | Found: cse.google.com [216.58.220.110, 2404:6800:4006:801::200e] 261 | Found: local.google.com [2404:6800:4006:801::200e, 216.58.220.110] 262 | Found: music.google.com [2404:6800:4006:801::200e, 216.58.220.110] 263 | Found: wap.google.com [216.58.220.110, 2404:6800:4006:801::200e] 264 | Found: blog.google.com [216.58.220.105, 2404:6800:4006:801::2009] 265 | Found: support.google.com [216.58.220.110, 2404:6800:4006:801::200e] 266 | Found: news.google.com [216.58.220.110, 2404:6800:4006:801::200e] 267 | Found: mail.google.com [216.58.220.101, 2404:6800:4006:801::2005] 268 | ===================================================== 269 | ``` 270 | 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. 271 | ``` 272 | $ gobuster -m dns -w subdomains.txt -u yp.to -i 273 | 274 | Gobuster v1.4.1 OJ Reeves (@TheColonial) 275 | ===================================================== 276 | [+] Mode : dns 277 | [+] Url/Domain : yp.to 278 | [+] Threads : 10 279 | [+] Wordlist : /tmp/test.txt 280 | ===================================================== 281 | [-] Unable to validate base domain: yp.to 282 | Found: cr.yp.to [131.155.70.11, 131.155.70.13] 283 | ===================================================== 284 | ``` 285 | Wildcard DNS is also detected properly: 286 | ``` 287 | $ gobuster -w subdomainsbig.txt -u doesntexist.com -m dns 288 | 289 | Gobuster v1.4.1 OJ Reeves (@TheColonial) 290 | ===================================================== 291 | [+] Mode : dns 292 | [+] Url/Domain : doesntexist.com 293 | [+] Threads : 10 294 | [+] Wordlist : subdomainsbig.txt 295 | ===================================================== 296 | [-] Wildcard DNS found. IP address(es): 123.123.123.123 297 | [-] To force processing of Wildcard DNS, specify the '-fw' switch. 298 | ===================================================== 299 | ``` 300 | If the user wants to force processing of a domain that has wildcard entries, use `-fw`: 301 | ``` 302 | $ gobuster -w subdomainsbig.txt -u doesntexist.com -m dns -fw 303 | 304 | Gobuster v1.4.1 OJ Reeves (@TheColonial) 305 | ===================================================== 306 | [+] Mode : dns 307 | [+] Url/Domain : doesntexist.com 308 | [+] Threads : 10 309 | [+] Wordlist : subdomainsbig.txt 310 | ===================================================== 311 | [-] Wildcard DNS found. IP address(es): 123.123.123.123 312 | Found: email.doesntexist.com 313 | ^C[!] Keyboard interrupt detected, terminating. 314 | ===================================================== 315 | ``` 316 | 317 | ### License 318 | 319 | See the LICENSE file. 320 | 321 | ### Thanks 322 | 323 | See the THANKS file for people who helped out. 324 | --------------------------------------------------------------------------------