mirror of https://github.com/daffainfo/nuclei.git
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>dev
parent
59376180b1
commit
9c2fa8f9c4
|
@ -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/ValueObject2.ser
|
||||
*.exe
|
||||
|
||||
v2/.gitignore
|
||||
|
|
|
@ -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(.+)"
|
|
@ -0,0 +1,5 @@
|
|||
one
|
||||
docs
|
||||
drive
|
||||
play
|
||||
|
|
@ -10,6 +10,7 @@ var dnsTestCases = map[string]testutils.TestCase{
|
|||
"dns/caa.yaml": &dnsCAA{},
|
||||
"dns/tlsa.yaml": &dnsTLSA{},
|
||||
"dns/variables.yaml": &dnsVariables{},
|
||||
"dns/payload.yaml": &dnsPayload{},
|
||||
"dns/dsl-matcher-variable.yaml": &dnsDSLMatcherVariable{},
|
||||
}
|
||||
|
||||
|
@ -68,6 +69,26 @@ func (h *dnsVariables) Execute(filePath string) error {
|
|||
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{}
|
||||
|
||||
// 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/protocols"
|
||||
"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/dns/dnsclientpool"
|
||||
"github.com/projectdiscovery/retryabledns"
|
||||
fileutil "github.com/projectdiscovery/utils/file"
|
||||
)
|
||||
|
||||
// Request contains a DNS protocol request to be made from a template
|
||||
|
@ -60,6 +62,21 @@ type Request struct {
|
|||
// 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"`
|
||||
|
||||
// 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:"-"`
|
||||
dnsClient *retryabledns.Client
|
||||
options *protocols.ExecuterOptions
|
||||
|
@ -143,6 +160,23 @@ func (request *Request) Compile(options *protocols.ExecuterOptions) error {
|
|||
request.class = classToInt(request.Class)
|
||||
request.options = options
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -170,6 +204,11 @@ func (request *Request) getDnsClient(options *protocols.ExecuterOptions, metadat
|
|||
|
||||
// Requests returns the total number of requests the YAML rule will perform
|
||||
func (request *Request) Requests() int {
|
||||
if request.generator != nil {
|
||||
payloadRequests := request.generator.NewIterator().Total()
|
||||
return payloadRequests
|
||||
}
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
|
|
|
@ -35,3 +35,55 @@ func TestDNSCompileMake(t *testing.T) {
|
|||
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")
|
||||
}
|
||||
|
||||
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"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/exp/maps"
|
||||
|
||||
"github.com/projectdiscovery/gologger"
|
||||
"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)
|
||||
vars = generators.MergeMaps(vars, metadata, optionVars)
|
||||
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 {
|
||||
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
|
||||
}
|
||||
}
|
||||
question := domain
|
||||
if len(compiledRequest.Question) > 0 {
|
||||
question = compiledRequest.Question[0].Name
|
||||
}
|
||||
// remove the last dot
|
||||
question = strings.TrimSuffix(question, ".")
|
||||
|
||||
requestString := compiledRequest.String()
|
||||
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
|
||||
}
|
||||
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 {
|
||||
gologger.Info().Str("domain", domain).Msgf(msg)
|
||||
gologger.Print().Msgf("%s", requestString)
|
||||
|
@ -98,14 +128,15 @@ func (request *Request) ExecuteWithResults(input *contextargs.Context, metadata,
|
|||
if err != nil {
|
||||
request.options.Output.Request(request.options.TemplatePath, domain, request.Type().String(), err)
|
||||
request.options.Progress.IncrementFailedRequestsBy(1)
|
||||
} else {
|
||||
request.options.Progress.IncrementRequests()
|
||||
}
|
||||
if response == nil {
|
||||
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)
|
||||
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
|
||||
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 {
|
||||
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)
|
||||
|
||||
dumpResponse(event, request, request.options, response.String(), domain)
|
||||
dumpResponse(event, request, request.options, response.String(), question)
|
||||
if request.Trace {
|
||||
dumpTraceData(event, request.options, traceToString(traceData, true), domain)
|
||||
dumpTraceData(event, request.options, traceToString(traceData, true), question)
|
||||
}
|
||||
|
||||
callback(event)
|
||||
|
|
Loading…
Reference in New Issue