diff --git a/README.md b/README.md index 7257a7cb..205cd347 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -Gobuster v1.2 (OJ Reeves @TheColonial) +Gobuster v1.3 (OJ Reeves @TheColonial) ====================================== Gobuster is a tool used to brute-force: @@ -29,6 +29,7 @@ Yes, you're probably correct. Feel free to: ### Common Command line options +* `-fw` - Force processing of a domain with wildcard results. * `-m ` - which mode to use, either `dir` or `dns` (default: `dir`) * `-q` - disables banner/underline output. * `-t ` - number of threads to run (default: `10`). @@ -38,18 +39,20 @@ Yes, you're probably correct. Feel free to: ### Command line options for `dns` mode -* `-fw` - Force processing of a domain with wildcard DNS. +* `-cn` - show CNAME records (cannot be used with '-i' option). * `-i` - show all IP addresses for the result. ### Command line options for `dir` mode -* `-a ` - specify a user agent string to send in the request header +* `-a ` - specify a user agent string to send in the request header. * `-c ` - use this to specify any cookies that you might need (simulating auth). * `-e` - specify extended mode that renders the full URL. * `-f` - append `/` for directory brute forces. +* `-k` - Skip verification of SSL certificates. * `-l` - show the length of the response. * `-n` - "no status" mode, disables the output of the result's status code. -* `-p ` - specify a proxy to use for all requests (scheme much match the URL scheme) +* `-o ` - specify a file name to write the output to. +* `-p ` - specify a proxy to use for all requests (scheme much match the URL scheme). * `-r` - follow redirects. * `-s ` - comma-separated set of the list of status codes to be deemed a "positive" (default: `200,204,301,302,307`). * `-x ` - list of extensions to check for, if any. @@ -94,7 +97,7 @@ Default options looks like this: ``` $ gobuster -u http://buffered.io/ -w words.txt -Gobuster v1.2 OJ Reeves (@TheColonial) +Gobuster v1.3 OJ Reeves (@TheColonial) ===================================================== [+] Mode : dir [+] Url/Domain : http://buffered.io/ @@ -111,7 +114,7 @@ Default options with status codes disabled looks like this: ``` $ gobuster -u http://buffered.io/ -w words.txt -n -Gobuster v1.2 OJ Reeves (@TheColonial) +Gobuster v1.3 OJ Reeves (@TheColonial) ===================================================== [+] Mode : dir [+] Url/Domain : http://buffered.io/ @@ -129,7 +132,7 @@ Verbose output looks like this: ``` $ gobuster -u http://buffered.io/ -w words.txt -v -Gobuster v1.2 OJ Reeves (@TheColonial) +Gobuster v1.3 OJ Reeves (@TheColonial) ===================================================== [+] Mode : dir [+] Url/Domain : http://buffered.io/ @@ -148,7 +151,7 @@ Example showing content length: ``` $ gobuster -u http://buffered.io/ -w words.txt -l -Gobuster v1.2 OJ Reeves (@TheColonial) +Gobuster v1.3 OJ Reeves (@TheColonial) ===================================================== [+] Mode : dir [+] Url/Domain : http://buffered.io/ @@ -180,7 +183,7 @@ Normal sample run goes like this: ``` $ gobuster -m dns -w subdomains.txt -u google.com -Gobuster v1.2 OJ Reeves (@TheColonial) +Gobuster v1.3 OJ Reeves (@TheColonial) ===================================================== [+] Mode : dns [+] Url/Domain : google.com @@ -211,7 +214,7 @@ Show IP sample run goes like this: ``` $ gobuster -m dns -w subdomains.txt -u google.com -i -Gobuster v1.2 OJ Reeves (@TheColonial) +Gobuster v1.3 OJ Reeves (@TheColonial) ===================================================== [+] Mode : dns [+] Url/Domain : google.com @@ -243,7 +246,7 @@ Base domain validation warning when the base domain fails to resolve. This is a ``` $ gobuster -m dns -w subdomains.txt -u yp.to -i -Gobuster v1.2 OJ Reeves (@TheColonial) +Gobuster v1.3 OJ Reeves (@TheColonial) ===================================================== [+] Mode : dns [+] Url/Domain : yp.to @@ -258,7 +261,7 @@ Wildcard DNS is also detected properly: ``` $ gobuster -w subdomainsbig.txt -u doesntexist.com -m dns -Gobuster v1.2 OJ Reeves (@TheColonial) +Gobuster v1.3 OJ Reeves (@TheColonial) ===================================================== [+] Mode : dns [+] Url/Domain : doesntexist.com @@ -273,7 +276,7 @@ If the user wants to force processing of a domain that has wildcard entries, use ``` $ gobuster -w subdomainsbig.txt -u doesntexist.com -m dns -fw -Gobuster v1.2 OJ Reeves (@TheColonial) +Gobuster v1.3 OJ Reeves (@TheColonial) ===================================================== [+] Mode : dns [+] Url/Domain : doesntexist.com diff --git a/THANKS b/THANKS index 14b76d9f..44b99a1d 100644 --- a/THANKS +++ b/THANKS @@ -1,8 +1,11 @@ @0x42424242 - initial DNS support @averagesecurityguy - quiet mode support @g0tmi1k - content length, wordlist and command line parsing fixes +@gehaxelt - DIR mode UUID wildcard detection @justinsteven - HTTP basic auth support @kevinnz - custom user agent support +@knapsy - saving output to file, and CNAME resolution for DNS mode +@rverton - CLI flag to skip SSL verification @viaMorgoth - base domain validation for DNS mode @UID1K - initial DNS wildcard check support @Ne0nd0g - STDIN support for wordlists diff --git a/main.go b/main.go index 56ef1b6b..6032ad5c 100644 --- a/main.go +++ b/main.go @@ -29,6 +29,7 @@ import ( "net/url" "os" "os/signal" + "regexp" "strconv" "strings" "sync" @@ -77,6 +78,7 @@ type State struct { Quiet bool Setup SetupFunc ShowIPs bool + ShowCNAME bool StatusCodes IntSet Threads int Url string @@ -85,12 +87,15 @@ type State struct { Username string Verbose bool Wordlist string + OutputFileName string + OutputFile *os.File IsWildcard bool WildcardForced bool WildcardIps StringSet SignalChan chan os.Signal Terminate bool StdIn bool + InsecureSSL bool } type RedirectHandler struct { @@ -187,6 +192,11 @@ func MakeRequest(s *State, fullUrl, cookie string) (*int, *int64) { if err != nil { if ue, ok := err.(*url.Error); ok { + + if strings.HasPrefix(ue.Err.Error(), "x509") { + fmt.Println("[-] Invalid certificate") + } + if re, ok := ue.Err.(*RedirectError); ok { return &re.StatusCode, nil } @@ -239,6 +249,7 @@ func ParseCmdLine() *State { flag.StringVar(&s.Mode, "m", "dir", "Directory/File mode (dir) or DNS mode (dns)") flag.StringVar(&s.Wordlist, "w", "", "Path to the wordlist") flag.StringVar(&codes, "s", "200,204,301,302,307", "Positive status codes (dir mode only)") + flag.StringVar(&s.OutputFileName, "o", "", "Output file to write results to (defaults to stdout)") flag.StringVar(&s.Url, "u", "", "The target URL or Domain") flag.StringVar(&s.Cookies, "c", "", "Cookies to use for the requests (dir mode only)") flag.StringVar(&s.Username, "U", "", "Username for Basic Auth (dir mode only)") @@ -248,13 +259,15 @@ func ParseCmdLine() *State { flag.StringVar(&proxy, "p", "", "Proxy to use for requests [http(s)://host:port] (dir mode only)") flag.BoolVar(&s.Verbose, "v", false, "Verbose output (errors)") flag.BoolVar(&s.ShowIPs, "i", false, "Show IP addresses (dns mode only)") + flag.BoolVar(&s.ShowCNAME, "cn", false, "Show CNAME records (dns mode only, cannot be used with '-i' option)") flag.BoolVar(&s.FollowRedirect, "r", false, "Follow redirects") flag.BoolVar(&s.Quiet, "q", false, "Don't print the banner and other noise") flag.BoolVar(&s.Expanded, "e", false, "Expanded mode, print full URLs") flag.BoolVar(&s.NoStatus, "n", false, "Don't print status codes") flag.BoolVar(&s.IncludeLength, "l", false, "Include the length of the body in the output (dir mode only)") flag.BoolVar(&s.UseSlash, "f", false, "Append a forward-slash to each directory request (dir mode only)") - flag.BoolVar(&s.WildcardForced, "fw", false, "Force continued operation when wildcard found (dns mode only)") + flag.BoolVar(&s.WildcardForced, "fw", false, "Force continued operation when wildcard found") + flag.BoolVar(&s.InsecureSSL, "k", false, "Skip SSL certificate verification") flag.Parse() @@ -310,7 +323,24 @@ func ParseCmdLine() *State { } if strings.HasPrefix(s.Url, "http") == false { - s.Url = "http://" + s.Url + // check to see if a port was specified + re := regexp.MustCompile(`^[^/]+:(\d+)`) + match := re.FindStringSubmatch(s.Url) + + if len(match) < 2 { + // no port, default to http on 80 + s.Url = "http://" + s.Url + } else { + port, err := strconv.Atoi(match[1]) + if err != nil || (port != 80 && port != 443) { + fmt.Println("[!] Url/Domain (-u): Scheme not specified.") + valid = false + } else if port == 80 { + s.Url = "http://" + s.Url + } else { + s.Url = "https://" + s.Url + } + } } // extensions are comma separated @@ -372,7 +402,7 @@ func ParseCmdLine() *State { Transport: &http.Transport{ Proxy: proxyUrlFunc, TLSClientConfig: &tls.Config{ - InsecureSkipVerify: true, + InsecureSkipVerify: s.InsecureSSL, }, }, }} @@ -466,6 +496,18 @@ func Process(s *State) { scanner = bufio.NewScanner(wordlist) } + var outputFile *os.File + if s.OutputFileName != "" { + outputFile, err := os.Create(s.OutputFileName) + if err != nil { + fmt.Printf("[!] Unable to write to %s, falling back to stdout.\n", s.OutputFileName) + s.OutputFileName = "" + s.OutputFile = nil + } else { + s.OutputFile = outputFile + } + } + for scanner.Scan() { if s.Terminate { break @@ -482,6 +524,9 @@ func Process(s *State) { processorGroup.Wait() close(resultChan) printerGroup.Wait() + if s.OutputFile != nil { + outputFile.Close() + } Ruler(s) } @@ -512,6 +557,18 @@ func SetupDns(s *State) bool { } func SetupDir(s *State) bool { + guid := uuid.NewV4() + wildcardResp, _ := GoGet(s, s.Url, fmt.Sprintf("%s", guid), s.Cookies) + + if s.StatusCodes.Contains(*wildcardResp) { + s.IsWildcard = true + fmt.Println("[-] Wildcard response found:", fmt.Sprintf("%s%s", s.Url, guid), "=>", *wildcardResp) + if !s.WildcardForced { + fmt.Println("[-] To force processing of Wildcard responses, specify the '-fw' switch.") + } + return s.WildcardForced + } + return true } @@ -526,6 +583,11 @@ func ProcessDnsEntry(s *State, word string, resultChan chan<- Result) { } if s.ShowIPs { result.Extra = strings.Join(ips, ", ") + } else if s.ShowCNAME { + cname, err := net.LookupCNAME(subdomain) + if err == nil { + result.Extra = cname + } } resultChan <- result } @@ -570,12 +632,20 @@ func ProcessDirEntry(s *State, word string, resultChan chan<- Result) { } func PrintDnsResult(s *State, r *Result) { + output := "" if r.Status == 404 { - fmt.Printf("Missing: %s\n", r.Entity) + output = fmt.Sprintf("Missing: %s\n", r.Entity) } else if s.ShowIPs { - fmt.Printf("Found: %s [%s]\n", r.Entity, r.Extra) + output = fmt.Sprintf("Found: %s [%s]\n", r.Entity, r.Extra) + } else if s.ShowCNAME { + output = fmt.Sprintf("Found: %s [%s]\n", r.Entity, r.Extra) } else { - fmt.Printf("Found: %s\n", r.Entity) + output = fmt.Sprintf("Found: %s\n", r.Entity) + } + fmt.Printf("%s", output) + + if s.OutputFile != nil { + WriteToFile(output, s) } } @@ -585,9 +655,9 @@ func PrintDirResult(s *State, r *Result) { // Prefix if we're in verbose mode if s.Verbose { if s.StatusCodes.Contains(r.Status) { - output += "Found : " + output = "Found : " } else { - output += "Missed: " + output = "Missed: " } } @@ -606,8 +676,20 @@ func PrintDirResult(s *State, r *Result) { if r.Size != nil { output += fmt.Sprintf(" [Size: %d]", *r.Size) } + output += "\n" - fmt.Println(output) + fmt.Printf(output) + + if s.OutputFile != nil { + WriteToFile(output, s) + } + } +} + +func WriteToFile(output string, s *State) { + _, err := s.OutputFile.WriteString(output) + if err != nil { + panic("[!] Unable to write to file " + s.OutputFileName) } } @@ -660,7 +742,7 @@ func Banner(state *State) { } fmt.Println("") - fmt.Println("Gobuster v1.2 OJ Reeves (@TheColonial)") + fmt.Println("Gobuster v1.3 OJ Reeves (@TheColonial)") Ruler(state) } @@ -680,6 +762,10 @@ func ShowConfig(state *State) { } fmt.Printf("[+] Wordlist : %s\n", wordlist) + if state.OutputFileName != "" { + fmt.Printf("[+] Output file : %s\n", state.OutputFileName) + } + if state.Mode == "dir" { fmt.Printf("[+] Status codes : %s\n", state.StatusCodes.Stringify())