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

     1  package dns
     2  
     3  import (
     4  	"encoding/hex"
     5  	"fmt"
     6  	"net/url"
     7  	"strings"
     8  
     9  	"github.com/miekg/dns"
    10  	"github.com/pkg/errors"
    11  	"golang.org/x/exp/maps"
    12  
    13  	"github.com/projectdiscovery/gologger"
    14  	"github.com/projectdiscovery/nuclei/v2/pkg/output"
    15  	"github.com/projectdiscovery/nuclei/v2/pkg/protocols"
    16  	"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/contextargs"
    17  	"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/expressions"
    18  	"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/generators"
    19  	"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/helpers/eventcreator"
    20  	"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/helpers/responsehighlighter"
    21  	"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/utils/vardump"
    22  	protocolutils "github.com/projectdiscovery/nuclei/v2/pkg/protocols/utils"
    23  	templateTypes "github.com/projectdiscovery/nuclei/v2/pkg/templates/types"
    24  	"github.com/projectdiscovery/nuclei/v2/pkg/utils"
    25  	"github.com/projectdiscovery/retryabledns"
    26  	iputil "github.com/projectdiscovery/utils/ip"
    27  )
    28  
    29  var _ protocols.Request = &Request{}
    30  
    31  // Type returns the type of the protocol request
    32  func (request *Request) Type() templateTypes.ProtocolType {
    33  	return templateTypes.DNSProtocol
    34  }
    35  
    36  // ExecuteWithResults executes the protocol requests and returns results instead of writing them.
    37  func (request *Request) ExecuteWithResults(input *contextargs.Context, metadata, previous output.InternalEvent, callback protocols.OutputEventCallback) error {
    38  	// Parse the URL and return domain if URL.
    39  	var domain string
    40  	if utils.IsURL(input.MetaInput.Input) {
    41  		domain = extractDomain(input.MetaInput.Input)
    42  	} else {
    43  		domain = input.MetaInput.Input
    44  	}
    45  
    46  	var err error
    47  	domain, err = request.parseDNSInput(domain)
    48  	if err != nil {
    49  		return errors.Wrap(err, "could not build request")
    50  	}
    51  
    52  	vars := protocolutils.GenerateDNSVariables(domain)
    53  	// optionvars are vars passed from CLI or env variables
    54  	optionVars := generators.BuildPayloadFromOptions(request.options.Options)
    55  	// merge with metadata (eg. from workflow context)
    56  	vars = generators.MergeMaps(vars, metadata, optionVars)
    57  	variablesMap := request.options.Variables.Evaluate(vars)
    58  	vars = generators.MergeMaps(vars, variablesMap, request.options.Constants)
    59  
    60  	if request.generator != nil {
    61  		iterator := request.generator.NewIterator()
    62  
    63  		for {
    64  			value, ok := iterator.Value()
    65  			if !ok {
    66  				break
    67  			}
    68  			value = generators.MergeMaps(vars, value)
    69  			if err := request.execute(domain, metadata, previous, value, callback); err != nil {
    70  				return err
    71  			}
    72  		}
    73  	} else {
    74  		value := maps.Clone(vars)
    75  		return request.execute(domain, metadata, previous, value, callback)
    76  	}
    77  	return nil
    78  }
    79  
    80  func (request *Request) execute(domain string, metadata, previous output.InternalEvent, vars map[string]interface{}, callback protocols.OutputEventCallback) error {
    81  
    82  	if vardump.EnableVarDump {
    83  		gologger.Debug().Msgf("Protocol request variables: \n%s\n", vardump.DumpVariables(vars))
    84  	}
    85  
    86  	// Compile each request for the template based on the URL
    87  	compiledRequest, err := request.Make(domain, vars)
    88  	if err != nil {
    89  		request.options.Output.Request(request.options.TemplatePath, domain, request.Type().String(), err)
    90  		request.options.Progress.IncrementFailedRequestsBy(1)
    91  		return errors.Wrap(err, "could not build request")
    92  	}
    93  
    94  	dnsClient := request.dnsClient
    95  	if varErr := expressions.ContainsUnresolvedVariables(request.Resolvers...); varErr != nil {
    96  		if dnsClient, varErr = request.getDnsClient(request.options, metadata); varErr != nil {
    97  			gologger.Warning().Msgf("[%s] Could not make dns request for %s: %v\n", request.options.TemplateID, domain, varErr)
    98  			return nil
    99  		}
   100  	}
   101  	question := domain
   102  	if len(compiledRequest.Question) > 0 {
   103  		question = compiledRequest.Question[0].Name
   104  	}
   105  	// remove the last dot
   106  	question = strings.TrimSuffix(question, ".")
   107  
   108  	requestString := compiledRequest.String()
   109  	if varErr := expressions.ContainsUnresolvedVariables(requestString); varErr != nil {
   110  		gologger.Warning().Msgf("[%s] Could not make dns request for %s: %v\n", request.options.TemplateID, question, varErr)
   111  		return nil
   112  	}
   113  	if request.options.Options.Debug || request.options.Options.DebugRequests || request.options.Options.StoreResponse {
   114  		msg := fmt.Sprintf("[%s] Dumped DNS request for %s", request.options.TemplateID, question)
   115  		if request.options.Options.Debug || request.options.Options.DebugRequests {
   116  			gologger.Info().Str("domain", domain).Msgf(msg)
   117  			gologger.Print().Msgf("%s", requestString)
   118  		}
   119  		if request.options.Options.StoreResponse {
   120  			request.options.Output.WriteStoreDebugData(domain, request.options.TemplateID, request.Type().String(), fmt.Sprintf("%s\n%s", msg, requestString))
   121  		}
   122  	}
   123  
   124  	request.options.RateLimiter.Take()
   125  
   126  	// Send the request to the target servers
   127  	response, err := dnsClient.Do(compiledRequest)
   128  	if err != nil {
   129  		request.options.Output.Request(request.options.TemplatePath, domain, request.Type().String(), err)
   130  		request.options.Progress.IncrementFailedRequestsBy(1)
   131  	} else {
   132  		request.options.Progress.IncrementRequests()
   133  	}
   134  	if response == nil {
   135  		return errors.Wrap(err, "could not send dns request")
   136  	}
   137  
   138  	request.options.Output.Request(request.options.TemplatePath, domain, request.Type().String(), err)
   139  	gologger.Verbose().Msgf("[%s] Sent DNS request to %s\n", request.options.TemplateID, question)
   140  
   141  	// perform trace if necessary
   142  	var traceData *retryabledns.TraceData
   143  	if request.Trace {
   144  		traceData, err = request.dnsClient.Trace(domain, request.question, request.TraceMaxRecursion)
   145  		if err != nil {
   146  			request.options.Output.Request(request.options.TemplatePath, domain, "dns", err)
   147  		}
   148  	}
   149  
   150  	// Create the output event
   151  	outputEvent := request.responseToDSLMap(compiledRequest, response, domain, question, traceData)
   152  	for k, v := range previous {
   153  		outputEvent[k] = v
   154  	}
   155  	for k, v := range vars {
   156  		outputEvent[k] = v
   157  	}
   158  	event := eventcreator.CreateEvent(request, outputEvent, request.options.Options.Debug || request.options.Options.DebugResponse)
   159  
   160  	dumpResponse(event, request, request.options, response.String(), question)
   161  	if request.Trace {
   162  		dumpTraceData(event, request.options, traceToString(traceData, true), question)
   163  	}
   164  
   165  	callback(event)
   166  	return nil
   167  }
   168  
   169  func (request *Request) parseDNSInput(host string) (string, error) {
   170  	isIP := iputil.IsIP(host)
   171  	switch {
   172  	case request.question == dns.TypePTR && isIP:
   173  		var err error
   174  		host, err = dns.ReverseAddr(host)
   175  		if err != nil {
   176  			return "", err
   177  		}
   178  	default:
   179  		if isIP {
   180  			return "", errors.New("cannot use IP address as DNS input")
   181  		}
   182  		host = dns.Fqdn(host)
   183  	}
   184  	return host, nil
   185  }
   186  
   187  func dumpResponse(event *output.InternalWrappedEvent, request *Request, requestOptions *protocols.ExecutorOptions, response, domain string) {
   188  	cliOptions := request.options.Options
   189  	if cliOptions.Debug || cliOptions.DebugResponse || cliOptions.StoreResponse {
   190  		hexDump := false
   191  		if responsehighlighter.HasBinaryContent(response) {
   192  			hexDump = true
   193  			response = hex.Dump([]byte(response))
   194  		}
   195  		highlightedResponse := responsehighlighter.Highlight(event.OperatorsResult, response, cliOptions.NoColor, hexDump)
   196  		msg := fmt.Sprintf("[%s] Dumped DNS response for %s\n\n%s", request.options.TemplateID, domain, highlightedResponse)
   197  		if cliOptions.Debug || cliOptions.DebugResponse {
   198  			gologger.Debug().Msg(msg)
   199  		}
   200  		if cliOptions.StoreResponse {
   201  			request.options.Output.WriteStoreDebugData(domain, request.options.TemplateID, request.Type().String(), msg)
   202  		}
   203  	}
   204  }
   205  
   206  func dumpTraceData(event *output.InternalWrappedEvent, requestOptions *protocols.ExecutorOptions, traceData, domain string) {
   207  	cliOptions := requestOptions.Options
   208  	if cliOptions.Debug || cliOptions.DebugResponse {
   209  		hexDump := false
   210  		if responsehighlighter.HasBinaryContent(traceData) {
   211  			hexDump = true
   212  			traceData = hex.Dump([]byte(traceData))
   213  		}
   214  		highlightedResponse := responsehighlighter.Highlight(event.OperatorsResult, traceData, cliOptions.NoColor, hexDump)
   215  		gologger.Debug().Msgf("[%s] Dumped DNS Trace data for %s\n\n%s", requestOptions.TemplateID, domain, highlightedResponse)
   216  	}
   217  }
   218  
   219  // extractDomain extracts the domain name of a URL
   220  func extractDomain(theURL string) string {
   221  	u, err := url.Parse(theURL)
   222  	if err != nil {
   223  		return ""
   224  	}
   225  	return u.Hostname()
   226  }