From 76e6ad7a1e2ceee334125da4d25e2bb3767e420f Mon Sep 17 00:00:00 2001 From: Adam Chovanec Date: Wed, 1 Apr 2026 17:46:37 +0200 Subject: [PATCH] feat: allow overriding default exclude-ports in CLI --- cmd/nuclei/main.go | 1 + .../common/contextargs/contextargs.go | 17 ++-- .../common/contextargs/contextargs_test.go | 86 +++++++++++++++++++ pkg/protocols/javascript/js.go | 2 +- pkg/protocols/network/request.go | 4 +- pkg/types/types.go | 3 + 6 files changed, 104 insertions(+), 9 deletions(-) create mode 100644 pkg/protocols/common/contextargs/contextargs_test.go diff --git a/cmd/nuclei/main.go b/cmd/nuclei/main.go index f4cb0280c2..1e12c8fdca 100644 --- a/cmd/nuclei/main.go +++ b/cmd/nuclei/main.go @@ -373,6 +373,7 @@ on extensive configurability, massive extensibility and ease of use.`) flagSet.StringVarP(&options.Interface, "interface", "i", "", "network interface to use for network scan"), flagSet.StringVarP(&options.AttackType, "attack-type", "at", "", "type of payload combinations to perform (batteringram,pitchfork,clusterbomb)"), flagSet.StringVarP(&options.SourceIP, "source-ip", "sip", "", "source ip address to use for network scan"), + flagSet.StringSliceVarP(&options.ReservedPorts, "reserved-ports", "rport", nil, "list of reserved ports that network templates should avoid", goflags.CommaSeparatedStringSliceOptions), flagSet.IntVarP(&options.ResponseReadSize, "response-size-read", "rsr", 0, "max response size to read in bytes"), flagSet.IntVarP(&options.ResponseSaveSize, "response-size-save", "rss", unitutils.Mega, "max response size to read in bytes"), flagSet.CallbackVar(resetCallback, "reset", "reset removes all nuclei configuration and data files (including nuclei-templates)"), diff --git a/pkg/protocols/common/contextargs/contextargs.go b/pkg/protocols/common/contextargs/contextargs.go index af55595498..39bd542ddf 100644 --- a/pkg/protocols/common/contextargs/contextargs.go +++ b/pkg/protocols/common/contextargs/contextargs.go @@ -14,8 +14,8 @@ import ( ) var ( - // reservedPorts contains list of reserved ports for non-network requests in nuclei - reservedPorts = []string{"80", "443", "8080", "8443", "8081", "53"} + // defaultReservedPorts contains the default list of reserved ports for non-network requests in nuclei + defaultReservedPorts = []string{"80", "443", "8080", "8443", "8081", "53"} ) // Context implements a shared context struct to share information across multiple templates within a workflow @@ -109,12 +109,17 @@ func (ctx *Context) Add(key string, v interface{}) { // UseNetworkPort updates input with required/default network port for that template // but is ignored if input/target contains non-http ports like 80,8080,8081 etc -func (ctx *Context) UseNetworkPort(port string, excludePorts string) error { - ignorePorts := reservedPorts - if excludePorts != "" { +// Precedence: cliExcludePorts > templateExcludePorts > default reserved ports +func (ctx *Context) UseNetworkPort(port string, templateExcludePorts string, cliExcludePorts []string) error { + ignorePorts := defaultReservedPorts + + if len(cliExcludePorts) > 0 { + ignorePorts = cliExcludePorts + } else if templateExcludePorts != "" { // TODO: add support for service names like http,https,ssh etc once https://github.com/projectdiscovery/netdb is ready - ignorePorts = sliceutil.Dedupe(strings.Split(excludePorts, ",")) + ignorePorts = sliceutil.Dedupe(strings.Split(templateExcludePorts, ",")) } + if port == "" { // if template does not contain port, do nothing return nil diff --git a/pkg/protocols/common/contextargs/contextargs_test.go b/pkg/protocols/common/contextargs/contextargs_test.go new file mode 100644 index 0000000000..9f9018bd89 --- /dev/null +++ b/pkg/protocols/common/contextargs/contextargs_test.go @@ -0,0 +1,86 @@ +package contextargs + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestUseNetworkPort(t *testing.T) { + tests := []struct { + name string + target string + templatePort string + templateExcl string + cliReserved []string + expectedHost string + }{ + { + name: "default-reserved-ports-work", + target: "example.com:80", + templatePort: "1234", + templateExcl: "", + cliReserved: nil, + expectedHost: "example.com:1234", + }, + { + name: "default-target-works", + target: "example.com:22", + templatePort: "80", + templateExcl: "", + cliReserved: nil, + expectedHost: "example.com:22", + }, + { + name: "template-overwrites-defaults-no-exclusions", + target: "example.com:80", + templatePort: "1234", + templateExcl: "0", + cliReserved: nil, + expectedHost: "example.com:80", + }, + { + name: "template-overwrites-defaults-some-exclusions", + target: "example.com:5353", + templatePort: "1234", + templateExcl: "5353", + cliReserved: nil, + expectedHost: "example.com:1234", + }, + { + name: "cli-overwrites-template", + target: "example.com:80", + templatePort: "1234", + templateExcl: "0", + cliReserved: []string{"80"}, + expectedHost: "example.com:1234", + }, + { + name: "cli-overwrites-default-no-exclusions", + target: "example.com:80", + templatePort: "1234", + templateExcl: "", + cliReserved: []string{"0"}, + expectedHost: "example.com:80", + }, + { + name: "cli-overwrites-default-some-exclusions", + target: "example.com:5353", + templatePort: "1234", + templateExcl: "", + cliReserved: []string{"5353"}, + expectedHost: "example.com:1234", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx := NewWithInput(context.Background(), tt.target) + err := ctx.UseNetworkPort(tt.templatePort, tt.templateExcl, tt.cliReserved) + + require.NoError(t, err, "unexpected error") + require.Equal(t, tt.expectedHost, ctx.MetaInput.Input) + }) + } +} diff --git a/pkg/protocols/javascript/js.go b/pkg/protocols/javascript/js.go index b95ea6b4a9..cd538ecbd9 100644 --- a/pkg/protocols/javascript/js.go +++ b/pkg/protocols/javascript/js.go @@ -309,7 +309,7 @@ func (request *Request) executeWithResults(port string, target *contextargs.Cont // 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.getExcludePorts()); err != nil { + if err := input.UseNetworkPort(port, request.getExcludePorts(), request.options.Options.ReservedPorts); err != nil { gologger.Debug().Msgf("Could not network port from constants: %s\n", err) } diff --git a/pkg/protocols/network/request.go b/pkg/protocols/network/request.go index c8a3dd9b39..144d475f35 100644 --- a/pkg/protocols/network/request.go +++ b/pkg/protocols/network/request.go @@ -56,7 +56,7 @@ func (request *Request) getOpenPorts(target *contextargs.Context) ([]string, err openPorts := make([]string, 0) for _, port := range request.ports { cloned := target.Clone() - if err := cloned.UseNetworkPort(port, request.ExcludePorts); err != nil { + if err := cloned.UseNetworkPort(port, request.ExcludePorts, request.options.Options.ReservedPorts); err != nil { errs = append(errs, err) continue } @@ -120,7 +120,7 @@ func (request *Request) ExecuteWithResults(target *contextargs.Context, metadata // 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 { + if err := input.UseNetworkPort(port, request.ExcludePorts, request.options.Options.ReservedPorts); err != nil { gologger.Debug().Msgf("Could not network port from constants: %s\n", err) } if err := request.executeOnTarget(input, visitedAddresses, metadata, previous, wrappedCallback); err != nil { diff --git a/pkg/types/types.go b/pkg/types/types.go index 80ba74812c..da6b987a3c 100644 --- a/pkg/types/types.go +++ b/pkg/types/types.go @@ -465,6 +465,8 @@ type Options struct { InlineTargetsList string `yaml:"targets-inline,omitempty"` // ListTemplateProfiles lists all available template profiles ListTemplateProfiles bool + // ReservedPorts is the list of ports that network templates should not use + ReservedPorts goflags.StringSlice // LoadHelperFileFunction is a function that will be used to execute LoadHelperFile. // If none is provided, then the default implementation will be used. LoadHelperFileFunction LoadHelperFileFunction @@ -695,6 +697,7 @@ func (options *Options) Copy() *Options { HttpApiEndpoint: options.HttpApiEndpoint, InlineTargetsList: options.InlineTargetsList, ListTemplateProfiles: options.ListTemplateProfiles, + ReservedPorts: options.ReservedPorts, LoadHelperFileFunction: options.LoadHelperFileFunction, Logger: options.Logger, DoNotCacheTemplates: options.DoNotCacheTemplates,