2020-04-04 10:29:05 +00:00
|
|
|
package runner
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bufio"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
2020-07-28 22:43:05 +00:00
|
|
|
"regexp"
|
2020-04-04 10:29:05 +00:00
|
|
|
"strings"
|
|
|
|
"sync"
|
|
|
|
|
2020-08-23 18:46:18 +00:00
|
|
|
"github.com/logrusorgru/aurora"
|
2020-04-04 10:29:05 +00:00
|
|
|
"github.com/projectdiscovery/gologger"
|
2020-09-10 11:02:01 +00:00
|
|
|
"github.com/projectdiscovery/nuclei/v2/internal/bufwriter"
|
2020-07-23 18:19:19 +00:00
|
|
|
"github.com/projectdiscovery/nuclei/v2/internal/progress"
|
2020-07-24 16:12:16 +00:00
|
|
|
"github.com/projectdiscovery/nuclei/v2/pkg/atomicboolean"
|
2020-09-19 12:43:35 +00:00
|
|
|
"github.com/projectdiscovery/nuclei/v2/pkg/colorizer"
|
2020-10-09 21:11:07 +00:00
|
|
|
"github.com/projectdiscovery/nuclei/v2/pkg/globalratelimiter"
|
2020-07-01 10:47:24 +00:00
|
|
|
"github.com/projectdiscovery/nuclei/v2/pkg/templates"
|
|
|
|
"github.com/projectdiscovery/nuclei/v2/pkg/workflows"
|
2020-04-04 10:29:05 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// Runner is a client for running the enumeration process.
|
|
|
|
type Runner struct {
|
2020-07-25 21:02:24 +00:00
|
|
|
input string
|
2020-07-23 18:19:19 +00:00
|
|
|
inputCount int64
|
|
|
|
|
2020-04-04 12:51:05 +00:00
|
|
|
// output is the output file to write if any
|
2020-09-11 16:05:29 +00:00
|
|
|
output *bufwriter.Writer
|
2020-04-26 01:18:10 +00:00
|
|
|
|
2020-06-24 22:23:37 +00:00
|
|
|
tempFile string
|
|
|
|
templatesConfig *nucleiConfig
|
2020-04-04 12:51:05 +00:00
|
|
|
// options contains configuration options for runner
|
2020-04-04 10:29:05 +00:00
|
|
|
options *Options
|
2020-07-23 18:19:19 +00:00
|
|
|
|
|
|
|
// progress tracking
|
2020-07-31 21:07:33 +00:00
|
|
|
progress progress.IProgress
|
2020-07-28 22:43:05 +00:00
|
|
|
|
|
|
|
// output coloring
|
2020-09-19 12:43:35 +00:00
|
|
|
colorizer colorizer.NucleiColorizer
|
2020-07-28 22:43:05 +00:00
|
|
|
decolorizer *regexp.Regexp
|
2020-04-04 10:29:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// New creates a new client for running enumeration process.
|
|
|
|
func New(options *Options) (*Runner, error) {
|
|
|
|
runner := &Runner{
|
2020-09-11 16:05:29 +00:00
|
|
|
options: options,
|
2020-04-04 10:29:05 +00:00
|
|
|
}
|
2020-04-04 11:42:29 +00:00
|
|
|
|
2020-06-24 22:23:37 +00:00
|
|
|
if err := runner.updateTemplates(); err != nil {
|
2020-08-31 20:27:02 +00:00
|
|
|
gologger.Labelf("Could not update templates: %s\n", err)
|
2020-06-24 22:23:37 +00:00
|
|
|
}
|
2020-08-25 21:24:31 +00:00
|
|
|
|
2020-09-19 12:43:35 +00:00
|
|
|
// output coloring
|
|
|
|
useColor := !options.NoColor
|
|
|
|
runner.colorizer = *colorizer.NewNucleiColorizer(aurora.NewAurora(useColor))
|
|
|
|
|
|
|
|
if useColor {
|
|
|
|
// compile a decolorization regex to cleanup file output messages
|
|
|
|
runner.decolorizer = regexp.MustCompile(`\x1B\[[0-9;]*[a-zA-Z]`)
|
|
|
|
}
|
|
|
|
|
2020-08-29 21:02:45 +00:00
|
|
|
if options.TemplateList {
|
|
|
|
runner.listAvailableTemplates()
|
2020-08-28 09:17:37 +00:00
|
|
|
os.Exit(0)
|
|
|
|
}
|
|
|
|
|
2020-07-13 22:04:19 +00:00
|
|
|
if (len(options.Templates) == 0 || (options.Targets == "" && !options.Stdin && options.Target == "")) && options.UpdateTemplates {
|
2020-06-24 22:23:37 +00:00
|
|
|
os.Exit(0)
|
|
|
|
}
|
2020-08-23 18:46:18 +00:00
|
|
|
// Read nucleiignore file if given a templateconfig
|
|
|
|
if runner.templatesConfig != nil {
|
|
|
|
runner.readNucleiIgnoreFile()
|
|
|
|
}
|
2020-06-24 22:23:37 +00:00
|
|
|
|
2020-04-26 01:18:10 +00:00
|
|
|
// If we have stdin, write it to a new file
|
|
|
|
if options.Stdin {
|
|
|
|
tempInput, err := ioutil.TempFile("", "stdin-input-*")
|
2020-08-25 21:24:31 +00:00
|
|
|
|
2020-04-26 01:18:10 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2020-08-25 21:24:31 +00:00
|
|
|
|
2020-04-26 01:30:28 +00:00
|
|
|
if _, err := io.Copy(tempInput, os.Stdin); err != nil {
|
2020-04-26 01:18:10 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
2020-08-25 21:24:31 +00:00
|
|
|
|
2020-04-26 01:18:10 +00:00
|
|
|
runner.tempFile = tempInput.Name()
|
|
|
|
tempInput.Close()
|
|
|
|
}
|
2020-06-25 16:10:20 +00:00
|
|
|
// If we have single target, write it to a new file
|
|
|
|
if options.Target != "" {
|
|
|
|
tempInput, err := ioutil.TempFile("", "stdin-input-*")
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2020-08-25 21:24:31 +00:00
|
|
|
|
2020-07-26 19:17:42 +00:00
|
|
|
fmt.Fprintf(tempInput, "%s\n", options.Target)
|
2020-06-25 16:10:20 +00:00
|
|
|
runner.tempFile = tempInput.Name()
|
|
|
|
tempInput.Close()
|
|
|
|
}
|
2020-04-26 01:18:10 +00:00
|
|
|
|
2020-07-23 18:19:19 +00:00
|
|
|
// Setup input, handle a list of hosts as argument
|
|
|
|
var err error
|
2020-08-25 21:24:31 +00:00
|
|
|
|
2020-07-25 21:02:24 +00:00
|
|
|
var input *os.File
|
2020-08-25 21:24:31 +00:00
|
|
|
|
2020-07-23 18:19:19 +00:00
|
|
|
if options.Targets != "" {
|
2020-07-25 21:02:24 +00:00
|
|
|
input, err = os.Open(options.Targets)
|
2020-07-23 18:19:19 +00:00
|
|
|
} else if options.Stdin || options.Target != "" {
|
2020-07-25 21:02:24 +00:00
|
|
|
input, err = os.Open(runner.tempFile)
|
2020-07-23 18:19:19 +00:00
|
|
|
}
|
2020-08-25 21:24:31 +00:00
|
|
|
|
2020-07-23 18:19:19 +00:00
|
|
|
if err != nil {
|
|
|
|
gologger.Fatalf("Could not open targets file '%s': %s\n", options.Targets, err)
|
|
|
|
}
|
|
|
|
|
2020-07-26 14:43:53 +00:00
|
|
|
// Sanitize input and pre-compute total number of targets
|
2020-10-09 21:11:07 +00:00
|
|
|
var usedInput = make(map[string]struct{})
|
2020-08-25 21:24:31 +00:00
|
|
|
|
2020-07-25 21:02:24 +00:00
|
|
|
dupeCount := 0
|
|
|
|
sb := strings.Builder{}
|
|
|
|
scanner := bufio.NewScanner(input)
|
2020-07-23 18:19:19 +00:00
|
|
|
runner.inputCount = 0
|
2020-08-25 21:24:31 +00:00
|
|
|
|
2020-07-23 18:19:19 +00:00
|
|
|
for scanner.Scan() {
|
2020-07-25 21:02:24 +00:00
|
|
|
url := scanner.Text()
|
2020-07-26 14:43:53 +00:00
|
|
|
// skip empty lines
|
2020-08-25 21:24:31 +00:00
|
|
|
if url == "" {
|
2020-07-26 14:43:53 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
// deduplication
|
2020-07-25 21:02:24 +00:00
|
|
|
if _, ok := usedInput[url]; !ok {
|
2020-10-09 21:11:07 +00:00
|
|
|
usedInput[url] = struct{}{}
|
2020-07-25 21:02:24 +00:00
|
|
|
runner.inputCount++
|
2020-08-25 21:24:31 +00:00
|
|
|
|
2020-10-09 21:11:07 +00:00
|
|
|
// allocate global rate limiters
|
|
|
|
globalratelimiter.Add(url, options.RateLimit)
|
|
|
|
|
2020-07-25 21:02:24 +00:00
|
|
|
sb.WriteString(url)
|
|
|
|
sb.WriteString("\n")
|
|
|
|
} else {
|
|
|
|
dupeCount++
|
|
|
|
}
|
|
|
|
}
|
2020-07-26 13:35:26 +00:00
|
|
|
input.Close()
|
2020-08-25 21:24:31 +00:00
|
|
|
|
2020-07-25 21:02:24 +00:00
|
|
|
runner.input = sb.String()
|
2020-08-25 21:24:31 +00:00
|
|
|
|
2020-07-25 21:02:24 +00:00
|
|
|
if dupeCount > 0 {
|
|
|
|
gologger.Labelf("Supplied input was automatically deduplicated (%d removed).", dupeCount)
|
2020-07-23 18:19:19 +00:00
|
|
|
}
|
|
|
|
|
2020-04-04 12:51:05 +00:00
|
|
|
// Create the output file if asked
|
|
|
|
if options.Output != "" {
|
2020-09-10 11:02:01 +00:00
|
|
|
output, err := bufwriter.New(options.Output)
|
2020-04-04 12:51:05 +00:00
|
|
|
if err != nil {
|
|
|
|
gologger.Fatalf("Could not create output file '%s': %s\n", options.Output, err)
|
|
|
|
}
|
|
|
|
runner.output = output
|
|
|
|
}
|
2020-07-23 18:19:19 +00:00
|
|
|
|
2020-07-31 21:07:33 +00:00
|
|
|
// Creates the progress tracking object
|
2020-09-19 12:43:35 +00:00
|
|
|
runner.progress = progress.NewProgress(runner.colorizer.Colorizer, options.EnableProgressBar)
|
2020-07-23 18:19:19 +00:00
|
|
|
|
2020-04-04 10:29:05 +00:00
|
|
|
return runner, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Close releases all the resources and cleans up
|
2020-04-04 12:51:05 +00:00
|
|
|
func (r *Runner) Close() {
|
2020-09-11 16:05:29 +00:00
|
|
|
if r.output != nil {
|
|
|
|
r.output.Close()
|
|
|
|
}
|
2020-04-26 01:18:10 +00:00
|
|
|
os.Remove(r.tempFile)
|
2020-04-04 12:51:05 +00:00
|
|
|
}
|
2020-04-04 10:29:05 +00:00
|
|
|
|
2020-08-02 11:49:16 +00:00
|
|
|
// RunEnumeration sets up the input layer for giving input nuclei.
|
|
|
|
// binary and runs the actual enumeration
|
|
|
|
func (r *Runner) RunEnumeration() {
|
2020-08-02 13:48:10 +00:00
|
|
|
// resolves input templates definitions and any optional exclusion
|
|
|
|
includedTemplates := r.getTemplatesFor(r.options.Templates)
|
|
|
|
excludedTemplates := r.getTemplatesFor(r.options.ExcludedTemplates)
|
|
|
|
// defaults to all templates
|
|
|
|
allTemplates := includedTemplates
|
2020-08-25 21:24:31 +00:00
|
|
|
|
2020-08-02 13:48:10 +00:00
|
|
|
if len(excludedTemplates) > 0 {
|
|
|
|
excludedMap := make(map[string]struct{}, len(excludedTemplates))
|
|
|
|
for _, excl := range excludedTemplates {
|
|
|
|
excludedMap[excl] = struct{}{}
|
|
|
|
}
|
|
|
|
// rebuild list with only non-excluded templates
|
|
|
|
allTemplates = []string{}
|
2020-08-25 21:24:31 +00:00
|
|
|
|
2020-08-02 13:48:10 +00:00
|
|
|
for _, incl := range includedTemplates {
|
|
|
|
if _, found := excludedMap[incl]; !found {
|
|
|
|
allTemplates = append(allTemplates, incl)
|
|
|
|
} else {
|
|
|
|
gologger.Warningf("Excluding '%s'", incl)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-08-02 11:49:16 +00:00
|
|
|
|
2020-08-02 16:33:55 +00:00
|
|
|
// pre-parse all the templates, apply filters
|
|
|
|
availableTemplates, workflowCount := r.getParsedTemplatesFor(allTemplates, r.options.Severity)
|
|
|
|
templateCount := len(availableTemplates)
|
|
|
|
hasWorkflows := workflowCount > 0
|
|
|
|
|
2020-05-02 17:10:52 +00:00
|
|
|
// 0 matches means no templates were found in directory
|
2020-08-02 16:33:55 +00:00
|
|
|
if templateCount == 0 {
|
2020-07-13 22:04:19 +00:00
|
|
|
gologger.Fatalf("Error, no templates were found.\n")
|
2020-04-04 10:29:05 +00:00
|
|
|
}
|
2020-06-27 15:20:43 +00:00
|
|
|
|
2020-08-02 16:33:55 +00:00
|
|
|
gologger.Infof("Using %s rules (%s templates, %s workflows)",
|
2020-09-19 12:43:35 +00:00
|
|
|
r.colorizer.Colorizer.Bold(templateCount).String(),
|
|
|
|
r.colorizer.Colorizer.Bold(templateCount-workflowCount).String(),
|
|
|
|
r.colorizer.Colorizer.Bold(workflowCount).String())
|
2020-07-23 18:19:19 +00:00
|
|
|
|
2020-07-25 21:22:09 +00:00
|
|
|
// precompute total request count
|
2020-07-25 12:56:20 +00:00
|
|
|
var totalRequests int64 = 0
|
|
|
|
|
2020-08-02 16:33:55 +00:00
|
|
|
for _, t := range availableTemplates {
|
2020-08-25 21:24:31 +00:00
|
|
|
switch av := t.(type) {
|
2020-07-24 16:38:27 +00:00
|
|
|
case *templates.Template:
|
2020-08-25 21:24:31 +00:00
|
|
|
totalRequests += (av.GetHTTPRequestCount() + av.GetDNSRequestCount()) * r.inputCount
|
2020-07-26 22:00:06 +00:00
|
|
|
case *workflows.Workflow:
|
|
|
|
// workflows will dynamically adjust the totals while running, as
|
|
|
|
// it can't be know in advance which requests will be called
|
2020-08-26 18:05:31 +00:00
|
|
|
} // nolint:wsl // comment
|
2020-07-25 12:56:20 +00:00
|
|
|
}
|
2020-07-23 18:19:19 +00:00
|
|
|
|
2020-07-24 16:12:16 +00:00
|
|
|
var (
|
|
|
|
wgtemplates sync.WaitGroup
|
|
|
|
results atomicboolean.AtomBool
|
|
|
|
)
|
|
|
|
|
2020-07-27 16:47:23 +00:00
|
|
|
if r.inputCount == 0 {
|
|
|
|
gologger.Errorf("Could not find any valid input URLs.")
|
2020-07-27 18:38:48 +00:00
|
|
|
} else if totalRequests > 0 || hasWorkflows {
|
2020-08-01 19:44:14 +00:00
|
|
|
// tracks global progress and captures stdout/stderr until p.Wait finishes
|
2020-08-02 16:33:55 +00:00
|
|
|
p := r.progress
|
2020-07-31 21:07:33 +00:00
|
|
|
p.InitProgressbar(r.inputCount, templateCount, totalRequests)
|
2020-07-27 16:47:23 +00:00
|
|
|
|
2020-08-02 16:33:55 +00:00
|
|
|
for _, t := range availableTemplates {
|
2020-07-27 16:47:23 +00:00
|
|
|
wgtemplates.Add(1)
|
2020-08-02 16:33:55 +00:00
|
|
|
go func(template interface{}) {
|
2020-07-27 16:47:23 +00:00
|
|
|
defer wgtemplates.Done()
|
2020-08-25 21:24:31 +00:00
|
|
|
switch tt := template.(type) {
|
2020-07-27 16:47:23 +00:00
|
|
|
case *templates.Template:
|
2020-08-25 21:24:31 +00:00
|
|
|
for _, request := range tt.RequestsDNS {
|
2020-10-09 21:11:07 +00:00
|
|
|
results.Or(r.processTemplateWithList(p, tt, request))
|
2020-07-27 16:47:23 +00:00
|
|
|
}
|
2020-08-25 21:24:31 +00:00
|
|
|
for _, request := range tt.BulkRequestsHTTP {
|
2020-10-09 21:11:07 +00:00
|
|
|
results.Or(r.processTemplateWithList(p, tt, request))
|
2020-07-27 16:47:23 +00:00
|
|
|
}
|
|
|
|
case *workflows.Workflow:
|
2020-09-19 15:55:05 +00:00
|
|
|
results.Or(r.processWorkflowWithList(p, template.(*workflows.Workflow)))
|
2020-06-26 12:37:55 +00:00
|
|
|
}
|
2020-08-02 16:33:55 +00:00
|
|
|
}(t)
|
2020-07-27 16:47:23 +00:00
|
|
|
}
|
2020-07-24 16:12:16 +00:00
|
|
|
|
2020-07-27 16:47:23 +00:00
|
|
|
wgtemplates.Wait()
|
2020-07-31 21:07:33 +00:00
|
|
|
p.Wait()
|
2020-07-27 16:47:23 +00:00
|
|
|
}
|
2020-07-23 18:19:19 +00:00
|
|
|
|
2020-07-24 16:12:16 +00:00
|
|
|
if !results.Get() {
|
2020-06-22 14:27:32 +00:00
|
|
|
if r.output != nil {
|
|
|
|
r.output.Close()
|
2020-09-10 11:02:01 +00:00
|
|
|
os.Remove(r.options.Output)
|
2020-06-22 14:27:32 +00:00
|
|
|
}
|
2020-08-25 21:24:31 +00:00
|
|
|
|
2020-07-18 18:32:00 +00:00
|
|
|
gologger.Infof("No results found. Happy hacking!")
|
2020-06-22 14:27:32 +00:00
|
|
|
}
|
2020-04-04 10:29:05 +00:00
|
|
|
}
|