Skip to content

Commit c1cf7b5

Browse files
committed
Merge upstream PR bitquark#23: Implemented rate limit for shortscan cmd
2 parents 6ef947b + 21eb087 commit c1cf7b5

File tree

1 file changed

+30
-0
lines changed

1 file changed

+30
-0
lines changed

pkg/shortscan/shortscan.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,9 @@ var statusCache map[string]map[int]struct{}
136136
var distanceCache map[string]map[int]distances
137137
var checksumRegex *regexp.Regexp
138138

139+
// Delay between requests set by the rate limit.
140+
var requestDelay time.Duration
141+
139142
// Command-line arguments and help
140143
type arguments struct {
141144
Urls []string `arg:"positional,required" help:"url to scan (multiple URLs can be provided; a file containing URLs can be specified with an «at» prefix, for example: @urls.txt)" placeholder:"URL"`
@@ -148,6 +151,7 @@ type arguments struct {
148151
FullUrl bool `arg:"-F" help:"display the full URL for confirmed files rather than just the filename" default:"false"`
149152
NoRecurse bool `arg:"-n" help:"don't detect and recurse into subdirectories (disabled when autocomplete is disabled)" default:"false"`
150153
Stabilise bool `arg:"-s" help:"attempt to get coherent autocomplete results from an unstable server (generates more requests)" default:"false"`
154+
Rate float32 `arg:"-r" help:"maximum requests per second, supports floating point values" default:"0.0"`
151155
Patience int `arg:"-p" help:"patience level when determining vulnerability (0 = patient; 1 = very patient)" placeholder:"LEVEL" default:"0"`
152156
Characters string `arg:"-C" help:"filename characters to enumerate" default:"JFKGOTMYVHSPCANDXLRWEBQUIZ8549176320-_()&'!#$%@^{}~"`
153157
Autocomplete string `arg:"-a" help:"autocomplete detection mode (auto = autoselect; method = HTTP method magic; status = HTTP status; distance = Levenshtein distance; none = disable)" placeholder:"mode" default:"auto"`
@@ -170,8 +174,25 @@ func pathEscape(url string) string {
170174
return strings.Replace(nurl.QueryEscape(url), "+", "%20", -1)
171175
}
172176

177+
// Helper variable to the requestDelay function, keeps track of the time since the last request
178+
var lastRequestTime time.Time
179+
180+
// delayRequest delays a request if it were to go over the rate limit
181+
func delayRequest() {
182+
// Sleep to prevent going over the rate limit
183+
sleepDuration := time.Until(lastRequestTime.Add(requestDelay))
184+
time.Sleep(sleepDuration)
185+
186+
log.WithFields(log.Fields{"lastRequestTime": lastRequestTime, "requestDelay": requestDelay}).Info("Rate limit metadata")
187+
188+
// Update last request time
189+
lastRequestTime = time.Now()
190+
}
191+
173192
// fetch requests the given URL and returns an HTTP response object, handling retries gracefully
174193
func fetch(hc *http.Client, st *httpStats, method string, url string) (*http.Response, error) {
194+
// Sleep until we are within rate limit constraints
195+
delayRequest()
175196

176197
// Create a request object
177198
req, err := http.NewRequest(method, url, nil)
@@ -1048,6 +1069,15 @@ func Run() {
10481069
p.Fail("output must be one of: human, json")
10491070
}
10501071

1072+
// If the rate limit is a negative treat it as no rate limit
1073+
delay := args.Rate
1074+
if args.Rate != 0 {
1075+
delay = 1_000_000.0 / args.Rate
1076+
}
1077+
1078+
// Initialize the calculated delay between requests, according to rate limit
1079+
requestDelay = time.Duration(delay) * time.Microsecond
1080+
10511081
// Build the list of URLs to scan
10521082
var urls []string
10531083
for _, url := range args.Urls {

0 commit comments

Comments
 (0)