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

     1  package network
     2  
     3  import (
     4  	"context"
     5  	"encoding/hex"
     6  	"fmt"
     7  	"io"
     8  	"net"
     9  	"net/url"
    10  	"os"
    11  	"strings"
    12  	"time"
    13  
    14  	"github.com/pkg/errors"
    15  	"golang.org/x/exp/maps"
    16  
    17  	"github.com/projectdiscovery/gologger"
    18  	"github.com/projectdiscovery/nuclei/v2/pkg/operators"
    19  	"github.com/projectdiscovery/nuclei/v2/pkg/output"
    20  	"github.com/projectdiscovery/nuclei/v2/pkg/protocols"
    21  	"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/contextargs"
    22  	"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/expressions"
    23  	"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/generators"
    24  	"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/helpers/eventcreator"
    25  	"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/helpers/responsehighlighter"
    26  	"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/interactsh"
    27  	"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/replacer"
    28  	"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/utils/vardump"
    29  	protocolutils "github.com/projectdiscovery/nuclei/v2/pkg/protocols/utils"
    30  	templateTypes "github.com/projectdiscovery/nuclei/v2/pkg/templates/types"
    31  	errorutil "github.com/projectdiscovery/utils/errors"
    32  	mapsutil "github.com/projectdiscovery/utils/maps"
    33  )
    34  
    35  var _ protocols.Request = &Request{}
    36  
    37  // Type returns the type of the protocol request
    38  func (request *Request) Type() templateTypes.ProtocolType {
    39  	return templateTypes.NetworkProtocol
    40  }
    41  
    42  // ExecuteWithResults executes the protocol requests and returns results instead of writing them.
    43  func (request *Request) ExecuteWithResults(target *contextargs.Context, metadata, previous output.InternalEvent, callback protocols.OutputEventCallback) error {
    44  	var address string
    45  	var err error
    46  
    47  	input := target.Clone()
    48  	// use network port updates input with new port requested in template file
    49  	// and it is ignored if input port is not standard http(s) ports like 80,8080,8081 etc
    50  	// idea is to reduce redundant dials to http ports
    51  	if err := input.UseNetworkPort(request.Port, request.ExcludePorts); err != nil {
    52  		gologger.Debug().Msgf("Could not network port from constants: %s\n", err)
    53  	}
    54  
    55  	if request.SelfContained {
    56  		address = ""
    57  	} else {
    58  		address, err = getAddress(input.MetaInput.Input)
    59  	}
    60  	if err != nil {
    61  		request.options.Output.Request(request.options.TemplatePath, input.MetaInput.Input, request.Type().String(), err)
    62  		request.options.Progress.IncrementFailedRequestsBy(1)
    63  		return errors.Wrap(err, "could not get address from url")
    64  	}
    65  	variables := protocolutils.GenerateVariables(address, false, nil)
    66  	variablesMap := request.options.Variables.Evaluate(variables)
    67  	variables = generators.MergeMaps(variablesMap, variables, request.options.Constants)
    68  
    69  	visitedAddresses := make(mapsutil.Map[string, struct{}])
    70  
    71  	for _, kv := range request.addresses {
    72  		actualAddress := replacer.Replace(kv.address, variables)
    73  
    74  		if visitedAddresses.Has(actualAddress) && !request.options.Options.DisableClustering {
    75  			continue
    76  		}
    77  		visitedAddresses.Set(actualAddress, struct{}{})
    78  
    79  		if err := request.executeAddress(variables, actualAddress, address, input.MetaInput.Input, kv.tls, previous, callback); err != nil {
    80  			outputEvent := request.responseToDSLMap("", "", "", address, "")
    81  			callback(&output.InternalWrappedEvent{InternalEvent: outputEvent})
    82  			gologger.Warning().Msgf("[%v] Could not make network request for (%s) : %s\n", request.options.TemplateID, actualAddress, err)
    83  			continue
    84  		}
    85  	}
    86  	return nil
    87  }
    88  
    89  // executeAddress executes the request for an address
    90  func (request *Request) executeAddress(variables map[string]interface{}, actualAddress, address, input string, shouldUseTLS bool, previous output.InternalEvent, callback protocols.OutputEventCallback) error {
    91  	variables = generators.MergeMaps(variables, map[string]interface{}{"Hostname": address})
    92  	payloads := generators.BuildPayloadFromOptions(request.options.Options)
    93  
    94  	if !strings.Contains(actualAddress, ":") {
    95  		err := errors.New("no port provided in network protocol request")
    96  		request.options.Output.Request(request.options.TemplatePath, address, request.Type().String(), err)
    97  		request.options.Progress.IncrementFailedRequestsBy(1)
    98  		return err
    99  	}
   100  
   101  	if request.generator != nil {
   102  		iterator := request.generator.NewIterator()
   103  
   104  		for {
   105  			value, ok := iterator.Value()
   106  			if !ok {
   107  				break
   108  			}
   109  			value = generators.MergeMaps(value, payloads)
   110  			if err := request.executeRequestWithPayloads(variables, actualAddress, address, input, shouldUseTLS, value, previous, callback); err != nil {
   111  				return err
   112  			}
   113  		}
   114  	} else {
   115  		value := maps.Clone(payloads)
   116  		if err := request.executeRequestWithPayloads(variables, actualAddress, address, input, shouldUseTLS, value, previous, callback); err != nil {
   117  			return err
   118  		}
   119  	}
   120  	return nil
   121  }
   122  
   123  func (request *Request) executeRequestWithPayloads(variables map[string]interface{}, actualAddress, address, input string, shouldUseTLS bool, payloads map[string]interface{}, previous output.InternalEvent, callback protocols.OutputEventCallback) error {
   124  	var (
   125  		hostname string
   126  		conn     net.Conn
   127  		err      error
   128  	)
   129  	if host, _, err := net.SplitHostPort(actualAddress); err == nil {
   130  		hostname = host
   131  	}
   132  
   133  	if shouldUseTLS {
   134  		conn, err = request.dialer.DialTLS(context.Background(), "tcp", actualAddress)
   135  	} else {
   136  		conn, err = request.dialer.Dial(context.Background(), "tcp", actualAddress)
   137  	}
   138  	if err != nil {
   139  		request.options.Output.Request(request.options.TemplatePath, address, request.Type().String(), err)
   140  		request.options.Progress.IncrementFailedRequestsBy(1)
   141  		return errors.Wrap(err, "could not connect to server")
   142  	}
   143  	defer conn.Close()
   144  	_ = conn.SetDeadline(time.Now().Add(time.Duration(request.options.Options.Timeout) * time.Second))
   145  
   146  	var interactshURLs []string
   147  
   148  	var responseBuilder, reqBuilder strings.Builder
   149  
   150  	interimValues := generators.MergeMaps(variables, payloads)
   151  
   152  	if vardump.EnableVarDump {
   153  		gologger.Debug().Msgf("Protocol request variables: \n%s\n", vardump.DumpVariables(interimValues))
   154  	}
   155  
   156  	inputEvents := make(map[string]interface{})
   157  
   158  	for _, input := range request.Inputs {
   159  		data := []byte(input.Data)
   160  
   161  		if request.options.Interactsh != nil {
   162  			var transformedData string
   163  			transformedData, interactshURLs = request.options.Interactsh.Replace(string(data), []string{})
   164  			data = []byte(transformedData)
   165  		}
   166  
   167  		finalData, err := expressions.EvaluateByte(data, interimValues)
   168  		if err != nil {
   169  			request.options.Output.Request(request.options.TemplatePath, address, request.Type().String(), err)
   170  			request.options.Progress.IncrementFailedRequestsBy(1)
   171  			return errors.Wrap(err, "could not evaluate template expressions")
   172  		}
   173  
   174  		reqBuilder.Write(finalData)
   175  
   176  		if err := expressions.ContainsUnresolvedVariables(string(finalData)); err != nil {
   177  			gologger.Warning().Msgf("[%s] Could not make network request for %s: %v\n", request.options.TemplateID, actualAddress, err)
   178  			return nil
   179  		}
   180  
   181  		if input.Type.GetType() == hexType {
   182  			finalData, err = hex.DecodeString(string(finalData))
   183  			if err != nil {
   184  				request.options.Output.Request(request.options.TemplatePath, address, request.Type().String(), err)
   185  				request.options.Progress.IncrementFailedRequestsBy(1)
   186  				return errors.Wrap(err, "could not write request to server")
   187  			}
   188  		}
   189  
   190  		if _, err := conn.Write(finalData); err != nil {
   191  			request.options.Output.Request(request.options.TemplatePath, address, request.Type().String(), err)
   192  			request.options.Progress.IncrementFailedRequestsBy(1)
   193  			return errors.Wrap(err, "could not write request to server")
   194  		}
   195  
   196  		if input.Read > 0 {
   197  			buffer := make([]byte, input.Read)
   198  			n, err := conn.Read(buffer)
   199  			if err != nil {
   200  				return errorutil.NewWithErr(err).Msgf("could not read response from connection")
   201  			}
   202  
   203  			responseBuilder.Write(buffer[:n])
   204  
   205  			bufferStr := string(buffer[:n])
   206  			if input.Name != "" {
   207  				inputEvents[input.Name] = bufferStr
   208  				interimValues[input.Name] = bufferStr
   209  			}
   210  
   211  			// Run any internal extractors for the request here and add found values to map.
   212  			if request.CompiledOperators != nil {
   213  				values := request.CompiledOperators.ExecuteInternalExtractors(map[string]interface{}{input.Name: bufferStr}, request.Extract)
   214  				for k, v := range values {
   215  					payloads[k] = v
   216  				}
   217  			}
   218  		}
   219  	}
   220  
   221  	request.options.Progress.IncrementRequests()
   222  
   223  	if request.options.Options.Debug || request.options.Options.DebugRequests || request.options.Options.StoreResponse {
   224  		requestBytes := []byte(reqBuilder.String())
   225  		msg := fmt.Sprintf("[%s] Dumped Network request for %s\n%s", request.options.TemplateID, actualAddress, hex.Dump(requestBytes))
   226  		if request.options.Options.Debug || request.options.Options.DebugRequests {
   227  			gologger.Info().Str("address", actualAddress).Msg(msg)
   228  		}
   229  		if request.options.Options.StoreResponse {
   230  			request.options.Output.WriteStoreDebugData(address, request.options.TemplateID, request.Type().String(), msg)
   231  		}
   232  		if request.options.Options.VerboseVerbose {
   233  			gologger.Print().Msgf("\nCompact HEX view:\n%s", hex.EncodeToString(requestBytes))
   234  		}
   235  	}
   236  
   237  	request.options.Output.Request(request.options.TemplatePath, actualAddress, request.Type().String(), err)
   238  	gologger.Verbose().Msgf("Sent TCP request to %s", actualAddress)
   239  
   240  	bufferSize := 1024
   241  	if request.ReadSize != 0 {
   242  		bufferSize = request.ReadSize
   243  	}
   244  
   245  	var (
   246  		final []byte
   247  		n     int
   248  	)
   249  
   250  	if request.ReadAll {
   251  		readInterval := time.NewTimer(time.Second * 1)
   252  		// stop the timer and drain the channel
   253  		closeTimer := func(t *time.Timer) {
   254  			if !t.Stop() {
   255  				<-t.C
   256  			}
   257  		}
   258  	readSocket:
   259  		for {
   260  			select {
   261  			case <-readInterval.C:
   262  				closeTimer(readInterval)
   263  				break readSocket
   264  			default:
   265  				buf := make([]byte, bufferSize)
   266  				nBuf, err := conn.Read(buf)
   267  				if err != nil && !os.IsTimeout(err) && err != io.EOF {
   268  					request.options.Output.Request(request.options.TemplatePath, address, request.Type().String(), err)
   269  					closeTimer(readInterval)
   270  					return errors.Wrap(err, "could not read from server")
   271  				}
   272  				responseBuilder.Write(buf[:nBuf])
   273  				final = append(final, buf...)
   274  				n += nBuf
   275  			}
   276  		}
   277  	} else {
   278  		final = make([]byte, bufferSize)
   279  		n, err = conn.Read(final)
   280  		if err != nil && !os.IsTimeout(err) && err != io.EOF {
   281  			request.options.Output.Request(request.options.TemplatePath, address, request.Type().String(), err)
   282  			return errors.Wrap(err, "could not read from server")
   283  		}
   284  		responseBuilder.Write(final[:n])
   285  	}
   286  
   287  	response := responseBuilder.String()
   288  	outputEvent := request.responseToDSLMap(reqBuilder.String(), string(final[:n]), response, input, actualAddress)
   289  	outputEvent["ip"] = request.dialer.GetDialedIP(hostname)
   290  	if request.options.StopAtFirstMatch {
   291  		outputEvent["stop-at-first-match"] = true
   292  	}
   293  	for k, v := range previous {
   294  		outputEvent[k] = v
   295  	}
   296  	for k, v := range interimValues {
   297  		outputEvent[k] = v
   298  	}
   299  	for k, v := range inputEvents {
   300  		outputEvent[k] = v
   301  	}
   302  	if request.options.Interactsh != nil {
   303  		request.options.Interactsh.MakePlaceholders(interactshURLs, outputEvent)
   304  	}
   305  
   306  	var event *output.InternalWrappedEvent
   307  	if len(interactshURLs) == 0 {
   308  		event = eventcreator.CreateEventWithAdditionalOptions(request, generators.MergeMaps(payloads, outputEvent), request.options.Options.Debug || request.options.Options.DebugResponse, func(wrappedEvent *output.InternalWrappedEvent) {
   309  			wrappedEvent.OperatorsResult.PayloadValues = payloads
   310  		})
   311  		callback(event)
   312  	} else if request.options.Interactsh != nil {
   313  		event = &output.InternalWrappedEvent{InternalEvent: outputEvent}
   314  		request.options.Interactsh.RequestEvent(interactshURLs, &interactsh.RequestData{
   315  			MakeResultFunc: request.MakeResultEvent,
   316  			Event:          event,
   317  			Operators:      request.CompiledOperators,
   318  			MatchFunc:      request.Match,
   319  			ExtractFunc:    request.Extract,
   320  		})
   321  	}
   322  	if len(interactshURLs) > 0 {
   323  		event.UsesInteractsh = true
   324  	}
   325  
   326  	dumpResponse(event, request, response, actualAddress, address)
   327  
   328  	return nil
   329  }
   330  
   331  func dumpResponse(event *output.InternalWrappedEvent, request *Request, response string, actualAddress, address string) {
   332  	cliOptions := request.options.Options
   333  	if cliOptions.Debug || cliOptions.DebugResponse || cliOptions.StoreResponse {
   334  		requestBytes := []byte(response)
   335  		highlightedResponse := responsehighlighter.Highlight(event.OperatorsResult, hex.Dump(requestBytes), cliOptions.NoColor, true)
   336  		msg := fmt.Sprintf("[%s] Dumped Network response for %s\n\n", request.options.TemplateID, actualAddress)
   337  		if cliOptions.Debug || cliOptions.DebugResponse {
   338  			gologger.Debug().Msg(fmt.Sprintf("%s%s", msg, highlightedResponse))
   339  		}
   340  		if cliOptions.StoreResponse {
   341  			request.options.Output.WriteStoreDebugData(address, request.options.TemplateID, request.Type().String(), fmt.Sprintf("%s%s", msg, hex.Dump(requestBytes)))
   342  		}
   343  		if cliOptions.VerboseVerbose {
   344  			displayCompactHexView(event, response, cliOptions.NoColor)
   345  		}
   346  	}
   347  }
   348  
   349  func displayCompactHexView(event *output.InternalWrappedEvent, response string, noColor bool) {
   350  	operatorsResult := event.OperatorsResult
   351  	if operatorsResult != nil {
   352  		var allMatches []string
   353  		for _, namedMatch := range operatorsResult.Matches {
   354  			for _, matchElement := range namedMatch {
   355  				allMatches = append(allMatches, hex.EncodeToString([]byte(matchElement)))
   356  			}
   357  		}
   358  		tempOperatorResult := &operators.Result{Matches: map[string][]string{"matchesInHex": allMatches}}
   359  		gologger.Print().Msgf("\nCompact HEX view:\n%s", responsehighlighter.Highlight(tempOperatorResult, hex.EncodeToString([]byte(response)), noColor, false))
   360  	}
   361  }
   362  
   363  // getAddress returns the address of the host to make request to
   364  func getAddress(toTest string) (string, error) {
   365  	if strings.Contains(toTest, "://") {
   366  		parsed, err := url.Parse(toTest)
   367  		if err != nil {
   368  			return "", err
   369  		}
   370  		toTest = parsed.Host
   371  	}
   372  	return toTest, nil
   373  }