mirror of
https://github.com/projectdiscovery/nuclei.git
synced 2025-12-17 23:35:27 +00:00
* feat: fixed max-host-error blocking wrong port for template with error * feat: log total results with time taken at end of execution * bugfix: skip non-executed requests with progress in flow protocol * feat: fixed request calculation in http protocol for progress * misc adjustments --------- Co-authored-by: Ice3man <nizamulrana@gmail.com>
202 lines
5.8 KiB
Go
202 lines
5.8 KiB
Go
package http
|
|
|
|
import (
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/protocols"
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators"
|
|
)
|
|
|
|
// requestGenerator generates requests sequentially based on various
|
|
// configurations for a http request template.
|
|
//
|
|
// If payload values are present, an iterator is created for the payload
|
|
// values. Paths and Raw requests are supported as base input, so
|
|
// it will automatically select between them based on the template.
|
|
type requestGenerator struct {
|
|
currentIndex int
|
|
currentPayloads map[string]interface{}
|
|
okCurrentPayload bool
|
|
request *Request
|
|
options *protocols.ExecutorOptions
|
|
payloadIterator *generators.Iterator
|
|
interactshURLs []string
|
|
onceFlow map[string]struct{}
|
|
}
|
|
|
|
// LeaveDefaultPorts skips normalization of default standard ports
|
|
var LeaveDefaultPorts = false
|
|
|
|
// newGenerator creates a new request generator instance
|
|
func (request *Request) newGenerator(disablePayloads bool) *requestGenerator {
|
|
generator := &requestGenerator{
|
|
request: request,
|
|
options: request.options,
|
|
onceFlow: make(map[string]struct{}),
|
|
}
|
|
|
|
if len(request.Payloads) > 0 && !disablePayloads {
|
|
generator.payloadIterator = request.generator.NewIterator()
|
|
}
|
|
return generator
|
|
}
|
|
|
|
// nextValue returns the next path or the next raw request depending on user input
|
|
// It returns false if all the inputs have been exhausted by the generator instance.
|
|
func (r *requestGenerator) nextValue() (value string, payloads map[string]interface{}, result bool) {
|
|
// Iterate each payload sequentially for each request path/raw
|
|
//
|
|
// If the sequence has finished for the current payload values
|
|
// then restart the sequence from the beginning and move on to the next payloads values
|
|
// otherwise use the last request.
|
|
var sequence []string
|
|
switch {
|
|
case len(r.request.Path) > 0:
|
|
sequence = r.request.Path
|
|
case len(r.request.Raw) > 0:
|
|
sequence = r.request.Raw
|
|
default:
|
|
return "", nil, false
|
|
}
|
|
|
|
hasPayloadIterator := r.payloadIterator != nil
|
|
|
|
if hasPayloadIterator && r.currentPayloads == nil {
|
|
r.currentPayloads, r.okCurrentPayload = r.payloadIterator.Value()
|
|
}
|
|
|
|
var request string
|
|
var shouldContinue bool
|
|
if nextRequest, nextIndex, found := r.findNextIteration(sequence, r.currentIndex); found {
|
|
r.currentIndex = nextIndex + 1
|
|
request = nextRequest
|
|
shouldContinue = true
|
|
} else {
|
|
// if found is false which happens at end of iteration of reqData(path or raw request)
|
|
// try again from start with index 0
|
|
if nextRequest, nextIndex, found := r.findNextIteration(sequence, 0); found && hasPayloadIterator {
|
|
r.currentIndex = nextIndex + 1
|
|
request = nextRequest
|
|
shouldContinue = true
|
|
}
|
|
}
|
|
|
|
if shouldContinue {
|
|
if r.hasMarker(request, Once) {
|
|
r.applyMark(request, Once)
|
|
}
|
|
if hasPayloadIterator {
|
|
return request, generators.MergeMaps(r.currentPayloads), r.okCurrentPayload
|
|
}
|
|
// next should return a copy of payloads and not pointer to payload to avoid data race
|
|
return request, generators.MergeMaps(r.currentPayloads), true
|
|
} else {
|
|
return "", nil, false
|
|
}
|
|
}
|
|
|
|
// findNextIteration iterates and returns next Request(path or raw request)
|
|
// at end of each iteration payload is incremented
|
|
func (r *requestGenerator) findNextIteration(sequence []string, index int) (string, int, bool) {
|
|
for i, request := range sequence[index:] {
|
|
if r.wasMarked(request, Once) {
|
|
// if request contains flowmark i.e `@once` and is marked skip it
|
|
continue
|
|
}
|
|
return request, index + i, true
|
|
|
|
}
|
|
// move on to next payload if current payload is applied/returned for all Requests(path or raw request)
|
|
if r.payloadIterator != nil {
|
|
r.currentPayloads, r.okCurrentPayload = r.payloadIterator.Value()
|
|
}
|
|
return "", 0, false
|
|
}
|
|
|
|
// applyMark marks given request i.e blacklist request
|
|
func (r *requestGenerator) applyMark(request string, mark flowMark) {
|
|
switch mark {
|
|
case Once:
|
|
r.onceFlow[request] = struct{}{}
|
|
}
|
|
|
|
}
|
|
|
|
// wasMarked checks if request is marked using request blacklist
|
|
func (r *requestGenerator) wasMarked(request string, mark flowMark) bool {
|
|
switch mark {
|
|
case Once:
|
|
_, ok := r.onceFlow[request]
|
|
return ok
|
|
}
|
|
return false
|
|
}
|
|
|
|
// hasMarker returns true if request has a marker (ex: @once which means request should only be executed once)
|
|
func (r *requestGenerator) hasMarker(request string, mark flowMark) bool {
|
|
fo, hasOverrides := parseFlowAnnotations(request)
|
|
return hasOverrides && fo == mark
|
|
}
|
|
|
|
// Remaining returns the number of requests that are still left to be
|
|
// generated (and therefore to be sent) by this generator.
|
|
func (r *requestGenerator) Remaining() int {
|
|
var sequence []string
|
|
switch {
|
|
case len(r.request.Path) > 0:
|
|
sequence = r.request.Path
|
|
case len(r.request.Raw) > 0:
|
|
sequence = r.request.Raw
|
|
default:
|
|
return 0
|
|
}
|
|
|
|
remainingInCurrentPass := 0
|
|
for i := r.currentIndex; i < len(sequence); i++ {
|
|
if !r.hasMarker(sequence[i], Once) {
|
|
remainingInCurrentPass++
|
|
}
|
|
}
|
|
|
|
if r.payloadIterator == nil {
|
|
return remainingInCurrentPass
|
|
}
|
|
|
|
numRemainingPayloadSets := r.payloadIterator.Remaining()
|
|
totalValidInSequence := 0
|
|
for _, req := range sequence {
|
|
if !r.hasMarker(req, Once) {
|
|
totalValidInSequence++
|
|
}
|
|
}
|
|
|
|
// Total remaining = remaining in current pass + (remaining payload sets * requests per full pass)
|
|
return remainingInCurrentPass + numRemainingPayloadSets*totalValidInSequence
|
|
}
|
|
|
|
func (r *requestGenerator) Total() int {
|
|
var sequence []string
|
|
switch {
|
|
case len(r.request.Path) > 0:
|
|
sequence = r.request.Path
|
|
case len(r.request.Raw) > 0:
|
|
sequence = r.request.Raw
|
|
default:
|
|
return 0
|
|
}
|
|
|
|
applicableRequests := 0
|
|
additionalRequests := 0
|
|
for _, request := range sequence {
|
|
if !r.hasMarker(request, Once) {
|
|
applicableRequests++
|
|
} else {
|
|
additionalRequests++
|
|
}
|
|
}
|
|
|
|
if r.payloadIterator == nil {
|
|
return applicableRequests + additionalRequests
|
|
}
|
|
|
|
return (applicableRequests * r.payloadIterator.Total()) + additionalRequests
|
|
}
|