github.com/projectdiscovery/nuclei/v2@v2.9.15/pkg/protocols/dns/dns.go (about)

     1  package dns
     2  
     3  import (
     4  	"strings"
     5  
     6  	"github.com/miekg/dns"
     7  	"github.com/pkg/errors"
     8  
     9  	"github.com/projectdiscovery/nuclei/v2/pkg/operators"
    10  	"github.com/projectdiscovery/nuclei/v2/pkg/protocols"
    11  	"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/expressions"
    12  	"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/generators"
    13  	"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/replacer"
    14  	"github.com/projectdiscovery/nuclei/v2/pkg/protocols/dns/dnsclientpool"
    15  	"github.com/projectdiscovery/retryabledns"
    16  	fileutil "github.com/projectdiscovery/utils/file"
    17  )
    18  
    19  // Request contains a DNS protocol request to be made from a template
    20  type Request struct {
    21  	// Operators for the current request go here.
    22  	operators.Operators `yaml:",inline"`
    23  
    24  	// ID is the optional id of the request
    25  	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"`
    26  
    27  	// description: |
    28  	//   Name is the Hostname to make DNS request for.
    29  	//
    30  	//   Generally, it is set to {{FQDN}} which is the domain we get from input.
    31  	// examples:
    32  	//   - value: "\"{{FQDN}}\""
    33  	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"`
    34  	// description: |
    35  	//   RequestType is the type of DNS request to make.
    36  	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"`
    37  	// description: |
    38  	//   Class is the class of the DNS request.
    39  	//
    40  	//   Usually it's enough to just leave it as INET.
    41  	// values:
    42  	//   - "inet"
    43  	//   - "csnet"
    44  	//   - "chaos"
    45  	//   - "hesiod"
    46  	//   - "none"
    47  	//   - "any"
    48  	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"`
    49  	// description: |
    50  	//   Retries is the number of retries for the DNS request
    51  	// examples:
    52  	//   - name: Use a retry of 3 to 5 generally
    53  	//     value: 5
    54  	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"`
    55  	// description: |
    56  	//   Trace performs a trace operation for the target.
    57  	Trace bool `yaml:"trace,omitempty" json:"trace,omitempty" jsonschema:"title=trace operation,description=Trace performs a trace operation for the target."`
    58  	// description: |
    59  	//   TraceMaxRecursion is the number of max recursion allowed for trace operations
    60  	// examples:
    61  	//   - name: Use a retry of 100 to 150 generally
    62  	//     value: 100
    63  	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"`
    64  
    65  	// description: |
    66  	//   Attack is the type of payload combinations to perform.
    67  	//
    68  	//   Batteringram is inserts the same payload into all defined payload positions at once, pitchfork combines multiple payload sets and clusterbomb generates
    69  	//   permutations and combinations for all payloads.
    70  	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"`
    71  	// description: |
    72  	//   Payloads contains any payloads for the current request.
    73  	//
    74  	//   Payloads support both key-values combinations where a list
    75  	//   of payloads is provided, or optionally a single file can also
    76  	//   be provided as payload which will be read on run-time.
    77  	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"`
    78  	generator *generators.PayloadGenerator
    79  
    80  	CompiledOperators *operators.Operators `yaml:"-"`
    81  	dnsClient         *retryabledns.Client
    82  	options           *protocols.ExecutorOptions
    83  
    84  	// cache any variables that may be needed for operation.
    85  	class    uint16
    86  	question uint16
    87  
    88  	// description: |
    89  	//   Recursion determines if resolver should recurse all records to get fresh results.
    90  	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"`
    91  	// Resolvers to use for the dns requests
    92  	Resolvers []string `yaml:"resolvers,omitempty" json:"resolvers,omitempty" jsonschema:"title=Resolvers,description=Define resolvers to use within the template"`
    93  }
    94  
    95  // RequestPartDefinitions contains a mapping of request part definitions and their
    96  // description. Multiple definitions are separated by commas.
    97  // Definitions not having a name (generated on runtime) are prefixed & suffixed by <>.
    98  var RequestPartDefinitions = map[string]string{
    99  	"template-id":   "ID of the template executed",
   100  	"template-info": "Info Block of the template executed",
   101  	"template-path": "Path of the template executed",
   102  	"host":          "Host is the input to the template",
   103  	"matched":       "Matched is the input which was matched upon",
   104  	"request":       "Request contains the DNS request in text format",
   105  	"type":          "Type is the type of request made",
   106  	"rcode":         "Rcode field returned for the DNS request",
   107  	"question":      "Question contains the DNS question field",
   108  	"extra":         "Extra contains the DNS response extra field",
   109  	"answer":        "Answer contains the DNS response answer field",
   110  	"ns":            "NS contains the DNS response NS field",
   111  	"raw,body,all":  "Raw contains the raw DNS response (default)",
   112  	"trace":         "Trace contains trace data for DNS request if enabled",
   113  }
   114  
   115  func (request *Request) GetCompiledOperators() []*operators.Operators {
   116  	return []*operators.Operators{request.CompiledOperators}
   117  }
   118  
   119  // GetID returns the unique ID of the request if any.
   120  func (request *Request) GetID() string {
   121  	return request.ID
   122  }
   123  
   124  // Options returns executer options for http request
   125  func (r *Request) Options() *protocols.ExecutorOptions {
   126  	return r.options
   127  }
   128  
   129  // Compile compiles the protocol request for further execution.
   130  func (request *Request) Compile(options *protocols.ExecutorOptions) error {
   131  	if request.Retries == 0 {
   132  		request.Retries = 3
   133  	}
   134  	if request.Recursion == nil {
   135  		recursion := true
   136  		request.Recursion = &recursion
   137  	}
   138  	dnsClientOptions := &dnsclientpool.Configuration{
   139  		Retries: request.Retries,
   140  	}
   141  	if len(request.Resolvers) > 0 {
   142  		dnsClientOptions.Resolvers = request.Resolvers
   143  	}
   144  	// Create a dns client for the class
   145  	client, err := request.getDnsClient(options, nil)
   146  	if err != nil {
   147  		return errors.Wrap(err, "could not get dns client")
   148  	}
   149  	request.dnsClient = client
   150  
   151  	if len(request.Matchers) > 0 || len(request.Extractors) > 0 {
   152  		compiled := &request.Operators
   153  		compiled.ExcludeMatchers = options.ExcludeMatchers
   154  		compiled.TemplateID = options.TemplateID
   155  		if err := compiled.Compile(); err != nil {
   156  			return errors.Wrap(err, "could not compile operators")
   157  		}
   158  		request.CompiledOperators = compiled
   159  	}
   160  	request.class = classToInt(request.Class)
   161  	request.options = options
   162  	request.question = questionTypeToInt(request.RequestType.String())
   163  	for name, payload := range options.Options.Vars.AsMap() {
   164  		payloadStr, ok := payload.(string)
   165  		// check if inputs contains the payload
   166  		if ok && fileutil.FileExists(payloadStr) {
   167  			if request.Payloads == nil {
   168  				request.Payloads = make(map[string]interface{})
   169  			}
   170  			request.Payloads[name] = payloadStr
   171  		}
   172  	}
   173  
   174  	if len(request.Payloads) > 0 {
   175  		request.generator, err = generators.New(request.Payloads, request.AttackType.Value, request.options.TemplatePath, request.options.Options.AllowLocalFileAccess, request.options.Catalog, request.options.Options.AttackType)
   176  		if err != nil {
   177  			return errors.Wrap(err, "could not parse payloads")
   178  		}
   179  	}
   180  	return nil
   181  }
   182  
   183  func (request *Request) getDnsClient(options *protocols.ExecutorOptions, metadata map[string]interface{}) (*retryabledns.Client, error) {
   184  	dnsClientOptions := &dnsclientpool.Configuration{
   185  		Retries: request.Retries,
   186  	}
   187  	if len(request.Resolvers) > 0 {
   188  		if len(request.Resolvers) > 0 {
   189  			for _, resolver := range request.Resolvers {
   190  				if expressions.ContainsUnresolvedVariables(resolver) != nil {
   191  					var err error
   192  					resolver, err = expressions.Evaluate(resolver, metadata)
   193  					if err != nil {
   194  						return nil, errors.Wrap(err, "could not resolve resolvers expressions")
   195  					}
   196  					dnsClientOptions.Resolvers = append(dnsClientOptions.Resolvers, resolver)
   197  				}
   198  			}
   199  		}
   200  		dnsClientOptions.Resolvers = request.Resolvers
   201  	}
   202  	return dnsclientpool.Get(options.Options, dnsClientOptions)
   203  }
   204  
   205  // Requests returns the total number of requests the YAML rule will perform
   206  func (request *Request) Requests() int {
   207  	if request.generator != nil {
   208  		payloadRequests := request.generator.NewIterator().Total()
   209  		return payloadRequests
   210  	}
   211  
   212  	return 1
   213  }
   214  
   215  // Make returns the request to be sent for the protocol
   216  func (request *Request) Make(host string, vars map[string]interface{}) (*dns.Msg, error) {
   217  	// Build a request on the specified URL
   218  	req := new(dns.Msg)
   219  	req.Id = dns.Id()
   220  	req.RecursionDesired = *request.Recursion
   221  
   222  	var q dns.Question
   223  	final := replacer.Replace(request.Name, vars)
   224  
   225  	q.Name = dns.Fqdn(final)
   226  	q.Qclass = request.class
   227  	q.Qtype = request.question
   228  	req.Question = append(req.Question, q)
   229  
   230  	req.SetEdns0(4096, false)
   231  
   232  	switch request.question {
   233  	case dns.TypeTXT:
   234  		req.AuthenticatedData = true
   235  	}
   236  
   237  	return req, nil
   238  }
   239  
   240  // questionTypeToInt converts DNS question type to internal representation
   241  func questionTypeToInt(questionType string) uint16 {
   242  	questionType = strings.TrimSpace(strings.ToUpper(questionType))
   243  	question := dns.TypeA
   244  
   245  	switch questionType {
   246  	case "A":
   247  		question = dns.TypeA
   248  	case "NS":
   249  		question = dns.TypeNS
   250  	case "CNAME":
   251  		question = dns.TypeCNAME
   252  	case "SOA":
   253  		question = dns.TypeSOA
   254  	case "PTR":
   255  		question = dns.TypePTR
   256  	case "MX":
   257  		question = dns.TypeMX
   258  	case "TXT":
   259  		question = dns.TypeTXT
   260  	case "DS":
   261  		question = dns.TypeDS
   262  	case "AAAA":
   263  		question = dns.TypeAAAA
   264  	case "CAA":
   265  		question = dns.TypeCAA
   266  	case "TLSA":
   267  		question = dns.TypeTLSA
   268  	case "ANY":
   269  		question = dns.TypeANY
   270  	}
   271  	return question
   272  }
   273  
   274  // classToInt converts a dns class name to its internal representation
   275  func classToInt(class string) uint16 {
   276  	class = strings.TrimSpace(strings.ToUpper(class))
   277  	result := dns.ClassINET
   278  
   279  	switch class {
   280  	case "INET":
   281  		result = dns.ClassINET
   282  	case "CSNET":
   283  		result = dns.ClassCSNET
   284  	case "CHAOS":
   285  		result = dns.ClassCHAOS
   286  	case "HESIOD":
   287  		result = dns.ClassHESIOD
   288  	case "NONE":
   289  		result = dns.ClassNONE
   290  	case "ANY":
   291  		result = dns.ClassANY
   292  	}
   293  	return uint16(result)
   294  }