Skip to content

Commit

Permalink
Timeout option added. Verb Tampering Case-switching included.
Browse files Browse the repository at this point in the history
  • Loading branch information
devploit committed Mar 19, 2024
1 parent 5b69d5d commit 4275e68
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 41 deletions.
8 changes: 4 additions & 4 deletions cmd/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ type header struct {
// request makes an HTTP request using headers `headers` and proxy `proxy`.
//
// If `method` is empty, it defaults to "GET".
func request(method, uri string, headers []header, proxy *url.URL, rateLimit bool, redirect bool) (int, []byte, error) {
func request(method, uri string, headers []header, proxy *url.URL, rateLimit bool, timeout int, redirect bool) (int, []byte, error) {
if method == "" {
method = "GET"
}
Expand All @@ -65,7 +65,7 @@ func request(method, uri string, headers []header, proxy *url.URL, rateLimit boo
InsecureSkipVerify: true,
},
DialContext: (&net.Dialer{
Timeout: 6 * time.Second,
Timeout: time.Duration(timeout) / 1000 * time.Second,
KeepAlive: 30 * time.Second,
}).DialContext,
MaxIdleConns: 100,
Expand All @@ -83,7 +83,7 @@ func request(method, uri string, headers []header, proxy *url.URL, rateLimit boo

req, err := http.NewRequest(method, uri, nil)
if err != nil {
return 0, nil, err
return 0, nil, nil
}
req.Close = true

Expand Down Expand Up @@ -146,5 +146,5 @@ func loadFlagsFromRequestFile(requestFile string, schema bool, verbose bool, red
}

// Assign the extracted values to the corresponding flag variables
requester(uri, proxy, userAgent, reqHeaders, bypassIP, folder, httpMethod, verbose, nobanner, rateLimit, redirect, randomAgent)
requester(uri, proxy, userAgent, reqHeaders, bypassIP, folder, httpMethod, verbose, nobanner, rateLimit, timeout, redirect, randomAgent)
}
149 changes: 114 additions & 35 deletions cmd/requester.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ type RequestOptions struct {
redirect bool
folder string
bypassIP string
timeout int
rateLimit bool
verbose bool
reqHeaders []string
Expand Down Expand Up @@ -107,16 +108,63 @@ func showInfo(options RequestOptions) {
}
fmt.Printf("%s \t%s\n", "Follow Redirects:", strconv.FormatBool(options.redirect))
fmt.Printf("%s \t%s\n", "Rate Limit detection:", strconv.FormatBool(options.rateLimit))
fmt.Printf("%s \t\t%d\n", "Timeout (ms):", options.timeout)
fmt.Printf("%s \t\t%d\n", "Delay (ms):", delay)
fmt.Printf("%s \t\t%t\n", "Verbose:", options.verbose)
}

// generateCaseCombinations generates all combinations of uppercase and lowercase letters for a given string.
func generateCaseCombinations(s string) []string {
if len(s) == 0 {
return []string{""}
}

firstCharCombinations := []string{string(unicode.ToLower(rune(s[0]))), string(unicode.ToUpper(rune(s[0])))}
subCombinations := generateCaseCombinations(s[1:])
combinations := []string{}

for _, char := range firstCharCombinations {
for _, comb := range subCombinations {
combinations = append(combinations, char+comb)
}
}

return combinations
}

// filterOriginalMethod extract the original method from the list of combinations
func filterOriginalMethod(originalMethod string, combinations []string) []string {
filtered := make([]string, 0, len(combinations))
for _, combination := range combinations {
if combination != originalMethod {
filtered = append(filtered, combination)
}
}
return filtered
}

// selectRandomCombinations selects up to n random combinations from a list of combinations.
func selectRandomCombinations(combinations []string, n int) []string {
rand.Seed(time.Now().UnixNano())

if len(combinations) <= n {
return combinations
}

rand.Shuffle(len(combinations), func(i, j int) {
combinations[i], combinations[j] = combinations[j], combinations[i]
})

return combinations[:n]
}

// requestDefault makes HTTP request to check the default response
func requestDefault(options RequestOptions) {
color.Cyan("\n━━━━━━━━━━━━━ DEFAULT REQUEST ━━━━━━━━━━━━━")
color.Cyan("\n━━━━━━━━━━━━━━━ DEFAULT REQUEST ━━━━━━━━━━━━━")

var results []Result

statusCode, response, err := request(options.method, options.uri, options.headers, options.proxy, options.rateLimit, options.redirect)
statusCode, response, err := request(options.method, options.uri, options.headers, options.proxy, options.rateLimit, options.timeout, options.redirect)
if err != nil {
log.Println(err)
}
Expand All @@ -131,7 +179,7 @@ func requestDefault(options RequestOptions) {

// requestMethods makes HTTP requests using a list of methods from a file and prints the results.
func requestMethods(options RequestOptions) {
color.Cyan("\n━━━━━━━━━━━━━ VERB TAMPERING ━━━━━━━━━━━━━━")
color.Cyan("\n━━━━━━━━━━━━━━━ VERB TAMPERING ━━━━━━━━━━━━━━")

var lines []string
lines, err := parseFile(folder + "/httpmethods")
Expand All @@ -145,7 +193,7 @@ func requestMethods(options RequestOptions) {
time.Sleep(time.Duration(delay) * time.Millisecond)
w.Wait()
go func(line string) {
statusCode, response, err := request(line, options.uri, options.headers, options.proxy, options.rateLimit, options.redirect)
statusCode, response, err := request(line, options.uri, options.headers, options.proxy, options.rateLimit, options.timeout, options.redirect)
if err != nil {
log.Println(err)
}
Expand All @@ -157,9 +205,41 @@ func requestMethods(options RequestOptions) {
w.WaitAllDone()
}

// requestMethodsCaseSwitching makes HTTP requests using a list of methods from a file and prints the results.
func requestMethodsCaseSwitching(options RequestOptions) {
color.Cyan("\n━━━━━━━ VERB TAMPERING CASE SWITCHING ━━━━━━━━")

var lines []string
lines, err := parseFile(folder + "/httpmethods")
if err != nil {
log.Fatalf("Error reading /httpmethods file: %v", err)
}

w := goccm.New(maxGoroutines)

for _, line := range lines {
methodCombinations := generateCaseCombinations(line)
filteredCombinations := filterOriginalMethod(line, methodCombinations)
selectedCombinations := selectRandomCombinations(filteredCombinations, 50)
for _, method := range selectedCombinations {
time.Sleep(time.Duration(delay) * time.Millisecond)
w.Wait()
go func(method string) {
statusCode, response, err := request(method, options.uri, options.headers, options.proxy, options.rateLimit, options.timeout, options.redirect)
if err != nil {
log.Println(err)
}
printResponse(Result{method, statusCode, len(response), false})
w.Done()
}(method)
}
}
w.WaitAllDone()
}

// requestHeaders makes HTTP requests using a list of headers from a file and prints the results. It can also bypass IP address restrictions by specifying a bypass IP address.
func requestHeaders(options RequestOptions) {
color.Cyan("\n━━━━━━━━━━━━━ HEADERS ━━━━━━━━━━━━━━━━━━━━━")
color.Cyan("\n━━━━━━━━━━━━━━━━━━ HEADERS ━━━━━━━━━━━━━━━━━━━")

var lines []string
lines, err := parseFile(folder + "/headers")
Expand Down Expand Up @@ -191,7 +271,7 @@ func requestHeaders(options RequestOptions) {
go func(line, ip string) {
headers := append(options.headers, header{line, ip})

statusCode, response, err := request(options.method, options.uri, headers, options.proxy, options.rateLimit, redirect)
statusCode, response, err := request(options.method, options.uri, headers, options.proxy, options.rateLimit, options.timeout, options.redirect)

if err != nil {
log.Println(err)
Expand All @@ -210,7 +290,7 @@ func requestHeaders(options RequestOptions) {
x := strings.Split(line, " ")
headers := append(options.headers, header{x[0], x[1]})

statusCode, response, err := request(options.method, options.uri, headers, options.proxy, rateLimit, redirect)
statusCode, response, err := request(options.method, options.uri, headers, options.proxy, rateLimit, options.timeout, redirect)
if err != nil {
log.Println(err)
}
Expand All @@ -224,7 +304,7 @@ func requestHeaders(options RequestOptions) {

// requestEndPaths makes HTTP requests using a list of custom end paths from a file and prints the results.
func requestEndPaths(options RequestOptions) {
color.Cyan("\n━━━━━━━━━━━━━ CUSTOM PATHS ━━━━━━━━━━━━━━━━")
color.Cyan("\n━━━━━━━━━━━━━━━ CUSTOM PATHS ━━━━━━━━━━━━━━━━")

var lines []string
lines, err := parseFile(folder + "/endpaths")
Expand All @@ -238,7 +318,7 @@ func requestEndPaths(options RequestOptions) {
time.Sleep(time.Duration(delay) * time.Millisecond)
w.Wait()
go func(line string) {
statusCode, response, err := request(options.method, options.uri+line, options.headers, options.proxy, options.rateLimit, options.redirect)
statusCode, response, err := request(options.method, options.uri+line, options.headers, options.proxy, options.rateLimit, options.timeout, options.redirect)
if err != nil {
log.Println(err)
}
Expand Down Expand Up @@ -289,7 +369,7 @@ func requestMidPaths(options RequestOptions) {
fullpath = baseuri + "/" + line + uripath
}

statusCode, response, err := request(options.method, fullpath, options.headers, options.proxy, options.rateLimit, options.redirect)
statusCode, response, err := request(options.method, fullpath, options.headers, options.proxy, options.rateLimit, options.timeout, options.redirect)
if err != nil {
log.Println(err)
}
Expand All @@ -304,7 +384,7 @@ func requestMidPaths(options RequestOptions) {

// requestHttpVersions makes HTTP requests using a list of HTTP versions from a file and prints the results. If server responds with an unique version it is because is not accepting the version provided.
func requestHttpVersions(options RequestOptions) {
color.Cyan("\n━━━━━━━━━━━━━ HTTP VERSIONS ━━━━━━━━━━━━━━━")
color.Cyan("\n━━━━━━━━━━━━━━━ HTTP VERSIONS ━━━━━━━━━━━━━━━")

httpVersions := []string{"--http1.0", "--http1.1", "--http2"}

Expand Down Expand Up @@ -361,9 +441,9 @@ func parseCurlOutput(output string, httpVersion string) Result {
return Result{httpVersionOutput, statusCode, len(output), false}
}

// requestCaseSwitching makes HTTP requests by capitalizing each letter in the last part of the URI and try to use URL encoded characters.
func requestCaseSwitching(options RequestOptions) {
color.Cyan("\n━━━━━━━━━━━━CASE SWITCHING ━━━━━━━━━━━━━")
// requestPathCaseSwitching makes HTTP requests by capitalizing each letter in the last part of the URI and try to use URL encoded characters.
func requestPathCaseSwitching(options RequestOptions) {
color.Cyan("\n━━━━━━━━━━━━ PATH CASE SWITCHING ━━━━━━━━━━━━━")

parsedURL, err := url.Parse(options.uri)
if err != nil {
Expand All @@ -375,38 +455,34 @@ func requestCaseSwitching(options RequestOptions) {
uripath := strings.Trim(parsedURL.Path, "/")

if len(uripath) == 0 {
os.Exit(0)
log.Println("No path to modify")
return
}

pathCombinations := generateCaseCombinations(uripath)
selectedPaths := selectRandomCombinations(pathCombinations, 60)

w := goccm.New(maxGoroutines)

for _, z := range uripath {
for _, path := range selectedPaths {
time.Sleep(time.Duration(delay) * time.Millisecond)
w.Wait()
go func(z rune) {
newpath := strings.Map(func(r rune) rune {
if r == z {
return unicode.ToUpper(r)
} else {
return r
}
}, uripath)

go func(path string) {
var fullpath string
if uri[len(uri)-1:] == "/" {
fullpath = baseuri + newpath + "/"
if strings.HasSuffix(options.uri, "/") {
fullpath = baseuri + "/" + path + "/"
} else {
fullpath = baseuri + "/" + newpath
fullpath = baseuri + "/" + path
}

statusCode, response, err := request(options.method, fullpath, options.headers, options.proxy, options.rateLimit, options.redirect)
statusCode, response, err := request(options.method, fullpath, options.headers, options.proxy, options.rateLimit, options.timeout, options.redirect)
if err != nil {
log.Println(err)
}

printResponse(Result{fullpath, statusCode, len(response), false})
w.Done()
}(z)
}(path)
}

for _, z := range uripath {
Expand All @@ -423,7 +499,7 @@ func requestCaseSwitching(options RequestOptions) {
fullpath = baseuri + "/" + newpath
}

statusCode, response, err := request(options.method, fullpath, options.headers, options.proxy, options.rateLimit, options.redirect)
statusCode, response, err := request(options.method, fullpath, options.headers, options.proxy, options.rateLimit, options.timeout, options.redirect)
if err != nil {
log.Println(err)
}
Expand All @@ -432,6 +508,7 @@ func requestCaseSwitching(options RequestOptions) {
w.Done()
}(z)
}

w.WaitAllDone()
}

Expand All @@ -453,16 +530,16 @@ func randomLine(filePath string) (string, error) {
return "", err
}

// Semilla para la generación de números aleatorios basada en la hora actual
// Seed the random number generator
rand.Seed(time.Now().UnixNano())
// Selecciona una línea aleatoria
// Select a random Line
randomLine := lines[rand.Intn(len(lines))]

return randomLine, nil
}

// requester is the main function that runs all the tests.
func requester(uri string, proxy string, userAgent string, reqHeaders []string, bypassIP string, folder string, method string, verbose bool, banner bool, rateLimit bool, redirect bool, randomAgent bool) {
func requester(uri string, proxy string, userAgent string, reqHeaders []string, bypassIP string, folder string, method string, verbose bool, banner bool, rateLimit bool, timeout int, redirect bool, randomAgent bool) {
// Set up proxy if provided.
if len(proxy) != 0 {
if !strings.Contains(proxy, "http") {
Expand Down Expand Up @@ -520,6 +597,7 @@ func requester(uri string, proxy string, userAgent string, reqHeaders []string,
redirect: redirect,
folder: folder,
bypassIP: bypassIP,
timeout: timeout,
rateLimit: rateLimit,
verbose: verbose,
reqHeaders: reqHeaders,
Expand All @@ -530,9 +608,10 @@ func requester(uri string, proxy string, userAgent string, reqHeaders []string,
showInfo(options)
requestDefault(options)
requestMethods(options)
requestMethodsCaseSwitching(options)
requestHeaders(options)
requestEndPaths(options)
requestMidPaths(options)
requestHttpVersions(options)
requestCaseSwitching(options)
requestPathCaseSwitching(options)
}
6 changes: 4 additions & 2 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ var (
proxy string
randomAgent bool
rateLimit bool
timeout int
redirect bool
reqHeaders []string
requestFile string
Expand Down Expand Up @@ -53,7 +54,7 @@ var rootCmd = &cobra.Command{
if uri == lastchar {
break
}
requester(uri, proxy, userAgent, reqHeaders, bypassIP, folder, httpMethod, verbose, nobanner, rateLimit, redirect, randomAgent)
requester(uri, proxy, userAgent, reqHeaders, bypassIP, folder, httpMethod, verbose, nobanner, rateLimit, timeout, redirect, randomAgent)
}
} else {
if len(requestFile) > 0 {
Expand All @@ -66,7 +67,7 @@ var rootCmd = &cobra.Command{
}
log.Fatal()
}
requester(uri, proxy, userAgent, reqHeaders, bypassIP, folder, httpMethod, verbose, nobanner, rateLimit, redirect, randomAgent)
requester(uri, proxy, userAgent, reqHeaders, bypassIP, folder, httpMethod, verbose, nobanner, rateLimit, timeout, redirect, randomAgent)
}
}
},
Expand Down Expand Up @@ -94,6 +95,7 @@ func init() {
rootCmd.PersistentFlags().BoolVarP(&rateLimit, "rate-limit", "l", false, "Halt requests upon encountering a 429 (rate limit) HTTP status code.")
rootCmd.PersistentFlags().BoolVarP(&redirect, "redirect", "r", false, "Automatically follow redirects in responses.")
rootCmd.PersistentFlags().StringVarP(&requestFile, "request-file", "", "", "Load request configuration and flags from a specified file.")
rootCmd.PersistentFlags().IntVarP(&timeout, "timeout", "", 6000, "Specify a max timeout time (default: 6000ms).")
rootCmd.PersistentFlags().StringVarP(&uri, "uri", "u", "", "Specify the target URL for the request.")
rootCmd.PersistentFlags().StringVarP(&userAgent, "user-agent", "a", "", "pecify a custom User-Agent string for requests (default: 'nomore403').")
rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "Enable verbose output for detailed request/response logging.")
Expand Down

0 comments on commit 4275e68

Please sign in to comment.