rebased on master

dev
Mzack9999 2020-06-27 22:01:01 +02:00
commit 2b109b5a82
9 changed files with 155 additions and 56 deletions

View File

@ -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()

View File

@ -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 {

View File

@ -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)
}

View File

@ -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")
}

View File

@ -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,

View File

@ -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,

View File

@ -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))

View File

@ -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)

View File

@ -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)