Skip to content

Commit 654e214

Browse files
author
pezhi
committed
add time out
1 parent f8ae611 commit 654e214

1 file changed

Lines changed: 71 additions & 6 deletions

File tree

main.go

Lines changed: 71 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@ import (
55
"crypto/tls"
66
"encoding/hex"
77
"encoding/json"
8+
"errors"
89
"flag"
910
"fmt"
1011
"io/ioutil"
12+
"net"
1113
"net/http"
1214
"net/http/cookiejar"
1315
"os"
@@ -24,6 +26,13 @@ var formatType string
2426
var outputDir string
2527
var rateLimiter chan struct{}
2628

29+
// Timeout handling: abort the whole scan after too many consecutive timeouts
30+
// (host likely went down), resetting the counter whenever a request succeeds.
31+
var scanTimeout time.Duration = 20 * time.Second
32+
var maxConsecutiveTimeouts int = 3
33+
var consecutiveTimeouts int = 0
34+
var scanAborted bool = false
35+
2736
// baselineSignature describes the 200-response a site returns for a path that
2837
// should NOT exist (soft-404 / SPA catch-all / wildcard routing).
2938
type baselineSignature struct {
@@ -73,9 +82,17 @@ func main() {
7382
Allfile := flag.Bool("all", false, "try all lists")
7483
success := flag.Bool("v", false, "show success result only")
7584
rateLimit := flag.Int("rate", 0, "rate limit: requests per second (0 = no limit)")
85+
timeoutSec := flag.Int("timeout", 20, "per-request timeout in seconds")
86+
maxTimeouts := flag.Int("maxtimeouts", 3, "abort scan after this many consecutive timeouts")
7687
flag.Parse()
7788
formatType = *format
7889
outputDir = *outDir
90+
if *timeoutSec > 0 {
91+
scanTimeout = time.Duration(*timeoutSec) * time.Second
92+
}
93+
if *maxTimeouts > 0 {
94+
maxConsecutiveTimeouts = *maxTimeouts
95+
}
7996
if !*gitfile && !*Sensfile && !*Envfile && !*Shellfile {
8097

8198
*Allfile = true
@@ -162,21 +179,33 @@ func main() {
162179

163180
if *gitfile {
164181
for i := 0; i < len(paths.Git); i++ {
182+
if scanAborted {
183+
break
184+
}
165185
checkurl(*address+paths.Git[i].Path, paths.Git[i].Content, paths.Git[i].Lentgh, "Git")
166186
}
167187
}
168188
if *Sensfile {
169189
for i := 0; i < len(paths.Sensitive); i++ {
190+
if scanAborted {
191+
break
192+
}
170193
checkurl(*address+paths.Sensitive[i].Path, paths.Sensitive[i].Content, paths.Sensitive[i].Lentgh, "Sensitive")
171194
}
172195
}
173196
if *Envfile {
174197
for i := 0; i < len(paths.Env); i++ {
198+
if scanAborted {
199+
break
200+
}
175201
checkurl(*address+paths.Env[i].Path, paths.Env[i].Content, paths.Env[i].Lentgh, "Env")
176202
}
177203
}
178204
if *Shellfile {
179205
for i := 0; i < len(paths.Shell); i++ {
206+
if scanAborted {
207+
break
208+
}
180209
checkurl(*address+paths.Shell[i].Path, paths.Shell[i].Content, paths.Shell[i].Lentgh, "Shell")
181210
}
182211
}
@@ -193,13 +222,16 @@ func main() {
193222
}
194223

195224
func checkurl(url string, content string, len string, category string) {
225+
if scanAborted {
226+
return
227+
}
196228
// Apply rate limiting if enabled
197229
if rateLimiter != nil {
198230
<-rateLimiter // Wait for a token
199231
}
200232

201-
// Set timeout of 20 seconds
202-
httpcc.Timeout = 20 * time.Second
233+
// Set the per-request timeout
234+
httpcc.Timeout = scanTimeout
203235

204236
resp, err := httpcc.Head(url)
205237

@@ -208,15 +240,22 @@ func checkurl(url string, content string, len string, category string) {
208240
if strings.Contains(err.Error(), "http: server gave HTTP response to HTTPS clien") {
209241
os.Exit(3)
210242
}
211-
if strings.Contains(err.Error(), "timeout") {
212-
fmt.Printf("Timeout occurred while checking '%s'\n", url)
243+
if isTimeout(err) {
244+
registerTimeout(url)
213245
return
214246
}
215247

216248
resp, err = httpcc.Get(url)
217-
249+
if err != nil {
250+
if isTimeout(err) {
251+
registerTimeout(url)
252+
}
253+
return
254+
}
218255
}
219256
if err == nil {
257+
// A response came back: the host is alive, so reset the timeout counter.
258+
consecutiveTimeouts = 0
220259
if !justsuccess {
221260
fmt.Printf("Checking '%s', '%s',\n", url, resp.Status)
222261
}
@@ -276,6 +315,32 @@ func checkurl(url string, content string, len string, category string) {
276315
}
277316
}
278317

318+
// isTimeout reports whether an error is a request timeout. It handles both the
319+
// typed net.Error case and the client-timeout ("context deadline exceeded")
320+
// message string, which the plain lowercase "timeout" check used to miss.
321+
func isTimeout(err error) bool {
322+
if err == nil {
323+
return false
324+
}
325+
var netErr net.Error
326+
if errors.As(err, &netErr) && netErr.Timeout() {
327+
return true
328+
}
329+
msg := strings.ToLower(err.Error())
330+
return strings.Contains(msg, "timeout") || strings.Contains(msg, "deadline exceeded")
331+
}
332+
333+
// registerTimeout records a timeout and aborts the scan once too many happen
334+
// consecutively (the counter is reset elsewhere whenever a request succeeds).
335+
func registerTimeout(url string) {
336+
consecutiveTimeouts++
337+
fmt.Printf("⏱️ Timeout while checking '%s' (%d/%d consecutive)\n", url, consecutiveTimeouts, maxConsecutiveTimeouts)
338+
if consecutiveTimeouts >= maxConsecutiveTimeouts {
339+
scanAborted = true
340+
fmt.Printf("🚨 %d consecutive timeouts reached — aborting scan\n", maxConsecutiveTimeouts)
341+
}
342+
}
343+
279344
// fetchBody performs a GET and returns the actual body size (in bytes) and the
280345
// Content-Type header. Returns (-1, "") if the request fails.
281346
func fetchBody(url string) (int64, string) {
@@ -506,7 +571,7 @@ func printResults(results map[string][]string) {
506571
// Add new function for site availability check
507572
func checkSiteIsUp(url string) bool {
508573
client := &http.Client{
509-
Timeout: 20 * time.Second,
574+
Timeout: scanTimeout,
510575
Transport: &http.Transport{
511576
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
512577
},

0 commit comments

Comments
 (0)