mirror of https://github.com/daffainfo/nuclei.git
327 lines
8.6 KiB
Go
327 lines
8.6 KiB
Go
|
package runner
|
||
|
|
||
|
import (
|
||
|
"bufio"
|
||
|
"context"
|
||
|
"fmt"
|
||
|
"net/http/cookiejar"
|
||
|
"os"
|
||
|
"path"
|
||
|
"path/filepath"
|
||
|
"strings"
|
||
|
"sync"
|
||
|
|
||
|
tengo "github.com/d5/tengo/v2"
|
||
|
"github.com/d5/tengo/v2/stdlib"
|
||
|
"github.com/karrick/godirwalk"
|
||
|
"github.com/projectdiscovery/gologger"
|
||
|
"github.com/projectdiscovery/nuclei/v2/internal/progress"
|
||
|
"github.com/projectdiscovery/nuclei/v2/pkg/atomicboolean"
|
||
|
"github.com/projectdiscovery/nuclei/v2/pkg/executer"
|
||
|
"github.com/projectdiscovery/nuclei/v2/pkg/requests"
|
||
|
"github.com/projectdiscovery/nuclei/v2/pkg/templates"
|
||
|
"github.com/projectdiscovery/nuclei/v2/pkg/workflows"
|
||
|
)
|
||
|
|
||
|
// workflowTemplates contains the initialized workflow templates per template group
|
||
|
type workflowTemplates struct {
|
||
|
Name string
|
||
|
Templates []*workflows.Template
|
||
|
}
|
||
|
|
||
|
// processTemplateWithList processes a template and runs the enumeration on all the targets
|
||
|
func (r *Runner) processTemplateWithList(ctx context.Context, p progress.IProgress, template *templates.Template, request interface{}) bool {
|
||
|
var httpExecuter *executer.HTTPExecuter
|
||
|
var dnsExecuter *executer.DNSExecuter
|
||
|
var err error
|
||
|
|
||
|
// Create an executer based on the request type.
|
||
|
switch value := request.(type) {
|
||
|
case *requests.DNSRequest:
|
||
|
dnsExecuter = executer.NewDNSExecuter(&executer.DNSOptions{
|
||
|
Debug: r.options.Debug,
|
||
|
Template: template,
|
||
|
DNSRequest: value,
|
||
|
Writer: r.output,
|
||
|
JSON: r.options.JSON,
|
||
|
JSONRequests: r.options.JSONRequests,
|
||
|
ColoredOutput: !r.options.NoColor,
|
||
|
Colorizer: r.colorizer,
|
||
|
Decolorizer: r.decolorizer,
|
||
|
})
|
||
|
case *requests.BulkHTTPRequest:
|
||
|
httpExecuter, err = executer.NewHTTPExecuter(&executer.HTTPOptions{
|
||
|
Debug: r.options.Debug,
|
||
|
Template: template,
|
||
|
BulkHTTPRequest: value,
|
||
|
Writer: r.output,
|
||
|
Timeout: r.options.Timeout,
|
||
|
Retries: r.options.Retries,
|
||
|
ProxyURL: r.options.ProxyURL,
|
||
|
ProxySocksURL: r.options.ProxySocksURL,
|
||
|
CustomHeaders: r.options.CustomHeaders,
|
||
|
JSON: r.options.JSON,
|
||
|
JSONRequests: r.options.JSONRequests,
|
||
|
CookieReuse: value.CookieReuse,
|
||
|
ColoredOutput: !r.options.NoColor,
|
||
|
Colorizer: &r.colorizer,
|
||
|
Decolorizer: r.decolorizer,
|
||
|
})
|
||
|
}
|
||
|
|
||
|
if err != nil {
|
||
|
p.Drop(request.(*requests.BulkHTTPRequest).GetRequestCount())
|
||
|
gologger.Warningf("Could not create http client: %s\n", err)
|
||
|
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
var globalresult atomicboolean.AtomBool
|
||
|
|
||
|
var wg sync.WaitGroup
|
||
|
|
||
|
scanner := bufio.NewScanner(strings.NewReader(r.input))
|
||
|
for scanner.Scan() {
|
||
|
text := scanner.Text()
|
||
|
|
||
|
r.limiter <- struct{}{}
|
||
|
|
||
|
wg.Add(1)
|
||
|
|
||
|
go func(URL string) {
|
||
|
defer wg.Done()
|
||
|
|
||
|
var result executer.Result
|
||
|
|
||
|
if httpExecuter != nil {
|
||
|
result = httpExecuter.ExecuteHTTP(ctx, p, URL)
|
||
|
globalresult.Or(result.GotResults)
|
||
|
}
|
||
|
|
||
|
if dnsExecuter != nil {
|
||
|
result = dnsExecuter.ExecuteDNS(p, URL)
|
||
|
globalresult.Or(result.GotResults)
|
||
|
}
|
||
|
|
||
|
if result.Error != nil {
|
||
|
gologger.Warningf("[%s] Could not execute step: %s\n", r.colorizer.Colorizer.BrightBlue(template.ID), result.Error)
|
||
|
}
|
||
|
|
||
|
<-r.limiter
|
||
|
}(text)
|
||
|
}
|
||
|
|
||
|
wg.Wait()
|
||
|
|
||
|
// See if we got any results from the executers
|
||
|
return globalresult.Get()
|
||
|
}
|
||
|
|
||
|
// ProcessWorkflowWithList coming from stdin or list of targets
|
||
|
func (r *Runner) processWorkflowWithList(p progress.IProgress, workflow *workflows.Workflow) bool {
|
||
|
result := false
|
||
|
|
||
|
workflowTemplatesList, err := r.preloadWorkflowTemplates(p, workflow)
|
||
|
if err != nil {
|
||
|
gologger.Warningf("Could not preload templates for workflow %s: %s\n", workflow.ID, err)
|
||
|
|
||
|
return result
|
||
|
}
|
||
|
|
||
|
logicBytes := []byte(workflow.Logic)
|
||
|
|
||
|
var wg sync.WaitGroup
|
||
|
|
||
|
scanner := bufio.NewScanner(strings.NewReader(r.input))
|
||
|
for scanner.Scan() {
|
||
|
targetURL := scanner.Text()
|
||
|
r.limiter <- struct{}{}
|
||
|
|
||
|
wg.Add(1)
|
||
|
|
||
|
go func(targetURL string) {
|
||
|
defer wg.Done()
|
||
|
|
||
|
script := tengo.NewScript(logicBytes)
|
||
|
script.SetImports(stdlib.GetModuleMap(stdlib.AllModuleNames()...))
|
||
|
|
||
|
variables := make(map[string]*workflows.NucleiVar)
|
||
|
|
||
|
for _, workflowTemplate := range *workflowTemplatesList {
|
||
|
name := workflowTemplate.Name
|
||
|
variable := &workflows.NucleiVar{Templates: workflowTemplate.Templates, URL: targetURL}
|
||
|
err := script.Add(name, variable)
|
||
|
if err != nil {
|
||
|
gologger.Errorf("Could not initialize script for workflow '%s': %s\n", workflow.ID, err)
|
||
|
|
||
|
continue
|
||
|
}
|
||
|
variables[name] = variable
|
||
|
}
|
||
|
|
||
|
_, err := script.RunContext(context.Background())
|
||
|
if err != nil {
|
||
|
gologger.Errorf("Could not execute workflow '%s': %s\n", workflow.ID, err)
|
||
|
}
|
||
|
|
||
|
for _, variable := range variables {
|
||
|
result = !variable.IsFalsy()
|
||
|
if result {
|
||
|
break
|
||
|
}
|
||
|
}
|
||
|
|
||
|
<-r.limiter
|
||
|
}(targetURL)
|
||
|
}
|
||
|
|
||
|
wg.Wait()
|
||
|
|
||
|
return result
|
||
|
}
|
||
|
|
||
|
func (r *Runner) preloadWorkflowTemplates(p progress.IProgress, workflow *workflows.Workflow) (*[]workflowTemplates, error) {
|
||
|
var jar *cookiejar.Jar
|
||
|
|
||
|
if workflow.CookieReuse {
|
||
|
var err error
|
||
|
jar, err = cookiejar.New(nil)
|
||
|
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Single yaml provided
|
||
|
var wflTemplatesList []workflowTemplates
|
||
|
|
||
|
for name, value := range workflow.Variables {
|
||
|
// Check if the template is an absolute path or relative path.
|
||
|
// If the path is absolute, use it. Otherwise,
|
||
|
if isRelative(value) {
|
||
|
newPath, err := r.resolvePath(value)
|
||
|
if err != nil {
|
||
|
newPath, err = resolvePathWithBaseFolder(filepath.Dir(workflow.GetPath()), value)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
}
|
||
|
|
||
|
value = newPath
|
||
|
}
|
||
|
|
||
|
var wtlst []*workflows.Template
|
||
|
|
||
|
if strings.HasSuffix(value, ".yaml") {
|
||
|
t, err := templates.Parse(value)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
template := &workflows.Template{Progress: p}
|
||
|
if len(t.BulkRequestsHTTP) > 0 {
|
||
|
template.HTTPOptions = &executer.HTTPOptions{
|
||
|
Debug: r.options.Debug,
|
||
|
Writer: r.output,
|
||
|
Template: t,
|
||
|
Timeout: r.options.Timeout,
|
||
|
Retries: r.options.Retries,
|
||
|
ProxyURL: r.options.ProxyURL,
|
||
|
ProxySocksURL: r.options.ProxySocksURL,
|
||
|
CustomHeaders: r.options.CustomHeaders,
|
||
|
JSON: r.options.JSON,
|
||
|
JSONRequests: r.options.JSONRequests,
|
||
|
CookieJar: jar,
|
||
|
ColoredOutput: !r.options.NoColor,
|
||
|
Colorizer: &r.colorizer,
|
||
|
Decolorizer: r.decolorizer,
|
||
|
}
|
||
|
} else if len(t.RequestsDNS) > 0 {
|
||
|
template.DNSOptions = &executer.DNSOptions{
|
||
|
Debug: r.options.Debug,
|
||
|
Template: t,
|
||
|
Writer: r.output,
|
||
|
JSON: r.options.JSON,
|
||
|
JSONRequests: r.options.JSONRequests,
|
||
|
ColoredOutput: !r.options.NoColor,
|
||
|
Colorizer: r.colorizer,
|
||
|
Decolorizer: r.decolorizer,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if template.DNSOptions != nil || template.HTTPOptions != nil {
|
||
|
wtlst = append(wtlst, template)
|
||
|
}
|
||
|
} else {
|
||
|
matches := []string{}
|
||
|
|
||
|
err := godirwalk.Walk(value, &godirwalk.Options{
|
||
|
Callback: func(path string, d *godirwalk.Dirent) error {
|
||
|
if !d.IsDir() && strings.HasSuffix(path, ".yaml") {
|
||
|
matches = append(matches, path)
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
},
|
||
|
ErrorCallback: func(path string, err error) godirwalk.ErrorAction {
|
||
|
return godirwalk.SkipNode
|
||
|
},
|
||
|
Unsorted: true,
|
||
|
})
|
||
|
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
// 0 matches means no templates were found in directory
|
||
|
if len(matches) == 0 {
|
||
|
return nil, fmt.Errorf("no match found in the directory %s", value)
|
||
|
}
|
||
|
|
||
|
for _, match := range matches {
|
||
|
t, err := templates.Parse(match)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
template := &workflows.Template{Progress: p}
|
||
|
if len(t.BulkRequestsHTTP) > 0 {
|
||
|
template.HTTPOptions = &executer.HTTPOptions{
|
||
|
Debug: r.options.Debug,
|
||
|
Writer: r.output,
|
||
|
Template: t,
|
||
|
Timeout: r.options.Timeout,
|
||
|
Retries: r.options.Retries,
|
||
|
ProxyURL: r.options.ProxyURL,
|
||
|
ProxySocksURL: r.options.ProxySocksURL,
|
||
|
CustomHeaders: r.options.CustomHeaders,
|
||
|
CookieJar: jar,
|
||
|
}
|
||
|
} else if len(t.RequestsDNS) > 0 {
|
||
|
template.DNSOptions = &executer.DNSOptions{
|
||
|
Debug: r.options.Debug,
|
||
|
Template: t,
|
||
|
Writer: r.output,
|
||
|
}
|
||
|
}
|
||
|
if template.DNSOptions != nil || template.HTTPOptions != nil {
|
||
|
wtlst = append(wtlst, template)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
wflTemplatesList = append(wflTemplatesList, workflowTemplates{Name: name, Templates: wtlst})
|
||
|
}
|
||
|
|
||
|
return &wflTemplatesList, nil
|
||
|
}
|
||
|
|
||
|
func resolvePathWithBaseFolder(baseFolder, templateName string) (string, error) {
|
||
|
templatePath := path.Join(baseFolder, templateName)
|
||
|
if _, err := os.Stat(templatePath); !os.IsNotExist(err) {
|
||
|
gologger.Debugf("Found template in current directory: %s\n", templatePath)
|
||
|
return templatePath, nil
|
||
|
}
|
||
|
|
||
|
return "", fmt.Errorf("no such path found: %s", templateName)
|
||
|
}
|