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
This commit is contained in:
Tarun Koyalwar 2023-11-23 16:34:26 +05:30 committed by GitHub
parent 8d7bbdd978
commit a09b8afd0f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 99 additions and 12 deletions

View File

@ -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)

View File

@ -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, "")