github.com/projectdiscovery/nuclei/v2@v2.9.15/pkg/protocols/dns/dns.go (about) 1 package dns 2 3 import ( 4 "strings" 5 6 "github.com/miekg/dns" 7 "github.com/pkg/errors" 8 9 "github.com/projectdiscovery/nuclei/v2/pkg/operators" 10 "github.com/projectdiscovery/nuclei/v2/pkg/protocols" 11 "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/expressions" 12 "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/generators" 13 "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/replacer" 14 "github.com/projectdiscovery/nuclei/v2/pkg/protocols/dns/dnsclientpool" 15 "github.com/projectdiscovery/retryabledns" 16 fileutil "github.com/projectdiscovery/utils/file" 17 ) 18 19 // Request contains a DNS protocol request to be made from a template 20 type Request struct { 21 // Operators for the current request go here. 22 operators.Operators `yaml:",inline"` 23 24 // ID is the optional id of the request 25 ID string `yaml:"id,omitempty" json:"id,omitempty" jsonschema:"title=id of the dns request,description=ID is the optional ID of the DNS Request"` 26 27 // description: | 28 // Name is the Hostname to make DNS request for. 29 // 30 // Generally, it is set to {{FQDN}} which is the domain we get from input. 31 // examples: 32 // - value: "\"{{FQDN}}\"" 33 Name string `yaml:"name,omitempty" json:"name,omitempty" jsonschema:"title=hostname to make dns request for,description=Name is the Hostname to make DNS request for"` 34 // description: | 35 // RequestType is the type of DNS request to make. 36 RequestType DNSRequestTypeHolder `yaml:"type,omitempty" json:"type,omitempty" jsonschema:"title=type of dns request to make,description=Type is the type of DNS request to make,enum=A,enum=NS,enum=DS,enum=CNAME,enum=SOA,enum=PTR,enum=MX,enum=TXT,enum=AAAA"` 37 // description: | 38 // Class is the class of the DNS request. 39 // 40 // Usually it's enough to just leave it as INET. 41 // values: 42 // - "inet" 43 // - "csnet" 44 // - "chaos" 45 // - "hesiod" 46 // - "none" 47 // - "any" 48 Class string `yaml:"class,omitempty" json:"class,omitempty" jsonschema:"title=class of DNS request,description=Class is the class of the DNS request,enum=inet,enum=csnet,enum=chaos,enum=hesiod,enum=none,enum=any"` 49 // description: | 50 // Retries is the number of retries for the DNS request 51 // examples: 52 // - name: Use a retry of 3 to 5 generally 53 // value: 5 54 Retries int `yaml:"retries,omitempty" json:"retries,omitempty" jsonschema:"title=retries for dns request,description=Retries is the number of retries for the DNS request"` 55 // description: | 56 // Trace performs a trace operation for the target. 57 Trace bool `yaml:"trace,omitempty" json:"trace,omitempty" jsonschema:"title=trace operation,description=Trace performs a trace operation for the target."` 58 // description: | 59 // TraceMaxRecursion is the number of max recursion allowed for trace operations 60 // examples: 61 // - name: Use a retry of 100 to 150 generally 62 // value: 100 63 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"` 64 65 // description: | 66 // Attack is the type of payload combinations to perform. 67 // 68 // Batteringram is inserts the same payload into all defined payload positions at once, pitchfork combines multiple payload sets and clusterbomb generates 69 // permutations and combinations for all payloads. 70 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"` 71 // description: | 72 // Payloads contains any payloads for the current request. 73 // 74 // Payloads support both key-values combinations where a list 75 // of payloads is provided, or optionally a single file can also 76 // be provided as payload which will be read on run-time. 77 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"` 78 generator *generators.PayloadGenerator 79 80 CompiledOperators *operators.Operators `yaml:"-"` 81 dnsClient *retryabledns.Client 82 options *protocols.ExecutorOptions 83 84 // cache any variables that may be needed for operation. 85 class uint16 86 question uint16 87 88 // description: | 89 // Recursion determines if resolver should recurse all records to get fresh results. 90 Recursion *bool `yaml:"recursion,omitempty" json:"recursion,omitempty" jsonschema:"title=recurse all servers,description=Recursion determines if resolver should recurse all records to get fresh results"` 91 // Resolvers to use for the dns requests 92 Resolvers []string `yaml:"resolvers,omitempty" json:"resolvers,omitempty" jsonschema:"title=Resolvers,description=Define resolvers to use within the template"` 93 } 94 95 // RequestPartDefinitions contains a mapping of request part definitions and their 96 // description. Multiple definitions are separated by commas. 97 // Definitions not having a name (generated on runtime) are prefixed & suffixed by <>. 98 var RequestPartDefinitions = map[string]string{ 99 "template-id": "ID of the template executed", 100 "template-info": "Info Block of the template executed", 101 "template-path": "Path of the template executed", 102 "host": "Host is the input to the template", 103 "matched": "Matched is the input which was matched upon", 104 "request": "Request contains the DNS request in text format", 105 "type": "Type is the type of request made", 106 "rcode": "Rcode field returned for the DNS request", 107 "question": "Question contains the DNS question field", 108 "extra": "Extra contains the DNS response extra field", 109 "answer": "Answer contains the DNS response answer field", 110 "ns": "NS contains the DNS response NS field", 111 "raw,body,all": "Raw contains the raw DNS response (default)", 112 "trace": "Trace contains trace data for DNS request if enabled", 113 } 114 115 func (request *Request) GetCompiledOperators() []*operators.Operators { 116 return []*operators.Operators{request.CompiledOperators} 117 } 118 119 // GetID returns the unique ID of the request if any. 120 func (request *Request) GetID() string { 121 return request.ID 122 } 123 124 // Options returns executer options for http request 125 func (r *Request) Options() *protocols.ExecutorOptions { 126 return r.options 127 } 128 129 // Compile compiles the protocol request for further execution. 130 func (request *Request) Compile(options *protocols.ExecutorOptions) error { 131 if request.Retries == 0 { 132 request.Retries = 3 133 } 134 if request.Recursion == nil { 135 recursion := true 136 request.Recursion = &recursion 137 } 138 dnsClientOptions := &dnsclientpool.Configuration{ 139 Retries: request.Retries, 140 } 141 if len(request.Resolvers) > 0 { 142 dnsClientOptions.Resolvers = request.Resolvers 143 } 144 // Create a dns client for the class 145 client, err := request.getDnsClient(options, nil) 146 if err != nil { 147 return errors.Wrap(err, "could not get dns client") 148 } 149 request.dnsClient = client 150 151 if len(request.Matchers) > 0 || len(request.Extractors) > 0 { 152 compiled := &request.Operators 153 compiled.ExcludeMatchers = options.ExcludeMatchers 154 compiled.TemplateID = options.TemplateID 155 if err := compiled.Compile(); err != nil { 156 return errors.Wrap(err, "could not compile operators") 157 } 158 request.CompiledOperators = compiled 159 } 160 request.class = classToInt(request.Class) 161 request.options = options 162 request.question = questionTypeToInt(request.RequestType.String()) 163 for name, payload := range options.Options.Vars.AsMap() { 164 payloadStr, ok := payload.(string) 165 // check if inputs contains the payload 166 if ok && fileutil.FileExists(payloadStr) { 167 if request.Payloads == nil { 168 request.Payloads = make(map[string]interface{}) 169 } 170 request.Payloads[name] = payloadStr 171 } 172 } 173 174 if len(request.Payloads) > 0 { 175 request.generator, err = generators.New(request.Payloads, request.AttackType.Value, request.options.TemplatePath, request.options.Options.AllowLocalFileAccess, request.options.Catalog, request.options.Options.AttackType) 176 if err != nil { 177 return errors.Wrap(err, "could not parse payloads") 178 } 179 } 180 return nil 181 } 182 183 func (request *Request) getDnsClient(options *protocols.ExecutorOptions, metadata map[string]interface{}) (*retryabledns.Client, error) { 184 dnsClientOptions := &dnsclientpool.Configuration{ 185 Retries: request.Retries, 186 } 187 if len(request.Resolvers) > 0 { 188 if len(request.Resolvers) > 0 { 189 for _, resolver := range request.Resolvers { 190 if expressions.ContainsUnresolvedVariables(resolver) != nil { 191 var err error 192 resolver, err = expressions.Evaluate(resolver, metadata) 193 if err != nil { 194 return nil, errors.Wrap(err, "could not resolve resolvers expressions") 195 } 196 dnsClientOptions.Resolvers = append(dnsClientOptions.Resolvers, resolver) 197 } 198 } 199 } 200 dnsClientOptions.Resolvers = request.Resolvers 201 } 202 return dnsclientpool.Get(options.Options, dnsClientOptions) 203 } 204 205 // Requests returns the total number of requests the YAML rule will perform 206 func (request *Request) Requests() int { 207 if request.generator != nil { 208 payloadRequests := request.generator.NewIterator().Total() 209 return payloadRequests 210 } 211 212 return 1 213 } 214 215 // Make returns the request to be sent for the protocol 216 func (request *Request) Make(host string, vars map[string]interface{}) (*dns.Msg, error) { 217 // Build a request on the specified URL 218 req := new(dns.Msg) 219 req.Id = dns.Id() 220 req.RecursionDesired = *request.Recursion 221 222 var q dns.Question 223 final := replacer.Replace(request.Name, vars) 224 225 q.Name = dns.Fqdn(final) 226 q.Qclass = request.class 227 q.Qtype = request.question 228 req.Question = append(req.Question, q) 229 230 req.SetEdns0(4096, false) 231 232 switch request.question { 233 case dns.TypeTXT: 234 req.AuthenticatedData = true 235 } 236 237 return req, nil 238 } 239 240 // questionTypeToInt converts DNS question type to internal representation 241 func questionTypeToInt(questionType string) uint16 { 242 questionType = strings.TrimSpace(strings.ToUpper(questionType)) 243 question := dns.TypeA 244 245 switch questionType { 246 case "A": 247 question = dns.TypeA 248 case "NS": 249 question = dns.TypeNS 250 case "CNAME": 251 question = dns.TypeCNAME 252 case "SOA": 253 question = dns.TypeSOA 254 case "PTR": 255 question = dns.TypePTR 256 case "MX": 257 question = dns.TypeMX 258 case "TXT": 259 question = dns.TypeTXT 260 case "DS": 261 question = dns.TypeDS 262 case "AAAA": 263 question = dns.TypeAAAA 264 case "CAA": 265 question = dns.TypeCAA 266 case "TLSA": 267 question = dns.TypeTLSA 268 case "ANY": 269 question = dns.TypeANY 270 } 271 return question 272 } 273 274 // classToInt converts a dns class name to its internal representation 275 func classToInt(class string) uint16 { 276 class = strings.TrimSpace(strings.ToUpper(class)) 277 result := dns.ClassINET 278 279 switch class { 280 case "INET": 281 result = dns.ClassINET 282 case "CSNET": 283 result = dns.ClassCSNET 284 case "CHAOS": 285 result = dns.ClassCHAOS 286 case "HESIOD": 287 result = dns.ClassHESIOD 288 case "NONE": 289 result = dns.ClassNONE 290 case "ANY": 291 result = dns.ClassANY 292 } 293 return uint16(result) 294 }