mirror of https://github.com/daffainfo/nuclei.git
303 lines
11 KiB
Go
303 lines
11 KiB
Go
package dns
|
|
|
|
import (
|
|
"strings"
|
|
|
|
"github.com/miekg/dns"
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/operators"
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/protocols"
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/expressions"
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators"
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/replacer"
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/dns/dnsclientpool"
|
|
"github.com/projectdiscovery/retryabledns"
|
|
fileutil "github.com/projectdiscovery/utils/file"
|
|
)
|
|
|
|
// Request contains a DNS protocol request to be made from a template
|
|
type Request struct {
|
|
// Operators for the current request go here.
|
|
operators.Operators `yaml:",inline"`
|
|
|
|
// ID is the optional id of the request
|
|
ID string `yaml:"id,omitempty" json:"id,omitempty" jsonschema:"title=id of the dns request,description=ID is the optional ID of the DNS Request"`
|
|
|
|
// description: |
|
|
// Name is the Hostname to make DNS request for.
|
|
//
|
|
// Generally, it is set to {{FQDN}} which is the domain we get from input.
|
|
// examples:
|
|
// - value: "\"{{FQDN}}\""
|
|
Name string `yaml:"name,omitempty" json:"name,omitempty" jsonschema:"title=hostname to make dns request for,description=Name is the Hostname to make DNS request for"`
|
|
// description: |
|
|
// RequestType is the type of DNS request to make.
|
|
RequestType DNSRequestTypeHolder `yaml:"type,omitempty" json:"type,omitempty" jsonschema:"title=type of dns request to make,description=Type is the type of DNS request to make,enum=A,enum=NS,enum=DS,enum=CNAME,enum=SOA,enum=PTR,enum=MX,enum=TXT,enum=AAAA"`
|
|
// description: |
|
|
// Class is the class of the DNS request.
|
|
//
|
|
// Usually it's enough to just leave it as INET.
|
|
// values:
|
|
// - "inet"
|
|
// - "csnet"
|
|
// - "chaos"
|
|
// - "hesiod"
|
|
// - "none"
|
|
// - "any"
|
|
Class string `yaml:"class,omitempty" json:"class,omitempty" jsonschema:"title=class of DNS request,description=Class is the class of the DNS request,enum=inet,enum=csnet,enum=chaos,enum=hesiod,enum=none,enum=any"`
|
|
// description: |
|
|
// Retries is the number of retries for the DNS request
|
|
// examples:
|
|
// - name: Use a retry of 3 to 5 generally
|
|
// value: 5
|
|
Retries int `yaml:"retries,omitempty" json:"retries,omitempty" jsonschema:"title=retries for dns request,description=Retries is the number of retries for the DNS request"`
|
|
// description: |
|
|
// Trace performs a trace operation for the target.
|
|
Trace bool `yaml:"trace,omitempty" json:"trace,omitempty" jsonschema:"title=trace operation,description=Trace performs a trace operation for the target."`
|
|
// description: |
|
|
// TraceMaxRecursion is the number of max recursion allowed for trace operations
|
|
// examples:
|
|
// - name: Use a retry of 100 to 150 generally
|
|
// value: 100
|
|
TraceMaxRecursion int `yaml:"trace-max-recursion,omitempty" jsonschema:"title=trace-max-recursion level for dns request,description=TraceMaxRecursion is the number of max recursion allowed for trace operations"`
|
|
|
|
// description: |
|
|
// Attack is the type of payload combinations to perform.
|
|
//
|
|
// Batteringram is inserts the same payload into all defined payload positions at once, pitchfork combines multiple payload sets and clusterbomb generates
|
|
// permutations and combinations for all payloads.
|
|
AttackType generators.AttackTypeHolder `yaml:"attack,omitempty" json:"attack,omitempty" jsonschema:"title=attack is the payload combination,description=Attack is the type of payload combinations to perform,enum=batteringram,enum=pitchfork,enum=clusterbomb"`
|
|
// description: |
|
|
// Payloads contains any payloads for the current request.
|
|
//
|
|
// Payloads support both key-values combinations where a list
|
|
// of payloads is provided, or optionally a single file can also
|
|
// be provided as payload which will be read on run-time.
|
|
Payloads map[string]interface{} `yaml:"payloads,omitempty" json:"payloads,omitempty" jsonschema:"title=payloads for the network request,description=Payloads contains any payloads for the current request"`
|
|
// description: |
|
|
// Threads to use when sending iterating over payloads
|
|
// examples:
|
|
// - name: Send requests using 10 concurrent threads
|
|
// value: 10
|
|
Threads int `yaml:"threads,omitempty" json:"threads,omitempty" jsonschema:"title=threads for sending requests,description=Threads specifies number of threads to use sending requests. This enables Connection Pooling"`
|
|
generator *generators.PayloadGenerator
|
|
|
|
CompiledOperators *operators.Operators `yaml:"-"`
|
|
dnsClient *retryabledns.Client
|
|
options *protocols.ExecutorOptions
|
|
|
|
// cache any variables that may be needed for operation.
|
|
class uint16
|
|
question uint16
|
|
|
|
// description: |
|
|
// Recursion determines if resolver should recurse all records to get fresh results.
|
|
Recursion *bool `yaml:"recursion,omitempty" json:"recursion,omitempty" jsonschema:"title=recurse all servers,description=Recursion determines if resolver should recurse all records to get fresh results"`
|
|
// Resolvers to use for the dns requests
|
|
Resolvers []string `yaml:"resolvers,omitempty" json:"resolvers,omitempty" jsonschema:"title=Resolvers,description=Define resolvers to use within the template"`
|
|
}
|
|
|
|
// RequestPartDefinitions contains a mapping of request part definitions and their
|
|
// description. Multiple definitions are separated by commas.
|
|
// Definitions not having a name (generated on runtime) are prefixed & suffixed by <>.
|
|
var RequestPartDefinitions = map[string]string{
|
|
"template-id": "ID of the template executed",
|
|
"template-info": "Info Block of the template executed",
|
|
"template-path": "Path of the template executed",
|
|
"host": "Host is the input to the template",
|
|
"matched": "Matched is the input which was matched upon",
|
|
"request": "Request contains the DNS request in text format",
|
|
"type": "Type is the type of request made",
|
|
"rcode": "Rcode field returned for the DNS request",
|
|
"question": "Question contains the DNS question field",
|
|
"extra": "Extra contains the DNS response extra field",
|
|
"answer": "Answer contains the DNS response answer field",
|
|
"ns": "NS contains the DNS response NS field",
|
|
"raw,body,all": "Raw contains the raw DNS response (default)",
|
|
"trace": "Trace contains trace data for DNS request if enabled",
|
|
}
|
|
|
|
func (request *Request) GetCompiledOperators() []*operators.Operators {
|
|
return []*operators.Operators{request.CompiledOperators}
|
|
}
|
|
|
|
// GetID returns the unique ID of the request if any.
|
|
func (request *Request) GetID() string {
|
|
return request.ID
|
|
}
|
|
|
|
// Options returns executer options for http request
|
|
func (r *Request) Options() *protocols.ExecutorOptions {
|
|
return r.options
|
|
}
|
|
|
|
// Compile compiles the protocol request for further execution.
|
|
func (request *Request) Compile(options *protocols.ExecutorOptions) error {
|
|
if request.Retries == 0 {
|
|
request.Retries = 3
|
|
}
|
|
if request.Recursion == nil {
|
|
recursion := true
|
|
request.Recursion = &recursion
|
|
}
|
|
dnsClientOptions := &dnsclientpool.Configuration{
|
|
Retries: request.Retries,
|
|
}
|
|
if len(request.Resolvers) > 0 {
|
|
dnsClientOptions.Resolvers = request.Resolvers
|
|
}
|
|
// Create a dns client for the class
|
|
client, err := request.getDnsClient(options, nil)
|
|
if err != nil {
|
|
return errors.Wrap(err, "could not get dns client")
|
|
}
|
|
request.dnsClient = client
|
|
|
|
if len(request.Matchers) > 0 || len(request.Extractors) > 0 {
|
|
compiled := &request.Operators
|
|
compiled.ExcludeMatchers = options.ExcludeMatchers
|
|
compiled.TemplateID = options.TemplateID
|
|
if err := compiled.Compile(); err != nil {
|
|
return errors.Wrap(err, "could not compile operators")
|
|
}
|
|
request.CompiledOperators = compiled
|
|
}
|
|
request.class = classToInt(request.Class)
|
|
request.options = options
|
|
request.question = questionTypeToInt(request.RequestType.String())
|
|
for name, payload := range options.Options.Vars.AsMap() {
|
|
payloadStr, ok := payload.(string)
|
|
// check if inputs contains the payload
|
|
if ok && fileutil.FileExists(payloadStr) {
|
|
if request.Payloads == nil {
|
|
request.Payloads = make(map[string]interface{})
|
|
}
|
|
request.Payloads[name] = payloadStr
|
|
}
|
|
}
|
|
|
|
if len(request.Payloads) > 0 {
|
|
request.generator, err = generators.New(request.Payloads, request.AttackType.Value, request.options.TemplatePath, request.options.Catalog, request.options.Options.AttackType, request.options.Options)
|
|
if err != nil {
|
|
return errors.Wrap(err, "could not parse payloads")
|
|
}
|
|
// default to 20 threads for payload requests
|
|
request.Threads = options.GetThreadsForNPayloadRequests(request.Requests(), request.Threads)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (request *Request) getDnsClient(options *protocols.ExecutorOptions, metadata map[string]interface{}) (*retryabledns.Client, error) {
|
|
dnsClientOptions := &dnsclientpool.Configuration{
|
|
Retries: request.Retries,
|
|
}
|
|
if len(request.Resolvers) > 0 {
|
|
if len(request.Resolvers) > 0 {
|
|
for _, resolver := range request.Resolvers {
|
|
if expressions.ContainsUnresolvedVariables(resolver) != nil {
|
|
var err error
|
|
resolver, err = expressions.Evaluate(resolver, metadata)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "could not resolve resolvers expressions")
|
|
}
|
|
dnsClientOptions.Resolvers = append(dnsClientOptions.Resolvers, resolver)
|
|
}
|
|
}
|
|
}
|
|
dnsClientOptions.Resolvers = request.Resolvers
|
|
}
|
|
return dnsclientpool.Get(options.Options, dnsClientOptions)
|
|
}
|
|
|
|
// Requests returns the total number of requests the YAML rule will perform
|
|
func (request *Request) Requests() int {
|
|
if request.generator != nil {
|
|
payloadRequests := request.generator.NewIterator().Total()
|
|
return payloadRequests
|
|
}
|
|
|
|
return 1
|
|
}
|
|
|
|
// Make returns the request to be sent for the protocol
|
|
func (request *Request) Make(host string, vars map[string]interface{}) (*dns.Msg, error) {
|
|
// Build a request on the specified URL
|
|
req := new(dns.Msg)
|
|
req.Id = dns.Id()
|
|
req.RecursionDesired = *request.Recursion
|
|
|
|
var q dns.Question
|
|
final := replacer.Replace(request.Name, vars)
|
|
|
|
q.Name = dns.Fqdn(final)
|
|
q.Qclass = request.class
|
|
q.Qtype = request.question
|
|
req.Question = append(req.Question, q)
|
|
|
|
req.SetEdns0(4096, false)
|
|
|
|
switch request.question {
|
|
case dns.TypeTXT:
|
|
req.AuthenticatedData = true
|
|
}
|
|
|
|
return req, nil
|
|
}
|
|
|
|
// questionTypeToInt converts DNS question type to internal representation
|
|
func questionTypeToInt(questionType string) uint16 {
|
|
questionType = strings.TrimSpace(strings.ToUpper(questionType))
|
|
question := dns.TypeA
|
|
|
|
switch questionType {
|
|
case "A":
|
|
question = dns.TypeA
|
|
case "NS":
|
|
question = dns.TypeNS
|
|
case "CNAME":
|
|
question = dns.TypeCNAME
|
|
case "SOA":
|
|
question = dns.TypeSOA
|
|
case "PTR":
|
|
question = dns.TypePTR
|
|
case "MX":
|
|
question = dns.TypeMX
|
|
case "TXT":
|
|
question = dns.TypeTXT
|
|
case "DS":
|
|
question = dns.TypeDS
|
|
case "AAAA":
|
|
question = dns.TypeAAAA
|
|
case "CAA":
|
|
question = dns.TypeCAA
|
|
case "TLSA":
|
|
question = dns.TypeTLSA
|
|
case "ANY":
|
|
question = dns.TypeANY
|
|
}
|
|
return question
|
|
}
|
|
|
|
// classToInt converts a dns class name to its internal representation
|
|
func classToInt(class string) uint16 {
|
|
class = strings.TrimSpace(strings.ToUpper(class))
|
|
result := dns.ClassINET
|
|
|
|
switch class {
|
|
case "INET":
|
|
result = dns.ClassINET
|
|
case "CSNET":
|
|
result = dns.ClassCSNET
|
|
case "CHAOS":
|
|
result = dns.ClassCHAOS
|
|
case "HESIOD":
|
|
result = dns.ClassHESIOD
|
|
case "NONE":
|
|
result = dns.ClassNONE
|
|
case "ANY":
|
|
result = dns.ClassANY
|
|
}
|
|
return uint16(result)
|
|
}
|