@@ -76,6 +76,7 @@ type attackConfig struct {
7676 fileChars map [string ]string
7777 extChars map [string ]string
7878 foundFiles map [string ]struct {}
79+ foundDirectories map [string ]struct {}
7980 wordlist wordlistConfig
8081 distanceMutex sync.Mutex
8182 autocompleteMutex sync.Mutex
@@ -108,7 +109,7 @@ type statsOutput struct {
108109}
109110
110111// Version, rainbow table magic, default character set
111- const version = "0.8 "
112+ const version = "0.9.0 "
112113const rainbowMagic = "#SHORTSCAN#"
113114const alphanum = "JFKGOTMYVHSPCANDXLRWEBQUIZ8549176320"
114115
@@ -137,15 +138,15 @@ var checksumRegex *regexp.Regexp
137138
138139// Command-line arguments and help
139140type arguments struct {
140- Urls []string `arg:"positional,required" help:"url to scan (use multiple times for multiple URLs) "`
141+ Urls []string `arg:"positional,required" help:"url to scan (multiple URLs can be specified)" placeholder:"URL "`
141142 Wordlist string `arg:"-w" help:"combined wordlist + rainbow table generated with shortutil" placeholder:"FILE"`
142143 Headers []string `arg:"--header,-H,separate" help:"header to send with each request (use multiple times for multiple headers)"`
143144 Concurrency int `arg:"-c" help:"number of requests to make at once" default:"20"`
144145 Timeout int `arg:"-t" help:"per-request timeout in seconds" placeholder:"SECONDS" default:"10"`
145146 Output string `arg:"-o" help:"output format (human = human readable; json = JSON)" placeholder:"format" default:"human"`
146147 Verbosity int `arg:"-v" help:"how much noise to make (0 = quiet; 1 = debug; 2 = trace)" default:"0"`
147- Recursive bool `arg:"-r" help:"detect and recurse into subdirectories" placeholder:"type" default:"true"`
148148 FullUrl bool `arg:"-F" help:"display the full URL for confirmed files rather than just the filename" default:"false"`
149+ NoRecurse bool `arg:"-n" help:"don't detect and recurse into subdirectories (disabled when autocomplete is disabled)" default:"false"`
149150 Stabilise bool `arg:"-s" help:"attempt to get coherent autocomplete results from an unstable server (generates more requests)" default:"false"`
150151 Patience int `arg:"-p" help:"patience level when determining vulnerability (0 = patient; 1 = very patient)" placeholder:"LEVEL" default:"0"`
151152 Characters string `arg:"-C" help:"filename characters to enumerate" default:"JFKGOTMYVHSPCANDXLRWEBQUIZ8549176320-_()&'!#$%@^{}~"`
@@ -341,6 +342,7 @@ func enumerate(sem chan struct{}, wg *sync.WaitGroup, hc *http.Client, st *httpS
341342 // Loop through each filename candidate
342343 for _ , c := range fnc {
343344
345+ // Encapsulated to simplify mutex handling
344346 func () {
345347
346348 // Lock the mutex
@@ -415,9 +417,33 @@ func enumerate(sem chan struct{}, wg *sync.WaitGroup, hc *http.Client, st *httpS
415417
416418 }
417419
418- // Add the autocompleted filename to the list
420+ // If a full filename was found
419421 if fnr != "" {
422+
423+ // Add the autocomplete filename to the list
420424 ac .foundFiles [fnr ] = struct {}{}
425+
426+ // If recursion is enabled
427+ if ! args .NoRecurse {
428+
429+ // Make a HEAD request to the autocompleted URL
430+ res , err := fetch (hc , st , "HEAD" , br .url + fnr )
431+ if err != nil {
432+ log .WithFields (log.Fields {"err" : err , "method" : "HEAD" , "url" : br .url + fnr }).Info ("Directory recursion check error" )
433+ } else {
434+
435+ // Check whether this looks like a directory redirect
436+ if l := res .Header .Get ("Location" ); strings .HasSuffix (strings .ToLower (l ), "/" + strings .ToLower (fnr )+ "/" ) {
437+
438+ // Add the directory to the list for later recursion
439+ ac .foundDirectories [fnr ] = struct {}{}
440+
441+ }
442+
443+ }
444+
445+ }
446+
421447 }
422448
423449 }()
@@ -426,6 +452,7 @@ func enumerate(sem chan struct{}, wg *sync.WaitGroup, hc *http.Client, st *httpS
426452 if fnr != "" {
427453 break
428454 }
455+
429456 }
430457
431458 }
@@ -977,6 +1004,7 @@ func Scan(urls []string, hc *http.Client, st *httpStats, wc wordlistConfig, mk m
9771004
9781005 // Initialise things
9791006 ac .foundFiles = make (map [string ]struct {})
1007+ ac .foundDirectories = make (map [string ]struct {})
9801008 sem := make (chan struct {}, args .Concurrency )
9811009 wg := new (sync.WaitGroup )
9821010
@@ -985,6 +1013,13 @@ func Scan(urls []string, hc *http.Client, st *httpStats, wc wordlistConfig, mk m
9851013 enumerate (sem , wg , hc , st , & ac , mk , baseRequest {url : url , file : "" , tilde : tilde , ext : "" })
9861014 }
9871015 wg .Wait ()
1016+
1017+ // Prepend discovered directories for processing next iteration
1018+ for dir := range ac .foundDirectories {
1019+ urls = append ([]string {url + dir + "/" }, urls ... )
1020+ }
1021+
1022+ // <hr>
9881023 printHuman ("════════════════════════════════════════════════════════════════════════════════" )
9891024
9901025 }
@@ -1002,15 +1037,13 @@ func Run() {
10021037 // First things first
10031038 rand .Seed (time .Now ().UTC ().UnixNano ())
10041039
1005- // Process command-line arguments and say hello
1040+ // Parse and validate command-line arguments
10061041 p := arg .MustParse (& args )
10071042 args .Autocomplete = strings .ToLower (args .Autocomplete )
1008- args .Output = strings .ToLower (args .Output )
1009-
1010- // Validate enum argument values
10111043 if args .Autocomplete != "auto" && args .Autocomplete != "method" && args .Autocomplete != "status" && args .Autocomplete != "distance" && args .Autocomplete != "none" {
10121044 p .Fail ("autocomplete must be one of: auto, status, method, none" )
10131045 }
1046+ args .Output = strings .ToLower (args .Output )
10141047 if args .Output != "human" && args .Output != "json" {
10151048 p .Fail ("output must be one of: human, json" )
10161049 }
0 commit comments