2020-04-04 15:59:05 +05:30
|
|
|
package runner
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"bufio"
|
|
|
|
|
"fmt"
|
|
|
|
|
"io"
|
|
|
|
|
"io/ioutil"
|
|
|
|
|
"os"
|
|
|
|
|
"path/filepath"
|
|
|
|
|
"strings"
|
|
|
|
|
"sync"
|
|
|
|
|
|
2020-05-02 12:10:52 -05:00
|
|
|
"github.com/karrick/godirwalk"
|
2020-04-04 15:59:05 +05:30
|
|
|
"github.com/projectdiscovery/gologger"
|
2020-04-26 06:48:10 +05:30
|
|
|
"github.com/projectdiscovery/nuclei/pkg/executor"
|
2020-04-23 03:56:41 +05:30
|
|
|
"github.com/projectdiscovery/nuclei/pkg/requests"
|
2020-04-04 15:59:05 +05:30
|
|
|
"github.com/projectdiscovery/nuclei/pkg/templates"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// Runner is a client for running the enumeration process.
|
|
|
|
|
type Runner struct {
|
2020-04-04 18:21:05 +05:30
|
|
|
// output is the output file to write if any
|
|
|
|
|
output *os.File
|
|
|
|
|
outputMutex *sync.Mutex
|
2020-04-26 06:48:10 +05:30
|
|
|
|
2020-06-25 03:53:37 +05:30
|
|
|
tempFile string
|
|
|
|
|
templatesConfig *nucleiConfig
|
2020-04-04 18:21:05 +05:30
|
|
|
// options contains configuration options for runner
|
2020-04-04 15:59:05 +05:30
|
|
|
options *Options
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// New creates a new client for running enumeration process.
|
|
|
|
|
func New(options *Options) (*Runner, error) {
|
|
|
|
|
runner := &Runner{
|
2020-04-04 18:21:05 +05:30
|
|
|
outputMutex: &sync.Mutex{},
|
|
|
|
|
options: options,
|
2020-04-04 15:59:05 +05:30
|
|
|
}
|
2020-04-04 17:12:29 +05:30
|
|
|
|
2020-06-25 03:53:37 +05:30
|
|
|
if err := runner.updateTemplates(); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2020-06-25 21:40:20 +05:30
|
|
|
if (options.Templates == "" || (options.Targets == "" && !options.Stdin && options.Target == "")) && options.UpdateTemplates {
|
2020-06-25 03:53:37 +05:30
|
|
|
os.Exit(0)
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-26 06:48:10 +05:30
|
|
|
// If we have stdin, write it to a new file
|
|
|
|
|
if options.Stdin {
|
|
|
|
|
tempInput, err := ioutil.TempFile("", "stdin-input-*")
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2020-04-26 07:00:28 +05:30
|
|
|
if _, err := io.Copy(tempInput, os.Stdin); err != nil {
|
2020-04-26 06:48:10 +05:30
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
runner.tempFile = tempInput.Name()
|
|
|
|
|
tempInput.Close()
|
|
|
|
|
}
|
2020-06-25 21:40:20 +05:30
|
|
|
// 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
|
|
|
|
|
}
|
|
|
|
|
tempInput.WriteString(options.Target)
|
|
|
|
|
runner.tempFile = tempInput.Name()
|
|
|
|
|
tempInput.Close()
|
|
|
|
|
}
|
2020-04-26 06:48:10 +05:30
|
|
|
|
2020-04-04 18:21:05 +05:30
|
|
|
// Create the output file if asked
|
|
|
|
|
if options.Output != "" {
|
|
|
|
|
output, err := os.Create(options.Output)
|
|
|
|
|
if err != nil {
|
|
|
|
|
gologger.Fatalf("Could not create output file '%s': %s\n", options.Output, err)
|
|
|
|
|
}
|
|
|
|
|
runner.output = output
|
|
|
|
|
}
|
2020-04-04 15:59:05 +05:30
|
|
|
return runner, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Close releases all the resources and cleans up
|
2020-04-04 18:21:05 +05:30
|
|
|
func (r *Runner) Close() {
|
|
|
|
|
r.output.Close()
|
2020-04-26 06:48:10 +05:30
|
|
|
os.Remove(r.tempFile)
|
2020-04-04 18:21:05 +05:30
|
|
|
}
|
2020-04-04 15:59:05 +05:30
|
|
|
|
2020-04-05 01:18:57 +05:30
|
|
|
// RunEnumeration sets up the input layer for giving input nuclei.
|
2020-04-04 15:59:05 +05:30
|
|
|
// binary and runs the actual enumeration
|
|
|
|
|
func (r *Runner) RunEnumeration() {
|
|
|
|
|
// If the template path is a single template and not a glob, use that.
|
2020-05-02 12:10:52 -05:00
|
|
|
if !strings.Contains(r.options.Templates, "*") && strings.HasSuffix(r.options.Templates, ".yaml") {
|
2020-04-23 03:56:41 +05:30
|
|
|
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
|
|
|
|
|
}
|
2020-04-22 22:45:02 +02:00
|
|
|
|
|
|
|
|
// process http requests
|
2020-06-22 19:57:32 +05:30
|
|
|
var results bool
|
2020-04-22 22:45:02 +02:00
|
|
|
for _, request := range template.RequestsHTTP {
|
2020-06-22 19:57:32 +05:30
|
|
|
results = r.processTemplateRequest(template, request)
|
2020-04-22 22:45:02 +02:00
|
|
|
}
|
|
|
|
|
// process dns requests
|
|
|
|
|
for _, request := range template.RequestsDNS {
|
2020-06-22 19:57:32 +05:30
|
|
|
dnsResults := r.processTemplateRequest(template, request)
|
|
|
|
|
if !results {
|
|
|
|
|
results = dnsResults
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if !results {
|
|
|
|
|
if r.output != nil {
|
|
|
|
|
outputFile := r.output.Name()
|
|
|
|
|
r.output.Close()
|
|
|
|
|
os.Remove(outputFile)
|
|
|
|
|
}
|
|
|
|
|
gologger.Infof("No results found for the template. Happy hacking!")
|
2020-04-23 03:56:41 +05:30
|
|
|
}
|
2020-04-04 17:36:20 +05:30
|
|
|
return
|
2020-04-04 15:59:05 +05:30
|
|
|
}
|
2020-05-02 12:10:52 -05:00
|
|
|
// If the template path is glob
|
|
|
|
|
if strings.Contains(r.options.Templates, "*") {
|
|
|
|
|
// Handle the glob, evaluate it and run all the template file checks
|
|
|
|
|
matches, err := filepath.Glob(r.options.Templates)
|
|
|
|
|
if err != nil {
|
|
|
|
|
gologger.Fatalf("Could not evaluate template path '%s': %s\n", r.options.Templates, err)
|
|
|
|
|
}
|
2020-04-04 15:59:05 +05:30
|
|
|
|
2020-06-22 19:57:32 +05:30
|
|
|
var results bool
|
2020-05-02 12:10:52 -05:00
|
|
|
for _, match := range matches {
|
|
|
|
|
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.RequestsDNS {
|
2020-06-22 19:57:32 +05:30
|
|
|
dnsResults := r.processTemplateRequest(template, request)
|
|
|
|
|
if dnsResults {
|
|
|
|
|
results = dnsResults
|
|
|
|
|
}
|
2020-05-02 12:10:52 -05:00
|
|
|
}
|
|
|
|
|
for _, request := range template.RequestsHTTP {
|
2020-06-22 19:57:32 +05:30
|
|
|
httpResults := r.processTemplateRequest(template, request)
|
|
|
|
|
if httpResults {
|
|
|
|
|
results = httpResults
|
|
|
|
|
}
|
2020-05-02 12:10:52 -05:00
|
|
|
}
|
|
|
|
|
}
|
2020-06-22 19:57:32 +05:30
|
|
|
if !results {
|
|
|
|
|
if r.output != nil {
|
|
|
|
|
outputFile := r.output.Name()
|
|
|
|
|
r.output.Close()
|
|
|
|
|
os.Remove(outputFile)
|
|
|
|
|
}
|
|
|
|
|
gologger.Infof("No results found for the templates. Happy hacking!")
|
|
|
|
|
}
|
2020-05-02 12:10:52 -05:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
// If the template passed is a directory
|
|
|
|
|
matches := []string{}
|
|
|
|
|
// Recursively walk down the Templates directory and run all the template file checks
|
|
|
|
|
err := godirwalk.Walk(r.options.Templates, &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,
|
|
|
|
|
})
|
2020-04-04 15:59:05 +05:30
|
|
|
if err != nil {
|
2020-05-02 12:22:05 -05:00
|
|
|
gologger.Fatalf("Could not find templates in directory '%s': %s\n", r.options.Templates, err)
|
2020-05-02 12:10:52 -05:00
|
|
|
}
|
|
|
|
|
// 0 matches means no templates were found in directory
|
|
|
|
|
if len(matches) == 0 {
|
|
|
|
|
gologger.Fatalf("Error, no templates found in directory: '%s'\n", r.options.Templates)
|
2020-04-04 15:59:05 +05:30
|
|
|
}
|
2020-06-22 19:57:32 +05:30
|
|
|
var results bool
|
2020-04-04 15:59:05 +05:30
|
|
|
for _, match := range matches {
|
2020-04-23 03:56:41 +05:30
|
|
|
template, err := templates.ParseTemplate(match)
|
|
|
|
|
if err != nil {
|
|
|
|
|
gologger.Errorf("Could not parse template file '%s': %s\n", match, err)
|
|
|
|
|
return
|
|
|
|
|
}
|
2020-04-26 06:48:10 +05:30
|
|
|
for _, request := range template.RequestsDNS {
|
2020-06-22 19:57:32 +05:30
|
|
|
dnsResults := r.processTemplateRequest(template, request)
|
|
|
|
|
if dnsResults {
|
|
|
|
|
results = dnsResults
|
|
|
|
|
}
|
2020-04-22 22:45:02 +02:00
|
|
|
}
|
2020-04-26 06:48:10 +05:30
|
|
|
for _, request := range template.RequestsHTTP {
|
2020-06-22 19:57:32 +05:30
|
|
|
httpResults := r.processTemplateRequest(template, request)
|
|
|
|
|
if httpResults {
|
|
|
|
|
results = httpResults
|
|
|
|
|
}
|
2020-04-23 03:56:41 +05:30
|
|
|
}
|
2020-04-04 15:59:05 +05:30
|
|
|
}
|
2020-06-22 19:57:32 +05:30
|
|
|
if !results {
|
|
|
|
|
if r.output != nil {
|
|
|
|
|
outputFile := r.output.Name()
|
|
|
|
|
r.output.Close()
|
|
|
|
|
os.Remove(outputFile)
|
|
|
|
|
}
|
|
|
|
|
gologger.Infof("No results found for the template. Happy hacking!")
|
|
|
|
|
}
|
2020-05-02 12:10:52 -05:00
|
|
|
return
|
2020-04-04 15:59:05 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// processTemplate processes a template and runs the enumeration on all the targets
|
2020-06-22 19:57:32 +05:30
|
|
|
func (r *Runner) processTemplateRequest(template *templates.Template, request interface{}) bool {
|
2020-04-26 06:48:10 +05:30
|
|
|
var file *os.File
|
|
|
|
|
var err error
|
|
|
|
|
|
2020-04-04 15:59:05 +05:30
|
|
|
// Handle a list of hosts as argument
|
|
|
|
|
if r.options.Targets != "" {
|
2020-04-26 06:48:10 +05:30
|
|
|
file, err = os.Open(r.options.Targets)
|
2020-06-25 21:40:20 +05:30
|
|
|
} else if r.options.Stdin || r.options.Target != "" {
|
2020-04-26 06:48:10 +05:30
|
|
|
file, err = os.Open(r.tempFile)
|
2020-04-04 15:59:05 +05:30
|
|
|
}
|
2020-04-26 06:48:10 +05:30
|
|
|
if err != nil {
|
|
|
|
|
gologger.Fatalf("Could not open targets file '%s': %s\n", r.options.Targets, err)
|
2020-04-04 15:59:05 +05:30
|
|
|
}
|
2020-06-22 19:57:32 +05:30
|
|
|
results := r.processTemplateWithList(template, request, file)
|
2020-04-26 06:48:10 +05:30
|
|
|
file.Close()
|
2020-06-22 19:57:32 +05:30
|
|
|
return results
|
2020-04-04 15:59:05 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// processDomain processes the list with a template
|
2020-06-22 19:57:32 +05:30
|
|
|
func (r *Runner) processTemplateWithList(template *templates.Template, request interface{}, reader io.Reader) bool {
|
2020-04-04 15:59:05 +05:30
|
|
|
// Display the message for the template
|
|
|
|
|
message := fmt.Sprintf("[%s] Loaded template %s (@%s)", template.ID, template.Info.Name, template.Info.Author)
|
|
|
|
|
if template.Info.Severity != "" {
|
|
|
|
|
message += " [" + template.Info.Severity + "]"
|
|
|
|
|
}
|
|
|
|
|
gologger.Infof("%s\n", message)
|
|
|
|
|
|
2020-04-04 18:21:05 +05:30
|
|
|
var writer *bufio.Writer
|
|
|
|
|
if r.output != nil {
|
|
|
|
|
writer = bufio.NewWriter(r.output)
|
|
|
|
|
defer writer.Flush()
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-26 06:48:10 +05:30
|
|
|
var httpExecutor *executor.HTTPExecutor
|
|
|
|
|
var dnsExecutor *executor.DNSExecutor
|
2020-04-27 23:49:53 +05:30
|
|
|
var err error
|
2020-04-26 06:48:10 +05:30
|
|
|
|
|
|
|
|
// Create an executor based on the request type.
|
|
|
|
|
switch value := request.(type) {
|
|
|
|
|
case *requests.DNSRequest:
|
|
|
|
|
dnsExecutor = executor.NewDNSExecutor(&executor.DNSOptions{
|
2020-06-22 19:30:01 +05:30
|
|
|
Debug: r.options.Debug,
|
2020-04-26 06:48:10 +05:30
|
|
|
Template: template,
|
|
|
|
|
DNSRequest: value,
|
|
|
|
|
Writer: writer,
|
|
|
|
|
})
|
|
|
|
|
case *requests.HTTPRequest:
|
2020-04-27 23:49:53 +05:30
|
|
|
httpExecutor, err = executor.NewHTTPExecutor(&executor.HTTPOptions{
|
2020-06-22 19:30:01 +05:30
|
|
|
Debug: r.options.Debug,
|
2020-04-28 13:24:12 +02:00
|
|
|
Template: template,
|
|
|
|
|
HTTPRequest: value,
|
|
|
|
|
Writer: writer,
|
|
|
|
|
Timeout: r.options.Timeout,
|
|
|
|
|
Retries: r.options.Retries,
|
|
|
|
|
ProxyURL: r.options.ProxyURL,
|
|
|
|
|
ProxySocksURL: r.options.ProxySocksURL,
|
2020-05-22 00:23:38 +02:00
|
|
|
CustomHeaders: r.options.CustomHeaders,
|
2020-04-26 06:48:10 +05:30
|
|
|
})
|
|
|
|
|
}
|
2020-04-27 23:49:53 +05:30
|
|
|
if err != nil {
|
|
|
|
|
gologger.Warningf("Could not create http client: %s\n", err)
|
2020-06-22 19:57:32 +05:30
|
|
|
return false
|
2020-04-27 23:49:53 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
limiter := make(chan struct{}, r.options.Threads)
|
|
|
|
|
wg := &sync.WaitGroup{}
|
2020-04-23 03:56:41 +05:30
|
|
|
|
2020-04-04 15:59:05 +05:30
|
|
|
scanner := bufio.NewScanner(reader)
|
|
|
|
|
for scanner.Scan() {
|
|
|
|
|
text := scanner.Text()
|
|
|
|
|
if text == "" {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
limiter <- struct{}{}
|
|
|
|
|
wg.Add(1)
|
|
|
|
|
|
|
|
|
|
go func(URL string) {
|
2020-04-26 06:48:10 +05:30
|
|
|
var err error
|
|
|
|
|
|
|
|
|
|
if httpExecutor != nil {
|
|
|
|
|
err = httpExecutor.ExecuteHTTP(text)
|
|
|
|
|
}
|
|
|
|
|
if dnsExecutor != nil {
|
|
|
|
|
err = dnsExecutor.ExecuteDNS(text)
|
|
|
|
|
}
|
|
|
|
|
if err != nil {
|
|
|
|
|
gologger.Warningf("Could not execute step: %s\n", err)
|
|
|
|
|
}
|
2020-04-04 15:59:05 +05:30
|
|
|
<-limiter
|
|
|
|
|
wg.Done()
|
|
|
|
|
}(text)
|
|
|
|
|
}
|
|
|
|
|
close(limiter)
|
|
|
|
|
wg.Wait()
|
2020-06-22 19:57:32 +05:30
|
|
|
|
|
|
|
|
// See if we got any results from the executors
|
|
|
|
|
var results bool
|
|
|
|
|
if httpExecutor != nil {
|
|
|
|
|
results = httpExecutor.GotResults()
|
|
|
|
|
}
|
|
|
|
|
if dnsExecutor != nil {
|
|
|
|
|
if !results {
|
|
|
|
|
results = dnsExecutor.GotResults()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return results
|
2020-04-04 15:59:05 +05:30
|
|
|
}
|