mirror of
https://github.com/projectdiscovery/nuclei.git
synced 2025-12-17 19:25:26 +00:00
Add payload in dns protocol (#3632)
* add execute function in dns * Add payload in dns protocol * Add integration test to cover dns payload - also check command line overriding a payload variable * Update matchedAt and remove trailing dot * Consider payload data for request count - Update verbose output to print question - Update dns requests Requests function to consider payload data * update gitignore * bump nuclei version to v2.9.4-dev --------- Co-authored-by: Tarun Koyalwar <tarun@projectdiscovery.io>
This commit is contained in:
parent
59376180b1
commit
9c2fa8f9c4
2
.gitignore
vendored
2
.gitignore
vendored
@ -15,4 +15,4 @@ v2/pkg/protocols/common/helpers/deserialization/testdata/Deserialize.class
|
|||||||
v2/pkg/protocols/common/helpers/deserialization/testdata/ValueObject.class
|
v2/pkg/protocols/common/helpers/deserialization/testdata/ValueObject.class
|
||||||
v2/pkg/protocols/common/helpers/deserialization/testdata/ValueObject2.ser
|
v2/pkg/protocols/common/helpers/deserialization/testdata/ValueObject2.ser
|
||||||
*.exe
|
*.exe
|
||||||
|
v2/.gitignore
|
||||||
|
|||||||
29
integration_tests/dns/payload.yaml
Normal file
29
integration_tests/dns/payload.yaml
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
id: dns-attack
|
||||||
|
|
||||||
|
info:
|
||||||
|
name: basic dns template
|
||||||
|
author: pdteam
|
||||||
|
severity: info
|
||||||
|
|
||||||
|
|
||||||
|
dns:
|
||||||
|
- name: "{{subdomain_wordlist}}.{{FQDN}}"
|
||||||
|
type: A
|
||||||
|
|
||||||
|
attack: batteringram
|
||||||
|
payloads:
|
||||||
|
subdomain_wordlist:
|
||||||
|
- one
|
||||||
|
- docs
|
||||||
|
- drive
|
||||||
|
|
||||||
|
matchers:
|
||||||
|
- type: word
|
||||||
|
words:
|
||||||
|
- "IN\tA"
|
||||||
|
|
||||||
|
extractors:
|
||||||
|
- type: regex
|
||||||
|
group: 1
|
||||||
|
regex:
|
||||||
|
- "IN\tA\t(.+)"
|
||||||
5
integration_tests/subdomains.txt
Normal file
5
integration_tests/subdomains.txt
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
one
|
||||||
|
docs
|
||||||
|
drive
|
||||||
|
play
|
||||||
|
|
||||||
@ -10,6 +10,7 @@ var dnsTestCases = map[string]testutils.TestCase{
|
|||||||
"dns/caa.yaml": &dnsCAA{},
|
"dns/caa.yaml": &dnsCAA{},
|
||||||
"dns/tlsa.yaml": &dnsTLSA{},
|
"dns/tlsa.yaml": &dnsTLSA{},
|
||||||
"dns/variables.yaml": &dnsVariables{},
|
"dns/variables.yaml": &dnsVariables{},
|
||||||
|
"dns/payload.yaml": &dnsPayload{},
|
||||||
"dns/dsl-matcher-variable.yaml": &dnsDSLMatcherVariable{},
|
"dns/dsl-matcher-variable.yaml": &dnsDSLMatcherVariable{},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,6 +69,26 @@ func (h *dnsVariables) Execute(filePath string) error {
|
|||||||
return expectResultsCount(results, 1)
|
return expectResultsCount(results, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type dnsPayload struct{}
|
||||||
|
|
||||||
|
// Execute executes a test case and returns an error if occurred
|
||||||
|
func (h *dnsPayload) Execute(filePath string) error {
|
||||||
|
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "google.com", debug)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := expectResultsCount(results, 3); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// override payload from CLI
|
||||||
|
results, err = testutils.RunNucleiTemplateAndGetResults(filePath, "google.com", debug, "-var", "subdomain_wordlist=subdomains.txt")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return expectResultsCount(results, 4)
|
||||||
|
}
|
||||||
|
|
||||||
type dnsDSLMatcherVariable struct{}
|
type dnsDSLMatcherVariable struct{}
|
||||||
|
|
||||||
// Execute executes a test case and returns an error if occurred
|
// Execute executes a test case and returns an error if occurred
|
||||||
|
|||||||
@ -9,9 +9,11 @@ import (
|
|||||||
"github.com/projectdiscovery/nuclei/v2/pkg/operators"
|
"github.com/projectdiscovery/nuclei/v2/pkg/operators"
|
||||||
"github.com/projectdiscovery/nuclei/v2/pkg/protocols"
|
"github.com/projectdiscovery/nuclei/v2/pkg/protocols"
|
||||||
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/expressions"
|
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/expressions"
|
||||||
|
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/generators"
|
||||||
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/replacer"
|
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/replacer"
|
||||||
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/dns/dnsclientpool"
|
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/dns/dnsclientpool"
|
||||||
"github.com/projectdiscovery/retryabledns"
|
"github.com/projectdiscovery/retryabledns"
|
||||||
|
fileutil "github.com/projectdiscovery/utils/file"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Request contains a DNS protocol request to be made from a template
|
// Request contains a DNS protocol request to be made from a template
|
||||||
@ -60,6 +62,21 @@ type Request struct {
|
|||||||
// value: 100
|
// value: 100
|
||||||
TraceMaxRecursion int `yaml:"trace-max-recursion,omitempty" jsonschema:"title=trace-max-recursion level for dns request,description=TraceMaxRecursion is the number of max recursion allowed for trace operations"`
|
TraceMaxRecursion int `yaml:"trace-max-recursion,omitempty" jsonschema:"title=trace-max-recursion level for dns request,description=TraceMaxRecursion is the number of max recursion allowed for trace operations"`
|
||||||
|
|
||||||
|
// description: |
|
||||||
|
// Attack is the type of payload combinations to perform.
|
||||||
|
//
|
||||||
|
// Batteringram is inserts the same payload into all defined payload positions at once, pitchfork combines multiple payload sets and clusterbomb generates
|
||||||
|
// permutations and combinations for all payloads.
|
||||||
|
AttackType generators.AttackTypeHolder `yaml:"attack,omitempty" json:"attack,omitempty" jsonschema:"title=attack is the payload combination,description=Attack is the type of payload combinations to perform,enum=batteringram,enum=pitchfork,enum=clusterbomb"`
|
||||||
|
// description: |
|
||||||
|
// Payloads contains any payloads for the current request.
|
||||||
|
//
|
||||||
|
// Payloads support both key-values combinations where a list
|
||||||
|
// of payloads is provided, or optionally a single file can also
|
||||||
|
// be provided as payload which will be read on run-time.
|
||||||
|
Payloads map[string]interface{} `yaml:"payloads,omitempty" json:"payloads,omitempty" jsonschema:"title=payloads for the network request,description=Payloads contains any payloads for the current request"`
|
||||||
|
generator *generators.PayloadGenerator
|
||||||
|
|
||||||
CompiledOperators *operators.Operators `yaml:"-"`
|
CompiledOperators *operators.Operators `yaml:"-"`
|
||||||
dnsClient *retryabledns.Client
|
dnsClient *retryabledns.Client
|
||||||
options *protocols.ExecuterOptions
|
options *protocols.ExecuterOptions
|
||||||
@ -143,6 +160,23 @@ func (request *Request) Compile(options *protocols.ExecuterOptions) error {
|
|||||||
request.class = classToInt(request.Class)
|
request.class = classToInt(request.Class)
|
||||||
request.options = options
|
request.options = options
|
||||||
request.question = questionTypeToInt(request.RequestType.String())
|
request.question = questionTypeToInt(request.RequestType.String())
|
||||||
|
for name, payload := range options.Options.Vars.AsMap() {
|
||||||
|
payloadStr, ok := payload.(string)
|
||||||
|
// check if inputs contains the payload
|
||||||
|
if ok && fileutil.FileExists(payloadStr) {
|
||||||
|
if request.Payloads == nil {
|
||||||
|
request.Payloads = make(map[string]interface{})
|
||||||
|
}
|
||||||
|
request.Payloads[name] = payloadStr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(request.Payloads) > 0 {
|
||||||
|
request.generator, err = generators.New(request.Payloads, request.AttackType.Value, request.options.TemplatePath, request.options.Options.Sandbox, request.options.Catalog, request.options.Options.AttackType)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "could not parse payloads")
|
||||||
|
}
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -170,6 +204,11 @@ func (request *Request) getDnsClient(options *protocols.ExecuterOptions, metadat
|
|||||||
|
|
||||||
// Requests returns the total number of requests the YAML rule will perform
|
// Requests returns the total number of requests the YAML rule will perform
|
||||||
func (request *Request) Requests() int {
|
func (request *Request) Requests() int {
|
||||||
|
if request.generator != nil {
|
||||||
|
payloadRequests := request.generator.NewIterator().Total()
|
||||||
|
return payloadRequests
|
||||||
|
}
|
||||||
|
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -35,3 +35,55 @@ func TestDNSCompileMake(t *testing.T) {
|
|||||||
require.Nil(t, err, "could not make dns request")
|
require.Nil(t, err, "could not make dns request")
|
||||||
require.Equal(t, "one.one.one.one.", req.Question[0].Name, "could not get correct dns question")
|
require.Equal(t, "one.one.one.one.", req.Question[0].Name, "could not get correct dns question")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDNSRequests(t *testing.T) {
|
||||||
|
options := testutils.DefaultOptions
|
||||||
|
|
||||||
|
recursion := false
|
||||||
|
testutils.Init(options)
|
||||||
|
const templateID = "testing-dns"
|
||||||
|
|
||||||
|
t.Run("dns-regular", func(t *testing.T) {
|
||||||
|
|
||||||
|
request := &Request{
|
||||||
|
RequestType: DNSRequestTypeHolder{DNSRequestType: A},
|
||||||
|
Class: "INET",
|
||||||
|
Retries: 5,
|
||||||
|
ID: templateID,
|
||||||
|
Recursion: &recursion,
|
||||||
|
Name: "{{FQDN}}",
|
||||||
|
}
|
||||||
|
executerOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{
|
||||||
|
ID: templateID,
|
||||||
|
Info: model.Info{SeverityHolder: severity.Holder{Severity: severity.Low}, Name: "test"},
|
||||||
|
})
|
||||||
|
err := request.Compile(executerOpts)
|
||||||
|
require.Nil(t, err, "could not compile dns request")
|
||||||
|
|
||||||
|
reqCount := request.Requests()
|
||||||
|
require.Equal(t, 1, reqCount, "could not get correct dns request count")
|
||||||
|
})
|
||||||
|
|
||||||
|
// test payload requests count is correct
|
||||||
|
t.Run("dns-payload", func(t *testing.T) {
|
||||||
|
|
||||||
|
request := &Request{
|
||||||
|
RequestType: DNSRequestTypeHolder{DNSRequestType: A},
|
||||||
|
Class: "INET",
|
||||||
|
Retries: 5,
|
||||||
|
ID: templateID,
|
||||||
|
Recursion: &recursion,
|
||||||
|
Name: "{{subdomain}}.{{FQDN}}",
|
||||||
|
Payloads: map[string]interface{}{"subdomain": []string{"a", "b", "c"}},
|
||||||
|
}
|
||||||
|
executerOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{
|
||||||
|
ID: templateID,
|
||||||
|
Info: model.Info{SeverityHolder: severity.Holder{Severity: severity.Low}, Name: "test"},
|
||||||
|
})
|
||||||
|
err := request.Compile(executerOpts)
|
||||||
|
require.Nil(t, err, "could not compile dns request")
|
||||||
|
|
||||||
|
reqCount := request.Requests()
|
||||||
|
require.Equal(t, 3, reqCount, "could not get correct dns request count")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@ -4,9 +4,11 @@ import (
|
|||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
"golang.org/x/exp/maps"
|
||||||
|
|
||||||
"github.com/projectdiscovery/gologger"
|
"github.com/projectdiscovery/gologger"
|
||||||
"github.com/projectdiscovery/nuclei/v2/pkg/output"
|
"github.com/projectdiscovery/nuclei/v2/pkg/output"
|
||||||
@ -53,7 +55,29 @@ func (request *Request) ExecuteWithResults(input *contextargs.Context, metadata,
|
|||||||
// merge with metadata (eg. from workflow context)
|
// merge with metadata (eg. from workflow context)
|
||||||
vars = generators.MergeMaps(vars, metadata, optionVars)
|
vars = generators.MergeMaps(vars, metadata, optionVars)
|
||||||
variablesMap := request.options.Variables.Evaluate(vars)
|
variablesMap := request.options.Variables.Evaluate(vars)
|
||||||
vars = generators.MergeMaps(variablesMap, vars)
|
vars = generators.MergeMaps(vars, variablesMap)
|
||||||
|
|
||||||
|
if request.generator != nil {
|
||||||
|
iterator := request.generator.NewIterator()
|
||||||
|
|
||||||
|
for {
|
||||||
|
value, ok := iterator.Value()
|
||||||
|
if !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
value = generators.MergeMaps(vars, value)
|
||||||
|
if err := request.execute(domain, metadata, previous, value, callback); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
value := maps.Clone(vars)
|
||||||
|
return request.execute(domain, metadata, previous, value, callback)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (request *Request) execute(domain string, metadata, previous output.InternalEvent, vars map[string]interface{}, callback protocols.OutputEventCallback) error {
|
||||||
|
|
||||||
if vardump.EnableVarDump {
|
if vardump.EnableVarDump {
|
||||||
gologger.Debug().Msgf("Protocol request variables: \n%s\n", vardump.DumpVariables(vars))
|
gologger.Debug().Msgf("Protocol request variables: \n%s\n", vardump.DumpVariables(vars))
|
||||||
@ -74,14 +98,20 @@ func (request *Request) ExecuteWithResults(input *contextargs.Context, metadata,
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
question := domain
|
||||||
|
if len(compiledRequest.Question) > 0 {
|
||||||
|
question = compiledRequest.Question[0].Name
|
||||||
|
}
|
||||||
|
// remove the last dot
|
||||||
|
question = strings.TrimSuffix(question, ".")
|
||||||
|
|
||||||
requestString := compiledRequest.String()
|
requestString := compiledRequest.String()
|
||||||
if varErr := expressions.ContainsUnresolvedVariables(requestString); varErr != nil {
|
if varErr := expressions.ContainsUnresolvedVariables(requestString); varErr != nil {
|
||||||
gologger.Warning().Msgf("[%s] Could not make dns request for %s: %v\n", request.options.TemplateID, domain, varErr)
|
gologger.Warning().Msgf("[%s] Could not make dns request for %s: %v\n", request.options.TemplateID, question, varErr)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if request.options.Options.Debug || request.options.Options.DebugRequests || request.options.Options.StoreResponse {
|
if request.options.Options.Debug || request.options.Options.DebugRequests || request.options.Options.StoreResponse {
|
||||||
msg := fmt.Sprintf("[%s] Dumped DNS request for %s", request.options.TemplateID, domain)
|
msg := fmt.Sprintf("[%s] Dumped DNS request for %s", request.options.TemplateID, question)
|
||||||
if request.options.Options.Debug || request.options.Options.DebugRequests {
|
if request.options.Options.Debug || request.options.Options.DebugRequests {
|
||||||
gologger.Info().Str("domain", domain).Msgf(msg)
|
gologger.Info().Str("domain", domain).Msgf(msg)
|
||||||
gologger.Print().Msgf("%s", requestString)
|
gologger.Print().Msgf("%s", requestString)
|
||||||
@ -98,14 +128,15 @@ func (request *Request) ExecuteWithResults(input *contextargs.Context, metadata,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
request.options.Output.Request(request.options.TemplatePath, domain, request.Type().String(), err)
|
request.options.Output.Request(request.options.TemplatePath, domain, request.Type().String(), err)
|
||||||
request.options.Progress.IncrementFailedRequestsBy(1)
|
request.options.Progress.IncrementFailedRequestsBy(1)
|
||||||
|
} else {
|
||||||
|
request.options.Progress.IncrementRequests()
|
||||||
}
|
}
|
||||||
if response == nil {
|
if response == nil {
|
||||||
return errors.Wrap(err, "could not send dns request")
|
return errors.Wrap(err, "could not send dns request")
|
||||||
}
|
}
|
||||||
request.options.Progress.IncrementRequests()
|
|
||||||
|
|
||||||
request.options.Output.Request(request.options.TemplatePath, domain, request.Type().String(), err)
|
request.options.Output.Request(request.options.TemplatePath, domain, request.Type().String(), err)
|
||||||
gologger.Verbose().Msgf("[%s] Sent DNS request to %s\n", request.options.TemplateID, domain)
|
gologger.Verbose().Msgf("[%s] Sent DNS request to %s\n", request.options.TemplateID, question)
|
||||||
|
|
||||||
// perform trace if necessary
|
// perform trace if necessary
|
||||||
var traceData *retryabledns.TraceData
|
var traceData *retryabledns.TraceData
|
||||||
@ -116,7 +147,8 @@ func (request *Request) ExecuteWithResults(input *contextargs.Context, metadata,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
outputEvent := request.responseToDSLMap(compiledRequest, response, input.MetaInput.Input, input.MetaInput.Input, traceData)
|
// Create the output event
|
||||||
|
outputEvent := request.responseToDSLMap(compiledRequest, response, domain, question, traceData)
|
||||||
for k, v := range previous {
|
for k, v := range previous {
|
||||||
outputEvent[k] = v
|
outputEvent[k] = v
|
||||||
}
|
}
|
||||||
@ -125,9 +157,9 @@ func (request *Request) ExecuteWithResults(input *contextargs.Context, metadata,
|
|||||||
}
|
}
|
||||||
event := eventcreator.CreateEvent(request, outputEvent, request.options.Options.Debug || request.options.Options.DebugResponse)
|
event := eventcreator.CreateEvent(request, outputEvent, request.options.Options.Debug || request.options.Options.DebugResponse)
|
||||||
|
|
||||||
dumpResponse(event, request, request.options, response.String(), domain)
|
dumpResponse(event, request, request.options, response.String(), question)
|
||||||
if request.Trace {
|
if request.Trace {
|
||||||
dumpTraceData(event, request.options, traceToString(traceData, true), domain)
|
dumpTraceData(event, request.options, traceToString(traceData, true), question)
|
||||||
}
|
}
|
||||||
|
|
||||||
callback(event)
|
callback(event)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user