From a09b8afd0f24a24b62880d17d2abfb23ca45c5b0 Mon Sep 17 00:00:00 2001 From: Tarun Koyalwar <45962551+tarunKoyalwar@users.noreply.github.com> Date: Thu, 23 Nov 2023 16:34:26 +0530 Subject: [PATCH] add support for multiple ports in network template (#4401) * add support for multiple ports in network template * backwords compatibility for templates without 'port' field * fix nil panic in compile --- pkg/protocols/network/network.go | 23 +++++++++ pkg/protocols/network/request.go | 88 +++++++++++++++++++++++++++----- 2 files changed, 99 insertions(+), 12 deletions(-) diff --git a/pkg/protocols/network/network.go b/pkg/protocols/network/network.go index 7975e4c17..5a9cb6949 100644 --- a/pkg/protocols/network/network.go +++ b/pkg/protocols/network/network.go @@ -1,6 +1,7 @@ package network import ( + "strconv" "strings" "github.com/pkg/errors" @@ -11,6 +12,7 @@ import ( "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/network/networkclientpool" + errorutil "github.com/projectdiscovery/utils/errors" fileutil "github.com/projectdiscovery/utils/file" ) @@ -74,6 +76,10 @@ type Request struct { // SelfContained specifies if the request is self-contained. SelfContained bool `yaml:"-" json:"-"` + // description: | + // ports is post processed list of ports to scan (obtained from Port) + ports []string `yaml:"-" json:"-"` + // Operators for the current request go here. operators.Operators `yaml:",inline,omitempty"` CompiledOperators *operators.Operators `yaml:"-"` @@ -169,6 +175,23 @@ func (request *Request) Compile(options *protocols.ExecutorOptions) error { } } + // parse ports and validate + if request.Port != "" { + for _, port := range strings.Split(request.Port, ",") { + if port == "" { + continue + } + portInt, err := strconv.Atoi(port) + if err != nil { + return errorutil.NewWithErr(err).Msgf("could not parse port %v from '%s'", port, request.Port) + } + if portInt < 1 || portInt > 65535 { + return errorutil.NewWithTag(request.TemplateID, "port %v is not in valid range", portInt) + } + request.ports = append(request.ports, port) + } + } + // Resolve payload paths from vars if they exists for name, payload := range request.options.Options.Vars.AsMap() { payloadStr, ok := payload.(string) diff --git a/pkg/protocols/network/request.go b/pkg/protocols/network/request.go index 489847849..e66524760 100644 --- a/pkg/protocols/network/request.go +++ b/pkg/protocols/network/request.go @@ -10,6 +10,7 @@ import ( "time" "github.com/pkg/errors" + "go.uber.org/multierr" "golang.org/x/exp/maps" "github.com/projectdiscovery/gologger" @@ -22,6 +23,7 @@ import ( "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/helpers/eventcreator" "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/helpers/responsehighlighter" "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/interactsh" + "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate" "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/replacer" "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/utils/vardump" protocolutils "github.com/projectdiscovery/nuclei/v3/pkg/protocols/utils" @@ -44,19 +46,83 @@ func (request *Request) Type() templateTypes.ProtocolType { return templateTypes.NetworkProtocol } +// getOpenPorts returns all open ports from list of ports provided in template +// if only 1 port is provided, no need to check if port is open or not +func (request *Request) getOpenPorts(target *contextargs.Context) ([]string, error) { + if len(request.ports) == 1 { + // no need to check if port is open or not + return request.ports, nil + } + errs := []error{} + // if more than 1 port is provided, check if port is open or not + openPorts := make([]string, 0) + for _, port := range request.ports { + cloned := target.Clone() + if err := cloned.UseNetworkPort(port, request.ExcludePorts); err != nil { + errs = append(errs, err) + continue + } + addr, err := getAddress(cloned.MetaInput.Input) + if err != nil { + errs = append(errs, err) + continue + } + conn, err := protocolstate.Dialer.Dial(context.TODO(), "tcp", addr) + if err != nil { + errs = append(errs, err) + continue + } + _ = conn.Close() + openPorts = append(openPorts, port) + } + if len(openPorts) == 0 { + return nil, multierr.Combine(errs...) + } + return openPorts, nil +} + // ExecuteWithResults executes the protocol requests and returns results instead of writing them. func (request *Request) ExecuteWithResults(target *contextargs.Context, metadata, previous output.InternalEvent, callback protocols.OutputEventCallback) error { + visitedAddresses := make(mapsutil.Map[string, struct{}]) + + if request.Port == "" { + // backwords compatibility or for other use cases + // where port is not provided in template + if err := request.executeOnTarget(target, visitedAddresses, metadata, previous, callback); err != nil { + return err + } + } + + // get open ports from list of ports provided in template + ports, err := request.getOpenPorts(target) + if len(ports) == 0 { + return err + } + if err != nil { + // TODO: replace this after scan context is implemented + gologger.Verbose().Msgf("[%v] got errors while checking open ports: %s\n", request.options.TemplateID, err) + } + + for _, port := range ports { + input := target.Clone() + // use network port updates input with new port requested in template file + // and it is ignored if input port is not standard http(s) ports like 80,8080,8081 etc + // idea is to reduce redundant dials to http ports + if err := input.UseNetworkPort(port, request.ExcludePorts); err != nil { + gologger.Debug().Msgf("Could not network port from constants: %s\n", err) + } + if err := request.executeOnTarget(input, visitedAddresses, metadata, previous, callback); err != nil { + return err + } + } + + return nil +} + +func (request *Request) executeOnTarget(input *contextargs.Context, visited mapsutil.Map[string, struct{}], metadata, previous output.InternalEvent, callback protocols.OutputEventCallback) error { var address string var err error - input := target.Clone() - // use network port updates input with new port requested in template file - // and it is ignored if input port is not standard http(s) ports like 80,8080,8081 etc - // idea is to reduce redundant dials to http ports - if err := input.UseNetworkPort(request.Port, request.ExcludePorts); err != nil { - gologger.Debug().Msgf("Could not network port from constants: %s\n", err) - } - if request.SelfContained { address = "" } else { @@ -73,15 +139,13 @@ func (request *Request) ExecuteWithResults(target *contextargs.Context, metadata variablesMap := request.options.Variables.Evaluate(variables) variables = generators.MergeMaps(variablesMap, variables, request.options.Constants) - visitedAddresses := make(mapsutil.Map[string, struct{}]) - for _, kv := range request.addresses { actualAddress := replacer.Replace(kv.address, variables) - if visitedAddresses.Has(actualAddress) && !request.options.Options.DisableClustering { + if visited.Has(actualAddress) && !request.options.Options.DisableClustering { continue } - visitedAddresses.Set(actualAddress, struct{}{}) + visited.Set(actualAddress, struct{}{}) if err := request.executeAddress(variables, actualAddress, address, input, kv.tls, previous, callback); err != nil { outputEvent := request.responseToDSLMap("", "", "", address, "")