mirror of https://github.com/daffainfo/nuclei.git
rebased on master
commit
2b109b5a82
|
@ -13,6 +13,7 @@ import (
|
|||
type Options struct {
|
||||
Debug bool // Debug mode allows debugging request/responses for the engine
|
||||
Templates string // Signature specifies the template/templates to use
|
||||
Target string // Target is a single URL/Domain to scan usng a template
|
||||
Targets string // Targets specifies the targets to scan using templates.
|
||||
Threads int // Thread controls the number of concurrent requests to make.
|
||||
Timeout int // Timeout is the seconds to wait for a response from the server.
|
||||
|
@ -27,6 +28,7 @@ type Options struct {
|
|||
CustomHeaders requests.CustomHeaders // Custom global headers
|
||||
UpdateTemplates bool // UpdateTemplates updates the templates installed at startup
|
||||
TemplatesDirectory string // TemplatesDirectory is the directory to use for storing templates
|
||||
JSON bool // JSON writes json output to files
|
||||
|
||||
Stdin bool // Stdin specifies whether stdin input was given to the process
|
||||
}
|
||||
|
@ -35,6 +37,7 @@ type Options struct {
|
|||
func ParseOptions() *Options {
|
||||
options := &Options{}
|
||||
|
||||
flag.StringVar(&options.Target, "target", "", "Target is a single target to scan using template")
|
||||
flag.StringVar(&options.Templates, "t", "", "Template input file/files to run on host")
|
||||
flag.StringVar(&options.Targets, "l", "", "List of URLs to run templates on")
|
||||
flag.StringVar(&options.Output, "o", "", "File to write output to (optional)")
|
||||
|
@ -50,7 +53,8 @@ func ParseOptions() *Options {
|
|||
flag.Var(&options.CustomHeaders, "H", "Custom Header.")
|
||||
flag.BoolVar(&options.Debug, "debug", false, "Allow debugging of request/responses")
|
||||
flag.BoolVar(&options.UpdateTemplates, "update-templates", false, "Update Templates updates the installed templates (optional)")
|
||||
flag.StringVar(&options.TemplatesDirectory, "templates-directory", "", "Directory to use for storing nuclei-templates")
|
||||
flag.StringVar(&options.TemplatesDirectory, "update-directory", "", "Directory to use for storing nuclei-templates")
|
||||
flag.BoolVar(&options.JSON, "json", false, "Write json output to files")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
|
|
|
@ -8,7 +8,6 @@ import (
|
|||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
|
@ -43,7 +42,7 @@ func New(options *Options) (*Runner, error) {
|
|||
if err := runner.updateTemplates(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if (options.Templates == "" || options.Targets == "" && !options.Stdin) && options.UpdateTemplates {
|
||||
if (options.Templates == "" || (options.Targets == "" && !options.Stdin && options.Target == "")) && options.UpdateTemplates {
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
|
@ -59,6 +58,16 @@ func New(options *Options) (*Runner, error) {
|
|||
runner.tempFile = tempInput.Name()
|
||||
tempInput.Close()
|
||||
}
|
||||
// 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()
|
||||
}
|
||||
|
||||
// Create the output file if asked
|
||||
if options.Output != "" {
|
||||
|
@ -80,22 +89,37 @@ func (r *Runner) Close() {
|
|||
// RunEnumeration sets up the input layer for giving input nuclei.
|
||||
// binary and runs the actual enumeration
|
||||
func (r *Runner) RunEnumeration() {
|
||||
var err error
|
||||
|
||||
// Check if the template is an absolute path or relative path.
|
||||
// If the path is absolute, use it. Otherwise,
|
||||
if r.isRelative(r.options.Templates) {
|
||||
r.options.Templates, err = r.resolvePath(r.options.Templates)
|
||||
if err != nil {
|
||||
gologger.Errorf("Could not find template file '%s': %s\n", r.options.Templates, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Single yaml provided
|
||||
if !strings.Contains(r.options.Templates, "*") && strings.HasSuffix(r.options.Templates, ".yaml") {
|
||||
if strings.HasSuffix(r.options.Templates, ".yaml") {
|
||||
t := r.parse(r.options.Templates)
|
||||
switch t.(type) {
|
||||
case *templates.Template:
|
||||
results := false
|
||||
var results bool
|
||||
template := t.(*templates.Template)
|
||||
// process http requests
|
||||
for _, request := range template.RequestsHTTP {
|
||||
results = r.processTemplateRequest(template, request)
|
||||
}
|
||||
// process dns requests
|
||||
for _, request := range template.RequestsDNS {
|
||||
dnsResults := r.processTemplateRequest(template, request)
|
||||
if !results {
|
||||
results = dnsResults
|
||||
}
|
||||
}
|
||||
|
||||
if !results {
|
||||
if r.output != nil {
|
||||
outputFile := r.output.Name()
|
||||
|
@ -113,53 +137,11 @@ func (r *Runner) RunEnumeration() {
|
|||
return
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
var results bool
|
||||
for _, match := range matches {
|
||||
t := r.parse(match)
|
||||
switch t.(type) {
|
||||
case *templates.Template:
|
||||
template := t.(*templates.Template)
|
||||
for _, request := range template.RequestsDNS {
|
||||
dnsResults := r.processTemplateRequest(template, request)
|
||||
if dnsResults {
|
||||
results = dnsResults
|
||||
}
|
||||
}
|
||||
for _, request := range template.RequestsHTTP {
|
||||
httpResults := r.processTemplateRequest(template, request)
|
||||
if httpResults {
|
||||
results = httpResults
|
||||
}
|
||||
}
|
||||
case *workflows.Workflow:
|
||||
workflow := t.(*workflows.Workflow)
|
||||
r.ProcessWorkflowWithList(workflow)
|
||||
default:
|
||||
gologger.Errorf("Could not parse file '%s'\n", r.options.Templates)
|
||||
}
|
||||
}
|
||||
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!")
|
||||
}
|
||||
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{
|
||||
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)
|
||||
|
@ -178,13 +160,13 @@ func (r *Runner) RunEnumeration() {
|
|||
if len(matches) == 0 {
|
||||
gologger.Fatalf("Error, no templates found in directory: '%s'\n", r.options.Templates)
|
||||
}
|
||||
|
||||
var results bool
|
||||
for _, match := range matches {
|
||||
t := r.parse(match)
|
||||
switch t.(type) {
|
||||
case *templates.Template:
|
||||
template := t.(*templates.Template)
|
||||
|
||||
for _, request := range template.RequestsDNS {
|
||||
dnsResults := r.processTemplateRequest(template, request)
|
||||
if dnsResults {
|
||||
|
@ -223,7 +205,7 @@ func (r *Runner) processTemplateRequest(template *templates.Template, request in
|
|||
// Handle a list of hosts as argument
|
||||
if r.options.Targets != "" {
|
||||
file, err = os.Open(r.options.Targets)
|
||||
} else if r.options.Stdin {
|
||||
} else if r.options.Stdin || r.options.Target != "" {
|
||||
file, err = os.Open(r.tempFile)
|
||||
}
|
||||
if err != nil {
|
||||
|
@ -261,6 +243,7 @@ func (r *Runner) processTemplateWithList(template *templates.Template, request i
|
|||
Template: template,
|
||||
DNSRequest: value,
|
||||
Writer: writer,
|
||||
JSON: r.options.JSON,
|
||||
})
|
||||
case *requests.HTTPRequest:
|
||||
httpExecutor, err = executor.NewHTTPExecutor(&executor.HTTPOptions{
|
||||
|
@ -273,6 +256,7 @@ func (r *Runner) processTemplateWithList(template *templates.Template, request i
|
|||
ProxyURL: r.options.ProxyURL,
|
||||
ProxySocksURL: r.options.ProxySocksURL,
|
||||
CustomHeaders: r.options.CustomHeaders,
|
||||
JSON: r.options.JSON,
|
||||
})
|
||||
}
|
||||
if err != nil {
|
||||
|
|
|
@ -96,7 +96,7 @@ func (r *Runner) updateTemplates() error {
|
|||
|
||||
if r.templatesConfig == nil || (r.options.TemplatesDirectory != "" && r.templatesConfig.TemplatesDirectory != r.options.TemplatesDirectory) {
|
||||
if !r.options.UpdateTemplates {
|
||||
gologger.Warningf("nuclei-templates are not installed, use update-templates flag.\n")
|
||||
gologger.Labelf("nuclei-templates are not installed, use update-templates flag.\n")
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -147,12 +147,12 @@ func (r *Runner) updateTemplates() error {
|
|||
}
|
||||
|
||||
if version.EQ(oldVersion) {
|
||||
gologger.Verbosef("Latest version of nuclei-templates installed: v%s\n", "update-templates", oldVersion.String())
|
||||
gologger.Labelf("Latest version of nuclei-templates installed: v%s\n", oldVersion.String())
|
||||
return nil
|
||||
}
|
||||
if version.GT(oldVersion) {
|
||||
if !r.options.UpdateTemplates {
|
||||
gologger.Warningf("You're using outdated nuclei-templates. Latest v%s\n", version.String())
|
||||
gologger.Labelf("You're using outdated nuclei-templates. Latest v%s\n", version.String())
|
||||
return nil
|
||||
}
|
||||
if r.options.TemplatesDirectory != "" {
|
||||
|
@ -273,3 +273,35 @@ func (r *Runner) downloadReleaseAndUnzip(downloadURL string) error {
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// isRelative checks if a given path is a relative path
|
||||
func (r *Runner) isRelative(path string) bool {
|
||||
if !strings.HasPrefix(path, "/") || !strings.Contains(path, ":\\") {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// resolvePath gets the absolute path to the template by either
|
||||
// looking in the current directory or checking the nuclei templates directory.
|
||||
//
|
||||
// Current directory is given preference over the nuclei-templates directory.
|
||||
func (r *Runner) resolvePath(templateName string) (string, error) {
|
||||
curDirectory, err := os.Getwd()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
templatePath := path.Join(curDirectory, templateName)
|
||||
if _, err := os.Stat(templatePath); !os.IsNotExist(err) {
|
||||
gologger.Infof("Found template in current directory: %s\n", templatePath)
|
||||
return templatePath, nil
|
||||
}
|
||||
if r.templatesConfig != nil {
|
||||
templatePath := path.Join(r.templatesConfig.TemplatesDirectory, templateName)
|
||||
if _, err := os.Stat(templatePath); !os.IsNotExist(err) {
|
||||
gologger.Infof("Found template in nuclei-templates directory: %s\n", templatePath)
|
||||
return templatePath, nil
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf("no such path found: %s", templateName)
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ func (options *Options) validateOptions() error {
|
|||
return errors.New("no template/templates provided")
|
||||
}
|
||||
|
||||
if options.Targets == "" && !options.Stdin && !options.UpdateTemplates {
|
||||
if options.Targets == "" && !options.Stdin && options.Target == "" && !options.UpdateTemplates {
|
||||
return errors.New("no target input provided")
|
||||
}
|
||||
|
||||
|
|
|
@ -30,6 +30,7 @@ import (
|
|||
type HTTPExecutor struct {
|
||||
debug bool
|
||||
results uint32
|
||||
jsonOutput bool
|
||||
httpClient *retryablehttp.Client
|
||||
template *templates.Template
|
||||
httpRequest *requests.HTTPRequest
|
||||
|
@ -48,6 +49,7 @@ type HTTPOptions struct {
|
|||
ProxyURL string
|
||||
ProxySocksURL string
|
||||
Debug bool
|
||||
JSON bool
|
||||
CustomHeaders requests.CustomHeaders
|
||||
}
|
||||
|
||||
|
@ -70,6 +72,7 @@ func NewHTTPExecutor(options *HTTPOptions) (*HTTPExecutor, error) {
|
|||
|
||||
executer := &HTTPExecutor{
|
||||
debug: options.Debug,
|
||||
jsonOutput: options.JSON,
|
||||
results: 0,
|
||||
httpClient: client,
|
||||
template: options.Template,
|
||||
|
|
|
@ -19,6 +19,7 @@ import (
|
|||
// for a template.
|
||||
type DNSExecutor struct {
|
||||
debug bool
|
||||
jsonOutput bool
|
||||
results uint32
|
||||
dnsClient *retryabledns.Client
|
||||
template *templates.Template
|
||||
|
@ -38,6 +39,7 @@ var DefaultResolvers = []string{
|
|||
// DNSOptions contains configuration options for the DNS executor.
|
||||
type DNSOptions struct {
|
||||
Debug bool
|
||||
JSON bool
|
||||
Template *templates.Template
|
||||
DNSRequest *requests.DNSRequest
|
||||
Writer *bufio.Writer
|
||||
|
@ -50,6 +52,7 @@ func NewDNSExecutor(options *DNSOptions) *DNSExecutor {
|
|||
|
||||
executer := &DNSExecutor{
|
||||
debug: options.Debug,
|
||||
jsonOutput: options.JSON,
|
||||
results: 0,
|
||||
dnsClient: dnsClient,
|
||||
template: options.Template,
|
||||
|
|
|
@ -6,6 +6,16 @@ import (
|
|||
"unsafe"
|
||||
)
|
||||
|
||||
type jsonOutput struct {
|
||||
Template string `json:"template"`
|
||||
Type string `json:"type"`
|
||||
Matched string `json:"matched"`
|
||||
MatcherName string `json:"matcher_name,omitempty"`
|
||||
ExtractedResults []string `json:"extracted_results,omitempty"`
|
||||
Severity string `json:"severity"`
|
||||
Author string `json:"author"`
|
||||
}
|
||||
|
||||
// unsafeToString converts byte slice to string with zero allocations
|
||||
func unsafeToString(bs []byte) string {
|
||||
return *(*string)(unsafe.Pointer(&bs))
|
||||
|
|
|
@ -3,12 +3,43 @@ package executor
|
|||
import (
|
||||
"strings"
|
||||
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"github.com/projectdiscovery/gologger"
|
||||
"github.com/projectdiscovery/nuclei/pkg/matchers"
|
||||
)
|
||||
|
||||
// writeOutputDNS writes dns output to streams
|
||||
func (e *DNSExecutor) writeOutputDNS(domain string, matcher *matchers.Matcher, extractorResults []string) {
|
||||
if e.jsonOutput {
|
||||
output := jsonOutput{
|
||||
Template: e.template.ID,
|
||||
Type: "dns",
|
||||
Matched: domain,
|
||||
Severity: e.template.Info.Severity,
|
||||
Author: e.template.Info.Author,
|
||||
}
|
||||
if matcher != nil && len(matcher.Name) > 0 {
|
||||
output.MatcherName = matcher.Name
|
||||
}
|
||||
if len(extractorResults) > 0 {
|
||||
output.ExtractedResults = extractorResults
|
||||
}
|
||||
data, err := jsoniter.Marshal(output)
|
||||
if err != nil {
|
||||
gologger.Warningf("Could not marshal json output: %s\n", err)
|
||||
}
|
||||
|
||||
gologger.Silentf("%s", string(data))
|
||||
|
||||
if e.writer != nil {
|
||||
e.outputMutex.Lock()
|
||||
e.writer.Write(data)
|
||||
e.writer.WriteRune('\n')
|
||||
e.outputMutex.Unlock()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
builder := &strings.Builder{}
|
||||
builder.WriteRune('[')
|
||||
builder.WriteString(e.template.ID)
|
||||
|
|
|
@ -3,6 +3,7 @@ package executor
|
|||
import (
|
||||
"strings"
|
||||
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"github.com/projectdiscovery/gologger"
|
||||
"github.com/projectdiscovery/nuclei/pkg/matchers"
|
||||
"github.com/projectdiscovery/nuclei/pkg/requests"
|
||||
|
@ -10,6 +11,38 @@ import (
|
|||
|
||||
// writeOutputHTTP writes http output to streams
|
||||
func (e *HTTPExecutor) writeOutputHTTP(req *requests.CompiledHTTP, matcher *matchers.Matcher, extractorResults []string) {
|
||||
URL := req.Request.URL.String()
|
||||
|
||||
if e.jsonOutput {
|
||||
output := jsonOutput{
|
||||
Template: e.template.ID,
|
||||
Type: "http",
|
||||
Matched: URL,
|
||||
Severity: e.template.Info.Severity,
|
||||
Author: e.template.Info.Author,
|
||||
}
|
||||
if matcher != nil && len(matcher.Name) > 0 {
|
||||
output.MatcherName = matcher.Name
|
||||
}
|
||||
if len(extractorResults) > 0 {
|
||||
output.ExtractedResults = extractorResults
|
||||
}
|
||||
data, err := jsoniter.Marshal(output)
|
||||
if err != nil {
|
||||
gologger.Warningf("Could not marshal json output: %s\n", err)
|
||||
}
|
||||
|
||||
gologger.Silentf("%s", string(data))
|
||||
|
||||
if e.writer != nil {
|
||||
e.outputMutex.Lock()
|
||||
e.writer.Write(data)
|
||||
e.writer.WriteRune('\n')
|
||||
e.outputMutex.Unlock()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
builder := &strings.Builder{}
|
||||
|
||||
builder.WriteRune('[')
|
||||
|
@ -21,7 +54,6 @@ func (e *HTTPExecutor) writeOutputHTTP(req *requests.CompiledHTTP, matcher *matc
|
|||
builder.WriteString("] [http] ")
|
||||
|
||||
// Escape the URL by replacing all % with %%
|
||||
URL := req.Request.URL.String()
|
||||
escapedURL := strings.Replace(URL, "%", "%%", -1)
|
||||
builder.WriteString(escapedURL)
|
||||
|
||||
|
|
Loading…
Reference in New Issue