mirror of
https://github.com/projectdiscovery/nuclei.git
synced 2025-12-18 03:55:23 +00:00
poc implementation of dns templating
This commit is contained in:
parent
dc6b010625
commit
603456ddbb
@ -7,6 +7,15 @@ import (
|
||||
"github.com/projectdiscovery/gologger"
|
||||
)
|
||||
|
||||
// DefaultResolvers contains the list of resolvers known to be trusted.
|
||||
var DefaultResolvers = []string{
|
||||
"1.1.1.1:53", // Cloudflare
|
||||
"1.0.0.1:53", // Cloudflare
|
||||
"8.8.8.8:53", // Google
|
||||
"8.8.4.4:53", // Google
|
||||
"9.9.9.9:53", // Quad9
|
||||
}
|
||||
|
||||
// Options contains the configuration options for tuning
|
||||
// the template requesting process.
|
||||
type Options struct {
|
||||
|
||||
@ -13,17 +13,20 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"github.com/projectdiscovery/gologger"
|
||||
"github.com/projectdiscovery/nuclei/pkg/extractors"
|
||||
"github.com/projectdiscovery/nuclei/pkg/matchers"
|
||||
"github.com/projectdiscovery/nuclei/pkg/templates"
|
||||
retryabledns "github.com/projectdiscovery/retryabledns"
|
||||
retryablehttp "github.com/projectdiscovery/retryablehttp-go"
|
||||
)
|
||||
|
||||
// Runner is a client for running the enumeration process.
|
||||
type Runner struct {
|
||||
// client is the http client with retries
|
||||
client *retryablehttp.Client
|
||||
// httpClient is the http client with retries
|
||||
httpClient *retryablehttp.Client
|
||||
dnsClient *retryabledns.Client
|
||||
// output is the output file to write if any
|
||||
output *os.File
|
||||
outputMutex *sync.Mutex
|
||||
@ -43,7 +46,7 @@ func New(options *Options) (*Runner, error) {
|
||||
retryablehttpOptions.RetryMax = options.Retries
|
||||
|
||||
// Create the HTTP Client
|
||||
client := retryablehttp.NewWithHTTPClient(&http.Client{
|
||||
httpClient := retryablehttp.NewWithHTTPClient(&http.Client{
|
||||
Transport: &http.Transport{
|
||||
MaxIdleConnsPerHost: -1,
|
||||
TLSClientConfig: &tls.Config{
|
||||
@ -57,9 +60,13 @@ func New(options *Options) (*Runner, error) {
|
||||
return http.ErrUseLastResponse
|
||||
},
|
||||
}, retryablehttpOptions)
|
||||
client.CheckRetry = retryablehttp.HostSprayRetryPolicy()
|
||||
httpClient.CheckRetry = retryablehttp.HostSprayRetryPolicy()
|
||||
|
||||
runner.client = client
|
||||
runner.httpClient = httpClient
|
||||
|
||||
// Create the dns client
|
||||
dnsClient, _ := retryabledns.New(DefaultResolvers, options.Retries)
|
||||
runner.dnsClient = dnsClient
|
||||
|
||||
// Create the output file if asked
|
||||
if options.Output != "" {
|
||||
@ -165,18 +172,19 @@ func (r *Runner) processTemplateWithList(template *templates.Template, reader io
|
||||
|
||||
// sendRequest sends a request to the target based on a template
|
||||
func (r *Runner) sendRequest(template *templates.Template, URL string, writer *bufio.Writer) {
|
||||
for _, request := range template.Requests {
|
||||
// process http requests
|
||||
for _, request := range template.RequestsHTTP {
|
||||
// Compile each request for the template based on the URL
|
||||
compiledRequest, err := request.MakeRequest(URL)
|
||||
compiledRequest, err := request.MakeHTTPRequest(URL)
|
||||
if err != nil {
|
||||
gologger.Warningf("[%s] Could not make request %s: %s\n", template.ID, URL, err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Send the request to the target servers
|
||||
reqLoop:
|
||||
reqLoopHTTP:
|
||||
for _, req := range compiledRequest {
|
||||
resp, err := r.client.Do(req)
|
||||
resp, err := r.httpClient.Do(req)
|
||||
if err != nil {
|
||||
if resp != nil {
|
||||
resp.Body.Close()
|
||||
@ -206,7 +214,7 @@ func (r *Runner) sendRequest(template *templates.Template, URL string, writer *b
|
||||
|
||||
// Check if the matcher matched
|
||||
if !matcher.Match(resp, body, headers) {
|
||||
continue reqLoop
|
||||
continue reqLoopHTTP
|
||||
}
|
||||
}
|
||||
|
||||
@ -231,6 +239,46 @@ func (r *Runner) sendRequest(template *templates.Template, URL string, writer *b
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// process dns messages
|
||||
for _, request := range template.RequestsDNS {
|
||||
// Compile each request for the template based on the URL
|
||||
compiledRequest, err := request.MakeDNSRequest(URL)
|
||||
if err != nil {
|
||||
gologger.Warningf("[%s] Could not make request %s: %s\n", template.ID, URL, err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Send the request to the target servers
|
||||
resp, err := r.dnsClient.Do(compiledRequest)
|
||||
if err != nil {
|
||||
gologger.Warningf("[%s] Could not send request %s: %s\n", template.ID, URL, err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, matcher := range request.Matchers {
|
||||
// Check if the matcher matched
|
||||
if !matcher.MatchDNS(resp) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// If there is an extractor, run it.
|
||||
var extractorResults []string
|
||||
for _, extractor := range request.Extractors {
|
||||
extractorResults = append(extractorResults, extractor.ExtractDNS(resp.String())...)
|
||||
}
|
||||
|
||||
// All the matchers matched, print the output on the screen
|
||||
output := buildOutputDNS(template, resp, extractorResults)
|
||||
gologger.Silentf("%s", output)
|
||||
|
||||
if writer != nil {
|
||||
r.outputMutex.Lock()
|
||||
writer.WriteString(output)
|
||||
r.outputMutex.Unlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// buildOutput builds an output text for writing results
|
||||
@ -259,3 +307,32 @@ func buildOutput(template *templates.Template, req *retryablehttp.Request, extra
|
||||
|
||||
return builder.String()
|
||||
}
|
||||
|
||||
// buildOutput builds an output text for writing results
|
||||
func buildOutputDNS(template *templates.Template, msg *dns.Msg, extractorResults []string) string {
|
||||
builder := &strings.Builder{}
|
||||
builder.WriteRune('[')
|
||||
builder.WriteString(template.ID)
|
||||
builder.WriteString("] ")
|
||||
|
||||
// domain name from question
|
||||
if len(msg.Question) > 0 {
|
||||
domain := msg.Question[0].Name
|
||||
builder.WriteString(domain)
|
||||
}
|
||||
|
||||
// If any extractors, write the results
|
||||
if len(extractorResults) > 0 {
|
||||
builder.WriteString(" [")
|
||||
for i, result := range extractorResults {
|
||||
builder.WriteString(result)
|
||||
if i != len(extractorResults)-1 {
|
||||
builder.WriteRune(',')
|
||||
}
|
||||
}
|
||||
builder.WriteString("]")
|
||||
}
|
||||
builder.WriteRune('\n')
|
||||
|
||||
return builder.String()
|
||||
}
|
||||
|
||||
@ -2,8 +2,11 @@ package runner
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
"github.com/asaskevich/govalidator"
|
||||
)
|
||||
|
||||
// unsafeToString converts byte slice to string with zero allocations
|
||||
@ -29,3 +32,25 @@ func headersToString(headers http.Header) string {
|
||||
}
|
||||
return builder.String()
|
||||
}
|
||||
|
||||
// isURL tests a string to determine if it is a well-structured url or not.
|
||||
func isURL(toTest string) bool {
|
||||
_, err := url.ParseRequestURI(toTest)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
u, err := url.Parse(toTest)
|
||||
if err != nil || u.Scheme == "" || u.Host == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// isDNS tests a string to determine if it is a well-structured dns or not
|
||||
// even if it's oneliner, we leave it wrapped in a function call for
|
||||
// future improvements
|
||||
func isDNS(toTest string) bool {
|
||||
return govalidator.IsDNSName(toTest)
|
||||
}
|
||||
|
||||
@ -16,6 +16,12 @@ func (e *Extractor) Extract(body, headers string) []string {
|
||||
}
|
||||
}
|
||||
|
||||
// ExtractDNS extracts response from dns message using a regex
|
||||
func (e *Extractor) ExtractDNS(msg string) []string {
|
||||
// Match the parts as required for regex check
|
||||
return e.extractRegex(msg)
|
||||
}
|
||||
|
||||
// extractRegex extracts text from a corpus and returns it
|
||||
func (e *Extractor) extractRegex(corpus string) []string {
|
||||
results := []string{}
|
||||
|
||||
@ -4,6 +4,8 @@ import (
|
||||
"encoding/hex"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
// Match matches a http response again a given matcher
|
||||
@ -53,6 +55,25 @@ func (m *Matcher) Match(resp *http.Response, body, headers string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// MatchDNS matches a dns response again a given matcher
|
||||
func (m *Matcher) MatchDNS(msg *dns.Msg) bool {
|
||||
switch m.matcherType {
|
||||
// [WIP] add dns status code matcher
|
||||
case SizeMatcher:
|
||||
return m.matchSizeCode(msg.Len())
|
||||
case WordsMatcher:
|
||||
// Match for word check
|
||||
return m.matchWords(msg.String())
|
||||
case RegexMatcher:
|
||||
// Match regex check
|
||||
return m.matchRegex(msg.String())
|
||||
case BinaryMatcher:
|
||||
// Match binary characters check
|
||||
return m.matchBinary(msg.String())
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// matchStatusCode matches a status code check against an HTTP Response
|
||||
func (m *Matcher) matchStatusCode(statusCode int) bool {
|
||||
// Iterate over all the status codes accepted as valid
|
||||
|
||||
112
pkg/requests/dns-question.go
Normal file
112
pkg/requests/dns-question.go
Normal file
@ -0,0 +1,112 @@
|
||||
package requests
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"github.com/projectdiscovery/nuclei/pkg/extractors"
|
||||
"github.com/projectdiscovery/nuclei/pkg/matchers"
|
||||
"github.com/valyala/fasttemplate"
|
||||
)
|
||||
|
||||
// DNSRequest contains a request to be made from a template
|
||||
type DNSRequest struct {
|
||||
Recursion bool `yaml:"recursion"`
|
||||
// Path contains the path/s for the request
|
||||
Name string `yaml:"name"`
|
||||
Type string `yaml:"type"`
|
||||
Class string `yaml:"class"`
|
||||
|
||||
// Matchers contains the detection mechanism for the request to identify
|
||||
// whether the request was successful
|
||||
Matchers []*matchers.Matcher `yaml:"matchers,omitempty"`
|
||||
// Extractors contains the extraction mechanism for the request to identify
|
||||
// and extract parts of the response.
|
||||
Extractors []*extractors.Extractor `yaml:"extractors,omitempty"`
|
||||
}
|
||||
|
||||
// MakeDNSRequest creates a *dns.Request from a request template
|
||||
func (r *DNSRequest) MakeDNSRequest(domain string) (*dns.Msg, error) {
|
||||
|
||||
domain = dns.Fqdn(domain)
|
||||
|
||||
// Build a request on the specified URL
|
||||
req := new(dns.Msg)
|
||||
req.Id = dns.Id()
|
||||
req.RecursionDesired = r.Recursion
|
||||
|
||||
var q dns.Question
|
||||
|
||||
t := fasttemplate.New(r.Name, "{{", "}}")
|
||||
q.Name = dns.Fqdn(t.ExecuteString(map[string]interface{}{
|
||||
"FQDN": domain,
|
||||
}))
|
||||
|
||||
qclass, err := toQClass(r.Class)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
q.Qclass = qclass
|
||||
|
||||
qtype, err := toQType(r.Type)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
q.Qtype = qtype
|
||||
|
||||
req.Question = append(req.Question, q)
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func toQType(ttype string) (rtype uint16, err error) {
|
||||
ttype = strings.TrimSpace(strings.ToUpper(ttype))
|
||||
|
||||
switch ttype {
|
||||
case "A":
|
||||
rtype = dns.TypeA
|
||||
case "NS":
|
||||
rtype = dns.TypeNS
|
||||
case "CNAME":
|
||||
rtype = dns.TypeCNAME
|
||||
case "SOA":
|
||||
rtype = dns.TypeSOA
|
||||
case "PTR":
|
||||
rtype = dns.TypePTR
|
||||
case "MX":
|
||||
rtype = dns.TypeMX
|
||||
case "TXT":
|
||||
rtype = dns.TypeTXT
|
||||
case "AAAA":
|
||||
rtype = dns.TypeAAAA
|
||||
default:
|
||||
rtype = dns.TypeNone
|
||||
err = fmt.Errorf("incorrect type")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func toQClass(tclass string) (rclass uint16, err error) {
|
||||
tclass = strings.TrimSpace(strings.ToUpper(tclass))
|
||||
|
||||
switch tclass {
|
||||
case "INET":
|
||||
rclass = dns.ClassINET
|
||||
case "CSNET":
|
||||
rclass = dns.ClassCSNET
|
||||
case "CHAOS":
|
||||
rclass = dns.ClassCHAOS
|
||||
case "HESIOD":
|
||||
rclass = dns.ClassHESIOD
|
||||
case "NONE":
|
||||
rclass = dns.ClassNONE
|
||||
case "ANY":
|
||||
rclass = dns.ClassANY
|
||||
default:
|
||||
err = fmt.Errorf("incorrect class")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
@ -12,8 +12,8 @@ import (
|
||||
"github.com/valyala/fasttemplate"
|
||||
)
|
||||
|
||||
// Request contains a request to be made from a template
|
||||
type Request struct {
|
||||
// HTTPRequest contains a request to be made from a template
|
||||
type HTTPRequest struct {
|
||||
// Method is the request method, whether GET, POST, PUT, etc
|
||||
Method string `yaml:"method"`
|
||||
// Path contains the path/s for the request
|
||||
@ -30,8 +30,8 @@ type Request struct {
|
||||
Extractors []*extractors.Extractor `yaml:"extractors,omitempty"`
|
||||
}
|
||||
|
||||
// MakeRequest creates a *http.Request from a request template
|
||||
func (r *Request) MakeRequest(baseURL string) ([]*retryablehttp.Request, error) {
|
||||
// MakeHTTPRequest creates a *http.Request from a request template
|
||||
func (r *HTTPRequest) MakeHTTPRequest(baseURL string) ([]*retryablehttp.Request, error) {
|
||||
parsed, err := url.Parse(baseURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -22,8 +22,23 @@ func ParseTemplate(file string) (*Template, error) {
|
||||
}
|
||||
f.Close()
|
||||
|
||||
// Compile the matchers and the extractors
|
||||
for _, request := range template.Requests {
|
||||
// Compile the matchers and the extractors for http requests
|
||||
for _, request := range template.RequestsHTTP {
|
||||
for _, matcher := range request.Matchers {
|
||||
if err = matcher.CompileMatchers(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
for _, extractor := range request.Extractors {
|
||||
if err := extractor.CompileExtractors(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Compile the matchers and the extractors for dns requests
|
||||
for _, request := range template.RequestsDNS {
|
||||
for _, matcher := range request.Matchers {
|
||||
if err = matcher.CompileMatchers(); err != nil {
|
||||
return nil, err
|
||||
|
||||
@ -10,8 +10,10 @@ type Template struct {
|
||||
ID string `yaml:"id"`
|
||||
// Info contains information about the template
|
||||
Info Info `yaml:"info"`
|
||||
// Request contains the request to make in the template
|
||||
Requests []*requests.Request `yaml:"requests"`
|
||||
// Request contains the http request to make in the template
|
||||
RequestsHTTP []*requests.HTTPRequest `yaml:"requests"`
|
||||
// Request contains the dns request to make in the template
|
||||
RequestsDNS []*requests.DNSRequest `yaml:"dns"`
|
||||
}
|
||||
|
||||
// Info contains information about the request template
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user