Skip to content

Commit 7d4dce4

Browse files
committed
Added recursion. For details see the commit message for this release.
1 parent 50ed362 commit 7d4dce4

File tree

1 file changed

+41
-8
lines changed

1 file changed

+41
-8
lines changed

pkg/shortscan/shortscan.go

Lines changed: 41 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -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"
112113
const rainbowMagic = "#SHORTSCAN#"
113114
const alphanum = "JFKGOTMYVHSPCANDXLRWEBQUIZ8549176320"
114115

@@ -137,15 +138,15 @@ var checksumRegex *regexp.Regexp
137138

138139
// Command-line arguments and help
139140
type 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

Comments
 (0)