Added configurable redirects per request

dev
Ice3man543 2020-04-23 03:56:41 +05:30
parent 9c6d5fbb9c
commit 2c58a33913
2 changed files with 116 additions and 91 deletions

View File

@ -16,14 +16,13 @@ import (
"github.com/projectdiscovery/gologger" "github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/nuclei/pkg/extractors" "github.com/projectdiscovery/nuclei/pkg/extractors"
"github.com/projectdiscovery/nuclei/pkg/matchers" "github.com/projectdiscovery/nuclei/pkg/matchers"
"github.com/projectdiscovery/nuclei/pkg/requests"
"github.com/projectdiscovery/nuclei/pkg/templates" "github.com/projectdiscovery/nuclei/pkg/templates"
retryablehttp "github.com/projectdiscovery/retryablehttp-go" retryablehttp "github.com/projectdiscovery/retryablehttp-go"
) )
// Runner is a client for running the enumeration process. // Runner is a client for running the enumeration process.
type Runner struct { type Runner struct {
// client is the http client with retries
client *retryablehttp.Client
// output is the output file to write if any // output is the output file to write if any
output *os.File output *os.File
outputMutex *sync.Mutex outputMutex *sync.Mutex
@ -38,29 +37,6 @@ func New(options *Options) (*Runner, error) {
options: options, options: options,
} }
retryablehttpOptions := retryablehttp.DefaultOptionsSpraying
retryablehttpOptions.RetryWaitMax = 10 * time.Second
retryablehttpOptions.RetryMax = options.Retries
// Create the HTTP Client
client := retryablehttp.NewWithHTTPClient(&http.Client{
Transport: &http.Transport{
MaxIdleConnsPerHost: -1,
TLSClientConfig: &tls.Config{
Renegotiation: tls.RenegotiateOnceAsClient,
InsecureSkipVerify: true,
},
DisableKeepAlives: true,
},
Timeout: time.Duration(options.Timeout) * time.Second,
CheckRedirect: func(_ *http.Request, _ []*http.Request) error {
return http.ErrUseLastResponse
},
}, retryablehttpOptions)
client.CheckRetry = retryablehttp.HostSprayRetryPolicy()
runner.client = client
// Create the output file if asked // Create the output file if asked
if options.Output != "" { if options.Output != "" {
output, err := os.Create(options.Output) output, err := os.Create(options.Output)
@ -86,7 +62,14 @@ func (r *Runner) RunEnumeration() {
// If the template path is a single template and not a glob, use that. // If the template path is a single template and not a glob, use that.
if !strings.Contains(r.options.Templates, "*") { if !strings.Contains(r.options.Templates, "*") {
r.processTemplate(r.options.Templates) template, err := templates.ParseTemplate(r.options.Templates)
if err != nil {
gologger.Errorf("Could not parse template file '%s': %s\n", r.options.Templates, err)
return
}
for _, request := range template.Requests {
r.processTemplateRequest(template, request)
}
return return
} }
@ -97,37 +80,38 @@ func (r *Runner) RunEnumeration() {
} }
for _, match := range matches { for _, match := range matches {
r.processTemplate(match) template, err := templates.ParseTemplate(match)
if err != nil {
gologger.Errorf("Could not parse template file '%s': %s\n", match, err)
return
}
for _, request := range template.Requests {
r.processTemplateRequest(template, request)
}
} }
} }
// processTemplate processes a template and runs the enumeration on all the targets // processTemplate processes a template and runs the enumeration on all the targets
func (r *Runner) processTemplate(templatePath string) { func (r *Runner) processTemplateRequest(template *templates.Template, request *requests.Request) {
template, err := templates.ParseTemplate(templatePath)
if err != nil {
gologger.Errorf("Could not parse template file '%s': %s\n", templatePath, err)
return
}
// Handle a list of hosts as argument // Handle a list of hosts as argument
if r.options.Targets != "" { if r.options.Targets != "" {
file, err := os.Open(r.options.Targets) file, err := os.Open(r.options.Targets)
if err != nil { if err != nil {
gologger.Fatalf("Could not open targets file '%s': %s\n", r.options.Targets, err) gologger.Fatalf("Could not open targets file '%s': %s\n", r.options.Targets, err)
} }
r.processTemplateWithList(template, file) r.processTemplateWithList(template, request, file)
file.Close() file.Close()
return return
} }
// Handle stdin input // Handle stdin input
if r.options.Stdin { if r.options.Stdin {
r.processTemplateWithList(template, os.Stdin) r.processTemplateWithList(template, request, os.Stdin)
} }
} }
// processDomain processes the list with a template // processDomain processes the list with a template
func (r *Runner) processTemplateWithList(template *templates.Template, reader io.Reader) { func (r *Runner) processTemplateWithList(template *templates.Template, request *requests.Request, reader io.Reader) {
// Display the message for the template // Display the message for the template
message := fmt.Sprintf("[%s] Loaded template %s (@%s)", template.ID, template.Info.Name, template.Info.Author) message := fmt.Sprintf("[%s] Loaded template %s (@%s)", template.ID, template.Info.Name, template.Info.Author)
if template.Info.Severity != "" { if template.Info.Severity != "" {
@ -144,6 +128,8 @@ func (r *Runner) processTemplateWithList(template *templates.Template, reader io
defer writer.Flush() defer writer.Flush()
} }
client := r.makeHTTPClient(request.Redirects, request.MaxRedirects)
scanner := bufio.NewScanner(reader) scanner := bufio.NewScanner(reader)
for scanner.Scan() { for scanner.Scan() {
text := scanner.Text() text := scanner.Text()
@ -154,7 +140,7 @@ func (r *Runner) processTemplateWithList(template *templates.Template, reader io
wg.Add(1) wg.Add(1)
go func(URL string) { go func(URL string) {
r.sendRequest(template, URL, writer) r.sendRequest(template, request, URL, writer, client)
<-limiter <-limiter
wg.Done() wg.Done()
}(text) }(text)
@ -164,71 +150,69 @@ func (r *Runner) processTemplateWithList(template *templates.Template, reader io
} }
// sendRequest sends a request to the target based on a template // sendRequest sends a request to the target based on a template
func (r *Runner) sendRequest(template *templates.Template, URL string, writer *bufio.Writer) { func (r *Runner) sendRequest(template *templates.Template, request *requests.Request, URL string, writer *bufio.Writer, client *retryablehttp.Client) {
for _, request := range template.Requests { // Compile each request for the template based on the URL
// Compile each request for the template based on the URL compiledRequest, err := request.MakeRequest(URL)
compiledRequest, err := request.MakeRequest(URL) if err != nil {
gologger.Warningf("[%s] Could not make request %s: %s\n", template.ID, URL, err)
return
}
// Send the request to the target servers
reqLoop:
for _, req := range compiledRequest {
resp, err := client.Do(req)
if err != nil { if err != nil {
gologger.Warningf("[%s] Could not make request %s: %s\n", template.ID, URL, err) if resp != nil {
resp.Body.Close()
}
gologger.Warningf("[%s] Could not send request %s: %s\n", template.ID, URL, err)
return
}
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
io.Copy(ioutil.Discard, resp.Body)
resp.Body.Close()
gologger.Warningf("[%s] Could not read body %s: %s\n", template.ID, URL, err)
continue continue
} }
resp.Body.Close()
// Send the request to the target servers body := unsafeToString(data)
reqLoop:
for _, req := range compiledRequest { var headers string
resp, err := r.client.Do(req) for _, matcher := range request.Matchers {
if err != nil { // Only build the headers string if the matcher asks for it
if resp != nil { part := matcher.GetPart()
resp.Body.Close() if part == matchers.AllPart || part == matchers.HeaderPart && headers == "" {
} headers = headersToString(resp.Header)
gologger.Warningf("[%s] Could not send request %s: %s\n", template.ID, URL, err)
return
} }
data, err := ioutil.ReadAll(resp.Body) // Check if the matcher matched
if err != nil { if matcher.Match(resp, body, headers) {
io.Copy(ioutil.Discard, resp.Body) // If there is an extractor, run it.
resp.Body.Close() var extractorResults []string
gologger.Warningf("[%s] Could not read body %s: %s\n", template.ID, URL, err) for _, extractor := range request.Extractors {
continue part := extractor.GetPart()
} if part == extractors.AllPart || part == extractors.HeaderPart && headers == "" {
resp.Body.Close() headers = headersToString(resp.Header)
body := unsafeToString(data)
var headers string
for _, matcher := range request.Matchers {
// Only build the headers string if the matcher asks for it
part := matcher.GetPart()
if part == matchers.AllPart || part == matchers.HeaderPart && headers == "" {
headers = headersToString(resp.Header)
}
// Check if the matcher matched
if matcher.Match(resp, body, headers) {
// If there is an extractor, run it.
var extractorResults []string
for _, extractor := range request.Extractors {
part := extractor.GetPart()
if part == extractors.AllPart || part == extractors.HeaderPart && headers == "" {
headers = headersToString(resp.Header)
}
extractorResults = append(extractorResults, extractor.Extract(body, headers)...)
} }
extractorResults = append(extractorResults, extractor.Extract(body, headers)...)
}
// All the matchers matched, print the output on the screen // All the matchers matched, print the output on the screen
output := buildOutput(template, req, extractorResults, matcher) output := buildOutput(template, req, extractorResults, matcher)
gologger.Silentf("%s", output) gologger.Silentf("%s", output)
if writer != nil { if writer != nil {
r.outputMutex.Lock() r.outputMutex.Lock()
writer.WriteString(output) writer.WriteString(output)
r.outputMutex.Unlock() r.outputMutex.Unlock()
}
} }
} }
continue reqLoop
} }
continue reqLoop
} }
} }
@ -263,3 +247,40 @@ func buildOutput(template *templates.Template, req *retryablehttp.Request, extra
return builder.String() return builder.String()
} }
// makeHTTPClient creates a HTTP client with configurable redirect field
func (r *Runner) makeHTTPClient(redirects bool, maxRedirects int) *retryablehttp.Client {
retryablehttpOptions := retryablehttp.DefaultOptionsSpraying
retryablehttpOptions.RetryWaitMax = 10 * time.Second
retryablehttpOptions.RetryMax = r.options.Retries
// Create the HTTP Client
client := retryablehttp.NewWithHTTPClient(&http.Client{
Transport: &http.Transport{
MaxIdleConnsPerHost: -1,
TLSClientConfig: &tls.Config{
Renegotiation: tls.RenegotiateOnceAsClient,
InsecureSkipVerify: true,
},
DisableKeepAlives: true,
},
Timeout: time.Duration(r.options.Timeout) * time.Second,
CheckRedirect: func(_ *http.Request, requests []*http.Request) error {
if !redirects {
return http.ErrUseLastResponse
}
if maxRedirects == 0 {
if len(requests) > 10 {
return http.ErrUseLastResponse
}
return nil
}
if len(requests) > maxRedirects {
return http.ErrUseLastResponse
}
return nil
},
}, retryablehttpOptions)
client.CheckRetry = retryablehttp.HostSprayRetryPolicy()
return client
}

View File

@ -28,6 +28,10 @@ type Request struct {
// Extractors contains the extraction mechanism for the request to identify // Extractors contains the extraction mechanism for the request to identify
// and extract parts of the response. // and extract parts of the response.
Extractors []*extractors.Extractor `yaml:"extractors,omitempty"` Extractors []*extractors.Extractor `yaml:"extractors,omitempty"`
// Redirects specifies whether redirects should be followed.
Redirects bool `yaml:"redirects,omitempty"`
// MaxRedirects is the maximum number of redirects that should be followed.
MaxRedirects int `yaml:"max-redirects,omitempty"`
} }
// MakeRequest creates a *http.Request from a request template // MakeRequest creates a *http.Request from a request template