mirror of
https://github.com/OJ/gobuster.git
synced 2024-06-10 09:06:03 +02:00
first implementation of cobra
This commit is contained in:
parent
5ca1584c16
commit
042f4f8fd7
|
@ -0,0 +1,223 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/OJ/gobuster/cli"
|
||||
"github.com/OJ/gobuster/gobusterdir"
|
||||
"github.com/OJ/gobuster/libgobuster"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
)
|
||||
|
||||
var cmdDir *cobra.Command
|
||||
|
||||
func runDir(cmd *cobra.Command, args []string) error {
|
||||
globalopts, pluginopts, err := parseDir()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error on parsing arguments: %v", err)
|
||||
}
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
plugin, err := gobusterdir.NewGobusterDir(ctx, globalopts, pluginopts)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error on creating gobusterdir: %v", err)
|
||||
}
|
||||
|
||||
signalChan := make(chan os.Signal, 1)
|
||||
signal.Notify(signalChan, os.Interrupt)
|
||||
go func() {
|
||||
for range signalChan {
|
||||
// caught CTRL+C
|
||||
if !globalopts.Quiet {
|
||||
fmt.Println("\n[!] Keyboard interrupt detected, terminating.")
|
||||
}
|
||||
cancel()
|
||||
}
|
||||
}()
|
||||
|
||||
if err := cli.Gobuster(ctx, globalopts, plugin); err != nil {
|
||||
return fmt.Errorf("error on running goubster: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseDir() (*libgobuster.Options, *gobusterdir.OptionsDir, error) {
|
||||
globalopts, err := parseGlobalOptions()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
plugin := gobusterdir.NewOptionsDir()
|
||||
|
||||
url, err := cmdDir.Flags().GetString("url")
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("invalid value for url: %v", err)
|
||||
}
|
||||
plugin.URL = url
|
||||
if !strings.HasPrefix(plugin.URL, "http") {
|
||||
// check to see if a port was specified
|
||||
re := regexp.MustCompile(`^[^/]+:(\d+)`)
|
||||
match := re.FindStringSubmatch(plugin.URL)
|
||||
|
||||
if len(match) < 2 {
|
||||
// no port, default to http on 80
|
||||
plugin.URL = fmt.Sprintf("http://%s", plugin.URL)
|
||||
} else {
|
||||
port, err2 := strconv.Atoi(match[1])
|
||||
if err2 != nil || (port != 80 && port != 443) {
|
||||
return nil, nil, fmt.Errorf("url scheme not specified")
|
||||
} else if port == 80 {
|
||||
plugin.URL = fmt.Sprintf("http://%s", plugin.URL)
|
||||
} else {
|
||||
plugin.URL = fmt.Sprintf("https://%s", plugin.URL)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
statuscodes, err := cmdDir.Flags().GetString("statuscodes")
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("invalid value for statuscodes: %v", err)
|
||||
}
|
||||
plugin.StatusCodes = statuscodes
|
||||
if err3 := plugin.ParseStatusCodes(); err3 != nil {
|
||||
return nil, nil, fmt.Errorf("invalid value for statuscodes: %v", err3)
|
||||
}
|
||||
|
||||
cookies, err := cmdDir.Flags().GetString("cookies")
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("invalid value for cookies: %v", err)
|
||||
}
|
||||
plugin.Cookies = cookies
|
||||
|
||||
username, err := cmdDir.Flags().GetString("username")
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("invalid value for username: %v", err)
|
||||
}
|
||||
plugin.Username = username
|
||||
|
||||
password, err := cmdDir.Flags().GetString("password")
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("invalid value for password: %v", err)
|
||||
}
|
||||
plugin.Password = password
|
||||
|
||||
extensions, err := cmdDir.Flags().GetString("extensions")
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("invalid value for extensions: %v", err)
|
||||
}
|
||||
plugin.Extensions = extensions
|
||||
if extensions != "" {
|
||||
if err2 := plugin.ParseExtensions(); err2 != nil {
|
||||
return nil, nil, fmt.Errorf("invalid value for extensions: %v", err2)
|
||||
}
|
||||
}
|
||||
|
||||
useragent, err := cmdDir.Flags().GetString("useragent")
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("invalid value for useragent: %v", err)
|
||||
}
|
||||
plugin.UserAgent = useragent
|
||||
|
||||
proxy, err := cmdDir.Flags().GetString("proxy")
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("invalid value for proxy: %v", err)
|
||||
}
|
||||
plugin.Proxy = proxy
|
||||
|
||||
timeout, err := cmdDir.Flags().GetDuration("timeout")
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("invalid value for timeout: %v", err)
|
||||
}
|
||||
plugin.Timeout = timeout
|
||||
|
||||
followredirect, err := cmdDir.Flags().GetBool("followredirect")
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("invalid value for followredirect: %v", err)
|
||||
}
|
||||
plugin.FollowRedirect = followredirect
|
||||
|
||||
expanded, err := cmdDir.Flags().GetBool("expanded")
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("invalid value for expanded: %v", err)
|
||||
}
|
||||
plugin.Expanded = expanded
|
||||
|
||||
nostatus, err := cmdDir.Flags().GetBool("nostatus")
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("invalid value for nostatus: %v", err)
|
||||
}
|
||||
plugin.NoStatus = nostatus
|
||||
|
||||
includelength, err := cmdDir.Flags().GetBool("includelength")
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("invalid value for includelength: %v", err)
|
||||
}
|
||||
plugin.IncludeLength = includelength
|
||||
|
||||
insecuressl, err := cmdDir.Flags().GetBool("insecuressl")
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("invalid value for insecuressl: %v", err)
|
||||
}
|
||||
plugin.InsecureSSL = insecuressl
|
||||
|
||||
wildcard, err := cmdDir.Flags().GetBool("wildcard")
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("invalid value for wildcard: %v", err)
|
||||
}
|
||||
plugin.WildcardForced = wildcard
|
||||
|
||||
// Prompt for PW if not provided
|
||||
if plugin.Username != "" && plugin.Password == "" {
|
||||
fmt.Printf("[?] Auth Password: ")
|
||||
passBytes, err := terminal.ReadPassword(syscall.Stdin)
|
||||
// print a newline to simulate the newline that was entered
|
||||
// this means that formatting/printing after doesn't look bad.
|
||||
fmt.Println("")
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("username given but reading of password failed")
|
||||
}
|
||||
plugin.Password = string(passBytes)
|
||||
}
|
||||
// if it's still empty bail out
|
||||
if plugin.Username != "" && plugin.Password == "" {
|
||||
return nil, nil, fmt.Errorf("username was provided but password is missing")
|
||||
}
|
||||
|
||||
return globalopts, plugin, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
cmdDir = &cobra.Command{
|
||||
Use: "dir",
|
||||
Short: "uses dir mode",
|
||||
RunE: runDir,
|
||||
}
|
||||
cmdDir.Flags().StringP("url", "u", "", "The target URL")
|
||||
cmdDir.Flags().StringP("statuscodes", "s", "200,204,301,302,307,403", "Positive status codes")
|
||||
cmdDir.Flags().StringP("cookies", "c", "", "Cookies to use for the requests")
|
||||
cmdDir.Flags().StringP("username", "U", "", "Username for Basic Auth")
|
||||
cmdDir.Flags().StringP("password", "P", "", "Password for Basic Auth")
|
||||
cmdDir.Flags().StringP("extensions", "x", "", "File extension(s) to search for")
|
||||
cmdDir.Flags().StringP("useragent", "a", libgobuster.DefaultUserAgent(), "Set the User-Agent string")
|
||||
cmdDir.Flags().StringP("proxy", "p", "", "Proxy to use for requests [http(s)://host:port]")
|
||||
cmdDir.Flags().DurationP("timeout", "d", 10*time.Second, "HTTP Timeout in seconds")
|
||||
cmdDir.Flags().BoolP("followredirect", "r", false, "Follow redirects")
|
||||
cmdDir.Flags().BoolP("expanded", "e", false, "Expanded mode, print full URLs")
|
||||
cmdDir.Flags().BoolP("nostatus", "n", false, "Don't print status codes")
|
||||
cmdDir.Flags().BoolP("includelength", "l", false, "Include the length of the body in the output")
|
||||
cmdDir.Flags().BoolP("insecuressl", "k", false, "Skip SSL certificate verification")
|
||||
cmdDir.Flags().BoolP("wildcard", "", false, "Force continued operation when wildcard found")
|
||||
if err := cmdDir.MarkFlagRequired("url"); err != nil {
|
||||
log.Fatalf("error on marking flag as required: %v", err)
|
||||
}
|
||||
rootCmd.AddCommand(cmdDir)
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
|
||||
"github.com/OJ/gobuster/cli"
|
||||
"github.com/OJ/gobuster/gobusterdns"
|
||||
"github.com/OJ/gobuster/libgobuster"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var cmdDNS *cobra.Command
|
||||
|
||||
func runDNS(cmd *cobra.Command, args []string) error {
|
||||
globalopts, pluginopts, err := parseDNS()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error on parsing arguments: %v", err)
|
||||
}
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
plugin, err := gobusterdns.NewGobusterDNS(globalopts, pluginopts)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error on creating gobusterdns: %v", err)
|
||||
}
|
||||
|
||||
signalChan := make(chan os.Signal, 1)
|
||||
signal.Notify(signalChan, os.Interrupt)
|
||||
go func() {
|
||||
for range signalChan {
|
||||
// caught CTRL+C
|
||||
if !globalopts.Quiet {
|
||||
fmt.Println("\n[!] Keyboard interrupt detected, terminating.")
|
||||
}
|
||||
cancel()
|
||||
}
|
||||
}()
|
||||
|
||||
if err := cli.Gobuster(ctx, globalopts, plugin); err != nil {
|
||||
return fmt.Errorf("error on running goubster: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseDNS() (*libgobuster.Options, *gobusterdns.OptionsDNS, error) {
|
||||
globalopts, err := parseGlobalOptions()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
plugin := gobusterdns.NewOptionsDNS()
|
||||
|
||||
domain, err := cmdDNS.Flags().GetString("domain")
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("invalid value for domain: %v", err)
|
||||
}
|
||||
plugin.Domain = domain
|
||||
|
||||
showips, err := cmdDNS.Flags().GetBool("showips")
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("invalid value for showips: %v", err)
|
||||
}
|
||||
plugin.ShowIPs = showips
|
||||
|
||||
showcname, err := cmdDNS.Flags().GetBool("showcname")
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("invalid value for showcname: %v", err)
|
||||
}
|
||||
plugin.ShowCNAME = showcname
|
||||
|
||||
wildcard, err := cmdDNS.Flags().GetBool("wildcard")
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("invalid value for wildcard: %v", err)
|
||||
}
|
||||
plugin.WildcardForced = wildcard
|
||||
|
||||
return globalopts, plugin, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
cmdDNS = &cobra.Command{
|
||||
Use: "dns",
|
||||
Short: "uses dns mode",
|
||||
RunE: runDNS,
|
||||
}
|
||||
|
||||
cmdDNS.Flags().StringP("domain", "d", "", "The target domain")
|
||||
cmdDNS.Flags().BoolP("showips", "i", false, "Show IP addresses")
|
||||
cmdDNS.Flags().BoolP("showcname", "c", false, "Show CNAME records (cannot be used with '-i' option)")
|
||||
cmdDNS.Flags().BoolP("wildcard", "", false, "Force continued operation when wildcard found")
|
||||
if err := cmdDNS.MarkFlagRequired("domain"); err != nil {
|
||||
log.Fatalf("error on marking flag as required: %v", err)
|
||||
}
|
||||
rootCmd.AddCommand(cmdDNS)
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/OJ/gobuster/libgobuster"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "gobuster",
|
||||
}
|
||||
|
||||
// Execute is the main cobra method
|
||||
func Execute() {
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func parseGlobalOptions() (*libgobuster.Options, error) {
|
||||
globalopts := libgobuster.NewOptions()
|
||||
threads, err := rootCmd.Flags().GetInt("threads")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid value for threads: %v", err)
|
||||
}
|
||||
if threads <= 0 {
|
||||
return nil, fmt.Errorf("threads must be bigger than 0")
|
||||
}
|
||||
globalopts.Threads = threads
|
||||
|
||||
wordlist, err := rootCmd.Flags().GetString("wordlist")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid value for wordlist: %v", err)
|
||||
}
|
||||
if wordlist == "-" {
|
||||
// STDIN
|
||||
} else if _, err2 := os.Stat(wordlist); os.IsNotExist(err2) {
|
||||
return nil, fmt.Errorf("wordlist file %q does not exist: %v", wordlist, err2)
|
||||
}
|
||||
globalopts.Wordlist = wordlist
|
||||
|
||||
outputfilename, err := rootCmd.Flags().GetString("output")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid value for output filename: %v", err)
|
||||
}
|
||||
globalopts.OutputFilename = outputfilename
|
||||
|
||||
verbose, err := rootCmd.Flags().GetBool("verbose")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid value for verbose: %v", err)
|
||||
}
|
||||
globalopts.Verbose = verbose
|
||||
|
||||
quiet, err := rootCmd.Flags().GetBool("quiet")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid value for quiet: %v", err)
|
||||
}
|
||||
globalopts.Quiet = quiet
|
||||
|
||||
noprogress, err := rootCmd.Flags().GetBool("noprogress")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid value for noprogress: %v", err)
|
||||
}
|
||||
globalopts.NoProgress = noprogress
|
||||
|
||||
return globalopts, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.PersistentFlags().IntP("threads", "t", 10, "Number of concurrent threads")
|
||||
rootCmd.PersistentFlags().StringP("wordlist", "w", "", "Path to the wordlist")
|
||||
rootCmd.PersistentFlags().StringP("output", "o", "", "Output file to write results to (defaults to stdout)")
|
||||
rootCmd.PersistentFlags().BoolP("verbose", "v", false, "Verbose output (errors)")
|
||||
rootCmd.PersistentFlags().BoolP("quiet", "q", false, "Don't print the banner and other noise")
|
||||
rootCmd.PersistentFlags().BoolP("noprogress", "", false, "Don't display progress")
|
||||
if err := rootCmd.MarkPersistentFlagRequired("wordlist"); err != nil {
|
||||
log.Fatalf("error on marking flag as required: %v", err)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,142 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/OJ/gobuster/libgobuster"
|
||||
)
|
||||
|
||||
func ruler() {
|
||||
fmt.Println("=====================================================")
|
||||
}
|
||||
|
||||
func banner() {
|
||||
fmt.Printf("Gobuster v%s OJ Reeves (@TheColonial)\n", libgobuster.VERSION)
|
||||
}
|
||||
|
||||
func resultWorker(g *libgobuster.Gobuster, filename string, wg *sync.WaitGroup) {
|
||||
defer wg.Done()
|
||||
var f *os.File
|
||||
var err error
|
||||
if filename != "" {
|
||||
f, err = os.Create(filename)
|
||||
if err != nil {
|
||||
log.Fatalf("error on creating output file: %v", err)
|
||||
}
|
||||
}
|
||||
for r := range g.Results() {
|
||||
s, err := r.ToString(g)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if s != "" {
|
||||
g.ClearProgress()
|
||||
s = strings.TrimSpace(s)
|
||||
fmt.Println(s)
|
||||
if f != nil {
|
||||
err = writeToFile(f, s)
|
||||
if err != nil {
|
||||
log.Fatalf("error on writing output file: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func errorWorker(g *libgobuster.Gobuster, wg *sync.WaitGroup) {
|
||||
defer wg.Done()
|
||||
for e := range g.Errors() {
|
||||
if !g.Opts.Quiet {
|
||||
g.ClearProgress()
|
||||
log.Printf("[!] %v", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func progressWorker(c context.Context, g *libgobuster.Gobuster) {
|
||||
tick := time.NewTicker(1 * time.Second)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-tick.C:
|
||||
g.PrintProgress()
|
||||
case <-c.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func writeToFile(f *os.File, output string) error {
|
||||
_, err := f.WriteString(fmt.Sprintf("%s\n", output))
|
||||
if err != nil {
|
||||
return fmt.Errorf("[!] Unable to write to file %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Gobuster is the main entry point for the CLI
|
||||
func Gobuster(prevCtx context.Context, opts *libgobuster.Options, plugin libgobuster.GobusterPlugin) error {
|
||||
// Sanity checks
|
||||
if opts == nil {
|
||||
return fmt.Errorf("please provide valid options")
|
||||
}
|
||||
|
||||
if plugin == nil {
|
||||
return fmt.Errorf("please provide a valid plugin")
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(prevCtx)
|
||||
defer cancel()
|
||||
|
||||
gobuster, err := libgobuster.NewGobuster(ctx, opts, plugin)
|
||||
if err != nil {
|
||||
log.Fatalf("[!] %v", err)
|
||||
}
|
||||
|
||||
if !opts.Quiet {
|
||||
fmt.Println("")
|
||||
ruler()
|
||||
banner()
|
||||
ruler()
|
||||
c, err := gobuster.GetConfigString()
|
||||
if err != nil {
|
||||
log.Fatalf("error on creating config string: %v", err)
|
||||
}
|
||||
fmt.Println(c)
|
||||
ruler()
|
||||
log.Println("Starting gobuster")
|
||||
ruler()
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
go errorWorker(gobuster, &wg)
|
||||
go resultWorker(gobuster, opts.OutputFilename, &wg)
|
||||
|
||||
if !opts.Quiet && !opts.NoProgress {
|
||||
go progressWorker(ctx, gobuster)
|
||||
}
|
||||
|
||||
if err := gobuster.Start(); err != nil {
|
||||
log.Printf("[!] %v", err)
|
||||
} else {
|
||||
// call cancel func to free ressources and stop progressFunc
|
||||
cancel()
|
||||
// wait for all output funcs to finish
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
if !opts.Quiet {
|
||||
gobuster.ClearProgress()
|
||||
ruler()
|
||||
log.Println("Finished")
|
||||
ruler()
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -2,52 +2,90 @@ package gobusterdir
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/OJ/gobuster/libgobuster"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// GobusterDir is the main type to implement the interface
|
||||
type GobusterDir struct{}
|
||||
type GobusterDir struct {
|
||||
options *OptionsDir
|
||||
globalopts *libgobuster.Options
|
||||
http *httpClient
|
||||
}
|
||||
|
||||
// Setup is the setup implementation of gobusterdir
|
||||
func (d GobusterDir) Setup(g *libgobuster.Gobuster) error {
|
||||
_, _, err := g.GetRequest(g.Opts.URL)
|
||||
// GetRequest issues a GET request to the target and returns
|
||||
// the status code, length and an error
|
||||
func (d *GobusterDir) get(url string) (*int, *int64, error) {
|
||||
return d.http.makeRequest(url, d.options.Cookies)
|
||||
}
|
||||
|
||||
// NewGobusterDir creates a new initialized GobusterDir
|
||||
func NewGobusterDir(cont context.Context, globalopts *libgobuster.Options, opts *OptionsDir) (*GobusterDir, error) {
|
||||
if globalopts == nil {
|
||||
return nil, fmt.Errorf("please provide valid global options")
|
||||
}
|
||||
|
||||
if opts == nil {
|
||||
return nil, fmt.Errorf("please provide valid plugin options")
|
||||
}
|
||||
|
||||
g := GobusterDir{
|
||||
options: opts,
|
||||
globalopts: globalopts,
|
||||
}
|
||||
h, err := newHTTPClient(cont, opts)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to connect to %s: %v", g.Opts.URL, err)
|
||||
return nil, err
|
||||
}
|
||||
g.http = h
|
||||
return &g, nil
|
||||
}
|
||||
|
||||
// PreRun is the pre run implementation of gobusterdir
|
||||
func (d *GobusterDir) PreRun() error {
|
||||
// add trailing slash
|
||||
if !strings.HasSuffix(d.options.URL, "/") {
|
||||
d.options.URL = fmt.Sprintf("%s/", d.options.URL)
|
||||
}
|
||||
|
||||
_, _, err := d.get(d.options.URL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to connect to %s: %v", d.options.URL, err)
|
||||
}
|
||||
|
||||
guid := uuid.New()
|
||||
url := fmt.Sprintf("%s%s", g.Opts.URL, guid)
|
||||
wildcardResp, _, err := g.GetRequest(url)
|
||||
|
||||
url := fmt.Sprintf("%s%s", d.options.URL, guid)
|
||||
wildcardResp, _, err := d.get(url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if g.Opts.StatusCodesParsed.Contains(*wildcardResp) {
|
||||
g.IsWildcard = true
|
||||
if d.options.StatusCodesParsed.Contains(*wildcardResp) {
|
||||
d.options.IsWildcard = true
|
||||
log.Printf("[-] Wildcard response found: %s => %d", url, *wildcardResp)
|
||||
if !g.Opts.WildcardForced {
|
||||
return fmt.Errorf("To force processing of Wildcard responses, specify the '-fw' switch.")
|
||||
if !d.options.WildcardForced {
|
||||
return fmt.Errorf("To force processing of Wildcard responses, specify the '--wildcard' switch.")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Process is the process implementation of gobusterdir
|
||||
func (d GobusterDir) Process(g *libgobuster.Gobuster, word string) ([]libgobuster.Result, error) {
|
||||
// Run is the process implementation of gobusterdir
|
||||
func (d *GobusterDir) Run(word string) ([]libgobuster.Result, error) {
|
||||
suffix := ""
|
||||
if g.Opts.UseSlash {
|
||||
if d.options.UseSlash {
|
||||
suffix = "/"
|
||||
}
|
||||
|
||||
// Try the DIR first
|
||||
url := fmt.Sprintf("%s%s%s", g.Opts.URL, word, suffix)
|
||||
dirResp, dirSize, err := g.GetRequest(url)
|
||||
url := fmt.Sprintf("%s%s%s", d.options.URL, word, suffix)
|
||||
dirResp, dirSize, err := d.get(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -61,10 +99,10 @@ func (d GobusterDir) Process(g *libgobuster.Gobuster, word string) ([]libgobuste
|
|||
}
|
||||
|
||||
// Follow up with files using each ext.
|
||||
for ext := range g.Opts.ExtensionsParsed.Set {
|
||||
for ext := range d.options.ExtensionsParsed.Set {
|
||||
file := fmt.Sprintf("%s.%s", word, ext)
|
||||
url = fmt.Sprintf("%s%s", g.Opts.URL, file)
|
||||
fileResp, fileSize, err := g.GetRequest(url)
|
||||
url = fmt.Sprintf("%s%s", d.options.URL, file)
|
||||
fileResp, fileSize, err := d.get(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -81,12 +119,12 @@ func (d GobusterDir) Process(g *libgobuster.Gobuster, word string) ([]libgobuste
|
|||
}
|
||||
|
||||
// ResultToString is the to string implementation of gobusterdir
|
||||
func (d GobusterDir) ResultToString(g *libgobuster.Gobuster, r *libgobuster.Result) (*string, error) {
|
||||
func (d *GobusterDir) ResultToString(r *libgobuster.Result) (*string, error) {
|
||||
buf := &bytes.Buffer{}
|
||||
|
||||
// Prefix if we're in verbose mode
|
||||
if g.Opts.Verbose {
|
||||
if g.Opts.StatusCodesParsed.Contains(r.Status) {
|
||||
if d.globalopts.Verbose {
|
||||
if d.options.StatusCodesParsed.Contains(r.Status) {
|
||||
if _, err := fmt.Fprintf(buf, "Found: "); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -97,9 +135,9 @@ func (d GobusterDir) ResultToString(g *libgobuster.Gobuster, r *libgobuster.Resu
|
|||
}
|
||||
}
|
||||
|
||||
if g.Opts.StatusCodesParsed.Contains(r.Status) || g.Opts.Verbose {
|
||||
if g.Opts.Expanded {
|
||||
if _, err := fmt.Fprintf(buf, g.Opts.URL); err != nil {
|
||||
if d.options.StatusCodesParsed.Contains(r.Status) || d.globalopts.Verbose {
|
||||
if d.options.Expanded {
|
||||
if _, err := fmt.Fprintf(buf, d.options.URL); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
|
@ -111,7 +149,7 @@ func (d GobusterDir) ResultToString(g *libgobuster.Gobuster, r *libgobuster.Resu
|
|||
return nil, err
|
||||
}
|
||||
|
||||
if !g.Opts.NoStatus {
|
||||
if !d.options.NoStatus {
|
||||
if _, err := fmt.Fprintf(buf, " (Status: %d)", r.Status); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -129,3 +167,99 @@ func (d GobusterDir) ResultToString(g *libgobuster.Gobuster, r *libgobuster.Resu
|
|||
s := buf.String()
|
||||
return &s, nil
|
||||
}
|
||||
|
||||
// GetConfigString returns the string representation of the current config
|
||||
func (d *GobusterDir) GetConfigString() (string, error) {
|
||||
buf := &bytes.Buffer{}
|
||||
o := d.options
|
||||
if _, err := fmt.Fprintf(buf, "[+] Url : %s\n", o.URL); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if _, err := fmt.Fprintf(buf, "[+] Threads : %d\n", d.globalopts.Threads); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
wordlist := "stdin (pipe)"
|
||||
if d.globalopts.Wordlist != "-" {
|
||||
wordlist = d.globalopts.Wordlist
|
||||
}
|
||||
if _, err := fmt.Fprintf(buf, "[+] Wordlist : %s\n", wordlist); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if _, err := fmt.Fprintf(buf, "[+] Status codes : %s\n", o.StatusCodesParsed.Stringify()); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if o.Proxy != "" {
|
||||
if _, err := fmt.Fprintf(buf, "[+] Proxy : %s\n", o.Proxy); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
if o.Cookies != "" {
|
||||
if _, err := fmt.Fprintf(buf, "[+] Cookies : %s\n", o.Cookies); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
if o.UserAgent != "" {
|
||||
if _, err := fmt.Fprintf(buf, "[+] User Agent : %s\n", o.UserAgent); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
if o.IncludeLength {
|
||||
if _, err := fmt.Fprintf(buf, "[+] Show length : true\n"); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
if o.Username != "" {
|
||||
if _, err := fmt.Fprintf(buf, "[+] Auth User : %s\n", o.Username); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
if o.Extensions != "" {
|
||||
if _, err := fmt.Fprintf(buf, "[+] Extensions : %s\n", o.ExtensionsParsed.Stringify()); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
if o.UseSlash {
|
||||
if _, err := fmt.Fprintf(buf, "[+] Add Slash : true\n"); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
if o.FollowRedirect {
|
||||
if _, err := fmt.Fprintf(buf, "[+] Follow Redir : true\n"); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
if o.Expanded {
|
||||
if _, err := fmt.Fprintf(buf, "[+] Expanded : true\n"); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
if o.NoStatus {
|
||||
if _, err := fmt.Fprintf(buf, "[+] No status : true\n"); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
if d.globalopts.Verbose {
|
||||
if _, err := fmt.Fprintf(buf, "[+] Verbose : true\n"); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := fmt.Fprintf(buf, "[+] Timeout : %s\n", o.Timeout.String()); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return strings.TrimSpace(buf.String()), nil
|
||||
}
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
package gobusterdir
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ParseExtensions parses the extensions provided as a comma seperated list
|
||||
func (opt *OptionsDir) ParseExtensions() error {
|
||||
if opt.Extensions == "" {
|
||||
return fmt.Errorf("invalid extension string provided")
|
||||
}
|
||||
|
||||
exts := strings.Split(opt.Extensions, ",")
|
||||
for _, e := range exts {
|
||||
e = strings.TrimSpace(e)
|
||||
// remove leading . from extensions
|
||||
opt.ExtensionsParsed.Add(strings.TrimPrefix(e, "."))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ParseStatusCodes parses the status codes provided as a comma seperated list
|
||||
func (opt *OptionsDir) ParseStatusCodes() error {
|
||||
if opt.StatusCodes == "" {
|
||||
return fmt.Errorf("invalid status code string provided")
|
||||
}
|
||||
|
||||
for _, c := range strings.Split(opt.StatusCodes, ",") {
|
||||
c = strings.TrimSpace(c)
|
||||
i, err := strconv.Atoi(c)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid status code given: %s", c)
|
||||
}
|
||||
opt.StatusCodesParsed.Add(i)
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
package gobusterdir
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/OJ/gobuster/libgobuster"
|
||||
)
|
||||
|
||||
func TestParseExtensions(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var tt = []struct {
|
||||
testName string
|
||||
Extensions string
|
||||
expectedExtensions libgobuster.StringSet
|
||||
expectedError string
|
||||
}{
|
||||
{"Valid extensions", "php,asp,txt", libgobuster.StringSet{Set: map[string]bool{"php": true, "asp": true, "txt": true}}, ""},
|
||||
{"Spaces", "php, asp , txt", libgobuster.StringSet{Set: map[string]bool{"php": true, "asp": true, "txt": true}}, ""},
|
||||
{"Double extensions", "php,asp,txt,php,asp,txt", libgobuster.StringSet{Set: map[string]bool{"php": true, "asp": true, "txt": true}}, ""},
|
||||
{"Leading dot", ".php,asp,.txt", libgobuster.StringSet{Set: map[string]bool{"php": true, "asp": true, "txt": true}}, ""},
|
||||
{"Empty string", "", libgobuster.NewStringSet(), "invalid extension string provided"},
|
||||
}
|
||||
|
||||
for _, x := range tt {
|
||||
t.Run(x.testName, func(t *testing.T) {
|
||||
o := NewOptionsDir()
|
||||
o.Extensions = x.Extensions
|
||||
err := o.ParseExtensions()
|
||||
if x.expectedError != "" {
|
||||
if err.Error() != x.expectedError {
|
||||
t.Fatalf("Expected error %q but got %q", x.expectedError, err.Error())
|
||||
}
|
||||
} else if !reflect.DeepEqual(x.expectedExtensions, o.ExtensionsParsed) {
|
||||
t.Fatalf("Expected %v but got %v", x.expectedExtensions, o.ExtensionsParsed)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseStatusCodes(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var tt = []struct {
|
||||
testName string
|
||||
stringCodes string
|
||||
expectedCodes libgobuster.IntSet
|
||||
expectedError string
|
||||
}{
|
||||
{"Valid codes", "200,100,202", libgobuster.IntSet{Set: map[int]bool{100: true, 200: true, 202: true}}, ""},
|
||||
{"Spaces", "200, 100 , 202", libgobuster.IntSet{Set: map[int]bool{100: true, 200: true, 202: true}}, ""},
|
||||
{"Double codes", "200, 100, 202, 100", libgobuster.IntSet{Set: map[int]bool{100: true, 200: true, 202: true}}, ""},
|
||||
{"Invalid code", "200,AAA", libgobuster.NewIntSet(), "invalid status code given: AAA"},
|
||||
{"Invalid integer", "2000000000000000000000000000000", libgobuster.NewIntSet(), "invalid status code given: 2000000000000000000000000000000"},
|
||||
{"Empty string", "", libgobuster.NewIntSet(), "invalid status code string provided"},
|
||||
}
|
||||
|
||||
for _, x := range tt {
|
||||
t.Run(x.testName, func(t *testing.T) {
|
||||
o := NewOptionsDir()
|
||||
o.StatusCodes = x.stringCodes
|
||||
err := o.ParseStatusCodes()
|
||||
if x.expectedError != "" {
|
||||
if err.Error() != x.expectedError {
|
||||
t.Fatalf("Expected error %q but got %q", x.expectedError, err.Error())
|
||||
}
|
||||
} else if !reflect.DeepEqual(x.expectedCodes, o.StatusCodesParsed) {
|
||||
t.Fatalf("Expected %v but got %v", x.expectedCodes, o.StatusCodesParsed)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package libgobuster
|
||||
package gobusterdir
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
@ -10,6 +10,8 @@ import (
|
|||
"net/url"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/OJ/gobuster/libgobuster"
|
||||
)
|
||||
|
||||
type httpClient struct {
|
||||
|
@ -22,7 +24,7 @@ type httpClient struct {
|
|||
}
|
||||
|
||||
// NewHTTPClient returns a new HTTPClient
|
||||
func newHTTPClient(c context.Context, opt *Options) (*httpClient, error) {
|
||||
func newHTTPClient(c context.Context, opt *OptionsDir) (*httpClient, error) {
|
||||
var proxyURLFunc func(*http.Request) (*url.URL, error)
|
||||
var client httpClient
|
||||
proxyURLFunc = http.ProxyFromEnvironment
|
||||
|
@ -80,7 +82,7 @@ func (client *httpClient) makeRequest(fullURL, cookie string) (*int, *int64, err
|
|||
req.Header.Set("Cookie", cookie)
|
||||
}
|
||||
|
||||
ua := fmt.Sprintf("gobuster %s", VERSION)
|
||||
ua := libgobuster.DefaultUserAgent()
|
||||
if client.userAgent != "" {
|
||||
ua = client.userAgent
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package libgobuster
|
||||
package gobusterdir
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
@ -19,7 +19,7 @@ func httpServer(t *testing.T, content string) *httptest.Server {
|
|||
func TestMakeRequest(t *testing.T) {
|
||||
h := httpServer(t, "test")
|
||||
defer h.Close()
|
||||
o := NewOptions()
|
||||
o := NewOptionsDir()
|
||||
c, err := newHTTPClient(context.Background(), o)
|
||||
if err != nil {
|
||||
t.Fatalf("Got Error: %v", err)
|
|
@ -0,0 +1,38 @@
|
|||
package gobusterdir
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/OJ/gobuster/libgobuster"
|
||||
)
|
||||
|
||||
// OptionsDir is the struct to hold all options for this plugin
|
||||
type OptionsDir struct {
|
||||
Extensions string
|
||||
ExtensionsParsed libgobuster.StringSet
|
||||
Password string
|
||||
StatusCodes string
|
||||
StatusCodesParsed libgobuster.IntSet
|
||||
URL string
|
||||
UserAgent string
|
||||
Username string
|
||||
Proxy string
|
||||
Cookies string
|
||||
Timeout time.Duration
|
||||
FollowRedirect bool
|
||||
IncludeLength bool
|
||||
Expanded bool
|
||||
NoStatus bool
|
||||
InsecureSSL bool
|
||||
UseSlash bool
|
||||
IsWildcard bool
|
||||
WildcardForced bool
|
||||
}
|
||||
|
||||
// NewOptionsDir returns a new initialized OptionsDir
|
||||
func NewOptionsDir() *OptionsDir {
|
||||
return &OptionsDir{
|
||||
StatusCodesParsed: libgobuster.NewIntSet(),
|
||||
ExtensionsParsed: libgobuster.NewStringSet(),
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
package gobusterdir
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestNewOptions(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
o := NewOptionsDir()
|
||||
if o.StatusCodesParsed.Set == nil {
|
||||
t.Fatal("StatusCodesParsed not initialized")
|
||||
}
|
||||
|
||||
if o.ExtensionsParsed.Set == nil {
|
||||
t.Fatal("ExtensionsParsed not initialized")
|
||||
}
|
||||
}
|
|
@ -4,6 +4,7 @@ import (
|
|||
"bytes"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/OJ/gobuster/libgobuster"
|
||||
|
@ -11,55 +12,78 @@ import (
|
|||
)
|
||||
|
||||
// GobusterDNS is the main type to implement the interface
|
||||
type GobusterDNS struct{}
|
||||
type GobusterDNS struct {
|
||||
globalopts *libgobuster.Options
|
||||
options *OptionsDNS
|
||||
isWildcard bool
|
||||
wildcardIps libgobuster.StringSet
|
||||
}
|
||||
|
||||
// Setup is the setup implementation of gobusterdns
|
||||
func (d GobusterDNS) Setup(g *libgobuster.Gobuster) error {
|
||||
// NewGobusterDNS creates a new initialized GobusterDNS
|
||||
func NewGobusterDNS(globalopts *libgobuster.Options, opts *OptionsDNS) (*GobusterDNS, error) {
|
||||
if globalopts == nil {
|
||||
return nil, fmt.Errorf("please provide valid global options")
|
||||
}
|
||||
|
||||
if opts == nil {
|
||||
return nil, fmt.Errorf("please provide valid plugin options")
|
||||
}
|
||||
|
||||
g := GobusterDNS{
|
||||
options: opts,
|
||||
globalopts: globalopts,
|
||||
wildcardIps: libgobuster.NewStringSet(),
|
||||
}
|
||||
return &g, nil
|
||||
}
|
||||
|
||||
// PreRun is the pre run implementation of gobusterdns
|
||||
func (d *GobusterDNS) PreRun() error {
|
||||
// Resolve a subdomain sthat probably shouldn't exist
|
||||
guid := uuid.New()
|
||||
wildcardIps, err := g.DNSLookup(fmt.Sprintf("%s.%s", guid, g.Opts.URL))
|
||||
wildcardIps, err := dnsLookup(fmt.Sprintf("%s.%s", guid, d.options.Domain))
|
||||
if err == nil {
|
||||
g.IsWildcard = true
|
||||
g.WildcardIps.AddRange(wildcardIps)
|
||||
log.Printf("[-] Wildcard DNS found. IP address(es): %s", g.WildcardIps.Stringify())
|
||||
if !g.Opts.WildcardForced {
|
||||
return fmt.Errorf("To force processing of Wildcard DNS, specify the '-fw' switch.")
|
||||
d.isWildcard = true
|
||||
d.wildcardIps.AddRange(wildcardIps)
|
||||
log.Printf("[-] Wildcard DNS found. IP address(es): %s", d.wildcardIps.Stringify())
|
||||
if !d.options.WildcardForced {
|
||||
return fmt.Errorf("To force processing of Wildcard DNS, specify the '--wildcard' switch.")
|
||||
}
|
||||
}
|
||||
|
||||
if !g.Opts.Quiet {
|
||||
if !d.globalopts.Quiet {
|
||||
// Provide a warning if the base domain doesn't resolve (in case of typo)
|
||||
_, err = g.DNSLookup(g.Opts.URL)
|
||||
_, err = dnsLookup(d.options.Domain)
|
||||
if err != nil {
|
||||
// Not an error, just a warning. Eg. `yp.to` doesn't resolve, but `cr.py.to` does!
|
||||
log.Printf("[-] Unable to validate base domain: %s", g.Opts.URL)
|
||||
log.Printf("[-] Unable to validate base domain: %s", d.options.Domain)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Process is the process implementation of gobusterdns
|
||||
func (d GobusterDNS) Process(g *libgobuster.Gobuster, word string) ([]libgobuster.Result, error) {
|
||||
subdomain := fmt.Sprintf("%s.%s", word, g.Opts.URL)
|
||||
ips, err := g.DNSLookup(subdomain)
|
||||
// Run is the process implementation of gobusterdns
|
||||
func (d *GobusterDNS) Run(word string) ([]libgobuster.Result, error) {
|
||||
subdomain := fmt.Sprintf("%s.%s", word, d.options.Domain)
|
||||
ips, err := dnsLookup(subdomain)
|
||||
var ret []libgobuster.Result
|
||||
if err == nil {
|
||||
if !g.IsWildcard || !g.WildcardIps.ContainsAny(ips) {
|
||||
if !d.isWildcard || !d.wildcardIps.ContainsAny(ips) {
|
||||
result := libgobuster.Result{
|
||||
Entity: subdomain,
|
||||
}
|
||||
if g.Opts.ShowIPs {
|
||||
if d.options.ShowIPs {
|
||||
result.Extra = strings.Join(ips, ", ")
|
||||
} else if g.Opts.ShowCNAME {
|
||||
cname, err := g.DNSLookupCname(subdomain)
|
||||
} else if d.options.ShowCNAME {
|
||||
cname, err := dnsLookupCname(subdomain)
|
||||
if err == nil {
|
||||
result.Extra = cname
|
||||
}
|
||||
}
|
||||
ret = append(ret, result)
|
||||
}
|
||||
} else if g.Opts.Verbose {
|
||||
} else if d.globalopts.Verbose {
|
||||
ret = append(ret, libgobuster.Result{
|
||||
Entity: subdomain,
|
||||
Status: 404,
|
||||
|
@ -69,18 +93,18 @@ func (d GobusterDNS) Process(g *libgobuster.Gobuster, word string) ([]libgobuste
|
|||
}
|
||||
|
||||
// ResultToString is the to string implementation of gobusterdns
|
||||
func (d GobusterDNS) ResultToString(g *libgobuster.Gobuster, r *libgobuster.Result) (*string, error) {
|
||||
func (d *GobusterDNS) ResultToString(r *libgobuster.Result) (*string, error) {
|
||||
buf := &bytes.Buffer{}
|
||||
|
||||
if r.Status == 404 {
|
||||
if _, err := fmt.Fprintf(buf, "Missing: %s\n", r.Entity); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if g.Opts.ShowIPs {
|
||||
} else if d.options.ShowIPs {
|
||||
if _, err := fmt.Fprintf(buf, "Found: %s [%s]\n", r.Entity, r.Extra); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if g.Opts.ShowCNAME {
|
||||
} else if d.options.ShowCNAME {
|
||||
if _, err := fmt.Fprintf(buf, "Found: %s [%s]\n", r.Entity, r.Extra); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -93,3 +117,57 @@ func (d GobusterDNS) ResultToString(g *libgobuster.Gobuster, r *libgobuster.Resu
|
|||
s := buf.String()
|
||||
return &s, nil
|
||||
}
|
||||
|
||||
// GetConfigString returns the string representation of the current config
|
||||
func (d *GobusterDNS) GetConfigString() (string, error) {
|
||||
buf := &bytes.Buffer{}
|
||||
o := d.options
|
||||
if _, err := fmt.Fprintf(buf, "[+] Domain : %s\n", o.Domain); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if _, err := fmt.Fprintf(buf, "[+] Threads : %d\n", d.globalopts.Threads); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if o.ShowCNAME {
|
||||
if _, err := fmt.Fprintf(buf, "[+] Show CNAME : true\n"); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
if o.ShowIPs {
|
||||
if _, err := fmt.Fprintf(buf, "[+] Show IPs : true\n"); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
if o.WildcardForced {
|
||||
if _, err := fmt.Fprintf(buf, "[+] Wildcard forced : true\n"); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
wordlist := "stdin (pipe)"
|
||||
if d.globalopts.Wordlist != "-" {
|
||||
wordlist = d.globalopts.Wordlist
|
||||
}
|
||||
if _, err := fmt.Fprintf(buf, "[+] Wordlist : %s\n", wordlist); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if d.globalopts.Verbose {
|
||||
if _, err := fmt.Fprintf(buf, "[+] Verbose : true\n"); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
return strings.TrimSpace(buf.String()), nil
|
||||
}
|
||||
|
||||
func dnsLookup(domain string) ([]string, error) {
|
||||
return net.LookupHost(domain)
|
||||
}
|
||||
|
||||
func dnsLookupCname(domain string) (string, error) {
|
||||
return net.LookupCNAME(domain)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
package gobusterdns
|
||||
|
||||
// OptionsDNS holds all options for the dns plugin
|
||||
type OptionsDNS struct {
|
||||
Domain string
|
||||
ShowIPs bool
|
||||
ShowCNAME bool
|
||||
WildcardForced bool
|
||||
}
|
||||
|
||||
// NewOptionsDNS returns a new initialized OptionsDNS
|
||||
func NewOptionsDNS() *OptionsDNS {
|
||||
return &OptionsDNS{}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
package gobusterdns
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestNewOptions(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
o := NewOptionsDNS()
|
||||
if o.WildcardIps.Set == nil {
|
||||
t.Fatal("WildcardIps not initialized")
|
||||
}
|
||||
}
|
|
@ -8,40 +8,43 @@ import (
|
|||
"strings"
|
||||
)
|
||||
|
||||
type intSet struct {
|
||||
// IntSet is a set of Ints
|
||||
type IntSet struct {
|
||||
Set map[int]bool
|
||||
}
|
||||
|
||||
type stringSet struct {
|
||||
// StringSet is a set of Strings
|
||||
type StringSet struct {
|
||||
Set map[string]bool
|
||||
}
|
||||
|
||||
func newStringSet() stringSet {
|
||||
return stringSet{Set: map[string]bool{}}
|
||||
// NewStringSet creates a new initialized StringSet
|
||||
func NewStringSet() StringSet {
|
||||
return StringSet{Set: map[string]bool{}}
|
||||
}
|
||||
|
||||
// Add an element to a set
|
||||
func (set *stringSet) Add(s string) bool {
|
||||
func (set *StringSet) Add(s string) bool {
|
||||
_, found := set.Set[s]
|
||||
set.Set[s] = true
|
||||
return !found
|
||||
}
|
||||
|
||||
// Add a list of elements to a set
|
||||
func (set *stringSet) AddRange(ss []string) {
|
||||
// AddRange adds a list of elements to a set
|
||||
func (set *StringSet) AddRange(ss []string) {
|
||||
for _, s := range ss {
|
||||
set.Set[s] = true
|
||||
}
|
||||
}
|
||||
|
||||
// Test if an element is in a set
|
||||
func (set *stringSet) Contains(s string) bool {
|
||||
// Contains tests if an element is in a set
|
||||
func (set *StringSet) Contains(s string) bool {
|
||||
_, found := set.Set[s]
|
||||
return found
|
||||
}
|
||||
|
||||
// Check if any of the elements exist
|
||||
func (set *stringSet) ContainsAny(ss []string) bool {
|
||||
// ContainsAny checks if any of the elements exist
|
||||
func (set *StringSet) ContainsAny(ss []string) bool {
|
||||
for _, s := range ss {
|
||||
if set.Set[s] {
|
||||
return true
|
||||
|
@ -50,8 +53,13 @@ func (set *stringSet) ContainsAny(ss []string) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
// Length returns the length of the Set
|
||||
func (set *StringSet) Length() int {
|
||||
return len(set.Set)
|
||||
}
|
||||
|
||||
// Stringify the set
|
||||
func (set *stringSet) Stringify() string {
|
||||
func (set *StringSet) Stringify() string {
|
||||
values := []string{}
|
||||
for s := range set.Set {
|
||||
values = append(values, s)
|
||||
|
@ -59,25 +67,26 @@ func (set *stringSet) Stringify() string {
|
|||
return strings.Join(values, ",")
|
||||
}
|
||||
|
||||
func newIntSet() intSet {
|
||||
return intSet{Set: map[int]bool{}}
|
||||
// NewIntSet creates a new initialized IntSet
|
||||
func NewIntSet() IntSet {
|
||||
return IntSet{Set: map[int]bool{}}
|
||||
}
|
||||
|
||||
// Add an element to a set
|
||||
func (set *intSet) Add(i int) bool {
|
||||
// Add adds an element to a set
|
||||
func (set *IntSet) Add(i int) bool {
|
||||
_, found := set.Set[i]
|
||||
set.Set[i] = true
|
||||
return !found
|
||||
}
|
||||
|
||||
// Test if an element is in a set
|
||||
func (set *intSet) Contains(i int) bool {
|
||||
// Contains tests if an element is in a set
|
||||
func (set *IntSet) Contains(i int) bool {
|
||||
_, found := set.Set[i]
|
||||
return found
|
||||
}
|
||||
|
||||
// Stringify the set
|
||||
func (set *intSet) Stringify() string {
|
||||
func (set *IntSet) Stringify() string {
|
||||
values := []int{}
|
||||
for s := range set.Set {
|
||||
values = append(values, s)
|
||||
|
@ -88,6 +97,11 @@ func (set *intSet) Stringify() string {
|
|||
return strings.Trim(strings.Join(strings.Fields(fmt.Sprint(values)), delim), "[]")
|
||||
}
|
||||
|
||||
// Length returns the length of the Set
|
||||
func (set *IntSet) Length() int {
|
||||
return len(set.Set)
|
||||
}
|
||||
|
||||
func lineCounter(r io.Reader) (int, error) {
|
||||
buf := make([]byte, 32*1024)
|
||||
count := 1
|
||||
|
@ -106,3 +120,8 @@ func lineCounter(r io.Reader) (int, error) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultUserAgent returns the default user agent to use in HTTP requests
|
||||
func DefaultUserAgent() string {
|
||||
return fmt.Sprintf("gobuster %s", VERSION)
|
||||
}
|
||||
|
|
|
@ -7,19 +7,19 @@ import (
|
|||
)
|
||||
|
||||
func TestNewStringSet(t *testing.T) {
|
||||
if newStringSet().Set == nil {
|
||||
if NewStringSet().Set == nil {
|
||||
t.Fatal("newStringSet returned nil Set")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewIntSet(t *testing.T) {
|
||||
if newIntSet().Set == nil {
|
||||
if NewIntSet().Set == nil {
|
||||
t.Fatal("newIntSet returned nil Set")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringSetAdd(t *testing.T) {
|
||||
x := newStringSet()
|
||||
x := NewStringSet()
|
||||
x.Add("test")
|
||||
if len(x.Set) != 1 {
|
||||
t.Fatalf("Unexptected size. Should have 1 Got %v", len(x.Set))
|
||||
|
@ -27,7 +27,7 @@ func TestStringSetAdd(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestStringSetAddDouble(t *testing.T) {
|
||||
x := newStringSet()
|
||||
x := NewStringSet()
|
||||
x.Add("test")
|
||||
x.Add("test")
|
||||
if len(x.Set) != 1 {
|
||||
|
@ -36,7 +36,7 @@ func TestStringSetAddDouble(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestStringSetAddRange(t *testing.T) {
|
||||
x := newStringSet()
|
||||
x := NewStringSet()
|
||||
x.AddRange([]string{"asdf", "ghjk"})
|
||||
if len(x.Set) != 2 {
|
||||
t.Fatalf("Unexptected size. Should have 2 Got %d", len(x.Set))
|
||||
|
@ -44,7 +44,7 @@ func TestStringSetAddRange(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestStringSetAddRangeDouble(t *testing.T) {
|
||||
x := newStringSet()
|
||||
x := NewStringSet()
|
||||
x.AddRange([]string{"asdf", "ghjk", "asdf", "ghjk"})
|
||||
if len(x.Set) != 2 {
|
||||
t.Fatalf("Unexptected size. Should have 2 Got %d", len(x.Set))
|
||||
|
@ -52,7 +52,7 @@ func TestStringSetAddRangeDouble(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestStringSetContains(t *testing.T) {
|
||||
x := newStringSet()
|
||||
x := NewStringSet()
|
||||
v := []string{"asdf", "ghjk", "1234", "5678"}
|
||||
x.AddRange(v)
|
||||
for _, y := range v {
|
||||
|
@ -63,7 +63,7 @@ func TestStringSetContains(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestStringSetContainsAny(t *testing.T) {
|
||||
x := newStringSet()
|
||||
x := NewStringSet()
|
||||
v := []string{"asdf", "ghjk", "1234", "5678"}
|
||||
x.AddRange(v)
|
||||
if !x.ContainsAny(v) {
|
||||
|
@ -77,7 +77,7 @@ func TestStringSetContainsAny(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestStringSetStringify(t *testing.T) {
|
||||
x := newStringSet()
|
||||
x := NewStringSet()
|
||||
v := []string{"asdf", "ghjk", "1234", "5678"}
|
||||
x.AddRange(v)
|
||||
z := x.Stringify()
|
||||
|
@ -90,7 +90,7 @@ func TestStringSetStringify(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestIntSetAdd(t *testing.T) {
|
||||
x := newIntSet()
|
||||
x := NewIntSet()
|
||||
x.Add(1)
|
||||
if len(x.Set) != 1 {
|
||||
t.Fatalf("Unexptected size. Should have 1 Got %d", len(x.Set))
|
||||
|
@ -98,7 +98,7 @@ func TestIntSetAdd(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestIntSetAddDouble(t *testing.T) {
|
||||
x := newIntSet()
|
||||
x := NewIntSet()
|
||||
x.Add(1)
|
||||
x.Add(1)
|
||||
if len(x.Set) != 1 {
|
||||
|
@ -107,7 +107,7 @@ func TestIntSetAddDouble(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestIntSetContains(t *testing.T) {
|
||||
x := newIntSet()
|
||||
x := NewIntSet()
|
||||
v := []int{1, 2, 3, 4}
|
||||
for _, y := range v {
|
||||
x.Add(y)
|
||||
|
@ -120,7 +120,7 @@ func TestIntSetContains(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestIntSetStringify(t *testing.T) {
|
||||
x := newIntSet()
|
||||
x := NewIntSet()
|
||||
v := []int{1, 3, 2, 4}
|
||||
expected := "1,2,3,4"
|
||||
for _, y := range v {
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
package libgobuster
|
||||
|
||||
// GobusterPlugin is an interface which plugins must implement
|
||||
type GobusterPlugin interface {
|
||||
PreRun() error
|
||||
Run(string) ([]Result, error)
|
||||
ResultToString(*Result) (*string, error)
|
||||
GetConfigString() (string, error)
|
||||
}
|
|
@ -2,20 +2,13 @@ package libgobuster
|
|||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const (
|
||||
// VERSION contains the current gobuster version
|
||||
VERSION = "2.0.1"
|
||||
)
|
||||
|
||||
// SetupFunc is the "setup" function prototype for implementations
|
||||
type SetupFunc func(*Gobuster) error
|
||||
|
||||
|
@ -28,46 +21,22 @@ type ResultToStringFunc func(*Gobuster, *Result) (*string, error)
|
|||
// Gobuster is the main object when creating a new run
|
||||
type Gobuster struct {
|
||||
Opts *Options
|
||||
http *httpClient
|
||||
WildcardIps stringSet
|
||||
context context.Context
|
||||
requestsExpected int
|
||||
requestsIssued int
|
||||
mu *sync.RWMutex
|
||||
plugin GobusterPlugin
|
||||
IsWildcard bool
|
||||
resultChan chan Result
|
||||
errorChan chan error
|
||||
}
|
||||
|
||||
// GobusterPlugin is an interface which plugins must implement
|
||||
type GobusterPlugin interface {
|
||||
Setup(*Gobuster) error
|
||||
Process(*Gobuster, string) ([]Result, error)
|
||||
ResultToString(*Gobuster, *Result) (*string, error)
|
||||
}
|
||||
|
||||
// NewGobuster returns a new Gobuster object
|
||||
func NewGobuster(c context.Context, opts *Options, plugin GobusterPlugin) (*Gobuster, error) {
|
||||
// validate given options
|
||||
multiErr := opts.validate()
|
||||
if multiErr != nil {
|
||||
return nil, multiErr
|
||||
}
|
||||
|
||||
var g Gobuster
|
||||
g.WildcardIps = newStringSet()
|
||||
g.context = c
|
||||
g.Opts = opts
|
||||
h, err := newHTTPClient(c, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
g.http = h
|
||||
|
||||
g.plugin = plugin
|
||||
g.mu = new(sync.RWMutex)
|
||||
|
||||
g.context = c
|
||||
g.resultChan = make(chan Result)
|
||||
g.errorChan = make(chan error)
|
||||
|
||||
|
@ -109,22 +78,6 @@ func (g *Gobuster) ClearProgress() {
|
|||
fmt.Fprint(os.Stderr, resetTerminal())
|
||||
}
|
||||
|
||||
// GetRequest issues a GET request to the target and returns
|
||||
// the status code, length and an error
|
||||
func (g *Gobuster) GetRequest(url string) (*int, *int64, error) {
|
||||
return g.http.makeRequest(url, g.Opts.Cookies)
|
||||
}
|
||||
|
||||
// DNSLookup looks up a domain via system default DNS servers
|
||||
func (g *Gobuster) DNSLookup(domain string) ([]string, error) {
|
||||
return net.LookupHost(domain)
|
||||
}
|
||||
|
||||
// DNSLookupCname looks up a CNAME record via system default DNS servers
|
||||
func (g *Gobuster) DNSLookupCname(domain string) (string, error) {
|
||||
return net.LookupCNAME(domain)
|
||||
}
|
||||
|
||||
func (g *Gobuster) worker(wordChan <-chan string, wg *sync.WaitGroup) {
|
||||
defer wg.Done()
|
||||
for {
|
||||
|
@ -138,7 +91,7 @@ func (g *Gobuster) worker(wordChan <-chan string, wg *sync.WaitGroup) {
|
|||
}
|
||||
g.incrementRequests()
|
||||
// Mode-specific processing
|
||||
res, err := g.plugin.Process(g, word)
|
||||
res, err := g.plugin.Run(word)
|
||||
if err != nil {
|
||||
// do not exit and continue
|
||||
g.errorChan <- err
|
||||
|
@ -182,7 +135,7 @@ func (g *Gobuster) getWordlist() (*bufio.Scanner, error) {
|
|||
// Start the busting of the website with the given
|
||||
// set of settings from the command line.
|
||||
func (g *Gobuster) Start() error {
|
||||
if err := g.plugin.Setup(g); err != nil {
|
||||
if err := g.plugin.PreRun(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -224,101 +177,5 @@ Scan:
|
|||
|
||||
// GetConfigString returns the current config as a printable string
|
||||
func (g *Gobuster) GetConfigString() (string, error) {
|
||||
buf := &bytes.Buffer{}
|
||||
o := g.Opts
|
||||
if _, err := fmt.Fprintf(buf, "[+] Mode : %s\n", o.Mode); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if _, err := fmt.Fprintf(buf, "[+] Url/Domain : %s\n", o.URL); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if _, err := fmt.Fprintf(buf, "[+] Threads : %d\n", o.Threads); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
wordlist := "stdin (pipe)"
|
||||
if o.Wordlist != "-" {
|
||||
wordlist = o.Wordlist
|
||||
}
|
||||
if _, err := fmt.Fprintf(buf, "[+] Wordlist : %s\n", wordlist); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if o.Mode == ModeDir {
|
||||
if _, err := fmt.Fprintf(buf, "[+] Status codes : %s\n", o.StatusCodesParsed.Stringify()); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if o.Proxy != "" {
|
||||
if _, err := fmt.Fprintf(buf, "[+] Proxy : %s\n", o.Proxy); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
if o.Cookies != "" {
|
||||
if _, err := fmt.Fprintf(buf, "[+] Cookies : %s\n", o.Cookies); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
if o.UserAgent != "" {
|
||||
if _, err := fmt.Fprintf(buf, "[+] User Agent : %s\n", o.UserAgent); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
if o.IncludeLength {
|
||||
if _, err := fmt.Fprintf(buf, "[+] Show length : true\n"); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
if o.Username != "" {
|
||||
if _, err := fmt.Fprintf(buf, "[+] Auth User : %s\n", o.Username); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
if len(o.Extensions) > 0 {
|
||||
if _, err := fmt.Fprintf(buf, "[+] Extensions : %s\n", o.ExtensionsParsed.Stringify()); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
if o.UseSlash {
|
||||
if _, err := fmt.Fprintf(buf, "[+] Add Slash : true\n"); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
if o.FollowRedirect {
|
||||
if _, err := fmt.Fprintf(buf, "[+] Follow Redir : true\n"); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
if o.Expanded {
|
||||
if _, err := fmt.Fprintf(buf, "[+] Expanded : true\n"); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
if o.NoStatus {
|
||||
if _, err := fmt.Fprintf(buf, "[+] No status : true\n"); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
if o.Verbose {
|
||||
if _, err := fmt.Fprintf(buf, "[+] Verbose : true\n"); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := fmt.Fprintf(buf, "[+] Timeout : %s\n", o.Timeout.String()); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
return strings.TrimSpace(buf.String()), nil
|
||||
return g.plugin.GetConfigString()
|
||||
}
|
||||
|
|
|
@ -1,170 +1,18 @@
|
|||
package libgobuster
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
multierror "github.com/hashicorp/go-multierror"
|
||||
)
|
||||
|
||||
const (
|
||||
// ModeDir represents -m dir
|
||||
ModeDir = "dir"
|
||||
// ModeDNS represents -m dns
|
||||
ModeDNS = "dns"
|
||||
)
|
||||
|
||||
// Options helds all options that can be passed to libgobuster
|
||||
type Options struct {
|
||||
Extensions string
|
||||
ExtensionsParsed stringSet
|
||||
Mode string
|
||||
Password string
|
||||
StatusCodes string
|
||||
StatusCodesParsed intSet
|
||||
Threads int
|
||||
URL string
|
||||
UserAgent string
|
||||
Username string
|
||||
Wordlist string
|
||||
Proxy string
|
||||
Cookies string
|
||||
Timeout time.Duration
|
||||
FollowRedirect bool
|
||||
IncludeLength bool
|
||||
OutputFilename string
|
||||
NoStatus bool
|
||||
NoProgress bool
|
||||
Expanded bool
|
||||
Quiet bool
|
||||
ShowIPs bool
|
||||
ShowCNAME bool
|
||||
InsecureSSL bool
|
||||
WildcardForced bool
|
||||
Verbose bool
|
||||
UseSlash bool
|
||||
}
|
||||
|
||||
// NewOptions returns a new initialized Options object
|
||||
func NewOptions() *Options {
|
||||
return &Options{
|
||||
StatusCodesParsed: newIntSet(),
|
||||
ExtensionsParsed: newStringSet(),
|
||||
}
|
||||
}
|
||||
|
||||
// Validate validates the given options
|
||||
func (opt *Options) validate() *multierror.Error {
|
||||
var errorList *multierror.Error
|
||||
|
||||
if strings.ToLower(opt.Mode) != ModeDir && strings.ToLower(opt.Mode) != ModeDNS {
|
||||
errorList = multierror.Append(errorList, fmt.Errorf("Mode (-m): Invalid value: %s", opt.Mode))
|
||||
}
|
||||
|
||||
if opt.Threads < 0 {
|
||||
errorList = multierror.Append(errorList, fmt.Errorf("Threads (-t): Invalid value: %d", opt.Threads))
|
||||
}
|
||||
|
||||
if opt.Wordlist == "" {
|
||||
errorList = multierror.Append(errorList, fmt.Errorf("WordList (-w): Must be specified (use `-w -` for stdin)"))
|
||||
} else if opt.Wordlist == "-" {
|
||||
// STDIN
|
||||
} else if _, err := os.Stat(opt.Wordlist); os.IsNotExist(err) {
|
||||
errorList = multierror.Append(errorList, fmt.Errorf("Wordlist (-w): File does not exist: %s", opt.Wordlist))
|
||||
}
|
||||
|
||||
if opt.URL == "" {
|
||||
errorList = multierror.Append(errorList, fmt.Errorf("Url/Domain (-u): Must be specified"))
|
||||
}
|
||||
|
||||
if opt.StatusCodes != "" {
|
||||
if err := opt.parseStatusCodes(); err != nil {
|
||||
errorList = multierror.Append(errorList, err)
|
||||
}
|
||||
}
|
||||
|
||||
if opt.Extensions != "" {
|
||||
if err := opt.parseExtensions(); err != nil {
|
||||
errorList = multierror.Append(errorList, err)
|
||||
}
|
||||
}
|
||||
|
||||
if opt.Mode == ModeDir {
|
||||
if !strings.HasSuffix(opt.URL, "/") {
|
||||
opt.URL = fmt.Sprintf("%s/", opt.URL)
|
||||
}
|
||||
|
||||
if err := opt.validateDirMode(); err != nil {
|
||||
errorList = multierror.Append(errorList, err)
|
||||
}
|
||||
}
|
||||
|
||||
return errorList
|
||||
}
|
||||
|
||||
// ParseExtensions parses the extensions provided as a comma seperated list
|
||||
func (opt *Options) parseExtensions() error {
|
||||
if opt.Extensions == "" {
|
||||
return fmt.Errorf("invalid extension string provided")
|
||||
}
|
||||
|
||||
exts := strings.Split(opt.Extensions, ",")
|
||||
for _, e := range exts {
|
||||
e = strings.TrimSpace(e)
|
||||
// remove leading . from extensions
|
||||
opt.ExtensionsParsed.Add(strings.TrimPrefix(e, "."))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ParseStatusCodes parses the status codes provided as a comma seperated list
|
||||
func (opt *Options) parseStatusCodes() error {
|
||||
if opt.StatusCodes == "" {
|
||||
return fmt.Errorf("invalid status code string provided")
|
||||
}
|
||||
|
||||
for _, c := range strings.Split(opt.StatusCodes, ",") {
|
||||
c = strings.TrimSpace(c)
|
||||
i, err := strconv.Atoi(c)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid status code given: %s", c)
|
||||
}
|
||||
opt.StatusCodesParsed.Add(i)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (opt *Options) validateDirMode() error {
|
||||
// bail out if we are not in dir mode
|
||||
if opt.Mode != ModeDir {
|
||||
return nil
|
||||
}
|
||||
if !strings.HasPrefix(opt.URL, "http") {
|
||||
// check to see if a port was specified
|
||||
re := regexp.MustCompile(`^[^/]+:(\d+)`)
|
||||
match := re.FindStringSubmatch(opt.URL)
|
||||
|
||||
if len(match) < 2 {
|
||||
// no port, default to http on 80
|
||||
opt.URL = fmt.Sprintf("http://%s", opt.URL)
|
||||
} else {
|
||||
port, err := strconv.Atoi(match[1])
|
||||
if err != nil || (port != 80 && port != 443) {
|
||||
return fmt.Errorf("url scheme not specified")
|
||||
} else if port == 80 {
|
||||
opt.URL = fmt.Sprintf("http://%s", opt.URL)
|
||||
} else {
|
||||
opt.URL = fmt.Sprintf("https://%s", opt.URL)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if opt.Username != "" && opt.Password == "" {
|
||||
return fmt.Errorf("username was provided but password is missing")
|
||||
}
|
||||
|
||||
return nil
|
||||
return &Options{}
|
||||
}
|
||||
|
|
|
@ -1,84 +0,0 @@
|
|||
package libgobuster
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewOptions(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
o := NewOptions()
|
||||
if o.StatusCodesParsed.Set == nil {
|
||||
t.Fatal("StatusCodesParsed not initialized")
|
||||
}
|
||||
|
||||
if o.ExtensionsParsed.Set == nil {
|
||||
t.Fatal("ExtensionsParsed not initialized")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseExtensions(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var tt = []struct {
|
||||
testName string
|
||||
Extensions string
|
||||
expectedExtensions stringSet
|
||||
expectedError string
|
||||
}{
|
||||
{"Valid extensions", "php,asp,txt", stringSet{Set: map[string]bool{"php": true, "asp": true, "txt": true}}, ""},
|
||||
{"Spaces", "php, asp , txt", stringSet{Set: map[string]bool{"php": true, "asp": true, "txt": true}}, ""},
|
||||
{"Double extensions", "php,asp,txt,php,asp,txt", stringSet{Set: map[string]bool{"php": true, "asp": true, "txt": true}}, ""},
|
||||
{"Leading dot", ".php,asp,.txt", stringSet{Set: map[string]bool{"php": true, "asp": true, "txt": true}}, ""},
|
||||
{"Empty string", "", newStringSet(), "invalid extension string provided"},
|
||||
}
|
||||
|
||||
for _, x := range tt {
|
||||
t.Run(x.testName, func(t *testing.T) {
|
||||
o := NewOptions()
|
||||
o.Extensions = x.Extensions
|
||||
err := o.parseExtensions()
|
||||
if x.expectedError != "" {
|
||||
if err.Error() != x.expectedError {
|
||||
t.Fatalf("Expected error %q but got %q", x.expectedError, err.Error())
|
||||
}
|
||||
} else if !reflect.DeepEqual(x.expectedExtensions, o.ExtensionsParsed) {
|
||||
t.Fatalf("Expected %v but got %v", x.expectedExtensions, o.ExtensionsParsed)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseStatusCodes(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var tt = []struct {
|
||||
testName string
|
||||
stringCodes string
|
||||
expectedCodes intSet
|
||||
expectedError string
|
||||
}{
|
||||
{"Valid codes", "200,100,202", intSet{Set: map[int]bool{100: true, 200: true, 202: true}}, ""},
|
||||
{"Spaces", "200, 100 , 202", intSet{Set: map[int]bool{100: true, 200: true, 202: true}}, ""},
|
||||
{"Double codes", "200, 100, 202, 100", intSet{Set: map[int]bool{100: true, 200: true, 202: true}}, ""},
|
||||
{"Invalid code", "200,AAA", newIntSet(), "invalid status code given: AAA"},
|
||||
{"Invalid integer", "2000000000000000000000000000000", newIntSet(), "invalid status code given: 2000000000000000000000000000000"},
|
||||
{"Empty string", "", newIntSet(), "invalid status code string provided"},
|
||||
}
|
||||
|
||||
for _, x := range tt {
|
||||
t.Run(x.testName, func(t *testing.T) {
|
||||
o := NewOptions()
|
||||
o.StatusCodes = x.stringCodes
|
||||
err := o.parseStatusCodes()
|
||||
if x.expectedError != "" {
|
||||
if err.Error() != x.expectedError {
|
||||
t.Fatalf("Expected error %q but got %q", x.expectedError, err.Error())
|
||||
}
|
||||
} else if !reflect.DeepEqual(x.expectedCodes, o.StatusCodesParsed) {
|
||||
t.Fatalf("Expected %v but got %v", x.expectedCodes, o.StatusCodesParsed)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -10,7 +10,7 @@ type Result struct {
|
|||
|
||||
// ToString converts the Result to it's textual representation
|
||||
func (r *Result) ToString(g *Gobuster) (string, error) {
|
||||
s, err := g.plugin.ResultToString(g, r)
|
||||
s, err := g.plugin.ResultToString(r)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
package libgobuster
|
||||
|
||||
const (
|
||||
// VERSION contains the current gobuster version
|
||||
VERSION = "2.0.1"
|
||||
)
|
195
main.go
195
main.go
|
@ -17,200 +17,9 @@ package main
|
|||
//----------------------------------------------------
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/OJ/gobuster/gobusterdir"
|
||||
"github.com/OJ/gobuster/gobusterdns"
|
||||
"github.com/OJ/gobuster/libgobuster"
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
"github.com/OJ/gobuster/cli/cmd"
|
||||
)
|
||||
|
||||
func ruler() {
|
||||
fmt.Println("=====================================================")
|
||||
}
|
||||
|
||||
func banner() {
|
||||
fmt.Printf("Gobuster v%s OJ Reeves (@TheColonial)\n", libgobuster.VERSION)
|
||||
}
|
||||
|
||||
func resultWorker(g *libgobuster.Gobuster, filename string, wg *sync.WaitGroup) {
|
||||
defer wg.Done()
|
||||
var f *os.File
|
||||
var err error
|
||||
if filename != "" {
|
||||
f, err = os.Create(filename)
|
||||
if err != nil {
|
||||
log.Fatalf("error on creating output file: %v", err)
|
||||
}
|
||||
}
|
||||
for r := range g.Results() {
|
||||
s, err := r.ToString(g)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if s != "" {
|
||||
g.ClearProgress()
|
||||
s = strings.TrimSpace(s)
|
||||
fmt.Println(s)
|
||||
if f != nil {
|
||||
err = writeToFile(f, s)
|
||||
if err != nil {
|
||||
log.Fatalf("error on writing output file: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func errorWorker(g *libgobuster.Gobuster, wg *sync.WaitGroup) {
|
||||
defer wg.Done()
|
||||
for e := range g.Errors() {
|
||||
if !g.Opts.Quiet {
|
||||
g.ClearProgress()
|
||||
log.Printf("[!] %v", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func progressWorker(c context.Context, g *libgobuster.Gobuster) {
|
||||
tick := time.NewTicker(1 * time.Second)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-tick.C:
|
||||
g.PrintProgress()
|
||||
case <-c.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func writeToFile(f *os.File, output string) error {
|
||||
_, err := f.WriteString(fmt.Sprintf("%s\n", output))
|
||||
if err != nil {
|
||||
return fmt.Errorf("[!] Unable to write to file %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
var outputFilename string
|
||||
o := libgobuster.NewOptions()
|
||||
flag.IntVar(&o.Threads, "t", 10, "Number of concurrent threads")
|
||||
flag.StringVar(&o.Mode, "m", "dir", "Directory/File mode (dir) or DNS mode (dns)")
|
||||
flag.StringVar(&o.Wordlist, "w", "", "Path to the wordlist")
|
||||
flag.StringVar(&o.StatusCodes, "s", "200,204,301,302,307,403", "Positive status codes (dir mode only)")
|
||||
flag.StringVar(&outputFilename, "o", "", "Output file to write results to (defaults to stdout)")
|
||||
flag.StringVar(&o.URL, "u", "", "The target URL or Domain")
|
||||
flag.StringVar(&o.Cookies, "c", "", "Cookies to use for the requests (dir mode only)")
|
||||
flag.StringVar(&o.Username, "U", "", "Username for Basic Auth (dir mode only)")
|
||||
flag.StringVar(&o.Password, "P", "", "Password for Basic Auth (dir mode only)")
|
||||
flag.StringVar(&o.Extensions, "x", "", "File extension(s) to search for (dir mode only)")
|
||||
flag.StringVar(&o.UserAgent, "a", "", "Set the User-Agent string (dir mode only)")
|
||||
flag.StringVar(&o.Proxy, "p", "", "Proxy to use for requests [http(s)://host:port] (dir mode only)")
|
||||
flag.DurationVar(&o.Timeout, "to", 10*time.Second, "HTTP Timeout in seconds (dir mode only)")
|
||||
flag.BoolVar(&o.Verbose, "v", false, "Verbose output (errors)")
|
||||
flag.BoolVar(&o.ShowIPs, "i", false, "Show IP addresses (dns mode only)")
|
||||
flag.BoolVar(&o.ShowCNAME, "cn", false, "Show CNAME records (dns mode only, cannot be used with '-i' option)")
|
||||
flag.BoolVar(&o.FollowRedirect, "r", false, "Follow redirects")
|
||||
flag.BoolVar(&o.Quiet, "q", false, "Don't print the banner and other noise")
|
||||
flag.BoolVar(&o.Expanded, "e", false, "Expanded mode, print full URLs")
|
||||
flag.BoolVar(&o.NoStatus, "n", false, "Don't print status codes")
|
||||
flag.BoolVar(&o.IncludeLength, "l", false, "Include the length of the body in the output (dir mode only)")
|
||||
flag.BoolVar(&o.UseSlash, "f", false, "Append a forward-slash to each directory request (dir mode only)")
|
||||
flag.BoolVar(&o.WildcardForced, "fw", false, "Force continued operation when wildcard found")
|
||||
flag.BoolVar(&o.InsecureSSL, "k", false, "Skip SSL certificate verification")
|
||||
flag.BoolVar(&o.NoProgress, "np", false, "Don't display progress")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
// Prompt for PW if not provided
|
||||
if o.Username != "" && o.Password == "" {
|
||||
fmt.Printf("[?] Auth Password: ")
|
||||
passBytes, err := terminal.ReadPassword(int(syscall.Stdin))
|
||||
// print a newline to simulate the newline that was entered
|
||||
// this means that formatting/printing after doesn't look bad.
|
||||
fmt.Println("")
|
||||
if err != nil {
|
||||
log.Fatal("[!] Auth username given but reading of password failed")
|
||||
}
|
||||
o.Password = string(passBytes)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
var plugin libgobuster.GobusterPlugin
|
||||
switch o.Mode {
|
||||
case libgobuster.ModeDir:
|
||||
plugin = gobusterdir.GobusterDir{}
|
||||
case libgobuster.ModeDNS:
|
||||
plugin = gobusterdns.GobusterDNS{}
|
||||
}
|
||||
|
||||
gobuster, err := libgobuster.NewGobuster(ctx, o, plugin)
|
||||
if err != nil {
|
||||
log.Fatalf("[!] %v", err)
|
||||
}
|
||||
|
||||
if !o.Quiet {
|
||||
fmt.Println("")
|
||||
ruler()
|
||||
banner()
|
||||
ruler()
|
||||
c, err := gobuster.GetConfigString()
|
||||
if err != nil {
|
||||
log.Fatalf("error on creating config string: %v", err)
|
||||
}
|
||||
fmt.Println(c)
|
||||
ruler()
|
||||
log.Println("Starting gobuster")
|
||||
ruler()
|
||||
}
|
||||
|
||||
signalChan := make(chan os.Signal, 1)
|
||||
signal.Notify(signalChan, os.Interrupt)
|
||||
go func() {
|
||||
for range signalChan {
|
||||
// caught CTRL+C
|
||||
if !gobuster.Opts.Quiet {
|
||||
fmt.Println("\n[!] Keyboard interrupt detected, terminating.")
|
||||
}
|
||||
cancel()
|
||||
}
|
||||
}()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
go errorWorker(gobuster, &wg)
|
||||
go resultWorker(gobuster, outputFilename, &wg)
|
||||
|
||||
if !o.Quiet && !o.NoProgress {
|
||||
go progressWorker(ctx, gobuster)
|
||||
}
|
||||
|
||||
if err := gobuster.Start(); err != nil {
|
||||
log.Printf("[!] %v", err)
|
||||
} else {
|
||||
// call cancel func to free ressources and stop progressFunc
|
||||
cancel()
|
||||
// wait for all output funcs to finish
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
if !o.Quiet {
|
||||
gobuster.ClearProgress()
|
||||
ruler()
|
||||
log.Println("Finished")
|
||||
ruler()
|
||||
}
|
||||
cmd.Execute()
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue